09、数据结构与算法C++:图

  前面我们已经介绍了一对一和一对多的数据结构,现在我们就来了解一下多对多的数据结构。

图( Graph )是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为: G (V, E ),其中, G 表示一个图, V 是图G 中顶点的集合, E 是图G 中的集合。

              *
图1 图1 图1
###术语总结

  图的术语很多:
  图按照有无方向分为无向图有向图。无向图由顶点构成,有向图由顶点何成。弧有弧尾弧头之分。
  图按照边或弧的多少分稀疏图稠密图。如果任意两个顶点之间都存在边叫完全图,有向的叫有向完全图。若无重复的边或顶点到自身的边则叫简单图
  图中顶点之间有邻接点依附的概念。无向图顶点的边数叫做,有向图顶点分为入度出度
  图上的边或弧上带权则称为
  图中顶点间存在路径,两顶点存在路径则说明是连通的,如果路径最终回到起始点则称为, 当中不重复叫简单路径。若任意两顶点都是连通的,则图就是连通图,有向则称强连通图。图中有子图, 若子图极大连通则就是连通分量, 有向的则称强连通分量
  无向图中连通且$n 个 顶 点 个顶点 个顶点n-1$条边叫生成树。有向图中一顶点入度为0其余顶点入度为1 的叫有向树。一个有向图由若干棵有向树构成生成森林

###图的存储结构

  图的结构比较复杂,不可能用简单的顺序存储结构来表示。现在我们介绍几种常用的图的存储结构。

####邻接矩阵

  考虑到图由顶点和边组成,所以我们可以将图分开分别储存。这就是邻接矩阵的思路。

图的邻接矩阵( Adjacency Matrix) 存储方式是用两个数组来表示图。一个一维
数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。

  其中邻接矩阵定义如下:
ar c [ i ] [ j ] = { 1 , ( v i , v j ) * E 或 < v i , v j > * E 0 , 其 他 arc[i][j]=\left\{\begin{matrix} 1,(v_i,v_j)\epsilon E或<v_i,v_j>\epsilon E\\ 0,其他 \end{matrix}\right. arc[i][j]={ 1,(vi,vj)*E或<vi,vj>*E0,其他

  对于网图邻接矩阵定义如下:
ar c [ i ] [ j ] = { W i j , ( v i , v j ) * E 或 < v i , v j > * E 0 , i = j ∞ , 其 他 arc[i][j]=\left\{\begin{matrix} W_{ij},(v_i,v_j)\epsilon E或<v_i,v_j>\epsilon E\\ 0,i=j\\ \infty ,其他 \end{matrix}\right. arc[i][j]=***Wij,(vi,vj)*E或<vi,vj>*E0,i=j∞,其他

  其中 W i j W_{ij} Wij为相对应边的权值。
  相关代码如下:

#include <vector>
#include <iostream>
using namespace std;
#define INFINITY 65535
class Graph
{
public:
	Graph();
	~Graph();
	void CreateGraph();//创建图
private:
	vector<char> vexs; //顶点表
	vector<vector<int>>arc;//邻接矩阵
	int numVertexes, numEdges;
};

  以无向网图为例的创建代码如下:

void Graph::CreateGraph()
{
	cout << "输入顶点数和边数:" << endl;
	cin >> numVertexes >> numEdges;
	cout << "输入顶点:" << endl;
	int i, j,w;
	char ch;
	for (i = 0; i < numVertexes; i++)
	{
		cin >> ch;
		vexs.push_back(ch);
	}
	arc.resize(numVertexes, vector<int>(numVertexes));
	for ( i = 0; i < numVertexes; i++) //初始化邻接矩阵
		for (j = 0; j < numVertexes; j++)
		{
			if (i!=j)arc[i][j] = INFINITY;
			else arc[i][j] = 0;
		}
			
	cout << "输入边( vi ,vj)上的下标i,下标j,和权w :" << endl;
	for (int k = 0; k < numEdges; k++)
	{
		cin >> i >> j >> w;
		arc[i][j]=arc[j][i]= w;
	}
}

####邻接表

  对于边数相对顶点较少的图,邻接矩阵存在对存储空间的极大浪费的。这时候可以选择采用邻接表 ,它是数组与链表相结合的存储方法。类似树存储结构的孩子表示法。

1、 .图中顶点用一个一维数组存储,另外,对于顶点数组中,每个数据元;
素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。
2、 图中每个顶点的所有邻接点构成一个线性表;

#include<vector>
#define INFINITY 65535
using namespace std;
 struct EdgeNode//边表结点
{
	int adjvex;//顶点下标
	int weight;//权值
	EdgeNode *next;//下一个结点
};
 struct VertexNode//顶点表结点
 {
	 char vert;//顶点信息
	 EdgeNode* firstedge;//边表头指针
 };
class GraphAdjList
{
public:
	GraphAdjList();
	~GraphAdjList();
	void CreateALGraph();//构建无向网图
private:
	vector<VertexNode> adjList;
	int numVertexes, numEdges;//顶点数、边数
};

  以无向网图为例的创建代码如下:

void GraphAdjList::CreateALGraph()//构建无向网图
{
	int i,j,w;
	cout << "输入顶点数和边数:" << endl;
	cin >> numVertexes >> numEdges;
	cout << "输入顶点:" << endl;
	VertexNode vert;
	for (int k = 0; k < numVertexes; k++)
	{
		cin >> vert.vert;
		vert.firstedge = NULL;
		adjList.push_back(vert);
	}
	cout << "输入边( vi ,vj)上的下标i,下标j,和权w :" << endl;
	for (int k = 0; k < numEdges; k++)
	{
		cin >> i >> j >> w;
		EdgeNode *p = new EdgeNode;
		p->adjvex = j;
		p->weight = w;
		p->next = adjList[i].firstedge;
		adjList[i].firstedge=p;
	}

}

###图的遍历

从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问一次,这一过程就叫做图的遍历(Traversing Graph )

####深度优先遍历

  深度优先遍历( Depth_First_Search ) 也有称为深度优先搜索,简称为DFS
  它从图中某个顶点v 出发,访问此顶点,然后从v 的未被访问的邻接点出发深度优先遍历图,直至图中所有和v 有路径相通的顶点都被访问到。若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。图1中图的邻接矩阵的深度优先遍历,代码如下:

#include <vector>
#include <iostream>
using namespace std;
#define INFINITY 65535
class Graph
{
public:
	Graph();
	~Graph();
	void CreateGraph();//创建图
	void DFSTraverse();//深度遍历操作
	
private:
	void DFS(int i);//深度遍历递归数组
	vector<char> vexs; //顶点表
	vector<vector<int>>arc;//邻接矩阵
	vector<bool>visited;//访问标志数组
	int numVertexes, numEdges;
};

  代码具体实现:

#include "Graph.h"
Graph::Graph()
{
}
Graph::~Graph()
{
}
void Graph::CreateGraph()
{
	cout << "输入顶点数和边数:" << endl;
	cin >> numVertexes >> numEdges;
	cout << "输入顶点:" << endl;
	int i, j,w;
	char ch;
	for (i = 0; i < numVertexes; i++)
	{
		cin >> ch;
		vexs.push_back(ch);
	}
	arc.resize(numVertexes, vector<int>(numVertexes));
	for ( i = 0; i < numVertexes; i++) //初始化邻接矩阵
		for (j = 0; j < numVertexes; j++)
		{
			if (i!=j)arc[i][j] = INFINITY;
			else arc[i][j] = 0;
		}
			
	cout << "输入边( vi ,vj)上的下标i,下标j,和权w :" << endl;
	for (int k = 0; k < numEdges; k++)
	{
		cin >> i >> j >> w;
		arc[i][j]=arc[j][i]= w;
	}
}
void Graph::DFSTraverse()//深度遍历操作
{
	for (int i = 0; i < numVertexes; i++)
		visited.push_back(false);
	for (int i = 0; i < numVertexes; i++)
		if (!visited[i])
			DFS(i);
	cout << endl;
}
void  Graph::DFS(int i)//深度遍历递归数组
{
	visited[i] = true;
	cout << vexs[i] << " ";
	for (int j = 0; j < numVertexes; j++)
		if ((arc[i][j] != 0 && arc[i][j] != INFINITY) && !visited[j])
			DFS(j);
}

#include <iostream>
#include "Graph.h"

using namespace std;

int main()
{
	Graph G;
	G.CreateGraph();
	G.DFSTraverse();
	system("pause");
	return 0;
}

  VS上运行结果如下:
*

####广度优先遍历

  广度优先遍历( Breadth_First_Search) ,又称为广度优先搜索,简称BFS。如果说图的深度优先遍历类似树的前序遍历, 那么图的广度优先遍历就类似于树的层序遍历了。图1中图的邻接表的广度优先遍历代码如下:

#include<vector>
#define INFINITY 65535
using namespace std;
 struct EdgeNode//边表结点
{
	int adjvex;//顶点下标
	int weight;//权值
	EdgeNode *next;//下一个结点
};
 struct VertexNode//顶点表结点
 {
	 char vert;//顶点信息
	 EdgeNode* firstedge;//边表头指针
 };
class GraphAdjList
{
public:
	GraphAdjList();
	~GraphAdjList();
	void CreateALGraph();//构建无向网图
	void BFSTraverse();//广度历遍算法
private:
	vector<VertexNode> adjList;
	vector<bool> visited;//访问标志数组
	int numVertexes, numEdges;//顶点数、边数
};

  具体操作代码如下:

#include "GraphAdjList.h"
#include<iostream>
#include<queue>
using namespace std;

GraphAdjList::GraphAdjList()
{
}
GraphAdjList::~GraphAdjList()
{
}

void GraphAdjList::CreateALGraph()//构建无向网图
{
	int i,j,w;
	cout << "输入顶点数和边数:" << endl;
	cin >> numVertexes >> numEdges;
	cout << "输入顶点:" << endl;
	VertexNode vert;
	for (int k = 0; k < numVertexes; k++)
	{
		cin >> vert.vert;
		vert.firstedge = NULL;
		adjList.push_back(vert);
	}
	cout << "输入边( vi ,vj)上的下标i,下标j,和权w :" << endl;
	for (int k = 0; k < numEdges; k++)
	{
		cin >> i >> j >> w;
		EdgeNode *p = new EdgeNode;
		p->adjvex = j;
		p->weight = w;
		p->next = adjList[i].firstedge;
		adjList[i].firstedge=p;
	}

}

void GraphAdjList::BFSTraverse()//广度历遍算法
{
	for (int i = 0; i < numVertexes; i++)
		visited.push_back(false);
	queue<int> Q;
	EdgeNode* p;
	for (int i = 0; i < numVertexes; i++)
	{
		if (!visited[i])
		{
			visited[i] = true;
			cout << adjList[i].vert << " ";
			Q.push(i);
			while (!Q.empty())
			{
				i = Q.front();
				Q.pop();
				p = adjList[i].firstedge;
				while (p)
				{
					if (!visited[p->adjvex])
					{
						visited[p->adjvex] = true;
						cout << adjList[p->adjvex].vert << " ";
						Q.push(p->adjvex);
					}
					p = p->next;
				}
			}
		}
	}
	cout << endl;
}
#include<iostream>
#include "GraphAdjList.h"

using namespace std;

int main()
{
	GraphAdjList G;
	G.CreateALGraph();
	G.BFSTraverse();
	system("pause");
	return 0;
}

  VS上运行结果如下:
*

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: