/****** * search_graph.c * * Illustration of breadth-first vs depth-first graph searching * using a "fringe", which is a list of nodes to be processed. * * Notes : * * * For breadth-first, the fringe is a queue, * while for depth-first it's a stack. * * * I've written the search() routine to show how little difference * implementation difference there is between the two search strategies. * * * This code includes simplistic interfaces to a global stack and queue. * * * Simplistic error handling is also included, * which just prints an error and quits when a problem is encountered. * * * A "connection matrix" is used to described the graph connectivity. * In this example, the matrix is hard-coded in; a more sophisticated * routine might read in the graph spec from some sort of file format, * or generate it on the fly from some sort of model. * * * Some testing of the infrastructure may be found in testing(); * uncomment it in main() to run the tests. * * * See example_one.dot and example_one.png for a picture of this * this graph created with graphviz. * * * The "run" file in this directory compiles and runs the whole thing : * mahoney@cs graphs$ ./run * $ dot -Tpng < example_one.dot > example_one.png * $ gcc -march=athlon64 -O2 search_graph.c -o search_graph * $ ./search_graph > search_graph.out * $ cat search_graph.out * -- search_graph -- * search breadth-first (use_stack = 0) : * alpha * beta * gamma * delta * zeta * epsilon * theta * iota * lambda * nu * search depth-first (use_stack = 1) : * alpha * delta * iota * nu * gamma * theta * lambda * epsilon * beta * zeta * * -- routines ------------------------------ * * void error_exit(char* msg) Report an error and exit. * * node_list new_node_list(int size) Create a node list. * node new_node(char* label, int n_neighbors) Create a node. * void set_link(node parent, node child) Link a child to its parent. * int n_connections(int node_id) from connections[][] matrix * void make_tree() init nodes[] and links * void reset_seen() Set all node->seen to 0 * * void verify_stack() initialize it if needed * void verify_queue() initialize it if needed * void stack_push(node n) * node stack_pop() * void queue_push(node n) * node queue_pop() * void fringe_push(int use_stack, node n) push onto stack or queue * void fringe_pop(int use_stack) pop from stack or queue * void fringe_size(int use_stack) get size of stack or queue * * void print_node_name(node n) * void search(char* which, void (*process_node)(node)) * int main() * * $Id: search_graph.c 11593 2007-03-05 15:39:14Z mahoney $ ********/ #include #include // ==== The graph that we're going to search looks like this. ==== // // // alpha---------------------- // / \ | // / \ | // / \ | // beta gamma--------- --delta // / \ / \ | | // zeta epsilon theta iota // \ / | // lambda----------/ nu // // ==== The same information in some arrays and matrices looks like this. == // (The connections matrix is true where node i is connected to node j. // I'm only going to use the upper triangular part, where // connections[i][j] has i < j. For each linked pair, I'll call the // node with the lower id the "parent" and the one with the higher id // the "child", consistent with tree graph conventions. #define N_NODES 10 char* names[N_NODES] = {"alpha", "beta", "gamma", "delta", "zeta", "epsilon", "theta", "iota", "lambda", "nu" }; // a b g d z e t i l n int connections[N_NODES][N_NODES] = {{2, 1, 1, 1, 0, 0, 0, 0, 0, 0}, // alpha {0, 2, 0, 0, 1, 1, 0, 0, 0, 0}, // beta {0, 0, 2, 0, 0, 1, 1, 1, 0, 0}, // gamma {0, 0, 0, 2, 0, 0, 0, 1, 0, 0}, // delta {0, 0, 0, 0, 2, 0, 0, 0, 1, 0}, // zeta {0, 0, 0, 0, 0, 2, 0, 0, 0, 0}, // epsilon {0, 0, 0, 0, 0, 0, 2, 0, 1, 0}, // theta {0, 0, 0, 0, 0, 0, 0, 2, 0, 1}, // iota {0, 0, 0, 0, 0, 0, 0, 0, 2, 0}, // lambda {0, 0, 0, 0, 0, 0, 0, 0, 0, 2}};// nu // ==== data definitions ============= typedef struct node_struct *node; // a node is a pointer to a node_struct. typedef node *node_list; // a list of nodes is a pointer to a node. struct node_struct { char* label; // e.g. "alpha" node_list links; // e.g. &{beta, gamma, delta} int max_links; // size of links list int n_links; // number of elements set in links lists int seen; // true (> 0) after search sees it. }; // ==== global variables =============== node_list nodes = NULL; // nodes[node_id] for 0 <= node_id < N_NODES // (The root of the tree will be nodes[0].) #define STACK_MAX 1000 node_list stack = NULL; int stack_size; #define QUEUE_MAX 1000 node_list queue = NULL; int queue_start; int queue_end; // ==== subroutines ====================== void error_exit(char* msg){ printf("\n\n Error: %s. \n\n", msg); exit(1); // "0" is "success" status; this just says an error occured. } // --- node creation and manipulation --- // Create a new node list and return a pointer to it. node_list new_node_list(int size){ return (node_list) malloc(size * sizeof(node)); } // Create a new node_struct and return a pointer to it. node new_node(char* label, int n_neighbors){ node the_node = (node) malloc(sizeof(struct node_struct)); the_node->n_links = 0; the_node->max_links = n_neighbors; the_node->seen = 0; if (label){ the_node->label = label; } if (n_neighbors){ the_node->links = new_node_list(n_neighbors); } return the_node; } // Add the child node to the parent node's list of links; // adjust the parent's internal data accordingly. void set_link(node parent, node child){ parent->links[parent->n_links] = child; parent->n_links++; } // Using the connections[i][j] matrix, // find and return the number of links (i.e. children) // expected for the node with the given id (where 0<=idseen = 0; } } // --- stack and queue operations --- // All these use the global stack and queue defined above. // All are lists of nodes only. // And they're also simplistic about memory managment, particularly the queue. // Allocate memory for the stack if needed, and // do anything else necessary to make sure stack_push and stack_pop will work. void verify_stack(){ if (stack == NULL){ stack = (node_list) malloc(STACK_MAX * sizeof(node)); stack_size = 0; } } // Allocate memory for the queue if needed, and // do anything else necessary to make sure queue_push and queue_pop will work. void verify_queue(){ if (queue == NULL){ queue = (node_list) malloc(QUEUE_MAX * sizeof(node)); queue_start = queue_end = 0; } } void stack_push(node n){ verify_stack(); if (stack_size >= STACK_MAX){ // A better error handler here would just grow the stack, // perhaps with realloc(ptr, size); printf("\n\n *** Error - stack size exceeded. *** \n\n"); exit(1); // "0" is "success" status; this just says an error occured. } stack[stack_size] = n; stack_size++; } // Pop a node off the end of the global stack, and return it. node stack_pop(){ node n; verify_stack(); if (stack_size <= 0){ error_exit("tried to pop an empty stack"); } stack_size--; n = stack[stack_size]; stack[stack_size] = NULL; return n; } // Push a node onto the end of global queue. void queue_push(node n){ verify_queue(); if (queue_end >= QUEUE_MAX){ // Better would be to slide the slots in use back if queue_start > 0 // with memmove(from_ptr, to_ptr, n_bytes) from // or to grow the size of the queue with realloc or malloc. error_exit("ran off end of queue"); } queue[queue_end] = n; queue_end++; } // Pop a node off the global front of the global queue. node queue_pop(){ node n; verify_queue(); if (queue_start >= queue_end){ error_exit("tried to pop an empty queue"); } n = queue[queue_start]; queue[queue_start] = NULL; queue_start++; return n; } // Push onto stack or queue void fringe_push(int use_stack, node n){ if (use_stack){ stack_push(n); } else { queue_push(n); } } // Pop from stack or queue node fringe_pop(int use_stack){ if (use_stack){ return stack_pop(); } else { return queue_pop(); } } // Get size of stack or queue int fringe_size(int use_stack){ if (use_stack){ return stack_size; } else { return queue_end - queue_start; } } // -- search routines ------------------------------------ // Display a node's name. // This is an example of a "process_node" function which // can be passed to search(). void print_node_name(node n){ printf(" %s\n", n->label); } // Search a graph of nodes and do something to each one. // Also, create the tree if need be, and make sure its ready to search. // Inputs : // which : "breadth-first" or "depth-first" // void process_node(node) : what should be done to each node // Note that this is *not* recursive; instead a list (stack or queue) is // kept of everything left to look at. When the list is empty, we're done. // Sample usage: // search("breadth-first", print_node_name) void search(char* which, void (*process_node)(node)){ int i; node n, child; int use_stack = (which[0] == 'b' ? 0 : 1); if (nodes == NULL){ // Create the tree if it doesn't exist. make_tree(); } reset_seen(); // Set all node->seen to 0. printf(" search %s ", which); printf(" (use_stack = %i) : \n", use_stack); fringe_push(use_stack, nodes[0]); // Add the root to fringe. while (fringe_size(use_stack) > 0){ // While there are still nodes to do, n = fringe_pop(use_stack); // get the next one, and process_node(n); // do whatever needs doing with it. for (i=0; i < n->n_links; i++){ // Loop over it's kids. child = n->links[i]; if (! child->seen){ // If kid hasn't been seen yet, fringe_push(use_stack, child); // add it to the fringe child->seen = 1; // and mark it as seen. } } } } // --- tests ---------------------------------------------------- // Reality check: make sure things do what they're supposed to. void testing() { int i, j; node one, two, three, n; printf("\n- testing -\n\n"); printf(" node labels and connection counts : \n"); for (i=0; i%i ", j, connections[i][j]); } printf("\n"); // newline at end of row } printf("\n"); printf(" stack and queue tests:\n"); printf(" pushing 'one' and 'two' on stack and queue. \n"); one = new_node("one", 0); two = new_node("two", 0); stack_push(one); stack_push(two); queue_push(one); queue_push(two); n = stack_pop(); printf(" stack pop should be 'two' : '%s'\n", n->label); n = queue_pop(); printf(" queue pop should be 'one' : '%s'\n", n->label); } // == main ================================================== int main(){ printf("-- search_graph --\n"); // Uncomment the next line to run the tests. // testing(); search("breadth-first", print_node_name); search("depth-first", print_node_name); }