数据结构探险之图篇

本文及代码收录于个人编程笔记(整理中,欢迎Star):
https://github.com/mtianyan/Programming-Notebook

图的简介

什么是图?

如下图:无向图 & 有向图(箭头分方向)。图可以看做节点和连线的集合,无向图中可以被认为是有来有回的两条线

无向图&有向图

图中的概念:

有向图中的概念
  • 结点称为顶点,之间的线称为弧。弧尾和弧头(箭头)。从顶点发出去和射入的。
  • 出度:一个顶点发射出去的线数 & 入度:一个顶点射入的线数。
无向图中的概念
  • 顶点 & 边(一般指无向图中的连线),连线连起来的两个结点称为邻接点。
连通图
  • 连通图: 对于无向图中的每一个顶点,都有通往其他顶点的连线,间接,直接的都可以。
完全图
  • 任意两个结点之间都有路径相互连接,就是完全图。完全图的边数计算边数=n(n-1)/2
生成树
  • 生成树,完全图简化到只有最小数量的边连接每一个顶点,边数为n-1

图的表示法 & 图的遍历 & 最小生成树(高速公路最小)。

图的应用

规划公路,光纤,找最近的路。

图的基本概念及存储方式

图的存储结构也就是把图变成数据存起来供我们使用:

无向图是由边 & 顶点组成的; 有向图是由弧 & 顶点组成

图的存储结构:邻接矩阵(数组);邻接表(链表)有向; 十字链表(链表)有向;邻接多重表(链表)无向

弧尾&权值&弧头

箭头的起端是弧尾,终端是弧头,箭头上面会有一个权值。道路,300公里,5天路程之类。

邻接矩阵(数组)

无论哪种方法都是要存储顶点的,顶点包括顶点的索引和顶点数据两部分。

有向图邻接矩阵

邻接矩阵只需要存储顶点的索引和顶点数据;顶点的索引不可重复;有弧用1,没弧用0,自身不能到自身。

无向图邻接矩阵

主对角线对称,只记录一半即可节省存储空间。

邻接矩阵定义为二维数组

变成一个int matrix[4][4] 的二维数组

顶点与图的结构体表示:

顶点与图的结构体表示

邻接表-链式存储

邻接表-链式存储

通过一个顶点索引,找到出弧链表头指针。然后找到下一个节点。下一个节点又可以找到出弧。

弧指针,0,1,2,3代表索引。四个顶点都有弧指针,说明v1既有弧指向v2,又有指向v3,又有指向v4。v2 没有出度直接指向NULL;v3 有一条指向v4的弧。
v4 有指向v1的弧。

邻接表记录出弧,逆邻接表记录入弧的;弧头改为弧尾。

数据结构代码体现:

struct Node
{顶点索引;该顶点弧链表的头结点; //重要信息顶点数据;
};
struct Map
{顶点数组;
};
struct Arc
{指向的顶点索引;指向下一条弧的指针;弧信息;
};

十字链表-链式存储

十字链表的顶点表示要存储: 顶点索引 顶点数据 以该顶点为弧尾的弧结点指针 以该顶点为弧头的弧结点指针

十字链表的弧表示要存储: 弧尾顶点索引 弧头顶点索引 弧尾相同的下一条弧的指针 弧头相同的下一条弧的指针 弧的数据

struct Arc
{弧尾顶点索引; 弧头顶点索引;指向下一条弧头相同的弧的指针;指向下一条弧尾相同的弧的指针;弧信息;
};
struct Node
{顶点索引;顶点数据;第一条入弧结点指针;第一条出弧结点指针;
};
struct Map
{
顶点数组;
};

邻接多重表-链式存储(无向图)

顶点的表示方法: 顶点索引;连接该顶点的边;顶点数据

边的表示方法: A顶点索引;B顶点索引;与A顶点相连接的下一条边的指针;与B顶点相连接的下一条边的指针;边数据

struct Egde
{
顶点A索引;
顶点B索引;
连接A的下一条边的指针;
连接B的下一条边的指针;;
边信息;
};struct Node
{
顶点索引;
顶点数据;
第一条边结点指针;
};struct Map
{
顶点数组;
};

