以下内容源于慕课网的学习整理,如有侵权,请告知删除。
数据结构探险——图篇
1、什么是图?
-
无向图:即图的边没有方向,边一般用弧形括号表示()
-
有向图:图的边有方向,边一般用尖括号表示<>
-
完全图:图的每两个顶点之间有边链接
-
连通图:图的每两个顶点之间有路径链接
-
生成树
-
图的遍历
-
最小生成树
2、图的存储结构、遍历方式及最小生成树算法原理
2.1 图的存储结构
- 邻接矩阵
- 邻接表
- 十字链表
- 邻接多重表
2.1.1 邻接矩阵
是由数组进行存储。
顶点索引是不能重复的。
有向图的邻接矩阵表示方法:
无向图的邻接矩阵表示方法:
邻接矩阵在代码中如何表示(表示成数组):
结点和图的结构体表示:
2.1.2 邻接表——链式存储
邻接表的相关表示方式:
逆邻接表:
逆邻接表是相对邻接表来说的,邻接表记录的是出弧的链表头指针,而逆邻接表记录的入弧的链表头指针。
顶点的表示方法:
顶点索引 | 入弧链表头指针 | 顶点数据 |
---|
弧的表示方法:
弧尾顶点索引 | 下一条弧指针 | 弧数据 |
---|
存储方法如何通过数据结构的代码来表示:
2.1.3 十字链表——链式存储
如何通过结构体进行存储:
2.1.4 邻接多重表——链式存储
用结构体进行表示如下:
2.2 图的遍历
- 深度优先搜索(类似树的前序遍历)
- 广度优先搜索
如上图中图来说,
进行深度优先搜索顺序为:A B C E F D G H
进行广度优先搜索顺序为:A B D C F G H E
2.3 最小生成树
- 普里姆(Prim)算法
- 克鲁斯卡尔( Kruskal )算法
2.3.1 普里姆算法
2.3.2 克鲁斯卡尔算法算法
3、图的编码实战
3.1 图的深度优先遍历和图的广度优先遍历
A
/ \
B D
/ \ / \
C F G - H
\ /
E
深度优先遍历搜索:A B C E F D D H
广度优先遍历搜索:A B D C F G H 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 1
F 1 1
G 1 1
H 1 1
Node.h
定义结点相关功能接口和元素的头文件(存入的数据都是char类型)
#ifndef NODE_H_
#define NODE_H_
class Node
{
public:
Node(char data = 0);
char m_cData;
bool m_bIsVisited;
};
#endif /* NODE_H_ */
Node.cpp
实现结点相关功能代码
#include "Node.h"
Node::Node(char data)
{
m_cData = data;
m_bIsVisited = false;
}
CMap.h
定义图相关功能接口和元素的头文件(存入的数据都是Node结点类型)
#ifndef CMAP_H_
#define CMAP_H_
#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 printMatrix(); //打印邻接矩阵
void depthFirstTraverse(int nodeIndex); //深度优先遍历
void breathFirstTraverse(int nodeIndex); //广度优先遍历
private:
bool getValueFromMatrix(int row, int col, int &val); //从矩阵中获取权值
void breathFirstTraverseImpl(vector<int> preVec); // 广度优先遍历实现函数
private:
int m_iCapacity; //图中最多可以容纳的顶点数
int m_iNodeCount; //已经添加的顶点(结点)个数
Node *m_pNodeArray; //用来存放顶点数组
int *m_pMatrix; //用来存放邻接矩阵
};
#endif /* CMAP_H_ */
CMap.cpp
实现图的相关功能代码
#include <iostream>
#include <vector>
#include <string.h>
#include "CMap.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];
memset(m_pMatrix, 0, m_iCapacity * m_iCapacity * sizeof(int));
/*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].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()
{
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 == 1) {
if (m_pNodeArray[i].m_bIsVisited) {
continue;
} else {
depthFirstTraverse(i);
}
} else {
continue;
}
}
}
//广度优先遍历
void CMap::breathFirstTraverse(int nodeIndex)
{
cout << m_pNodeArray[nodeIndex].m_cData << " ";
m_pNodeArray[nodeIndex].m_bIsVisited = true;
vector<int> curVec;
curVec.push_back(nodeIndex);
breathFirstTraverseImpl(curVec);
}
void CMap::breathFirstTraverseImpl(vector<int> preVec)
{
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);
}
}
}
}
if (0 == curVec.size()) {
return;
} else {
breathFirstTraverseImpl(curVec);
}
}
MapDemo.cpp
测试入口代码文件
/*
图的形式:
A
/ \
B D
/ \ / \
C F G - H
\ /
E
*/
#include <iostream>
#include "CMap.h"
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);
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->printMatrix();
pMap->depthFirstTraverse(0);
cout << endl;
pMap->resetNode();
pMap->breathFirstTraverse(0);
delete pMap;
pMap = NULL;
return 0;
}
测试结果为:
图的矩阵:
0 1 0 1 0 0 0 0
1 0 1 0 0 1 0 0
0 1 0 0 1 0 0 0
1 0 0 0 0 0 1 1
0 0 1 0 0 1 0 0
0 1 0 0 1 0 0 0
0 0 0 1 0 0 0 1
0 0 0 1 0 0 1 0
深度优先遍历顺序:
A B C E F D G H
广度优先遍历顺序:
A B D C F G H E
3.2 图的最小生成树算法(普利姆算法和克鲁斯卡尔算法)
3.2.1最小生成树之普利姆算法代码实现
/*
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 2
D-F 4 D-E 2
E-F 9
*/
Edge.h
定义边的相关元素的头文件(存入的数据都是int类型)
#ifndef EDGE_H_
#define EDGE_H_
class Edge
{
public:
Edge(int nodeIndexA = 0, int nodeIndex = 0, int weightValue = 0);
int m_iNodeIndexA;
int m_iNodeIndexB;
int m_iWeightValue;
bool m_bSelected;
};
#endif /* EDGE_H_ */
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
定义结点相关功能接口和元素的头文件(存入的数据都是char类型)
#ifndef NODE_H_
#define NODE_H_
class Node
{
public:
Node(char data = 0);
char m_cData;
bool m_bIsVisited;
};
#endif /* NODE_H_ */
Node.cpp
实现结点相关功能代码
#include "Node.h"
Node::Node(char data)
{
m_cData = data;
m_bIsVisited = false;
}
CMap.h–>修改后定义图相关功能接口和元素的头文件(存入的数据包含Node结点和Edge边的类型)
#ifndef CMAP_H_
#define CMAP_H_
#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 printMatrix(); //打印邻接矩阵
void depthFirstTraverse(int nodeIndex); //深度优先遍历
void breathFirstTraverse(int nodeIndex); //广度优先遍历
void primTree(int nodeIndex); //普利姆生成树
private:
bool getValueFromMatrix(int row, int col, int &val); //从矩阵中获取权值
void breathFirstTraverseImpl(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_H_ */
CMap.cpp
修改后实现图的相关功能代码
#include <iostream>
#include <vector>
#include <string.h>
#include "CMap.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];
memset(m_pMatrix, 0, m_iCapacity * m_iCapacity * sizeof(int));
/*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()
{
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 == 1) {
if (m_pNodeArray[i].m_bIsVisited) {
continue;
} else {
depthFirstTraverse(i);
}
} else {
continue;
}
}
}
//广度优先遍历
void CMap::breathFirstTraverse(int nodeIndex)
{
cout << m_pNodeArray[nodeIndex].m_cData << " ";
m_pNodeArray[nodeIndex].m_bIsVisited = true;
vector<int> curVec;
curVec.push_back(nodeIndex);
breathFirstTraverseImpl(curVec);
}
void CMap::breathFirstTraverseImpl(vector<int> preVec)
{
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);
}
}
}
}
if (0 == curVec.size()) {
return;
} else {
breathFirstTraverseImpl(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();
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;
}
MapDemo.cpp
修改后的测试入口代码文件
#include <iostream>
#include "CMap.h"
using namespace std;
/*
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 2
D-F 4 D-E 2
E-F 9
*/
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);
delete pMap;
pMap = NULL;
return 0;
}
测试结果为:
A
0----5 1
F
5----1 2
B
1----2 3
C
5----3 4
D
3----4 2
E
3.2.2 最小生成树之克鲁斯卡尔算法代码实现
/*
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 2
D-F 4 D-E 2
E-F 9
*/
// 克鲁斯卡尔算法生成树
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);
} else if (nodeAInSetLabel == -1 && nodeBInSetLabel != -1) {
nodeSets[nodeBInSetLabel].push_back(nodeAIndex);
} else if (nodeAInSetLabel != -1 && nodeBInSetLabel == -1) {
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(); k++) {
nodeSets[k] = nodeSets[k + 1];
}
} else if (nodeAInSetLabel != -1 && nodeBInSetLabel != -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;;
}
}