目录
一:图的基类函数
template<class typeOfVer,class typeOfEdge> class graph{//基类:提供点数和边数信息 protected: int numVers; int numEdges; public: int getNumVers() const{return numVers;} int getNumEdges() const{ return numEdges;} };
注意:点数和边数用protected修饰,较新的编译器中,其继承函数要用:this->去访问
二:有向图的数据成员定义
//边结点 struct edgeNode{ int end; typeOfEdge weight; edgeNode* next; //边的构造函数 edgeNode(int e,typeOfEdge w,edgeNode* p=NULL): end(e),weight(w),next(p){} }; //点结点 struct verNode{ typeOfVer ver; edgeNode* head; //点的构造函数 verNode(edgeNode* h=NULL):head(h){} }; //点集动态数组 verNode* verList;
说明:
- 点集动态数组存放由verNode类型组成的数组,每个verNode类型记录顶点是什么+其引出的没有头顶点的邻接表的边的首地址
- 边集存放了边的终点(起点不用记录,就是指向的),以及边的权重和连在一起的next指针
/*工具函数*/ //返回顶点数组下标 int find(typeOfVer v) const{ for(int i=0;i<this->numVers;i++) if(verList[i].ver==v) return i; return -1; }
这是一个私有函数,目的是---将顶点输出成数组存储的下标
三:构造函数和析构函数
/*构造函数*/ adjListGraph(int vSize,const typeOfVer d[]){ //初始化保护成员 this->numVers=vSize; this->numEdges=0; //初始化顶点数组 verList=new verNode[vSize]; for(int i=0;i<this->numVers;i++){ verList[i].ver=d[i]; } }
注意:传入的const typeOfVer d[]前没有任何修饰符
别忘了对基类成员的点数和边数赋值
/*析构函数*/ ~adjListGraph(){ //释放邻接表 for(int i=0;i<this->numVers;i++){ edgeNode* p=verList[i].head; while(p){ verList[i].head=verList[i].head->next; delete p; p=p->next; } } //释放点集 delete[] verList; }
暂时会报错---不过注意先释放点的邻接表,再释放点集
四:增加+删除+判断边的存在性
先看有向图
1:增边----insert,要传入两点+边权值
/*insert--添加*/ void insert(typeOfVer x,typeOfVer y,typeOfEdge w){ int u=find(x); int v=find(y); verList[u].head=new edgeNode(v,w,verList[u].head); ++this->numEdges; }
这里:由于是没有头结点,而且头插法,所以先new一个结点,插入到当前头结点前,然后新的头结点指向它
edgeNode* tmp-----其next是verList[u].head,然后:verList[u].head=tmp
2:判断----exist,传入两个顶点
/*esist--判断*/ bool exist(typeOfVer x,typeOfVer y)const{ int u=find(x); int v=find(y); edgeNode* p=verList[u].head; while(p && p->end!=v) p=p->next; if(p) return true; else return false; }
这里用到编程一个常用思想---找结点
这里找的是:终点end是v的指针p,所以还有对while结束的两个条件判断是哪个不成立
3:删除----remove,传入两个顶点
/*删除---remove*/ void remove(typeOfVer x,typeOfVer y){ int u=find(x); int v=find(y); edgeNode* p=verList[u].head; /*看看头结点*/ if(p==NULL) return;//1:不存在 if(p->end==v){//2:头结点就是待删结点 verList[u].head=verList[u].head->next; delete p; --this->numEdges; return;//别忘了退出 } /*待删结点是中间的结点*/ //先找到待删结点的前驱 edgeNode* pre=p; while(pre->next&&pre->next->end!=v) pre=pre->next; if(pre->next){//待删结点存在 p=pre->next; pre->next=p->next; delete p; --this->numEdges; //由于程序到底了,无需return } }
较为麻烦的地方在于-----需要分析待删结点是不是头结点
因为头结点不用找,中间节点需要找
如果是无向图,由于边在邻接表中存两次
1:insert()中加入: verLst[v].head=new edgeNode(u,w,verList[v].head)
2:remove()末尾加一句:remove(y,x)---递归一次
exist无需改变,因为查一个即可
五:两种遍历
深度优先遍历
//深度优先遍历 void dfs()const{ //初始化标记数组 bool* visited=new bool[this->numVers]; for(int i=0;i<this->numVers;i++){ visited[i]=false; } //开始遍历 cout<<"深度优先遍历:"; for(int i=0;i<this->numVers;i++){ if(!visited[i]) dfs(i,visited); } cout<<endl; }
由于图不一定连通,必须用for遍历一遍顶点,深度遍历递归实现
私有函数实现:
//为公有dfs传递verlist void dfs(int i,bool visited[]) const{ cout<<verList[i].ver<<" "; visited[i]=true;//访问过了 edgeNode* p=verList[i].head; while(p){ if(!visited[p->end]) dfs(p->end,visited); p=p->next; } }
广度优先遍历
需要用到队列---调用标准库#include<queue>
//广度优先遍历 void bfs() const{ //初始化标记数组 bool* visited=new bool[this->numVers]; for(int i=0;i<this->numVers;i++){ visited[i]=false; } //遍历辅助变量 queue<int> q; int tmp; edgeNode* p; //开始遍历 cout<<"广度优先遍历:"; for(int i=0;i<this->numVers;i++){ if(!visited[i]) q.push(i);//考虑不连通图 while(!q.empty()){ tmp=q.front(); q.pop(); if(visited[tmp]) break; visited[tmp]=true; cout<<verList[tmp].ver<<" "; p=verList[tmp].head; while(p){ if(!visited[p->end]) q.push(p->end); p=p->next; } } } cout<<endl; }
一定在while输出前加一句:if(visited[tmp]) break;
防止重复访问-----因为在输出该节点前,可能因为以其是终点的边过多而多次入队
六:拓扑排序
//拓扑排序 void topSort() const{ //初始化入度数组--indegree[] int* indegree=new int[this->numVers]; for(int i=0;i<this->numVers;i++) indegree[i]=0; //计算起始图的入读数组 edgeNode* p; for(int i=0;i<this->numVers;i++){ for(p=verList[i].head;p;p=p->next){ ++indegree[p->end];//入度求法 } } cout<<"拓扑排序序列:"; //将入度为0的结点入队 queue<int> que; int tmp; for(int i=0;i<this->numVers;i++){ if(indegree[i]==0) que.push(i); } //拓扑过程 while(!que.empty()){ tmp=que.front(); que.pop(); cout<<verList[tmp].ver<<" "; for(p=verList[tmp].head;p;p=p->next){ if(--indegree[p->end]==0) que.push(p->end); } } cout<<endl; }
关注一下:有关邻接表入度计算的方法
for(int i=0;i<this->numVers;i++){ for(edgeNode* p=verList[i].head;p;p=p->next){ ++indegree[p->end];//入度求法 } }
注意是:++indegree[p->end]
七:主函数实现
int main(){ char dd[]={'a','b','c','d','e','f','g','h','i','j','k','l'}; adjListGraph<char,int> alg(12,dd); alg.insert('a','d',1); alg.insert('a','c',2); alg.insert('a','b',3); alg.insert('a','b',1); alg.insert('d','g',2); alg.insert('b','f',3); alg.insert('b','e',1); alg.insert('h','g',2); alg.insert('h','i',3); alg.insert('i','k',1); alg.insert('j','l',2); alg.remove('a','d'); alg.remove('b','e'); alg.dfs(); alg.bfs(); return 0; }