CSCI 260 Fall 2010: Adjacency list implementation of graphs

The graph implementation below (which may still contain a couple of glitches in the remove routines, haven't tested them much yet) stores an avltree of vertices for the graph as a whole, and each vertex has its own avltree of edges.

The avltree is set up so its data components can store either a vertex pointer or an edge pointer, and it has an id field to sort by, which can be either a vertex id or an edge id. An IsVertex flag is included to keep track of whether the current node is being used to store an edge or a vertex.

The avltree is set up to accomodate directional or bidirectional edges, controlled by an optional boolean parameter in the graph constructor. (Graphs with directional edges are implemented by having the insert and remove statements manipulate a single edge, while for graphs with bidirectional edges the insert and remove statements insert or remove two edges - one in each direction.)

// ========================================================
// =============== nodes.h ================================
// ========================================================
// here we define the contents of individual edges
//    and vertices, for use in both the avltree class
//    and the graph class
// (via an "edge" struct and a "vertex" struct)

#ifndef NODES_H
#define NODES_H 1

#include <string>
#include <iostream>
using namespace std;

#include "avltree.h"

// global flag to turn debugging statements on/off
const bool DEBUG = true;

// forward declaration of avltrees,
//    the avltrees need to incorporate edge and vertex structs,
//    but the vertex structs also need to incorporate avltrees,
//    hence the need for a forward declaration
class avltree;

// definition of a graph edge and its weight
//    the edge is identified by the ids of
//    the source and destination vertices 
struct edge {
   int src, dest;
   float weight;
};

// definition of a graph vertex,
//    including an identifier, data field(s),
//    edgelist, and supplementary processing flags
struct vertex {
   int id;
   float data;
   avltree *edgelist;
   bool processed, visited;
};

#endif
// ========================================================
// =============== avltree.h ==============================
// ========================================================
// define an avltree of edges or vertices   
//
//    each node in the avltree contains a unique integer id,
//       corresponding to either a vertex id or to the 
//       destination vertex id for an edge
//    while the data component consists of pointers to edge or 
//       vertex structs along with a flag indicating which one
//       is currently in use
//
//    the housekeeping portions of the avltree nodes consist of
//       pointers to the node's left and right children,
//       a pointer to the node's parent, and the size and
//       height of the subtree rooted at that node
//
// the public avltree methods allow insertion, removal, and
//    searches for nodes representing edges or vertices,
//    obtaining the size of the avl tree, and the ability to
//    retrieve edges or vertices based on their position in
//    the avltree (e.g. get the 1st edge, 2nd edge, etc)
//
// the private avltree methods are primarily either recursive
//    versions of the public methods (called by the public methods
//    on the root of the tree) or are used for maintaining the
//    avltree structure (checking for and carrying out rotations)

#ifndef AVLTREE_H
#define AVLTREE_H 1

#include "nodes.h"

class avltree {

   private:

      // define the structure of individual nodes within the avl tree,
      //    and declare a pointer to the root node for the tree
      struct node {
         // "data" portion of the avltree node
         bool IsVertex;   // true if this node stores a vertex pointer,
                          // false if this node stores an edge pointer
         edge *eptr;      // null iff IsVertex is false
         vertex *vptr;    // null iff IsVertex is true
         int  id;         // the vertex or edge id for the stored data,
                          // edges use the id of their destination vertex

         // "housekeeping" portion of the avltree node
         int subtreesize; // the number of nodes in the subtree of this node
         int  height;     // the height of the subtree rooted at this node,
                          // 1 for leaf nodes
         node *parent;    // ptr to the parent of this node
         node *left;      // ptr to this node's left child
         node *right;     // ptr to this node's right child
      };

      // data shared for the avl tree
      node *root; // the root node of the avl tree (null when the tree is empty)
      int size;   // the number of nodes in the avl tree

      // private/helper methods for the avl tree

      // deallocate and nullify the subtree rooted at node n
      void deallocate(node* &n);

      // if the subtree rooted at node n is unbalanced
      //    perform the necessary rotations to restore balance,
      // *assumes the two subtrees of n have already been balanced!
      void checkforrotation(node* &n);

      // update the height and subtree sizes for node n
      // *assumes the two subtrees of n have already been updated  
      void updateheight(node* n);

      // perform a left rotation through node n
      void rotateleft(node* &n);

      // perform a right rotation through node n
      void rotateright(node* &n);

      // find the "topmost" node in the tree with a matching id
      //    and return a pointer to it,
      // return null if there is no matching node
      node *search(int id);

      // remove the "topmost" node in the tree with a matching id
      // return true iff successful
      bool remove(node* &n, int id);

      // print the contents of the tree rooted at node n
      //    (uses an inorder traversal)
      // return true iff successful
      bool print(node *n);

      // find the "topmost" node in the tree with a matching id
      //    and print its data components
      // for edges the data components are the ids of 
      //    the source/destination vertices and the edge weight
      // for vertices the data components are the vertex id,
      //    data field, and edgelist (which is an avltree of its own)
      // return true iff successful
      bool print(node *n, int id);

      // create an avltree node containing the passed edge ptr
      //    and insert it in the avltree rooted at n,
      //    noting that the parent of n is also passed as a parameter
      bool insert(int id, edge *e, node* &n, node *parent); 

      // create an avltree node containing the passed vertex ptr
      //    and insert it in the avltree rooted at n,
      //    noting that the parent of n is also passed as a parameter
      bool insert(int id, vertex *v, node* &n, node *parent);

   public:
      // default avltree constructor,
      //    creates an empty tree
      avltree() { size = 0; root = NULL; }

      // avltree destructor, deallocates the entire tree
      ~avltree() { deallocate(root); }

      // an edge search method, which takes an edge id
      //    and finds the topmost node with a matching id,
      //    copying the edge pointer field into parameter e
      // returns true iff successful
      bool search(int id, edge* &e) {
         node *n = search(id);
         if (!n) return false;
         e = n->eptr;
         return true;
      }

      // a vertex search method, which takes a vertex id
      //    and finds the topmost node with a matching id,
      //    copying the vertex pointer field into parameter v
      // returns true iff successful
      bool search(int id, vertex* &v) {
         node *n = search(id);
         if (!n) return false;
         v = n->vptr;
         return true;
      }

      // the remove method takes a node id,
      //     and finds, removes, and deletes the topmost node
      //     with a matching id
      // returns true iff successful
      bool remove(int id) { return remove(root, id); }

      // the edge insert method, takes an edge id and pointer to the edge
      //     and creates an avltree node to store them,
      //     and inserts the node into the tree
      // returns true iff successful
      bool insert(int id, edge *e) { insert(id, e, root, NULL); }

      // the vertex insert method, takes a vertex id and pointer to the vertex
      //     and creates an avltree node to store them,
      //     and inserts the node into the tree
      // returns true iff successful
      bool insert(int id, vertex *v) { insert(id, v, root, NULL); }

      // the tree print method, prints the data components of all nodes
      //    in the entire avltree
      // for edges the data components are the ids of 
      //    the source/destination vertices and the edge weight
      // for vertices the data components are the vertex id,
      //    data field, and edgelist (which is an avltree of its own)
      // return true iff successful
      bool print() { print(root); }

      // find the "topmost" node in the tree with a matching id
      //    and print its data components
      // return true iff successful
      bool print(int id) { print(root, id); }

      // return the size of the avltree
      int getsize() { return size; }

      // return the edge pointer stored by the i'th node of the avltree
      // (the ordering matches that of an inorder traversal, i.e. sorted)
      edge *getedge(int i);

      // return the vertex pointer stored by the i'th node of the avltree
      // (the ordering matches that of an inorder traversal, i.e. sorted)
      vertex *getvertex(int i);
};

#endif
// ========================================================
// =============== graph.h ================================
// ========================================================
// the graph class implements a graph of vertices and
//     their connecting edges 
// vertices have an integer id value (which must be unique),
//     a floating point data field, and a collection of
//     (outgoing) edges
// edges are assumed to be weighted and can be direction or
//     bidirectional (though a single graph cannot contain
//     a mix of the two), with bidirectional edges simulated
//     by having the insert and remove statements insert or
//     remove one edge in each direction
// an avltree of vertices is maintained for the graph,
//     while each vertex also has its own avltree of 
//     outgoing edges
// graph methods allow inserts, removes, and searches    
//     for nodes and edges, and display of the entire
//     graph or individual vertices within the graph
#ifndef GRAPH_H
#define GRAPH_H 1

#include "avltree.h"

class graph {
   private:
      avltree *Vertices;  // the avl tree of vertices in the graph
      int size;           // the number of vertices in the graph
      bool bidirectional; // true iff the edges in the graph are bidirectional
                          
   public:
      // the graph constructor, creates an empty graph
      //     if bidir is true the graph is created with bidirectional edges,
      //     otherwise it is created with directional edges
      graph(bool bidir = false);

      // the graph destructor, 
      //     deallocates all vertices (and their edges) in the graph
      ~graph();

      // create and insert a new vertex in the graph,
      //    return true iff successful
      bool insert(int vid, float d);

      // create and insert a new edge in the graph,
      //    (an edge in each direction for bidirectional graphs)
      //    returns true iff successful
      bool insert(int src, int dest, float wt);

      // remove the vertex with the specified id,
      //   along with all edges to/from it,
      //   returning true iff successful
      bool remove(int vid);

      // remove the edge from the vertex with id src to the
      //   vertex with id dest, returning true iff successful
      //   (also removes the edge in the opposite direction
      //    for bidirectional graphs)
      bool remove(int src, int dest);

      // print the id, data, and edgelist for the specified vertex
      //   returning true iff successful
      bool print(int vid);

      // print the id, data, and edgelist for all vertices
      //   in sorted order (by vertex ids),
      // returning true iff successful
      bool print();

      // find the vertex with the matching id and store its data
      //   component in parameter d,
      // returning true iff successful
      bool search(int vid, float &d);

      // find the edge from the source vertex (with id src)
      //    to the destination vertex (with id dest)
      //    and copy its weight to the wt parameter
      // returning true iff successful
      bool search(int src, int dest, float &wt);
};

#endif


// ========================================================
// =============== avltree.C ==============================
// ========================================================
#include "avltree.h"

// perform a binary search of the tree for the
//    node with id matching i,
// if one is found then return its edge pointer,
// otherwise return null
edge *avltree::getedge(int i)
{
   if ((i < 0) || (i >= size)) return NULL;
   node *n = root;
   while (n) {
      int leftsize = 0;
      if (n->left) leftsize = n->left->subtreesize;
      if (i == (leftsize + 1)) return n->eptr;
      if (i <= leftsize) n = n->left;
      else {
         i = i - (leftsize + 1);
         n = n->right;
      }
   }
   if (n) return n->eptr;
   else return NULL;
}

// perform a binary search of the tree for the
//    node with id matching i,
// if one is found then return its vertex pointer,
// otherwise return null
vertex *avltree::getvertex(int i)
{
   if ((i < 0) || (i >= size)) return NULL;
   node *n = root;
   while (n) {
      int leftsize = 0;
      if (n->left) leftsize = n->left->subtreesize;
      if (i == (leftsize + 1)) return n->vptr;
      if (i <= leftsize) n = n->left;
      else {
         i = i - (leftsize + 1);
         n = n->right;
      }
   }
   if (n) return n->vptr;
   else return NULL;
}

// perform a binary search of the tree rooted at node n,
//   looking for a node with an id matching the id parameter,
// if a matching node is found then print the data components:
//    for edges the data components are the 
//        source and destination vertices' ids
//        and the edge weight
//    for vertices the data components are the
//        vertex id, data field, and edgelist
//        (displayed via its own avltree print)
// return true iff successful
void avltree::print(node *n, int id)
{
   if (!n) return false;
   if (n->id < id) return print(n->right, id);
   else if (n->id > id) return print(n->left, id);
   else {
      if ((n->IsVertex) && (n->vptr)) {
         cout << n->vptr->id << "," << n->vptr->data << ":";
         if (n->vptr->edgelist) n->vptr->edgelist->print();
         cout << endl;
         return true;
      } else if (n->eptr) {
         cout << "(" << n->eptr->dest << "," << n->eptr->weight << ")";
         return true;
      } else if (DEBUG) {
         cout << "ERROR: null e/v ptr in avltree node" << endl;
         return false;
      }
   }
}

// perform an inorder traversal of the tree rooted at node n,
//    printing the data components for every node encountered
// for edges the data components are the 
//     source and destination vertices' ids
//     and the edge weight
// for vertices the data components are the
//     vertex id, data field, and edgelist
//     (displayed via its own avltree print)
// return true iff successful
void avltree::print(node *n)
{
   if (!n) return true;
   bool result = print(n->left));
   if ((n->IsVertex) && (n->vptr)) {
      cout << n->vptr->id << "," << n->vptr->data << ":";
      if (n->vptr->edgelist) n->vptr->edgelist->print();
      cout << endl;
   } else if (n->eptr) {
      cout << "(" << n->eptr->dest << "," << n->eptr->weight << ")";
   } else if (DEBUG) {
      cout << "ERROR: null e/v ptr in avltree node" << endl;
      result = false;
   }
   result = print(n->right) && result;
   return result;
}