图的遍历

  • 图的遍历分为两种:深度优先搜索 & 广度优先搜索

深度:a-b-c-e-f-d-g-h(也就是前序遍历:根左右)

广度优先搜索:按着一层一层搜索

不同的遍历方式形成不同的生成树。

最小生成树

有a.b.c.e.f六个城市修路,线上的权值为成本。a修到b,不如a-f-b。希望得到的结果是如右图。

最小生成树算法我们这里介绍两种: 普里姆(Prim)算法; 克鲁斯卡尔(Kruskal)算法

  • 普里姆(Prim)算法介绍

先有一个点的集合。这个点的集合是纳入最小生成树的点的集合,还要有一个边的集合,这个边的集合也是纳入最小生成树的边的集合;待选边集合:当我们选定一个顶点,该顶点可以走的边的集合。

假设从A开始做最小生成树。从A出去有三条待选边: A-B(6) A-F(1) A-E(5)。在待选边集合中找到权值最小的边,如A-F(1),将其放入边集合。这样我们就确定了第一条边和第一个点。

我们的第一条边连接了F,从A和F伸出的所有的边都会被纳入待选边的集合中。

可以看到如上图所示,我们的待选边集合一下子就变大了。此时的待选边集合中F-B(2)权值最小,因此F-B(2)加入边集合,B加入点集合。

此时我们的待选边集合又会因为B的加入而变大,找到B-C(3)权值最小,因此B-C(3)加入边集合,C加入点集合。

此时因为C的加入,我们的待选边集合又会进一步扩大,这其中F-D(4)是最小权值的一条,D加入点集合。

加入D之后又形成了更新的待选边集合,此时D-E(2)权值最小,加入边集合,E加入点集合。

当点集合已经包含所有顶点树,该最小生成树生成完毕。

  • 克鲁斯卡尔(Kruskal)算法介绍

把所有边放入待选边集合中,在所有边中选取一条权值最小的边放入已选边集合,把该边放入已选边集合中,选定了边就是选定了涉及的点,将该边的两个点放入已涉及点集合中。

然后再次寻找待选边集合中权值次小的边:F-B(2) / D-E(2)权值为2都可以选。此时要判断有没有和原来的边形成闭环,形成闭环就抛弃掉该边。

选择D-E(2)边之后,因为DE和原来的AFB没有关联关系,所以涉及点集合被分为两个。

此时再选就是选择B-C(3), 将BC放入集合中。

选出边F-D(4)之后,两个集合合二为一。

此时最小生成树生成,生成条件: 所有的点被涉及,并且已经纳入同一个集合。

图的编码实战-图的编码说明与展示

图的存储(邻接矩阵) & 图的深度优先 & 广度优先

如上图所示的一个无向图,可以被映射为如下面这样一个邻接矩阵。

3-1-MapDepthBreadth

Node.h

