I've recently been learning C on my own, and I thought I'd try my hand at writing a graph implementation.
// graph.h
#ifndef GRAPH_H
#define GRAPH_H
#include <stdlib.h>
#include <string.h>
typedef struct node node_t;
typedef struct node {
int value;
node_t **links;
int links_count;
} node_t;
node_t *create_node(int value);
void destroy_node(node_t *node);
void link_nodes(node_t *first_node, node_t *second_node);
void unlink_nodes(node_t *first_node, node_t *second_node);
#endif /* GRAPH_H */
// graph.c
#include "graph.h"
static void remove_link(node_t *node, int index) {
// If the index is out of bounds, return early
if (index >= node->links_count || index < 0) return;
// Move links down the array, overwriting the link's index
for (int i = index; i < node->links_count - 1; i++) {
node->links[i] = node->links[i + 1];
}
// Get rid of the dangling pointer
node->links[node->links_count - 1] = NULL;
if (node->links_count == 1) {
// If this is the only link, deallocate arrays
free(node->links);
node->links = NULL;
} else {
// Otherwise, shrink them
node->links = realloc(node->links, sizeof(node_t *) * --node->links_count);
}
}
static void increase_link_space(node_t *node) {
if (node->links_count == 0) {
// If there isn't an array, create one
node->links = malloc(sizeof(node_t *));
} else {
// Otherwise, expand it
node->links = realloc(node->links, sizeof(node_t *) * (node->links_count + 1));
}
}
node_t *create_node(int value) {
// Allocate memory for the node
node_t *node = malloc(sizeof(node_t));
if (node == NULL) exit(-1);
node->links_count = 0;
node->value = value;
return node;
}
void destroy_node(node_t *node) {
// Remove all links to and from other nodes
// For each link in this node...
for (int i = 0; i < node->links_count; i++) {
// For each link in the link...
for (int j = 0; j < node->links[i]->links_count; j++) {
// If a link matches the original node (i.e. loopback)...
if (node->links[i]->links[j] == node) {
// Remove the link and break
remove_link(node->links[i]->links[j], j);
break;
}
}
}
// Free up malloc'd memory
free(node->links);
free(node);
}
void link_nodes(node_t *first_node, node_t *second_node) {
// Create space for the new link
increase_link_space(first_node);
increase_link_space(second_node);
// Link the first node to the second node
first_node->links[first_node->links_count] = second_node;
// Link the second node to the first
second_node->links[second_node->links_count] = first_node;
}
void unlink_nodes(node_t *first_node, node_t *second_node) {
// The index of second_node in first_node's links
int first_index = -1;
// The index of first_node in second_node's links
int second_index = -1;
// Search through first_node->links
for (int i = 0; i < first_node->links_count; i++) {
if (first_node->links[i] == second_node) {
first_index = i;
break;
}
}
// If no match was found, return
if (first_index < 0) return;
// Search through second_node->links
for (int i = 0; i < second_node->links_count; i++) {
if (second_node->links[i] == first_node) {
second_index = i;
break;
}
}
// We don't need a second check, because as long as the program is using node_t correctly, one-way links won't exist.
// Even if a one-way link exists, remove_link has a check for out-of-bounds (including negative) indices.
// Remove the found indices
remove_link(first_node, first_index);
remove_link(second_node, second_index);
}
Here's a test program:
#include "graph.h"
int main(void) {
node_t *first_node = create_node(5);
node_t *second_node = create_node(7);
node_t *third_node = create_node(3);
/*
3
7
5
*/
link_nodes(first_node, second_node);
link_nodes(first_node, third_node);
/*
3
^ 7
| ^
->5<-----|
*/
destroy_node(third_node);
/*
7
^
5<-----|
*/
unlink_nodes(first_node, second_node);
/*
7
5
*/
destroy_node(first_node);
destroy_node(second_node);
}
This is my first data structure implementation, so any and all tips/criticism would be really helpful.