// determine if the subtree rooted at n is unbalanced
//   (the heights of its two subtrees differ by more than 1)
// and perform a single rotation or a double rotation
//   to restore balance
// ***assumes the two subtrees have already been balanced***
void avltree::checkforrotation(node* &n)
{
   if (!n) return;
   updateheight(n);
   int leftht = 0;
   int rightht = 0;
   if (n->left) leftht = n->left->height;
   if (n->right) rightht = n->right->height;

   // don't rotate if we're balanced
   if ((leftht <= (rightht + 1)) && (leftht >= (rightht - 1))) return;
   
   // if left subtree is taller, 
   //    decide whether to rotate once or twice
   if (leftht > rightht) {
      int lleft = 0;
      int lright = 0;
      if (n->left->left) lleft = n->left->left->height;
      if (n->left->right) lright = n->left->right->height;
      if (lright > lleft) {
         rotateleft(n->left);
      }
      rotateright(n);
   }

   // if right subtree is taller,
   //    decide whether to rotate once or twice
   else {
      int rleft = 0;
      int rright = 0;
      if (n->right->left) rleft = n->right->left->height;
      if (n->right->right) rright = n->right->right->height;
      if (rright < rleft) {
         rotateright(n->right);
      }
      rotateleft(n);
   }
}

// perform a left rotation through node n, i.e.
//       N             B
//      / \           / \
//     /   \         /   \
//    A     B  ==>  N     F   
//         / \     / \ 
//        E   F   A   E 
//
void avltree::rotateleft(node* &n)
{
   if (!n) return;
   node *tmp = n;
   n = n->right;
   tmp->right = n->left;
   n->left = tmp;
   n->parent = tmp->parent;
   n->left->parent = n;
   if (n->left->right) n->left->right->parent = n->left;
   updateheight(tmp);
   updateheight(n);
}