#ifndef NODE_H
#define NODE_Hclass Tree
{
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#include "Node.h"
#include <vector>
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 printMatrix();//打印邻接矩阵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>
#include <vector>
#include "Node.h"
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]; // 邻接矩阵大小size乘size// 邻接矩阵初始化为全0// 设定内存大小: memset(m_pMatrix, 0, m_iCapacity * m_iCapacity * sizeof(int)); 与下面for循环初始化二选一即可。// 将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值,第一个值为指定的内存地址,块的大小由第三个参数指定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;}// 保存节点数据 数组;new后保存数据,而不是把外部的挂载上去m_pNodeArray[m_iNodeCount].m_cData = pNode->m_cData;m_iNodeCount++;return true;
}void CMap::resetNode()
{// for (int i = 0; i < m_iNodeCount; i++){m_pNodeArray[i].m_bIsVisited = false;}
}// 行和列的本质据说顶点的索引
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;
}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::printMatrix()
{//两重循环(i行k列)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;}
}// mtiandou: 深度优先
void CMap::depthFirstTraverse(int nodeIndex)
{// 访问根左右,与先序遍历类似。把子树延展的所有节点访问完才会回到根。之前我们是根据node的递归实现。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)// 判断有弧连接其他顶点{if (m_pNodeArray[i].m_bIsVisited){continue;}else{// 当前点没有访问过,调用递归函数。depthFirstTraverse(i);}}else{continue;}}
}
// 每一层放在一个数组中
void CMap::breadthFirstTraverse(int nodeIndex)
{cout << m_pNodeArray[nodeIndex].m_cData << " ";m_pNodeArray[nodeIndex].m_bIsVisited = true;// 将节点索引保存到数组中vector<int> curVec;curVec.push_back(nodeIndex); // 将节点索引使用push_back放入breadthFirstTraverseImpl(curVec); // 真正实现广度优先的函数}void CMap::breadthFirstTraverseImpl(vector<int> preVec)
{int value = 0;vector<int> curVec;// mtiandou:prevec为上一层节点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);}}}}// 本层不存在被访问的点if (curVec.size() == 0){return;}else // 下层还有连接{breadthFirstTraverseImpl(curVec);}}

main.cpp:

#include "CMap.h"
#include <stdlib.h>
#include "Node.h"
#include <iostream>
using namespace std;int main()
{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); // 还有一个隐藏的默认参数,权值1pMap->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->printMatrix();cout << endl;pMap->resetNode();// 指定起始点的索引pMap->depthFirstTraverse(0);cout << endl;pMap->resetNode();pMap->breadthFirstTraverse(0);cout << endl;return 0;
}

运行结果:

Map前加上C是因为Map是一个标准模板库,为了防止重名。

图的编码实战-最小生成树(普里姆算法)

例子

4-3-MapMinimumSpanningTreePrim

七个顶点,边与边之间存在权值,如a-b权值为6,一共十条边。

Edge.h

#ifndef EDGE_H
#define EDGE_Hclass 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;
}

Node.h

#ifndef NODE_H
#define NODE_Hclass 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
#include "Node.h"
#include <vector>
#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 printMatrix();//打印邻接矩阵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; // 边,map.cpp中分配内存};#endif

CMap.cpp

#include "CMap.h"
#include <iostream>
#include <vector>
#include "Node.h"
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];//邻接矩阵size乘size//邻接矩阵初始化为全0//memset(m_pMatrix, 0, m_iCapacity * m_iCapacity * sizeof(int));//将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值, 第一个值为指定的内存地址,块的大小由第三个参数指定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].m_cData = pNode->m_cData;m_iNodeCount++;return true;
}void CMap::resetNode()
{for (int i = 0; i < m_iNodeCount; i++){m_pNodeArray[i].m_bIsVisited = false;}
}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;
}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::printMatrix()
{//两重循环(i行k列)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;}
}//深度优先
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)//判断有弧连接其他顶点{if (m_pNodeArray[i].m_bIsVisited){continue;}else{depthFirstTraverse(i);}}else{continue;}}
}
//每一层放在一个数组中
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)
{int value = 0;vector<int> curVec;//prevec为上一层节点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);}}}}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); // nodeIndexm_pNodeArray[nodeIndex].m_bIsVisited = true;// 什么时候停下来,边数等于点数-1时,edgeCount计数边while (edgeCount < m_iCapacity - 1){int temp = nodeVec.back();// 从数组中取出最尾部的元素// 寻找与该节点连接的所有边for (int i=0;i<m_iCapacity;i++){// 取相应的边,temp是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 minWeight = 0;int edgeIndex = 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){return -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;
}

main.cpp:

#include "CMap.h"
#include <stdlib.h>
#include "Node.h"
#include <iostream>
using namespace std;int main()
{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);return 0;
}

运行结果:

图的编码实战-最小生成树之克鲁斯卡尔算法

题目

以边为基础,第一步找到所有的边,在边中找到最小生成树的集合

第二步:从所有边中取出组成最小生成树的边

找到算法结束条件;从边集合中找到最小边;找出最小边连接的点;找出点所在的点集合;根据点所在集合的不同做出不同处理

Node.cpp & Node.h Edge.cpp & Edge.h与之前的一样

4-5-MapMinimumSpanningTreeKruskal

CMap.h

