您的位置:首页 > 其它

图结构系列—基于邻接表的图实现

2012-11-27 22:53 316 查看
上一文中介绍了基于邻接矩阵的图实现,介绍了一些邻接矩阵的一些表示优势。下面来说一下基于邻接表的描述无向图的具体实现,在实现前有一些结点类型说明一下:

首先是边信息类型:

#pragma once
#include<iostream>
using namespace std;

template<class T, class E>
class EdgeNode
{
public:
int dest; //该边的对称顶点在NodeTable中的索引值
E weight; //边的权重值(虚拟类型)
EdgeNode<T,E> *link; //指向下一条边的指针
public:
EdgeNode()
{
link = NULL;
}
EdgeNode(int de, E wei, EdgeNode<T,E> *lk = NULL) //默认参数 根据权值与索引构造出一个边
{
dest = de;
weight = wei;
link = lk;
}
~EdgeNode()
{

}
bool operator!= (EdgeNode<T,E> &E) //判断两条边不相等
{
return ((dest != E.dest) ? true : false);
}
};


然后是顶点类:

#pragma once
#include<iostream>
using namespace std;
#include"EdgeNode.h"

template<class T, class E>
class VertexNode
{
public:
T data; //顶点数据类型
EdgeNode<T,E> *first_Adj; //该顶点所关联的边链表的首指针
public:
VertexNode()
{
first_Adj = NULL;
}
VertexNode(T da, EdgeNode<T,E> *firadj = NULL)
{
data = da;
first_Adj = firadj;
}
~VertexNode()
{

}

};


下面是图的实现代码:

#pragma once
#include"VertexNode.h"
#include<iostream>
#include<fstream>
using namespace std;
const int DefaultVertexNum = 30; //默认顶点最大数目 常量

template<class T, class E> //因为顶点是一个类型 边上的权值又是一个类型 所以声明了两个类型的模板
class GraphAdjList
{
protected:
int maxVerticseNum; //最大顶点数目
int numVertices; //当前顶点数目
int numEdges; //当前边数目
VertexNode<T,E> *nodeTable; //顶点顺序表
const T nullVertex; //顶点类型的NULL 用于找不到指定顶点的时候的返回值 在GraphAdjList类中是常量
const E nullEdge; //边类型的NULL 用于找不到指定边的时候返回的值
static const int s_NULLidx = -1; //用来标示无法找到指定正确索引,返回-1 此写法是在C++2003标准出现的 在以后的编译器中均识别

int GetVertexPos(const T &targetValue); //内部提供服务,根据传进来的参数返回该顶点所在的索引,如果失败则返回-1
public:
GraphAdjList(T nullver, E nulled, int vertexsize = DefaultVertexNum); //配置一个图
~GraphAdjList(); //析构
void MakeEmpty(); //销毁一个基于邻接表的图

T GetVertexValue(int vertex_idx); //获取指定索引的顶点的值 没有则返回nullVertex
T GetFirstNeighbor(const T &target_vertex); //获取以vertex为顶点的第一个邻接顶点
T GetNextNeighbor(const T &vertex, const T &adj_vertex); //获vertex的邻接顶点adj_vertex的下一个邻接顶点
E GetEdgeWeight(const T &begvtx, const T &endvtx); //获取目标顶点begvtx,endvtx之间的权重值

bool InsertVertex(const T &target_vertex); //插入一个顶点 返回布尔值
bool InsertEdge(const T &begvtx, const T &endvtx, const E &weight); //在顶点begvtex,endvtx之间插入一条边(插入权重值) 返回布尔值
bool RemoveVertex(const T &target_vertex); //删除指定顶点,返回布尔值
bool RemoveEdge(const T &begvtx, const T &endvtx); //删除指定顶点之间的边,返回布尔值

void print(); //无参标准输出
void print(ostream &output); //输出这个图的信息
template<class T, class E> friend ifstream& operator>>(ifstream &fin, GraphAdjList<T,E> &G); //文件输入流重载
template<class T, class E> friend ofstream& operator<<(ofstream &fout, GraphAdjList<T,E> &G); //文件输出流重载
template<class T, class E> friend istream& operator>>(istream &input, GraphAdjList<T,E> &G); //标准输入流重载
template<class T, class E> friend ostream& operator<<(ostream &output, GraphAdjList<T,E> &G); //标准输出流重载
};