// perform a right rotation through node n, i.e.
//       N             A
//      / \           / \
//     /   \         /   \
//    A     B  ==>  C     N
//   / \                 / \
//  C   D               D   B
//
void avltree::rotateright(node* &n)
{
   if (!n) return;
   node *tmp = n;
   n = n->left;
   tmp->left = n->right;
   n->right = tmp;
   n->parent = tmp->parent;
   n->right->parent = n;
   if (n->right->left) n->right->left->parent = n->right;
   updateheight(tmp);
   updateheight(n);
}

// recompute the height and subtree size for the avltree
//    rooted at node n, assuming n's subtrees are up to date
// the height of a leaf node is 1,
//    the height of internal nodes is one greater than the
//    height of its taller subtree
// the size of a leaf node is 1,
//    the size of an internal node is one plus the sizes
//    of its two subtrees
void avltree::updateheight(node* n)
{
   if (!n) return;
   int leftht = 0;
   n->subtreesize = 1;
   if (n->left) {
      leftht = n->left->height;
      n->subtreesize += n->left->subtreesize;
   }
   int rightht = 0;
   if (n->right) {
      rightht = n->right->height;
      n->subtreesize += n->right->subtreesize;
   }
   if (leftht > rightht) n->height = leftht + 1;
   else n->height = rightht + 1;
}