#ifndef CMAP_H
#define CMAP_H
#include "Node.h"
#include <vector>
#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 printMatrix();//打印邻接矩阵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);//广度优先遍历实现函数bool isInSet(vector<int> nodeSet, int target); //判断顶点是否在点集合中void mergeNodeSet(vector<int> &nodeSetA, vector<int> nodeSetB);//合并两个点集合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>
#include <vector>
#include "Node.h"
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];//邻接矩阵size乘size//邻接矩阵初始化为全0//memset(m_pMatrix, 0, m_iCapacity * m_iCapacity * sizeof(int));//将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值, 第一个值为指定的内存地址,块的大小由第三个参数指定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].m_cData = pNode->m_cData;m_iNodeCount++;return true;
}void CMap::resetNode()
{for (int i = 0; i < m_iNodeCount; i++){m_pNodeArray[i].m_bIsVisited = false;}
}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;
}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::printMatrix()
{//两重循环(i行k列)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;}
}//深度优先
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)//判断有弧连接其他顶点{if (m_pNodeArray[i].m_bIsVisited){continue;}else{depthFirstTraverse(i);}}else{continue;}}
}
//每一层放在一个数组中
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)
{int value = 0;vector<int> curVec;//prevec为上一层节点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);}}}}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;//什么时候停下来,边数等于点数-1时while (edgeCount < m_iCapacity - 1){int temp = nodeVec.back();//从数组中取出最尾部的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 minWeight = 0;int edgeIndex = 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){return -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);// 权值不等于0才有意义if (value!=0){Edge edge(i, k, value);edgeVec.push_back(edge);}}}// 第二步:从所有边中取出最小生成树的边// 1. 找到算法的结束条件(边数等于顶点数-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){// 保存i的值,i能知道a所在集合的索引nodeAInSetLabel = i;}}for (int i = 0; i < (int)nodeSets.size(); i++){nodeBIsInSet = isInSet(nodeSets[i], nodeBIndex);if (nodeBIsInSet){//保存i的值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 && nodeBInSetLabel !=-1){// 此时a不在任何一个集合中。而nodeB已经在某一集合中// 因为node a,b为一边的两点。所有将a也加入b的集合nodeSets[nodeBInSetLabel].push_back(nodeAIndex);}else if (nodeAInSetLabel != -1 && nodeBInSetLabel == -1){//此时b不在任何一个集合中。而nodeA已经在某一集合中//因为nodea&b为一边的两点。所有将b也加入a的集合nodeSets[nodeAInSetLabel].push_back(nodeBIndex);}else if (nodeAInSetLabel != -1 && nodeBInSetLabel != -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 && nodeBInSetLabel != -1 && nodeAInSetLabel == nodeBInSetLabel){//两个都不等于-1.相等还在同一个集合中。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++){//合并就是将b的点依次加到a的后面nodeSetA.push_back(nodeSetB[i]);}
}

main.cpp:

#include "CMap.h"
#include <stdlib.h>
#include "Node.h"
#include <iostream>
using namespace std;int main()
{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);pMap->kruskalTree();return 0;
}

程序编写完成之后,查看资源内存都有没有正确的释放。目前我们的顶点是很简单的顶点,将顶点替换成城市等。

运行结果:

