<C/C++图>图的邻接表存储(C++模板实现)
2015-03-09 23:13
806 查看
一,邻接表表示法
图的邻接矩阵存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。邻接表由表头结点和表结点两部分组成,其中图中每个顶点均对应一个存储在数组中的表头结点。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。如图8.12所示,表结点存放的是邻接顶点在数组中的索引。对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。
有向图的邻接表有出边表和入边表(又称逆邻接表)之分。出边表的表结点存放的是从表头结点出发的有向边所指的尾顶点;入边表的表结点存放的则是指向表头结点的某个头顶点。如图所示,图(b)和(c)分别为有向图(a)的出边表和入边表。
以上所讨论的邻接表所表示的都是不带权的图,如果要表示带权图,可以在表结点中增加一个存放权的字段,其效果如图8.14所示
注意:观察图8.14可以发现,当删除存储表头结点的数组中的某一元素,有可能使部分表头结点索引号的改变,从而导致大面积修改表结点的情况发生。可以在表结点中直接存放指向表头结点的指针以解决这个
问题。在实际创建邻接表时,甚至可以使用链表代替数组存放表头结点或使用顺序表代替链表存放表结点。对所学的数据结构知识应当根据实际情况及所使用语言的特点灵活应用,切不可生搬硬套。
【2】《维基百科》http://zh.wikipedia.org/wiki
【3】《算法导论》
图的邻接矩阵存储方法跟树的孩子链表示法相类似,是一种顺序分配和链式分配相结合的存储结构。邻接表由表头结点和表结点两部分组成,其中图中每个顶点均对应一个存储在数组中的表头结点。如这个表头结点所对应的顶点存在相邻顶点,则把相邻顶点依次存放于表头结点所指向的单向链表中。如图8.12所示,表结点存放的是邻接顶点在数组中的索引。对于无向图来说,使用邻接表进行存储也会出现数据冗余,表头结点A所指链表中存在一个指向C的表结点的同时,表头结点C所指链表也会存在一个指向A的表结点。
有向图的邻接表有出边表和入边表(又称逆邻接表)之分。出边表的表结点存放的是从表头结点出发的有向边所指的尾顶点;入边表的表结点存放的则是指向表头结点的某个头顶点。如图所示,图(b)和(c)分别为有向图(a)的出边表和入边表。
以上所讨论的邻接表所表示的都是不带权的图,如果要表示带权图,可以在表结点中增加一个存放权的字段,其效果如图8.14所示
注意:观察图8.14可以发现,当删除存储表头结点的数组中的某一元素,有可能使部分表头结点索引号的改变,从而导致大面积修改表结点的情况发生。可以在表结点中直接存放指向表头结点的指针以解决这个
问题。在实际创建邻接表时,甚至可以使用链表代替数组存放表头结点或使用顺序表代替链表存放表结点。对所学的数据结构知识应当根据实际情况及所使用语言的特点灵活应用,切不可生搬硬套。
二,C++模板类实现
1,Graph.h的代码实现
(以下邻结表实现与以上并不一致):#include "windows.h" #include <stdio.h> #include "iostream" #include "vector" #include "algorithm" #include "math.h" #include <thread> #include <mutex> #include <condition_variable> #include <stdio.h> using namespace std; template<class DistType/*边的权值的类型*/> class Edge//边的定义 { public: Edge(int dest, DistType weight) { m_nposTable = dest; m_distWeight = weight; m_pnext = NULL; } ~Edge() { } public: int m_nposTable;//该边的目的顶点在顶点集中的位置 DistType m_distWeight;//边的权重值 Edge<DistType> *m_pnext;//下一条边(注意不是下一个顶点,因为m_nposTable已经知道了这个顶点的位置) }; //声明 template<class NameType/*顶点集名字类型*/, class DistType/*距离的数据类型*/> class Graph; template<class NameType/*顶点集名字类型*/, class DistType/*距离的数据类型*/> class Vertex//顶点的定义 { public: Vertex() { padjEdge = NULL; m_vertexName = 0; } ~Vertex() { Edge<DistType> *pmove = padjEdge; while (pmove) { padjEdge = pmove->m_pnext; delete pmove; pmove = padjEdge; } } private: friend class Graph<NameType, DistType>;//允许Graph类任意访问 NameType m_vertexName;//顶点中的数据内容 Edge<DistType> *padjEdge;//顶点的邻边 }; template<class NameType/*顶点集名字类型*/, class DistType/*距离的数据类型*/> class Graph { public: Graph(int size = m_nDefaultSize/*图顶点集的规模*/) { m_pVertexTable = new Vertex<NameType, DistType>[size]; //为顶点集分配内存 if (m_pVertexTable == NULL) { exit(1); } m_numVertexs = 0; m_nmaxSize = size; m_nnumEdges = 0; } ~Graph() { Edge<DistType> *pmove; for (int i = 0; i < this->m_numVertexs; i++) { pmove = this->m_pVertexTable[i].padjEdge; if (pmove){ this->m_pVertexTable[i].padjEdge = pmove->m_pnext; delete pmove; pmove = this->m_pVertexTable[i].padjEdge; } } delete[] m_pVertexTable; } int GetNumEdges() { return m_nnumEdges / 2; } int GetNumVertexs() { return m_numVertexs; } bool IsGraphFull() const { //图满的? return m_nmaxSize == m_numVertexs; } //在顶点集中位置为v1和v2的顶点之间插入边 bool InsertEdge(int v1, int v2, DistType weight = m_Infinity); bool InsertVertex(const NameType vertex); //插入顶点名字为vertex的顶点 void PrintGraph(); //打印图 private: Vertex<NameType, DistType> *m_pVertexTable; //顶点集 int m_numVertexs;//图中当前的顶点数量 int m_nmaxSize;//图允许的最大顶点数 static const int m_nDefaultSize = 10; //默认的最大顶点集数目 static const DistType m_Infinity = 65536; //边的默认权值(可以看成是无穷大) int m_nnumEdges;//图中边的数目 int GetVertexPosTable(const NameType vertex); //用该顶点的名字来寻找其在顶点集中的位置 }; //返回顶点vertexname在m_pVertexTable(顶点集)中的位置 //如果不在顶点集中就返回-1 template<class NameType, class DistType> int Graph<NameType, DistType>::GetVertexPosTable(const NameType vertexname) { for (int i = 0; i < this->m_numVertexs; i++) { if (vertexname == m_pVertexTable[i].m_vertexName) { return i; } } return -1; } //打印图中的各个顶点及其链接的边的权重 template<class NameType, class DistType> void Graph<NameType, DistType>::PrintGraph() { Edge<DistType> *pmove; for (int i = 0; i<this->m_numVertexs; i++) { cout << this->m_pVertexTable[i].m_vertexName << "--->"; pmove = this->m_pVertexTable[i].padjEdge; while (pmove) { cout << pmove->m_distWeight << "--->" << this->m_pVertexTable[pmove->m_nposTable].m_vertexName << "--->"; pmove = pmove->m_pnext; } cout << "NULL" << endl; } } //顶点依次插入到分配好的顶点集中 template<class NameType, class DistType> bool Graph<NameType, DistType>::InsertVertex(const NameType vertexname) { if (IsGraphFull()) { cerr << "图已经满,请勿再插入顶点!" << endl; return false; } else { this->m_pVertexTable[this->m_numVertexs].m_vertexName = vertexname; this->m_numVertexs++; } return true; } //在顶点集位置为v1和v2的顶点之间插入权值为weght的边(务必保持输入的准确性,否则.....) template<class NameType, class DistType> bool Graph<NameType, DistType>::InsertEdge(int v1, int v2, DistType weight) { if (v1 < 0 && v1 > this->m_numVertexs && v2 < 0 && v2 > this->m_numVertexs) { cerr << "边的位置参数错误,请检查! " << endl; return false; } else { Edge<DistType> *pmove = m_pVertexTable[v1].padjEdge; if (pmove == NULL)//如果顶点v1没有邻边 { //建立顶点v1的第一个邻边(该邻边指明了目的顶点) m_pVertexTable[v1].padjEdge = new Edge<DistType>(v2, weight); m_nnumEdges++;//图中边的数目 return true; } else//如果有邻边 { while (pmove->m_pnext) { pmove = pmove->m_pnext; } pmove->m_pnext = new Edge<DistType>(v2, weight); m_nnumEdges++;//图中边的数目 return true; } } }
2,主测试代码:
// ConsoleAppMyGraph.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include "Graph.h" #include <iostream> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { Graph<char *, int> graph(7); char *vertex[7] = {"【地大】", "【武大】", "【华科】", "【交大】", "【北大】", "【清华】", "【复旦】"};//顶点集 for (int i=0; i<7; i++) { graph.InsertVertex(vertex[i]); } cout<<"一,图的初始化(邻结表存储):======================================"<<endl; graph.PrintGraph(); cout<<"图中顶点的数目:"<<graph.GetNumVertexs()<<endl; cout <<endl; int edge[7][3] = {{0, 1, 43}/*地大到武大的距离*/, {0, 2, 12}, {1, 2, 38}, {2, 3 ,1325}, {3, 6, 55}, {4, 5, 34}, {4, 6, 248}}; //分配距离 for (int i=0; i<7; i++) { graph.InsertEdge(edge[i][0], edge[i][1], edge[i][2]); graph.InsertEdge(edge[i][1], edge[i][0], edge[i][2]); } cout<<"二,添加边后的图(无向图):=================================="<<endl; graph.PrintGraph(); cout<<"图中边的数目(实际上是所示边数的两倍,因为是双向的):"<<graph.GetNumEdges()<<endl; cout <<endl; system("color 0A"); system("pause"); return 0; }
3,测试结果:
参考资源:
【1】http://www.cnblogs.com/rollenholt/archive/2012/04/09/2439055.html【2】《维基百科》http://zh.wikipedia.org/wiki
【3】《算法导论》
相关文章推荐
- 【数字图像处理】YUV420转RGB并BMP存储<纯C++实现>
- <C/C++数据结构>散列表(C++模板实现)
- <C/C++数据结构>队列(C++模板实现)
- 带权图(网)的邻接表存储的C++实现
- 最小生成树Prim算法实现(采用邻接表存储)C++实现
- [置顶] 从零开始学C++之对象语义与值语义、资源管理(RAII、资源所有权)、模拟实现auto_ptr<class>、实现Ptr_vector
- C++模板编程->嵌套实现元组
- 数据结构_图_邻接表做存储结构实现求无向图的连通分量_C++实现
- C++第九周【任务二】定义Time类中的<<和>>运算符重载,实现时间的输入输出
- (原创)一个和c#中Lazy<T>类似的c++ Lazy<T>类的实现
- 用C++模板实现线性表的链式存储的基本算法(数据结构C++版 北京科海)
- C++自定义修饰键,实现如<Capslock+J>等组合键的按键映射
- 第九周C++任务三。定义分数类中<<和>>运算符重载,实现分数的输入输出
- 关于c++中模板 T 无法限制的替代方案template< T extends Base>
- <C++实践系列>C++中的模板(template)
- 求有向图的强连通分支(邻接表存储)C++实现
- 《C++第九周实验报告1-1》--------接第8周任务1,定义Complex类中的<<和>>运算符的重载,实现输入和输出
- 《C++第九周实验报告2-1》---接第8周任务2,定义Time类中的<<和>>运算符重载,实现时间的输入输出
- <C++>C++中四类成员在内存中存储的方式
- 【数字图像处理】<纯C++>读取、裁剪、缩放、旋转和存储8位bmp灰度图像