/*----------------------私有函数域-----------------------*/

template<class T, class E>
int GraphAdjList<T,E>::GetVertexPos(const T &targetValue)
{
for(int idx = 0; idx < numVertices; idx++)
{
if(nodeTable[idx].data == targetValue) //遍历找目标顶点 如果是自定义类型的顶点 则须重载自定义类型的==运算符
return idx;
}

return s_NULLidx;
}

/*-----------------------对外接口-------------------------*/

template<class T, class E>
GraphAdjList<T,E>::GraphAdjList(T nullver, E nulled, int vertexsize = DefaultVertexNum):nullVertex(nullver),nullEdge(nulled)
{
//nullVertex = nullver; //初始化类中的常量 只能用参数初始化列表的形式做
//nullEdge = nulled;
maxVerticseNum = vertexsize;
numVertices = 0;
numEdges = 0;

nodeTable = new VertexNode<T,E>[maxVerticseNum]; //开辟顶点类空间 所有的顶点类的边指针域均为NULL

if(nodeTable == NULL)
{
cerr<<"内存分配错误"<<endl;
exit(1);
}
}

template<class T, class E>
GraphAdjList<T,E>::~GraphAdjList()
{
//类模板的虚函数好像必须要有的,每次都是这个地方会错一下
MakeEmpty();
}

template<class T, class E>
void GraphAdjList<T,E>::MakeEmpty()
{
EdgeNode<T,E> *first_temp;
for(int idx = 0; idx < numVertices; idx++)
{
while(nodeTable[idx].first_Adj != NULL) //删除边链表
{
first_temp = nodeTable[idx].first_Adj;
nodeTable[idx].first_Adj = first_temp->link;
delete first_temp;
}
}

delete []nodeTable; //删除顶点数组空间 一定要加[] 否则就是删除空间的第一个元素空间 后果是内存"黑洞"
}

template<class T, class E>
T GraphAdjList<T,E>::GetVertexValue(int vertex_idx)
{
if((0 <= vertex_idx) && (vertex_idx <= numVertices - 1)) //参数是索引值,而不是序数
{
return nodeTable[vertex_idx].data; //自定义类型的顶点类要重载=(赋值)运算符
}
else
{
return nullVertex;
}
}

template<class T, class E>
T GraphAdjList<T,E>::GetFirstNeighbor(const T &target_vertex)
{
int idx = GetVertexPos(target_vertex); //找到参数顶点的索引

if(idx != s_NULLidx) //该顶点确实存在于图中
{
if(nodeTable[idx].first_Adj != NULL) //该顶点有边连接于其他的顶点
{
EdgeNode<T,E> *temp_link = nodeTable[idx].first_Adj;
int target_idx = temp_link->dest; //找到目标索引

return nodeTable[target_idx].data; //返回这个邻接顶点值
}
}

return nullVertex; //其他情况都是不符合的 返回空
}

template<class T, class E>
T GraphAdjList<T,E>::GetNextNeighbor(const T &vertex, const T &adj_vertex)
{
int idx = GetVertexPos(vertex); //先转换为索引
int adj_idx = GetVertexPos(adj_vertex);

if(idx != s_NULLidx) //该顶点存在图中
{
if(nodeTable[idx].first_Adj != NULL) //该顶点有边链表
{
EdgeNode<T,E> *temp_link = nodeTable[idx].first_Adj;
while((temp_link != NULL) && (temp_link->dest != adj_idx))
{
temp_link = temp_link->link; //在边链表中找到adj_vertex
}

if((temp_link != NULL) && (temp_link->link != NULL)) //在adj_vertex于其下一个均存在的情况下
{
int target_idx = temp_link->link->dest; //获取目标值 索引
return nodeTable[target_idx].data;
}
}
}

return nullVertex; //其他情况均不正确
}

