c++数据结构之图篇
一、图的基本概念与原理
什么是图?
有向图与无向图;
顶点
弧:弧尾和弧头
度:出度和入度
顶点
边
邻接点
连通图
完全图:边数=n(n-1)/2
生成树:边数=n-1
图的表示法
图的遍历
最小生成树:注意最小生成树的应用
图的应用:
- 路径规划
- 工程规划
- 战略规划
二、图的存储方式
对于无向图:存储顶点及边
对于有向图:存储顶点及弧
弧尾———-权值———狐尾
邻接矩阵
顶点的表示方法:
顶点索引 顶点数据
弧的表示方法:
邻接矩阵
c++
struct Node
{
顶点索引;
顶点数据;
};
c++
struct Map
{
顶点数据;
邻接矩阵;
};
邻接表(链式存储)
用于表示有向表
顶点的表示方法:
顶点索引 出弧链表头指针 顶点数据
弧的表示方法:
弧头顶点索引 下一条弧指针 弧数据
下面是上面链表表示法的具体体现:
最难理解的是出弧链表头指针及下一条弧指针
一个顶点发出多条弧时,第一条弧里带有下一条弧的指针;
弧数据相当于权重
c++
struct Node
{
顶点索引;
该顶点弧链表的头结点;
顶点数据;
};
c++
struct Arc
{
指向的顶点索引;
指向下一条弧的指针;
弧信息;
};
c++
struct Map
{
顶点数组;
};
十字链表
也用于存储有向图;
顶点的表示方法 | |||
---|---|---|---|
顶点索引 | 顶点数据 | 以该顶点为弧尾的弧节点指针 | 以该顶点为弧头的弧节点指针 |
弧的表示方法 | ||||
---|---|---|---|---|
弧尾顶点索引 | 弧头顶点索引 | 弧尾相同的下一条弧的指针 | 弧头相同的下一条弧的指针 | 弧的数据 |
十字链表通过结构体存储的方式如下:
c++
struct Node
{
顶点索引;
顶点数据;
第一条入弧节点指针;
第一条出弧节点指针;
};
c++
struct Arc
{
弧尾顶点索引;
弧头顶点索引;
指向下一条弧头相同的弧的指针;
指向下一条弧尾相同的弧的指针;
弧信息;
};
c++
struct Map
{
顶点数组;
};
邻接多重表
用于存储无向图;
顶点的表示方法 | ||
---|---|---|
顶点索引 | 连接该顶点的边 | 顶点数据 |
边的表示方法 | ||||
---|---|---|---|---|
A顶点索引 | B顶点索引 | 与A顶点相连接的下一条边的指针 | 与B顶点相连接的下一条边的指针 | 边数据 |
用结构体表示为:
c++
struct Node
{
顶点索引;
顶点数据;
第一条边节点指针;
};
c++
struct Egde
{
顶点A索引;
顶点B索引;
连接A的下一条边的指针;
连接B的下一条边的指针;
边信息;
};
c++
struct Map
{
顶点数组;
};
三、图的遍历
图的搜索按搜索方向分为:深度优先搜索和广度优先搜索;
深度优先搜索遵循二叉树搜索的“根左右”的原则,广度优先搜索是一层一层的节点进行搜索;
1、最小生成树
有普利姆算法,克鲁斯卡尔算法两种算法;普里姆是归并点;而克鲁斯卡尔是归并边;
普利姆(prim)算法
算法思想:
- [ ] 任意选择一个顶点,将其放入点集合中;下图选的顶点为A;
- [ ] 将与A顶点相连的边放入待选边集合中,如下图的A-B(6) A-F(1) A-E(5)所示;
- [ ] 选择一个权值最小的边放入边集合中,下图中是A-F(1);将该边所连的顶点F放入点集合中;
- [ ] 将与F点相连的边放入待选边集合中,如F-B(2) F-E(9) F-C(8) F-D(4),重复上面2~3步的过程,直到所有的点都被包含在点集合中(图中的点集合少写了个E)
- [ ] 普利姆算法适用于稠密的图,即边数较多的图,时间复杂度o(n^2);
克鲁斯卡尔(Kruskal)算法
算法思想:
- [ ] 将所有边放入待选边集合中;
- [ ] 取出当前待选边集合中权值最小的边放入已选边集合中,将边所连接的两个顶点放入已涉及点集合中
- [ ] 重复步骤2,如果已涉及的点的集合中的点不处于同一棵树中,则继续重复步骤2,直到已涉及的点的集合包含所有的点且所有点都在同一棵树中,最小生成树也就随之产生了;
- [ ] 克鲁斯卡尔算法适用于边数较少的稀疏图,算法复杂度为o(n^2);
四、图的编码实战
图的基本操作与遍历
Node.h文件:
#ifndef NODE_H_ #define NODE_H_ class Node { public: Node(char data = 0); //节点的构造函数 char m_cData; //节点的数据域 bool m_bIsVisited; //标记该节点是否被访问过 }; #endif
Node.cpp文件:
#include "Node.h" Node::Node(char data) { m_cData = data; m_bIsVisited = false; //默认为该节点没有被访问过 }
CMap.h文件:
#ifndef CMAP_H_ #define CMAP_H_ //包含vector类模板,后面的函数breadthFirstTraverseImpl(vector<int> preVec)会用到 #include <vector> #include "Node.h" using namespace std; class CMap { public: CMap(int capacity); //构造函数,创建图 ~CMap(); //析构函数,销毁图 bool addNode(Node *pNode); //增加节点 void resetNode(); //重置节点的访问状态为未访问 //给有向图的邻接矩阵的元素设置值 bool setValueToMatrixForDirectedGraph(int row,int col,int val = 1); //给无向图的邻接矩阵的元素设置值 bool setValueToMatrixForUndirectedGraph(int row,int col,int val = 1); void printMartrix(); //打印邻接矩阵 void depthFirstTraverse(int nodeIndex); //深度优先遍历 void breadthFirstTraverse(int nodeIndex); //广度优先遍历 private: bool getValueFromMatrix(int row,int col,int &val); //获取邻接矩阵中元素的值 //广度优先遍历的工具函数,用来存储某一层的元素 void breadthFirstTraverseImpl(vector<int> preVec); private: int m_iCapacity; //图的节点的容量 int m_iNodeCount; //图当前的节点的数量 Node *m_pNodeArray; //指向图中首节点的指针 int *m_pMatrix; //指向邻接矩阵首元素的指针 }; #endif
CMap.cpp文件:
#include "CMap.h" #include <iostream> using namespace std; CMap::CMap(int capacity) { m_iCapacity = capacity; m_iNodeCount = 0; m_pNodeArray = new Node[m_iCapacity]; m_pMatrix = new int[m_iCapacity*m_iCapacity]; //memset(m_pMatrix,0,m_iCapacity*m_iCapacity*sizeof(int)); //使用c++标准函数void *memset(void *s, int ch, size_t n)实现快速初始化 for(int i = 0;i < m_iCapacity*m_iCapacity;i++) { m_pMatrix[i] = 0; } } CMap::~CMap() { delete []m_pNodeArray; //先释放节点所对应的内存 delete []m_pMatrix; //再释放邻接矩阵所对应的内存 } bool CMap::addNode(Node *pNode) { if(pNode == NULL) //表示无法添加一个空节点 { return false; } //往节点矩阵中增加节点,新增加的节点的索引总是处于当前节点的末尾位置 m_pNodeArray[m_iNodeCount] = pNode->m_cData; m_iNodeCount++; //这种设计刚好在Node类中避免了添加数据成员:节点的索引 return true; } void CMap::resetNode() { for(int i = 0;i < m_iNodeCount;i++) { m_pNodeArray[i].m_bIsVisited = false; } } //给有向表的邻接矩阵中row行,col列的元素设置值,默认是1 bool CMap::setValueToMatrixForDirectedGraph(int row,int col,int val) { if(row < 0 || row >= m_iCapacity) { return false; } if(col < 0 || col >= m_iCapacity) { return false; } m_pMatrix[row*m_iCapacity+col] = val; return true; } //给无向表的邻接矩阵中row行,col列的元素设置值,默认是1 bool CMap::setValueToMatrixForUndirectedGraph(int row,int col,int val) { if(row < 0 || row >= m_iCapacity) { return false; } if(col < 0 || col >= m_iCapacity) { return false; } m_pMatrix[row*m_iCapacity+col] = val;//将二维数组按行展开为一维数组 m_pMatrix[col*m_iCapacity+row] = val;//将二维数组按列展开为一维数组 return true; } bool CMap::getValueFromMatrix(int row,int col,int &val) //获取邻接矩阵对应位置处的元素值 { if(row < 0 || row >= m_iCapacity) { return false; } if(col < 0 || col >= m_iCapacity) { return false; } val = m_pMatrix[row*m_iCapacity+col]; return true; } void CMap::printMartrix() //打印邻接矩阵中每个元素的值 { for(int i = 0;i < m_iCapacity;i++) { for(int k = 0;k < m_iCapacity;k++) { cout << m_pMatrix[i*m_iCapacity+k] << " "; } cout << endl; //每打印m_iCapacity个元素换一行 } } //深度优先遍历 用到了递归 void CMap::depthFirstTraverse(int nodeIndex) //既适用于无向图也适用于有向图 { int value = 0; cout << m_pNodeArray[nodeIndex].m_cData << " "; //打印节点的数据域的值 m_pNodeArray[nodeIndex].m_bIsVisited = true; //将节点的访问状态置为访问了的状态 for(int i = 0; i < m_iCapacity;i++) { getValueFromMatrix(nodeIndex,i,value); if(value != 0) //表示nodeIndex这个索引的节点与索引为i的节点相连 { if(m_pNodeArray[i].m_bIsVisited)//已经访问过的话就避免重复访问 { continue; } else { depthFirstTraverse(i); //使用到了递归 } } else { continue; } } } //广度优先遍历 这个遍历的算法实现起来最为麻烦,要使用到下面的函数breadthFirstTraverseImpl(vector<int> preVec) void CMap::breadthFirstTraverse(int nodeIndex) { cout << m_pNodeArray[nodeIndex].m_cData << " "; m_pNodeArray[nodeIndex].m_bIsVisited = true; vector<int> curVec; curVec.push_back(nodeIndex); breadthFirstTraverseImpl(curVec); } void CMap::breadthFirstTraverseImpl(vector<int> preVec)//vector类其实就是一个封装的数组 { int value = 0; vector<int> curVec; for(int j = 0;j < (int)preVec.size(); j++) { for(int i = 0;i < m_iCapacity;i++) { getValueFromMatrix(preVec[j],i,value);//查看上一层节点与当前层节点是否有连接的 if(value != 0) { if(m_pNodeArray[i].m_bIsVisited) { continue; } else { cout << m_pNodeArray[i].m_cData << " "; m_pNodeArray[i].m_bIsVisited = true; curVec.push_back(i); //把这一层存入curVec数组中 } } } } if(curVec.size() == 0) //如果该层没有点,则直接返回函数调用处,且什么都不做 { return; else //若该层有点,则递归执行当前的遍历操作 { breadthFirstTraverseImpl(curVec); } }
演示代码demo.cpp:
#include <iostream> #include "CMap.h" using namespace std; /* 图的存储 与 图的遍历 */ /* A / \ B D / \ / \ C F G — H \ / E */ /* 深度优先遍历:A B C E F D G H 广度优先遍历:A B D C F G F E 邻接矩阵: A B C D E F G H A 1 1 B 1 1 1 C 1 1 1 D 1 1 1 E 1 F 1 1 G 1 1 H 1 1 */ /*Time:2018年9月9日11:48:48 */ int main(void) { CMap *pMap = new CMap(8); Node *pNodeA = new Node('A'); Node *pNodeB = new Node('B'); Node *pNodeC = new Node('C'); Node *pNodeD = new Node('D'); Node *pNodeE = new Node('E'); Node *pNodeF = new Node('F'); Node *pNodeG = new Node('G'); Node *pNodeH = new Node('H'); pMap->addNode(pNodeA); pMap->addNode(pNodeB); pMap->addNode(pNodeC); pMap->addNode(pNodeD); pMap->addNode(pNodeE); pMap->addNode(pNodeF); pMap->addNode(pNodeG); pMap->addNode(pNodeH); pMap->setValueToMatrixForUndirectedGraph(0,1); pMap->setValueToMatrixForUndirectedGraph(0,3); pMap->setValueToMatrixForUndirectedGraph(1,2); pMap->setValueToMatrixForUndirectedGraph(1,5); pMap->setValueToMatrixForUndirectedGraph(3,6); pMap->setValueToMatrixForUndirectedGraph(3,7); pMap->setValueToMatrixForUndirectedGraph(6,7); pMap->setValueToMatrixForUndirectedGraph(2,4); pMap->setValueToMatrixForUndirectedGraph(4,5); pMap->printMartrix(); cout << endl; pMap->depthFirstTraverse(0); cout << endl; pMap->resetNode(); pMap->breadthFirstTraverse(0); cin.get(); return 0; }
注意图的深度优先遍历算法及图的广度优先遍历算法的算法的具体描述过程:
- 广度优先搜索(BFS)类似于二叉树的层序遍历算法,它的基本思想是:首先访问起始顶点v,然后由v出发,依次访问v的各个未被访问过的邻接顶点w1,w2,w3….wn,然后再依次访问w1,w2,…,wi的所有未被访问过的邻接顶点,再从这些访问过的顶点出发,再访问它们所有未被访问过的邻接顶点….
图的最小生成树算法
prim算法的代码演示如下:
Node.h文件:
#ifndef NODE_H_ #define NODE_H_ class Node { public: Node(char data = 0); //节点的构造函数 char m_cData; //节点的数据域 bool m_bIsVisited; //标记该节点是否被访问过 }; #endif
Node.cpp文件:
#include "Node.h" Node::Node(char data) { m_cData = data; m_bIsVisited = false; //默认为该节点没有被访问过 }
Edge.h文件:
#ifndef EDGE_H_ #define EDGE_H_ class Edge { public: Edge(int nodeIndexA = 0,int nodeIndexB = 0,int weightValue = 0); int m_iNodeIndexA; int m_iNodeIndexB; int m_iWeightValue; bool m_bSelected; }; #endif
Edge.cpp文件:
#include "Edge.h" Edge::Edge(int nodeIndexA,int nodeIndexB,int weightValue) { m_iNodeIndexA = nodeIndexA; m_iNodeIndexB = nodeIndexB; m_iWeightValue = weightValue; m_bSelected = false; }
CMap.h文件:
#ifndef CMAP_H_ #define CMAP_H_ //包含vector类模板,后面的函数breadthFirstTraverseImpl(vector<int> preVec)会用到 #include <vector> #include "Node.h" #include "Edge.h" using namespace std; class CMap { public: CMap(int capacity); //构造函数,创建图 ~CMap(); //析构函数,销毁图 bool addNode(Node *pNode); //增加节点 void resetNode(); //重置节点的访问状态为未访问 //给有向图的邻接矩阵的元素设置值 bool setValueToMatrixForDirectedGraph(int row,int col,int val = 1); //给无向图的邻接矩阵的元素设置值 bool setValueToMatrixForUndirectedGraph(int row,int col,int val = 1); void printMartrix(); //打印邻接矩阵 void depthFirstTraverse(int nodeIndex); //深度优先遍历 void breadthFirstTraverse(int nodeIndex); //广度优先遍历 void primTree(int nodeIndex); //普利姆生成树 private: bool getValueFromMatrix(int row,int col,int &val); //获取邻接矩阵中元素的值 //广度优先遍历的工具函数,用来存储某一层的元素 void breadthFirstTraverseImpl(vector<int> preVec); int getMinEdge(vector<Edge> edgeVec); private: int m_iCapacity; //图的节点的容量 int m_iNodeCount; //图当前的节点的数量 Node *m_pNodeArray; //指向图中首节点的指针 int *m_pMatrix; //指向邻接矩阵首元素的指针 Edge *m_pEdge; //指向最小生成树边的指针 }; #endif
CMap.cpp文件:
#include "CMap.h" #include <iostream> using namespace std; CMap::CMap(int capacity) { m_iCapacity = capacity; m_iNodeCount = 0; m_pNodeArray = new Node[m_iCapacity]; m_pMatrix = new int[m_iCapacity*m_iCapacity]; //memset(m_pMatrix,0,m_iCapacity*m_iCapacity*sizeof(int)); //使用c++标准函数void *memset(void *s, int ch, size_t n)实现快速初始化 for(int i = 0;i < m_iCapacity*m_iCapacity;i++) { m_pMatrix[i] = 0; } m_pEdge = new Edge[m_iCapacity-1]; //为边对象申请一段内存 } CMap::~CMap() { delete []m_pNodeArray; //先释放节点所对应的内存 delete []m_pMatrix; //再释放邻接矩阵所对应的内存 } bool CMap::addNode(Node *pNode) { if(pNode == NULL) //表示无法添加一个空节点 { return false; } //往节点矩阵中增加节点,新增加的节点的索引总是处于当前节点的末尾位置 m_pNodeArray[m_iNodeCount] = pNode->m_cData; m_iNodeCount++; //这种设计刚好在Node类中避免了添加数据成员:节点的索引 return true; } void CMap::resetNode() { for(int i = 0;i < m_iNodeCount;i++) { m_pNodeArray[i].m_bIsVisited = false; } } //给有向表的邻接矩阵中row行,col列的元素设置值,默认是1 bool CMap::setValueToMatrixForDirectedGraph(int row,int col,int val) { if(row < 0 || row >= m_iCapacity) { return false; } if(col < 0 || col >= m_iCapacity) { return false; } m_pMatrix[row*m_iCapacity+col] = val; return true; } //给无向表的邻接矩阵中row行,col列的元素设置值,默认是1 bool CMap::setValueToMatrixForUndirectedGraph(int row,int col,int val) { if(row < 0 || row >= m_iCapacity) { return false; } if(col < 0 || col >= m_iCapacity) { return false; } m_pMatrix[row*m_iCapacity+col] = val;//将二维数组按行展开为一维数组 m_pMatrix[col*m_iCapacity+row] = val;//将二维数组按列展开为一维数组 return true; } bool CMap::getValueFromMatrix(int row,int col,int &val) //获取邻接矩阵对应位置处的元素值 { if(row < 0 || row >= m_iCapacity) { return false; } if(col < 0 || col >= m_iCapacity) { return false; } val = m_pMatrix[row*m_iCapacity+col]; return true; } void CMap::printMartrix() //打印邻接矩阵中每个元素的值 { for(int i = 0;i < m_iCapacity;i++) { for(int k = 0;k < m_iCapacity;k++) { cout << m_pMatrix[i*m_iCapacity+k] << " "; } cout << endl; //每打印m_iCapacity个元素换一行 } } //深度优先遍历 用到了递归 void CMap::depthFirstTraverse(int nodeIndex) //既适用于无向图也适用于有向图 { int value = 0; cout << m_pNodeArray[nodeIndex].m_cData << " "; //打印节点的数据域的值 m_pNodeArray[nodeIndex].m_bIsVisited = true; //将节点的访问状态置为访问了的状态 for(int i = 0; i < m_iCapacity;i++) { getValueFromMatrix(nodeIndex,i,value); if(value != 0) //表示nodeIndex这个索引的节点与索引为i的节点相连 { if(m_pNodeArray[i].m_bIsVisited)//已经访问过的话就避免重复访问 { continue; } else { depthFirstTraverse(i); //使用到了递归 } } else { continue; } } } //广度优先遍历 这个遍历的算法实现起来最为麻烦,要使用到下面的函数breadthFirstTraverseImpl(vector<int> preVec) void CMap::breadthFirstTraverse(int nodeIndex) { cout << m_pNodeArray[nodeIndex].m_cData << " "; m_pNodeArray[nodeIndex].m_bIsVisited = true; vector<int> curVec; curVec.push_back(nodeIndex); breadthFirstTraverseImpl(curVec); } void CMap::breadthFirstTraverseImpl(vector<int> preVec)//vector类其实就是一个封装的数组 { int value = 0; vector<int> curVec; for(int j = 0;j < (int)preVec.size(); j++) { for(int i = 0;i < m_iCapacity;i++) { getValueFromMatrix(preVec[j],i,value);//查看上一层节点与当前层节点是否有连接的 if(value != 0) { if(m_pNodeArray[i].m_bIsVisited) { continue; } else { cout << m_pNodeArray[i].m_cData << " "; m_pNodeArray[i].m_bIsVisited = true; curVec.push_back(i); //把这一层存入curVec数组中 } } } } if(curVec.size() == 0) //如果该层没有点,则直接返回函数调用处,且什么都不做 { return; } else //若该层有点,则递归执行当前的遍历操作 { breadthFirstTraverseImpl(curVec); } } void CMap::primTree(int nodeIndex) { int value = 0; int edgeCount = 0; vector<int> nodeVec; //点集合 vector<Edge> edgeVec; //待选表集合 cout << m_pNodeArray[nodeIndex].m_cData << endl; nodeVec.push_back(nodeIndex); m_pNodeArray[nodeIndex].m_bIsVisited = true; //先判断当前算法的终止条件 while(edgeCount < m_iCapacity-1) //待选边的最大数目等于 { int temp = nodeVec.back(); //取出nodeVec向量中末尾的元素 for(int i = 0;i < m_iCapacity;i++) { getValueFromMatrix(temp,i,value); if(value != 0) { if(m_pNodeArray[i].m_bIsVisited) { continue; } else { Edge edge(temp,i,value); edgeVec.push_back(edge); //待选边集合 } } } //从可选边集合中找出最小的边 int edgeIndex = getMinEdge(edgeVec); edgeVec[edgeIndex].m_bSelected = true; cout << edgeVec[edgeIndex].m_iNodeIndexA << "----" << edgeVec[edgeIndex].m_iNodeIndexB << " "; cout << edgeVec[edgeIndex].m_iWeightValue << endl; m_pEdge[edgeCount] = edgeVec[edgeIndex]; //把选择的最小的边放入最小生成树的边集合中 edgeCount++; int nextNodeIndex = edgeVec[edgeIndex].m_iNodeIndexB; //通过选择的最小生成树的边找到下一个点 nodeVec.push_back(nextNodeIndex); //把这个点放入点集合中 m_pNodeArray[nextNodeIndex].m_bIsVisited = true; cout << m_pNodeArray[nextNodeIndex].m_cData << endl; } } int CMap::getMinEdge(vector<Edge> edgeVec) //取得最小边的函数 { int edgeIndex = 0; int minWeight = 0; int i = 0; for(;i < edgeVec.size();i++) { if(!edgeVec[i].m_bSelected) { minWeight = edgeVec[i].m_iWeightValue; edgeIndex = i; break; } } if(minWeight == 0) //即minWeight没有改变,即上面那个if没被执行 { return -1; //返回-1,程序终止,下面的代码就不会执行了 } for(;i < edgeVec.size();i++) { if(edgeVec[i].m_bSelected) { continue; } else { if(minWeight > edgeVec[i].m_iWeightValue) { minWeight = edgeVec[i].m_iWeightValue; edgeIndex = i; } } } return edgeIndex; }
演示代码demo.cpp:
#include <iostream> #include "CMap.h" using namespace std; /************************************************************/ /*Time:2018年9月11日11:25:29 /* A / \ B --- F -- E \ / \ / C --- D A B C D E F 0 1 2 3 4 5 A-B 6 A-E 5 A-F 1 B-C 3 B-F 2 C-F 8 C-D 7 D-F 4 D-E 2 E-F 9 */ int main(void) { CMap *pMap = new CMap(6); Node *pNodeA = new Node('A'); Node *pNodeB = new Node('B'); Node *pNodeC = new Node('C'); Node *pNodeD = new Node('D'); Node *pNodeE = new Node('E'); Node *pNodeF = new Node('F'); pMap->addNode(pNodeA); pMap->addNode(pNodeB); pMap->addNode(pNodeC); pMap->addNode(pNodeD); pMap->addNode(pNodeE); pMap->addNode(pNodeF); pMap->setValueToMatrixForUndirectedGraph(0,1,6); pMap->setValueToMatrixForUndirectedGraph(0,4,5); pMap->setValueToMatrixForUndirectedGraph(0,5,1); pMap->setValueToMatrixForUndirectedGraph(1,2,3); pMap->setValueToMatrixForUndirectedGraph(1,5,2); pMap->setValueToMatrixForUndirectedGraph(2,5,8); pMap->setValueToMatrixForUndirectedGraph(2,3,7); pMap->setValueToMatrixForUndirectedGraph(3,5,4); pMap->setValueToMatrixForUndirectedGraph(3,4,2); pMap->setValueToMatrixForUndirectedGraph(4,5,9); pMap->primTree(0); cin.get(); return 0; }
克鲁斯卡尔算法代码演示:
Node.h,Node.cpp,Edge.h,Edge.cpp文件与上面一致,下面来看CMap.h、CMap.cpp文件的内容
CMap.h文件:
#ifndef CMAP_H_ #define CMAP_H_ //包含vector类模板,后面的函数breadthFirstTraverseImpl(vector<int> preVec)会用到 #include <vector> #include "Node.h" #include "Edge.h" using namespace std; class CMap { public: CMap(int capacity); //构造函数,创建图 ~CMap(); //析构函数,销毁图 bool addNode(Node *pNode); //增加节点 void resetNode(); //重置节点的访问状态为未访问 //给有向图的邻接矩阵的元素设置值 bool setValueToMatrixForDirectedGraph(int row,int col,int val = 1); //给无向图的邻接矩阵的元素设置值 bool setValueToMatrixForUndirectedGraph(int row,int col,int val = 1); void printMartrix(); //打印邻接矩阵 void depthFirstTraverse(int nodeIndex); //深度优先遍历 void breadthFirstTraverse(int nodeIndex); //广度优先遍历 void primTree(int nodeIndex); //普利姆生成树 void KruskalTree(); //克鲁斯卡尔算法生成最小生成树 private: bool getValueFromMatrix(int row,int col,int &val); //获取邻接矩阵中元素的值 //广度优先遍历的工具函数,用来存储某一层的元素 void breadthFirstTraverseImpl(vector<int> preVec); int getMinEdge(vector<Edge> edgeVec);//获取最小边的函数 bool isInSet(vector<int> nodeSet,int target);//判断边是否处于同一通路 void mergeNodeSet(vector<int> &nodeSetA,vector<int> nodeSetB); private: int m_iCapacity; //图的节点的容量 int m_iNodeCount; //图当前的节点的数量 Node *m_pNodeArray; //指向图中首节点的指针 int *m_pMatrix; //指向邻接矩阵首元素的指针 Edge *m_pEdge; //指向最小生成树边的指针 }; #endif
CMap.cpp文件:
#include "CMap.h" #include <iostream> using namespace std; CMap::CMap(int capacity) { m_iCapacity = capacity; m_iNodeCount = 0; m_pNodeArray = new Node[m_iCapacity]; m_pMatrix = new int[m_iCapacity*m_iCapacity]; //memset(m_pMatrix,0,m_iCapacity*m_iCapacity*sizeof(int)); //使用c++标准函数void *memset(void *s, int ch, size_t n)实现快速初始化 for(int i = 0;i < m_iCapacity*m_iCapacity;i++) { m_pMatrix[i] = 0; } m_pEdge = new Edge[m_iCapacity-1]; //为边对象申请一段内存 } CMap::~CMap() { delete []m_pNodeArray; //先释放节点所对应的内存 delete []m_pMatrix; //再释放邻接矩阵所对应的内存 delete []m_pEdge; //释放边所占用的内存 } bool CMap::addNode(Node *pNode) { if(pNode == NULL) //表示无法添加一个空节点 { return false; } //往节点矩阵中增加节点,新增加的节点的索引总是处于当前节点的末尾位置 m_pNodeArray[m_iNodeCount] = pNode->m_cData; m_iNodeCount++; //这种设计刚好在Node类中避免了添加数据成员:节点的索引 return true; } void CMap::resetNode() { for(int i = 0;i < m_iNodeCount;i++) { m_pNodeArray[i].m_bIsVisited = false; } } //给有向表的邻接矩阵中row行,col列的元素设置值,默认是1 bool CMap::setValueToMatrixForDirectedGraph(int row,int col,int val) { if(row < 0 || row >= m_iCapacity) { return false; } if(col < 0 || col >= m_iCapacity) { return false; } m_pMatrix[row*m_iCapacity+col] = val; return true; } //给无向表的邻接矩阵中row行,col列的元素设置值,默认是1 bool CMap::setValueToMatrixForUndirectedGraph(int row,int col,int val) { if(row < 0 || row >= m_iCapacity) { return false; } if(col < 0 || col >= m_iCapacity) { return false; } m_pMatrix[row*m_iCapacity+col] = val;//将二维数组按行展开为一维数组 m_pMatrix[col*m_iCapacity+row] = val;//将二维数组按列展开为一维数组 return true; } bool CMap::getValueFromMatrix(int row,int col,int &val) //获取邻接矩阵对应位置处的元素值 { if(row < 0 || row >= m_iCapacity) { return false; } if(col < 0 || col >= m_iCapacity) { return false; } val = m_pMatrix[row*m_iCapacity+col]; return true; } void CMap::printMartrix() //打印邻接矩阵中每个元素的值 { for(int i = 0;i < m_iCapacity;i++) { for(int k = 0;k < m_iCapacity;k++) { cout << m_pMatrix[i*m_iCapacity+k] << " "; } cout << endl; //每打印m_iCapacity个元素换一行 } } //深度优先遍历 用到了递归 void CMap::depthFirstTraverse(int nodeIndex) //既适用于无向图也适用于有向图 { int value = 0; cout << m_pNodeArray[nodeIndex].m_cData << " "; //打印节点的数据域的值 m_pNodeArray[nodeIndex].m_bIsVisited = true; //将节点的访问状态置为访问了的状态 for(int i = 0; i < m_iCapacity;i++) { getValueFromMatrix(nodeIndex,i,value); if(value != 0) //表示nodeIndex这个索引的节点与索引为i的节点相连 { if(m_pNodeArray[i].m_bIsVisited)//已经访问过的话就避免重复访问 { continue; } else { depthFirstTraverse(i); //使用到了递归 } } else { continue; } } } //广度优先遍历 这个遍历的算法实现起来最为麻烦,要使用到下面的函数breadthFirstTraverseImpl(vector<int> preVec) void CMap::breadthFirstTraverse(int nodeIndex) { cout << m_pNodeArray[nodeIndex].m_cData << " "; m_pNodeArray[nodeIndex].m_bIsVisited = true; vector<int> curVec; curVec.push_back(nodeIndex); breadthFirstTraverseImpl(curVec); } void CMap::breadthFirstTraverseImpl(vector<int> preVec)//vector类其实就是一个封装的数组 { int value = 0; vector<int> curVec; for(int j = 0;j < (int)preVec.size(); j++) { for(int i = 0;i < m_iCapacity;i++) { getValueFromMatrix(preVec[j],i,value);//查看上一层节点与当前层节点是否有连接的 if(value != 0) { if(m_pNodeArray[i].m_bIsVisited) { continue; } else { cout << m_pNodeArray[i].m_cData << " "; m_pNodeArray[i].m_bIsVisited = true; curVec.push_back(i); //把这一层存入curVec数组中 } } } } if(curVec.size() == 0) //如果该层没有点,则直接返回函数调用处,且什么都不做 { return; } else //若该层有点,则递归执行当前的遍历操作 { breadthFirstTraverseImpl(curVec); } } void CMap::primTree(int nodeIndex) { int value = 0; int edgeCount = 0; vector<int> nodeVec; //点集合 vector<Edge> edgeVec; //待选表集合 cout << m_pNodeArray[nodeIndex].m_cData << endl; nodeVec.push_back(nodeIndex); m_pNodeArray[nodeIndex].m_bIsVisited = true; //先判断当前算法的终止条件 while(edgeCount < m_iCapacity-1) //待选边的最大数目等于 { int temp = nodeVec.back(); //取出nodeVec向量中末尾的元素 for(int i = 0;i < m_iCapacity;i++) { getValueFromMatrix(temp,i,value); if(value != 0) { if(m_pNodeArray[i].m_bIsVisited) { continue; } else { Edge edge(temp,i,value); edgeVec.push_back(edge); //待选边集合 } } } //从可选边集合中找出最小的边 int edgeIndex = getMinEdge(edgeVec); edgeVec[edgeIndex].m_bSelected = true; cout << edgeVec[edgeIndex].m_iNodeIndexA << "----" << edgeVec[edgeIndex].m_iNodeIndexB << " "; cout << edgeVec[edgeIndex].m_iWeightValue << endl; m_pEdge[edgeCount] = edgeVec[edgeIndex]; //把选择的最小的边放入最小生成树的边集合中 edgeCount++; int nextNodeIndex = edgeVec[edgeIndex].m_iNodeIndexB; //通过选择的最小生成树的边找到下一个点 nodeVec.push_back(nextNodeIndex); //把这个点放入点集合中 m_pNodeArray[nextNodeIndex].m_bIsVisited = true; cout << m_pNodeArray[nextNodeIndex].m_cData << endl; } } int CMap::getMinEdge(vector<Edge> edgeVec) //取得最小边的函数 { int edgeIndex = 0; int minWeight = 0; int i = 0; for(;i < (int)edgeVec.size();i++) { if(!edgeVec[i].m_bSelected) { minWeight = edgeVec[i].m_iWeightValue; edgeIndex = i; break; } } if(minWeight == 0) //即minWeight没有改变,即上面那个if没被执行 { return -1; //返回-1,程序终止,下面的代码就不会执行了 } for(;i < (int)edgeVec.size();i++) { if(edgeVec[i].m_bSelected) { continue; } else { if(minWeight > edgeVec[i].m_iWeightValue) { minWeight = edgeVec[i].m_iWeightValue; edgeIndex = i; } } } return edgeIndex; } //克鲁斯卡尔算法生成树 void CMap::KruskalTree() { int value = 0; int edgeCount = 0; //定义存放节点集合的数组 vector <vector <int>> nodeSets; //第一步:取出所有边 vector <Edge> edgeVec; for(int i = 0;i < m_iCapacity;i++) { for(int k = i+1;k < m_iCapacity;k++) { getValueFromMatrix(i,k,value); if(value != 0) { Edge edge(i,k,value); edgeVec.push_back(edge); } } } //第二步:从所有边中取出组成最小生成树的边 //1.找到算法的结束条件 while(edgeCount < m_iCapacity-1) { //2.从边集合中找到最小边 int minEdgeIndex = getMinEdge(edgeVec); edgeVec[minEdgeIndex].m_bSelected = true; //3.找出最小边连接的点 int nodeAIndex = edgeVec[minEdgeIndex].m_iNodeIndexA; int nodeBIndex = edgeVec[minEdgeIndex].m_iNodeIndexB; bool nodeAIsInSet = false; bool nodeBIsInSet = false; int nodeAInSetLabel = -1; int nodeBInSetLabel = -1; //4.找出点所在的集合 for(int i = 0;i < (int)nodeSets.size();i++) { nodeAIsInSet = isInSet(nodeSets[i],nodeAIndex); if(nodeAIsInSet) { nodeAInSetLabel = i; } } for(int i = 0;i < (int)nodeSets.size();i++) { nodeBIsInSet = isInSet(nodeSets[i],nodeBIndex); if(nodeBIsInSet) { nodeBInSetLabel = i; } } //5.根据点所在的集合的不同做出不同处理 if(nodeAInSetLabel == -1 && nodeBInSetLabel == -1) { vector <int> vec; vec.push_back(nodeAIndex); vec.push_back(nodeBIndex); nodeSets.push_back(vec); } else if(nodeAInSetLabel == -1 && nodeAInSetLabel != -1) { nodeSets[nodeBInSetLabel].push_back(nodeAIndex); } else if(nodeAInSetLabel != -1 && nodeAInSetLabel == -1) { nodeSets[nodeAInSetLabel].push_back(nodeBIndex); } else if(nodeAInSetLabel != -1 && nodeAInSetLabel != -1 && nodeAInSetLabel != nodeBInSetLabel) { mergeNodeSet(nodeSets[nodeAInSetLabel],nodeSets[nodeBInSetLabel]); for(int k = nodeBInSetLabel;k < (int)nodeSets.size()-1;k++) { nodeSets[k] = nodeSets[k+1]; } } else if(nodeAInSetLabel != -1 && nodeAInSetLabel != -1 && nodeAInSetLabel == nodeBInSetLabel) { continue; } m_pEdge[edgeCount] = edgeVec[minEdgeIndex]; edgeCount++; cout << edgeVec[minEdgeIndex].m_iNodeIndexA << "--" << edgeVec[minEdgeIndex].m_iNodeIndexB << " "; cout << edgeVec[minEdgeIndex].m_iWeightValue << endl; } } bool CMap::isInSet(vector<int> nodeSet,int target) { for(int i = 0;i < (int)nodeSet.size();i++) { if(nodeSet[i] == target) { return true; } } return false; } void CMap::mergeNodeSet(vector<int> &nodeSetA,vector<int> nodeSetB) { for(int i = 0;i < (int)nodeSetB.size();i++) { nodeSetA.push_back(nodeSetB[i]); } }