// performs a binary search (recursively) of the subtree 
//    rooted at n to find a node whose id matches the parameter,
//    and removes and deletes that node if found
// if a remove is successfully performed in one of n's subtrees
//    then perform a checkforrotation on n to rebalance the tree
//    (if necessary)
// returns true iff successful
bool avltree::remove(node* &n, int id)
{
   node *victim;
   int tmpid;
   edge *tmpe; 
   vertex *tmpv;
   if (!n) return false;
   if (n->id < id) {
      if (!remove(n->right, id)) return false;
   } else if (n->id > id) {
      if (!remove(n->left, id)) return false;
   } else {
      node *parent = n->parent;
      if (!n->right && !n->left) {
         delete n;
         n = NULL;
      } else if (!n->right) {
         victim = n;
         n = n->left;
         delete victim;
      } else if (!n->left) {
         victim = n;
         n = n->right;
         delete victim;
      } else {
         victim = n->right;
         while (victim->left) victim = victim->left;
         tmpid = victim->id;
         tmpe = victim->eptr;
         tmpv = victim->vptr;
         if (!remove(victim, tmpid)) return false;
         n->id = tmpid;
         n->eptr = tmpe;
         n->vptr = tmpv;
      }
   }
   checkforrotation(n);
   return true;
}

// deallocate and nullify all nodes in the subtree
//    rooted at node n
void avltree::deallocate(node* &n)
{
   if (!n) return;
   deallocate(n->left);
   deallocate(n->right);
   delete n;
   n = NULL;
}

// perform a binary search of the entire avl tree to
//    find a node whose id matches the parameter,
//    return a pointer to the node if found,
//    or return null if no matching node is found
avltree::node *avltree::search(int id)
{
   node *n = root;
   while (n) {
      if (n->id == id) return n;
      else if (n->id > id) n = n->left;
      else n = n->right;
   }
   return NULL;
}

// perform a binary search (recursively) of the avltree rooted at n
//    to find an insertion point for a node with the passed id,
//    creating and inserting an avltree node to store edge data
//    if an insertion point is found
// if the recursive insertion succeeds (i.e. a new node is inserted
//    somewhere below n) then use checkforrotation to rebalance the
//    subtree rooted at n (if necessary)
// note that a pointer to the parent node is also passed,
//    to simplify the insertion process
// returns true iff successful
bool avltree::insert(int id, edge *e, node* &n, node *parent)
{
   bool result = false;
   if (n == NULL) {
      n = new node;
      if (!n) return false;
      n->id = id;
      n->vptr = NULL;
      n->eptr = e;
      n->parent = parent;
      n->left = NULL;
      n->right = NULL;
      n->IsVertex = false;
      n->height = 1;
      return true;
   } else if (id < n->id) {
      result = insert(id, e, n->left, n);
      if (!n->left) return false;
   } else if (id > n->id) {
      result = insert(id, e, n->right, n);
      if (!n->right) return false;
   } else {
      if (DEBUG) cout << "Trying to insert duplicate edge" << endl;
      return false;
   }
   if (!result) return false;
   checkforrotation(n);
   return true;
}

// perform a binary search (recursively) of the avltree rooted at n
//    to find an insertion point for a node with the passed id,
//    creating and inserting an avltree node to store vertex data
//    if an insertion point is found
// if the recursive insertion succeeds (i.e. a new node is inserted
//    somewhere below n) then use checkforrotation to rebalance the
//    subtree rooted at n (if necessary)
// note that a pointer to the parent node is also passed,
//    to simplify the insertion process
// returns true iff successful
bool avltree::insert(int id, vertex *v, node* &n, node *parent)
{
   bool result = false;
   if (n == NULL) {
      n = new node;
      if (!n) {
         if (DEBUG) cout << "Out of mem" << endl;
         return false;
      }
      n->id = id;
      n->vptr = v;
      n->eptr = NULL;
      n->parent = parent;
      n->left = NULL;
      n->right = NULL;
      n->IsVertex = true;
      n->height = 1;
      if (!root) root = n;
      return true;
   } else if (id < n->id) {
      result = insert(id, v, n->left, n);
      if (!n->left) {
         if (DEBUG) cout << "AVL tree insert: no left ptr produced" << endl;
         return false;
      }
   } else if (id > n->id) {
      result = insert(id, v, n->right, n);
      if (!n->right)  {
         if (DEBUG) cout << "AVL tree insert: no right ptr produced" << endl;
         return false;
      }
   } else {
      if (DEBUG) cout << "Trying to insert duplicate vertex" << endl;
      return false;
   }
   if (!result) return false;
   checkforrotation(n);
   return true;
}


// ========================================================
// =============== graph.C ================================
// ========================================================
#include "graph.h"

// avltree *Vertices;
// int size;

// create an empty graph,
//    including allocating a new avltree to hold the vertices,
//    set the size to zero (no vertices yet)
//    and record whether or not edges are bidirectional
graph::graph(bool bidir)
{
   Vertices = new avltree;
   if (!Vertices && DEBUG) cout << "ERROR: graph constructor failure" << endl;
   size = 0;
   bidirectional = bidir;
}

// deallocate the avltree holding all the vertices in the graph, 
//    but that must be preceeded by deallocating the edgelist 
//    (avltree) for each individual vertex
graph::~graph()
{
   if (!Vertices) return;
   int size = Vertices->getsize();
   for (int i = 0; i < size; i++) {
       vertex *v = Vertices->getvertex(i);
       if (v && v->edgelist) delete v->edgelist;
   }
   delete Vertices;
}

// create a new vertex struct to hold the vertex information,
//    initialize it with the (unique) vertex id,
//       the vertex data field, and a new (empty) edgelist,
//    then insert the struct into the avltree of vertices
// note that avl trees for vertices are ordered by the
//    id of the vertex
// return true iff successful
bool graph::insert(int vid, float d)
{
   if (!Vertices) return false;
   vertex *v = new vertex;
   if (!v) return false;
   v->id = vid;
   v->data = d;
   v->edgelist = new avltree;
   if (!v->edgelist) {
      delete v;
      return false;
   }
   if (!Vertices->insert(vid, v)) {
      if (DEBUG) cout << "Insert of vertex " << vid << " failed" << endl; 
      delete v;
      return false;
   } else return true;
}

// create a new edge struct to hold the edge information
//    (two new edge structs for bidirectional graphs),
//    initialize with the source vertex id,
//       the destination vertex id, and the edge weight
//    then find the source and destination vertices and 
//       insert the edge struct(s)
// note that avl trees for edges are ordered by the
//    id of the destination vertex
// return true iff successful
bool graph::insert(int src, int dest, float wt)
{
   if (!Vertices) return false;
   vertex *s, *d;
   if (!Vertices->search(src, s)) return false;
   if (!Vertices->search(dest, d)) return false;
   if (!d || !s) return false;
   if (!d->edgelist || !s->edgelist) return false;
   edge *e1 = new edge;
   if (!e1) return false;
   e1->src = src;
   e1->dest = dest;
   e1->weight = wt;
   if (!d->edgelist->insert(dest, e2)) {
      if (DEBUG) cout << "Insert of edge " << dest << "," << src << " failed" << endl; 
      delete e1;
      return false;
   }
   if (bidirectional) {
      edge *e2 = new edge;
      if (!e2) {
         if (!d->edgelist->remove(src)) 
            cout << "ERROR: CORRUPTED EDGELIST FOR VERTEX " << dest << endl;
         delete e1;
         return false;
      }
      e2->src = dest;
      e2->dest = src;
      e2->weight = wt;
      if (!s->edgelist->insert(src, e1)) {
         if (DEBUG) cout << "Insert of edge " << src << "," << dest << " failed" << endl; 
         if (!d->edgelist->remove(src)) 
            cout << "ERROR: CORRUPTED EDGELIST FOR VERTEX " << dest << endl;
         delete e1;
         delete e2;
         return false;
      }
   }
   return true;
}

// print the data components for the vertex with the specified id
//    (the data components are the id, data field, and edges)
// return true iff successful
bool graph::print(int vid)
{
   if (!Vertices) return false;
   return Vertices->print(vid);
}

// print the data components for all vertices in the graph,
//   in sorted order by vertex id
// return true iff successful
bool graph::print()
{
   if (!Vertices) return false;
   return Vertices->print();
}

// use the avltree search routine to find a graph vertex
//   whose id matches the vid parameter,
//   and (if found) copy the data value to parameter d
// return true iff successful
bool graph::search(int vid, float &d)
{
   if (!Vertices) return false;
   vertex *v;
   if (!Vertices->search(vid, v)) return false;
   if (!v) return false;
   d = v->data;
   return true;
}

// use the avltree search routine to find graph vertices
//   whose id matches the src and dest parameters,
//   and (if found) use a second search routine (on the edgelist)
//       to find a matching edge between them and copy 
//       the edge's weight into parameter wt
// if the graph is bidirectional then there must be an edge
//   in each direction, and the two edges weights must match
// return true iff successful
bool graph::search(int src, int dest, float &wt)
{
   vertex *s, *d;
   if (!Vertices) return false;
   if (!Vertices->search(src, s)) return false;
   if (!Vertices->search(dest, d)) return false;
   if (!s || !d) return false;
   if (!s->edgelist || !d->edgelist) return false;
   edge *e1, *e2;
   if (!s->edgelist->search(dest, e1)) return false;
   if (!e1) return false;
   if (bidirectional) {
      if (!s->edgelist->search(src, e2)) return false;
      if (!e2) return false;
      if (e1->weight != e2->weight) {
         if (DEBUG) {
             cout << "ERROR, weight mismatch " << src << "," << dest << " ";
             cout << e1->weight << " vs " << e2->weight << endl;
         }
         return false;
      }
   }
   wt = e1->weight;
   return true;
}

// use the avltree search method to find vertices whose
//     ids match the passed source and destination vertex ids,
//     obtain their edgelists,
//     and if there is an edge from source to destination
//         remove it from the source's edgelist
//     if the graph is bidirectional remove it from
//         both edgelists
// return true iff successful
bool graph::remove(int src, int dest)
{
   if (!Vertices) return false;
   vertex *s, *d;
   if (!Vertices->search(src, s)) return false;
   if (!Vertices->search(dest, d)) return false;
   if (!s || !d) return false;
   if (!s->edgelist || !d->edgelist) return false;
   if (!s->edgelist->remove(dest)) return false;
   if (bidirectional) {
      if (!d->edgelist->remove(src)) return false;
   }
   return true;
}

// use the avltree search method to find a vertex whose id
//     matches the passed parameter, and (if found) remove
//     the vertex from the graph and deallocate it
// part of the vertex deallocation process must include
//     deallocating its edgelist, while another part must
//     include removing all edges which go *to* the removed
//     vertex
bool graph::remove(int vid)
{
   if (!Vertices) return false;
   vertex *v;
   if (!Vertices->search(vid, v)) return false;
   if (!v->edgelist) return false;
   if (bidirectional) {
      // for bidirectional graphs, the list of edges going
      //    from the current node also tells use which vertices
      //    have edges going to this node
      // so we can look up each edge,
      //    get the destination vertex from it,
      //    then look up that vertex
      //       and remove the edge going to this one
      int esize = v->edgelist->getsize();
      for (int i = 0; i < esize; i++) {
          edge *e = v->edgelist->getedge(i);
          if (e) {
             int dest = e->dest;
             vertex *v2;
             if (Vertices->search(dest, v2)) {
                if (v2 && v2->edgelist) {
                   v2->edgelist->remove(vid);
                }
             }
          }
      }
   } else {
       // if the graph is NOT bidirectional then
       //    (with the current implementation)
       //    we have no way of knowing which vertices
       //    have edges going to this vertex,
       // so we have to try an edge remove on each
       //    other vertex in the graph
       int vsize = Vertices->getsize();
       for (int i = 0; i < vsize; i++) {
           vertex *v = Vertices->getvertex(i);
           if (v && !v->edgelist) {
              v->edgelist->remove(vid);
           }
       }
   }
   return Vertices->remove(vid);
}