数据结构之图(C++)–邻接矩阵表示(一)
基本概念
简单地说,图是一个用线或边链接爱一起的顶点或节点的集合。严格地说,图是有限集V和E的有序对,即G=(V,E),其中V的元素称为顶点(也称节点或点),E的元素称为边(也叫弧或线)。每一条边链接两个不同的顶点,而且用元组(i,j)来表示,其中i和j是边所连接的两个顶点。
- 图的术语:顶点,边,邻接,关联,度,回路,路径,连通构件,生成树
- 图的类型:无向图,有向图和加权图
- 图的常用描述方式:邻接矩阵,矩阵邻接表和邻接链表
- 图的搜索方式:广度优先搜索和深度优先搜索
- 相关算法:拓扑排序,二分覆盖,最短路径,最小生成树,旅行推销员等等
图的相关定义
如果图的所有边都是无向边(不带箭头的边),那么该图叫做无向图。如果图的所有边都是有向边,那么该图叫做有向图。
一个图不能有重复的边。在有向图中,任意两个顶点 i 和 j 之间,从顶点 i 到顶点 j 最多有一条边。在无向图中,任意两个顶点之间,最多只能有一条边。而且一个图不能包含自连边(即“顶点”自己与自己连线)
当我们给每条边赋予一个表示成本的值,我们称之为权(例如,高速公路上,从A地到B地要走300公里,A地到C地要走500公里,这里的300和500可以看成是AB和AC边的权)。这是的图我们成为加权有向图或加权五向图。
度:一个顶点i相关联的边数称为该顶点的度。
有向图中的入度和出度
- 入度: 关联至该顶点的边数。
- 出度:关联于该顶点的边数
例如上面的有向图,顶点3的入度为2, 出度为2。顶点5的入度为2,出度为1.
图的相关操作
抽象类graph
template <typename T>
class graph
{
public:
virtual ~graph() { }
// ADT 方法
virtual int numberOfVertices() const = 0; // 返回图的顶点数目
virtual int numberOfEdges() const = 0; // 返回图的边数
virtual bool existsEge(int, int) const = 0; // 如果边(i, j)存在,返回true
virtual void insertEdge(edge<T>*) = 0; // 插入边
virtual void eraseEdge(int, int) = 0; // 删除边
virtual int degree(int) const = 0; // 返回顶点的i的度
virtual int inDegree(int) const = 0; // 返回顶点i的入度
virtual int outDegree(int) const = 0; // 返回顶点i的出度
virtual bool directed() const = 0; // 如果是有向图返回true
virtual bool weighted() const = 0; // 如果是加权图返回true
virtual vertexIterator<T>* interator(int) = 0; // 访问指定顶点的相邻顶点
};
上述的相关操作将在下一节讲解
邻接矩阵
一个n顶点图G=(V,E)的邻接矩阵是一个n×n的矩阵,其中每个元素是0或者1.
- 无向图中元素定义:A(i,j) = 1 (如果(i,j)∈E或(j,i)∈E) 或者 A(i,j)= 0
无向图的邻接矩阵是对称的。
例如下面表示的就是无限图
有向图元素定义:A(i,j)= 1 (如果(i,j)∈E)或者 A(i,j)= 0
代码实现
// val值默认为1
bool Graph::setDirectGraph(int row, int col, int val)
{
if (row < 0 || row > m_iCapacity || col < 0 || col > m_iCapacity) {
return false;
}
m_pMatrix[row * m_iCapacity + col] = val;
return true;
}
bool Graph::setUndiretGraph(int row, int col, int val)
{
if (row < 0 || row >= m_iCapacity || col < 0 || col >= m_iCapacity) {
return false;
}
m_pMatrix[row * m_iCapacity + col] = val;
m_pMatrix[col * m_iCapacity + row] = val;
return true;
}
图的顶点类
类中只包含了构造函数用于设置顶点的信息,可以自行扩展,例如加入权值
// Node.h
#ifndef NODE_H__
#define NODE_H__
class Node
{
public:
Node(char data = 0);
char m_cData;
bool m_bIsVisited; //用于标记是否遍历过
};
Node::Node(char data)
{
m_cData = data;
m_bIsVisited = false;
}
#endif //NODE_H__
图的边类
对于边来说我们一边保存边的两端的顶点,和边的权值
// Edge.h
#ifndef EDGE_H__
#define EDGE_H__
class Edge
{
public:
Edge(int nodeIndexA = 0, int nodeIndexB = 0, int weightValue = 0);
int m_iNodeIndexA; // 顶点A的索引
int m_iNodeIndexB; // 顶点B的索引
int m_iWeightValue; // 边的权值
bool m_bSelected; // 用于标记是否遍历过
};
Edge::Edge(int nodeIndexA, int nodeIndexB, int weightValue)
{
m_iNodeIndexA = nodeIndexA;
m_iNodeIndexB = nodeIndexB;
m_iWeightValue = weightValue;
m_bSelected = false;
}
#endif //EDGE_H__
图的Graph类
#include <vector>
#include "Node.h"
#include "Edge.h"
using namespace std;
class Graph
{
public:
Graph(int capacity);
~Graph();
bool addNode(Node* pNode); // 添加顶点
void resetNode(); // 重置顶点(遍历一次会导致m_bIsVisited=true,需要重置将其设为false)
bool setDirectGraph(int row, int col, int val = 1); // 设置有向图
bool setUndiretGraph(int row, int col, int val = 1); // 设置无向图
void depthTraverse(int nodeIndex); // 深度优先搜索
void breadthTraverse(int nodeIndex); // 广度优先搜索
void printMatrix(); //打印邻接矩阵
//void primTree(int nodeIndex); //普利姆生成树
//void kruskalTree(); //克鲁斯卡尔算法生成树
private:
bool getValueFromMatrix(int row, int col, int& val);
void breadthTraverseImpl(vector<int> preVec);
private:
int m_iCapacity; // 图的最大容量
int m_iNodeCount; // 顶点的个数
int* m_pMatrix; // 邻接矩阵的指针
Edge* m_pEdge; // 边的指针
Node* m_pNodeArray; // 数组中顶点的指针
};
#endif //GRAPH_H__
构造函数和析构函数
Graph::Graph(int capacity)
{
m_iCapacity = capacity;
m_iNodeCount = 0;
m_pMatrix = new int[m_iCapacity * m_iCapacity]; // 建立一个m_iCapacity*m_iCapacity大小的数组
m_pNodeArray = new Node[m_iCapacity];
memset(m_pMatrix, 0, m_iCapacity * m_iCapacity * sizeof(int));
m_pEdge = new Edge[m_iCapacity - 1];
}
Graph::~Graph()
{
delete[] m_pMatrix;
delete[] m_pNodeArray;
delete[] m_pEdge;
}
深度优先搜索
深度优先搜索有点类似二叉搜索树中的前序遍历
如图,利用深度优先搜素遍历:A-B-C-D-E-F-G-H
代码利用递归实现
void Graph::depthTraverse(int nodeIndex)
{
// 每次递归输出该节点信息
int value = 0;
cout << m_pNodeArray[nodeIndex].m_cData << " ";
// 将节点设置成true
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 == true) {
continue;
}
else {
depthTraverse(i);
}
}
else {
continue;
}
}
}
广度优先搜索
从一个顶点开始,搜索所有可以到达顶点的方法叫做广度优先搜索。
如图,利用广度优先搜索遍历:A-B-F-C-E-F-G-H-D
代码实现
void Graph::breadthTraverse(int nodeIndex)
{
cout << m_pNodeArray[nodeIndex].m_cData << " ";
m_pNodeArray[nodeIndex].m_bIsVisited = true;
vector<int> curVec;
curVec.push_back(nodeIndex);
breadthTraverseImpl(curVec);
}
bool Graph::getValueFromMatrix(int row, int col, int& val)
{
if (row < 0 || row >= m_iCapacity || col < 0 || col >= m_iCapacity) {
return false;
}
val = m_pMatrix[row * m_iCapacity + col];
return true;
}
void Graph::breadthTraverseImpl(vector<int> preVec)
{
int value = 0;
vector<int> curVec;
for (unsigned j = 0; j < preVec.size(); j++) {
for (int k = 0; k < m_iCapacity; k++) {
getValueFromMatrix(preVec[j], k, value);
if (value != 0) {
if (m_pNodeArray[k].m_bIsVisited == true) {
continue;
}
else {
cout << m_pNodeArray[k].m_cData << " ";
m_pNodeArray[k].m_bIsVisited = true;
curVec.push_back(k);
}
}
}
}
if (curVec.size() == 0) {
return;
}
else {
breadthTraverseImpl(curVec);
}
}
其他代码
bool Graph::addNode(Node* pNode)
{
if (pNode == NULL) {
return false;
}
m_pNodeArray[m_iNodeCount].m_cData = pNode->m_cData;
m_iNodeCount++;
return false;
}
void Graph::resetNode()
{
for (int i = 0; i < m_iCapacity; i++) {
m_pNodeArray[i].m_bIsVisited = false;
}
}
void Graph::printMatrix()
{
for (int i = 0; i < m_iCapacity; i++) {
for (int j = 0; j < m_iCapacity; j++) {
cout << m_pMatrix[i*m_iCapacity + j] << " ";
}
cout << endl;
}
}
bool Graph::getValueFromMatrix(int row, int col, int& val)
{
if (row < 0 || row >= m_iCapacity || col < 0 || col >= m_iCapacity) {
return false;
}
val = m_pMatrix[row * m_iCapacity + col];
return true;
}