template<class T, class E>
E GraphAdjList<T,E>::GetEdgeWeight(const T &begvtx, const T &endvtx)
{
int begidx = GetVertexPos(begvtx); //获取索引
int endidx = GetVertexPos(endvtx);

if((begidx != s_NULLidx) && (endidx != s_NULLidx)) //这两个顶点均存在图中
{
EdgeNode<T,E> *temp_link = nodeTable[begidx].first_Adj;
while((temp_link != NULL) && (temp_link->dest != endidx)) //遍历边链表 中间没有中断 并且找到了endvtx
{
temp_link = temp_link->link;
}
if(temp_link != NULL) //temp_link指向的是endvtx所在的边结点
{
return temp_link->weight; //返回权值
}
}

return nullEdge; //返回空边结点
}

template<class T, class E>
bool GraphAdjList<T,E>::InsertVertex(const T &target_vertex)
{
if(numVertices == maxVerticseNum)
{
return false; //图满
}

nodeTable[numVertices].data = target_vertex; //插入动态数组最后面
numVertices++; //顶点数目+1

return true;
}

template<class T, class E>
bool GraphAdjList<T,E>::InsertEdge(const T &begvtx, const T &endvtx, const E &weight)
{
int begidx = GetVertexPos(begvtx); //获取相应顶点的索引
int endidx = GetVertexPos(endvtx);

if((begidx != s_NULLidx) && (endidx != s_NULLidx)) //两顶点都存在图中
{
//此为无向图 首先为begvtx建立边关系
if(nodeTable[begidx].first_Adj != NULL) //起始顶点有边链表
{
EdgeNode<T,E> *temp_link = nodeTable[begidx].first_Adj;
while((temp_link->link != NULL) && (temp_link->dest != endidx)) //当前边的后面还有边并且当前的边是没有出现过的
{
temp_link  = temp_link->link;
}
//跳出循环有三种可能
//1:单方面由当前边的后面没有边了
//2:单方面由当前边已存在
//3:恰巧两个均起作用
if((temp_link->link == NULL) && (temp_link->dest != endidx)) //后面没有变了并且当前边是没有出现过的
{
temp_link->link = new EdgeNode<T,E>(endidx, weight); //根据边结点信息开辟一个新的边结点放在边链表的后面
}
else
{
return false; //begvtx--->endvtx的边无法插入 直接返回 下面不执行
}
}
//当起始顶点没有边链表的时候
else
{
nodeTable[begidx].first_Adj = new EdgeNode<T,E>(endidx, weight); //插在边链表的首位置
}

//能执行下来说明可以插入begvtx--->endvtx的边 也就可以插入endvtx--->begvtx 所以不用做判断
//下面为endvtx建立边关系 采用另一种简便的考虑情况少的方法
EdgeNode<T,E> *newEdgeNode = new EdgeNode<T,E>(begidx, weight);
newEdgeNode->link = nodeTable[endidx].first_Adj;
nodeTable[endidx].first_Adj = newEdgeNode;

//增加边数目
numEdges++;
return true;
}

return false; //其余情况均错
}