15-数据结构探险系列-图篇相关推荐

  1. 数据结构探险之图篇(上)理论篇

    数据结构探险之图篇 什么是图? 如下图:无向图 & 有向图 箭头分方向. 无向图中每条认为有来有回两条线 无向图&有向图 图中的概念: 有向图中的概念 结点称为顶点. 之间的线称为弧. ...

  2. 数据结构探险系列—栈篇-学习笔记

    数据结构探险-栈篇 什么是栈? 古代栈就是牲口棚的意思. 栈是一种机制:后进先出 LIFO(last in first out) 电梯 栈要素 空栈.栈底,栈顶.没有元素的时候,栈顶和栈底指向同一个元 ...

  3. 数据结构探险——线性表篇

    以下内容源于慕课网的学习整理,如有侵权,请告知删除. 1.线性表 概念 机制的实现 2.顺序表 构造函数.析构函数 清空线性表,判空 求当前线性表长度,获取某个序号的元素 定位某个元素的位置 找前驱( ...

  4. 焱老师带你学习MYSQL系列 第二篇 (MYSQL 数据结构)

    相关系列链接 焱老师带你学习MYSQL系列 第六篇 (MYSQL是如何实现锁的) 焱老师带你学习MYSQL系列 第五篇 (MYSQL事务隔离级别是如何实现的) 焱老师带你学习MYSQL系列 第四篇 ( ...

  5. Android 系统(243)---Android进程系列第一篇---进程基础

    Android进程系列第一篇---进程基础 内容预览.png 概述: 本文主要讲解进程基础,更深入的认识有血有肉的进程,内容涉及进程控制块,信号,进程FD泄露等等.仅供参考,欢迎指正. 一.从Linu ...

  6. 表达式类型的实现数据结构_Redis系列(九)底层数据结构之五种基础数据类型的实现...

    前言 定义 字符串对象 int raw embstr 浮点数如何保存? 编码转换条件 总结 列表对象 总结 集合对象 intset hashtable 总结 有序集合对象 ziplist 编码 ski ...

  7. 如何用三元组表表示下列稀疏矩阵_盘一盘 Python 系列特别篇21之:SciPy 稀疏矩阵...

    引言 和稠密矩阵相比,稀疏矩阵的最大好处就是节省大量的内存空间来储存零.稀疏矩阵本质上还是矩阵,只不过多数位置是空的,那么存储所有的 0 非常浪费.稀疏矩阵的存储机制有很多种 (列出常用的五种): C ...

  8. 数据结构实践项目——图的基本运算及遍历操作

    本文是针对[数据结构基础系列(7):图]中第1-9课时的实践项目. 0701 图结构导学 0702 图的定义 0703 图的基本术语 0704 图的邻接矩阵存储结构及算法 0705 图的邻接表存储结构 ...

  9. Java私塾跟我学系列——JAVA篇 第四章Java类和对象

    教学目标: i面向对象基础 i掌握对象的三大特性 i掌握Java类的构建 i掌握如何使用Java类 i理解引用类型 i理解按值传递和按引用传递 i深入理解变量 i掌握包装类 i理解类型转换 i理解Ja ...

最新文章

  1. C++运行时类型信息 (RTTI)
  2. 【路径规划】Astart算法——图文直观解析
  3. #pragma pack
  4. vs2013 乱码问题
  5. The Joy of Clojure – Clojure philosophy(1)
  6. 《C++ Primer》7.3.4节练习
  7. ssl2342-打击犯罪【并查集】
  8. BitmapEffect位图效果是简单的像素处理操作。它可以呈现下面几种特殊效果。
  9. Qt文档阅读笔记-qRegisterMetaType()的原理及其使用
  10. JQ中使用FormData+Ajax发送请求及使用express接收处理FormData数据
  11. 大专学计算机应用难吗,上了两年技校,专业是计算机应用,什么也没学到。现在想在去上个大专。学什么专业好呢。?...
  12. break和continue的区别和执行过程
  13. 【渝粤教育】国家开放大学2018年春季 0359-21T会计学原理 参考试题
  14. 【语音处理】基于matlab GUI音频信号提取分析【含Matlab源码 1738期】
  15. 一篇文章完全搞懂正则化(Regularization)
  16. unpivot行转列 oracle,oracle-行转列点评oracle11g sql新功能pivot/unpivot
  17. DWF低代码开发技术及其在数字化运营和运维平台建设中的应用
  18. java 指令发送短信_Java短信发送机的实现
  19. 刀片服务器的机箱显示器,刀片服务器机箱如何配置网络交换器端口
  20. EFR32MG22与TI CC2652RSIP对比

热门文章

  1. 机器学习笔记:线性规划,梯度下降
  2. RequireJS和AMD规范
  3. 迁移数据文件到ASM【转】
  4. 关于ACCESS的事务与存储过程的调用
  5. 自建git服务器连接Pycharm系列二:在centos7上搭建git服务器
  6. 理解学习this指向问题
  7. CSS 后台布局实例
  8. 添加10个用户user1到user10,但要求只有用户不存在的情况下才能添加
  9. EXECL导入(检查服务器版本.包括NPOI方式导入.可以通过配置文件信息导入EXECL)代码记录下....
  10. 一致的数据访问技术ADO/OLE DB