template<class T, class E>
bool GraphAdjList<T,E>::RemoveVertex(const T &target_vertex)
{
int target_idx = GetVertexPos(target_vertex);
if((target_idx < 0) || (target_idx >= numVertices) || (numVertices == 1))
{
return false; //要删除的顶点不存在 或是 最后剩下一个顶点不允许删除
}

EdgeNode<T,E> *beg_dellink = NULL; //指针务必初始化 哪怕是为NULL都行
EdgeNode<T,E> *end_dellink = NULL;
EdgeNode<T,E> *end_templink = NULL;
int end_idx;

while(nodeTable[target_idx].first_Adj != NULL) //外层循环要删除这个顶点的边链表
{
beg_dellink = nodeTable[target_idx].first_Adj;
end_idx = beg_dellink->dest; //找到边链表中的终边索引
end_dellink = nodeTable[end_idx].first_Adj;
end_templink = NULL;

while((end_dellink != NULL) && (end_dellink->dest != target_idx)) //找对称存储的边
{
end_templink = end_dellink;
end_dellink = end_dellink->link;
}
if(end_dellink != NULL)
{
if(end_templink == NULL) //对称边上只有这一个待删除的顶点
{
nodeTable[end_idx].first_Adj = NULL; //将对称顶点链表置为NULL
}
else
{
end_templink->link = end_dellink->link; //删除链表中的一个元素
}
delete end_dellink;
}

nodeTable[target_idx].first_Adj = beg_dellink->link; //回到主循环里,待删除的边链表往下遍历
delete beg_dellink;
numEdges--; //每做一次就要删除一条边
}//end of while

EdgeNode<T,E> *beg_link = NULL; //这里是为了将顶点动态数组中的最后一个顶点转移到已删除的这个顶点位置
EdgeNode<T,E> *end_link = NULL;
numVertices--; //首先定点数目-1
nodeTable[target_idx].data = nodeTable[numVertices].data; //顶点信息填补
//下面是:循环遍历最后顶点的边链表 找与他连接的边 然后找到对称的顶点 把这个顶点中存放的边信息索引更新为这个target_idx
beg_link = nodeTable[target_idx].first_Adj = nodeTable[numVertices].first_Adj;
while(beg_link != NULL)
{
end_link = nodeTable[beg_link->dest].first_Adj;
while(end_link != NULL)
{
if(end_link->dest == numVertices)
{
end_link->dest = target_idx;
break;
}
else
{
end_link = end_link->link;
}
}//小while循环结束

beg_link = beg_link->link;
}//大while循环结束
}

template<class T, class E>
bool GraphAdjList<T,E>::RemoveEdge(const T &begvtx, const T &endvtx)
{
int begidx = GetVertexPos(begvtx); //获取两个顶点的索引值
int endidx = GetVertexPos(endvtx);
if((begidx != s_NULLidx) && (endidx != s_NULLidx)) //两个顶点均存在
{
//先删除begvtx--->endvtx的边
EdgeNode<T,E> *beg_link = nodeTable[begidx].first_Adj;
EdgeNode<T,E> *temp_link = NULL;
while((beg_link != NULL)&& (beg_link->dest != endidx)) //在为遇到NULL 未遇到目标边的情况下 继续循环
{
temp_link = beg_link;
beg_link = beg_link->link;
}
if(beg_link !=NULL) //找到目标边
{
if(temp_link == NULL) //begvtx顶点只有这么一条边
{
nodeTable[begidx].first_Adj = NULL;
}
else
{
temp_link->link = beg_link->link;
}
delete beg_link;
}
else //未找到目标边
{
return false;
}

//因为是无向边所以现在删除endvtx--->begvtx的边
EdgeNode<T,E> *end_link = nodeTable[endidx].first_Adj;
temp_link = NULL;
while(end_link->dest != begidx) //能执行到这里 说明这个对称边也是存在的 所以不用加!=NULL的条件
{
temp_link = end_link;
end_link = end_link->link;
}
if(temp_link == NULL)
{
nodeTable[endidx].first_Adj = NULL;
}
else
{
temp_link->link = end_link->link;
}
delete end_link;

return true;
}
else //匹配最外层的if 至少有一个顶点不存在图中
{
return false;
}
}

template<class T, class E>
void GraphAdjList<T,E>::print()
{
int vertices_Num = numVertices;
int edges_Num = numEdges;

cout<<"顶点数目: "<<vertices_Num<<endl;
cout<<"边数目: "<<edges_Num<<endl;

EdgeNode<T,E> *first_temp = NULL;
T begvtx,endvtx;
E weight;
int endidx;
int edgeCount = 0;
for(int idx = 0; idx < numVertices; idx++) //顶点遍历
{
if(nodeTable[idx].first_Adj != NULL) //当前顶点的边链表不为空
{
first_temp = nodeTable[idx].first_Adj;
begvtx = GetVertexValue(idx); //获取该顶点的信息
while(first_temp != NULL) //遍历顶点边链表
{
endidx = first_temp->dest;
if(endidx > idx) //因为是无向图 所以只需输出一半的边信息
{
endvtx = GetVertexValue(endidx);
weight = GetEdgeWeight(begvtx,endvtx);
cout<<"<"<<begvtx<<", "<<endvtx<<">  权值:"<<weight<<endl;

edgeCount++; //在这里做一个增加效率的判断 如果一旦边输出完 不管后面还有多少重复的边 都立即返回
if(edgeCount == numEdges)
{
return;
}
}

first_temp = first_temp->link; //顶点边链表指针后移
}//end of while
}// end of if
}//end of for
}

template<class T, class E>
void GraphAdjList<T,E>::print(ostream &output)
{
int vertices_Num = numVertices;
int edges_Num = numEdges;

output<<"顶点数目: "<<vertices_Num<<endl;
output<<"边数目: "<<edges_Num<<endl;

EdgeNode<T,E> *first_temp = NULL;
T begvtx,endvtx;
E weight;
int endidx;
int edgeCount = 0;
for(int idx = 0; idx < numVertices; idx++)
{
if(nodeTable[idx].first_Adj != NULL)
{
first_temp = nodeTable[idx].first_Adj;
begvtx = GetVertexValue(idx);
while(first_temp != NULL)
{
endidx = first_temp->dest;
if(endidx > idx)
{
endvtx = GetVertexValue(endidx);
weight = GetEdgeWeight(begvtx,endvtx);
output<<"<"<<begvtx<<", "<<endvtx<<">  权值:"<<weight<<endl;

edgeCount++;
if(edgeCount == numEdges)
{
return;
}
}

first_temp = first_temp->link;
}//end of while
}// end of if
}//end of for
}

template<class T, class E>
ifstream& operator>>(ifstream &fin, GraphAdjList<T,E> &G)
{
int initializeVerticesNum; //从文件中读取 初始化定点数目
fin>>initializeVerticesNum;

T value;
for(int idx = 0; idx < initializeVerticesNum; idx++)
{
fin>>value;
G.InsertVertex(value); //以此插入顶点信息
}

int initializeEdgesNum; //配置边信息
fin>>initializeEdgesNum;
if(initializeEdgesNum > 0)
{
T begvtx, endvtx;
E weight;
int begidx, endidx;
for(int idx = 0; idx < initializeEdgesNum; idx++)
{
fin>>begvtx>>endvtx>>weight;

begidx = G.GetVertexPos(begvtx); //在这里做判断 文件读取进来的边的两个顶点信息是否均存在图中
endidx = G.GetVertexPos(endvtx);
if((begidx != G.s_NULLidx) && (endidx != G.s_NULLidx))
{
G.InsertEdge(begvtx, endvtx, weight);
}
}
}

return fin;
}

template<class T, class E>
ostream& operator<<(ostream &output, GraphAdjList<T,E> &G)
{
G.print(output);

return output;
}


同样也是用到了两个虚拟类型数据。在这里要说明一下的是,为什么要强调描述的是无向图,在这个图中用到了一个VertexNode<T,E> *nodeTable。如果是要描述有向图的话,就需要用到两个这种变量了,因为有向图中需要区别某一个顶点的出表(从此顶点出发,能走的边),入表(所有的顶点能到达该顶点的表)。关于有向图的邻接表表示后序贴出。

下面是主函数:

#include<iostream>
#include<fstream>
#include"GraphAdjList.h"
using namespace std;
void main()
{
GraphAdjList<char,int> gh('#',-1);
ifstream in("GRAPH.txt");
in>>gh;
cout<<gh<<endl;
gh.RemoveVertex('A');
cout<<gh<<endl;
}


这次没有自定义类型,用的都是基本内置类型。

截图如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: