数据结构 第六章 图

//我还可以再肝!!!//写不完了哭哭


图结构是一种比树结构更复杂的非线性结构,任意两项之间都有可能有关系。

图的逻辑结构

定义

顶点:图中的数据元素

:由顶点的有穷非空集合和顶点之间边的集合组成。

 

无向边:两个顶点之间没有方向。(𝑣𝑖,𝑣𝑗)

有向边:两个顶点之间有方向。有序偶对表示。<𝑣𝑖,𝑣𝑗>,𝑣𝑖弧尾,𝑣𝑗弧头

目录

图的逻辑结构

定义

基本术语

图的遍历

深度优先遍历

广度优先遍历

图的存储结构

邻接矩阵

邻接表

邻接矩阵vs邻接表

最小生成树

Prim算法

Kruskal算法


​​​​​​​

无向图:任意两点之间的边都是无向边。

有向图:任意两点之间的边都是有向边。

:对边赋予的有意义的数值量。

带权图(网图):边上带权的图。

基本术语

1.邻接:对于任意两个顶点存在边,则这两个顶点互为邻接点。

    入度:顶点为弧头的弧的个数。 (进入该顶点)

    出度:顶点为弧尾的弧的个数。(由该顶点出去)

   出度 = 入度 =边的条数

3.无向完全图:任意两点都有边。

   有向完全图:任意两点都有方向相反的两条弧。

4.稀疏图:边很少。

  稠密图:边很多。

5.路径:一个顶点到另一个顶点的一个顶点序列。

  路径长度:路径上边的数目。

  回路:第一个顶点=最后一个顶点的路径。

6.简单路径:顶点不重复出现的路径。

   简单回路:除第一个和最后一个顶点外,其余顶点不重复出现的回路。

7.子图:顶点属于原来的顶点集且边属于原来边集的图。  一个图可以有多个子图。

8.连通:无向图中两点之间存在路径,则这两点连通。

   连通图:任意两个顶点之间均有路径。

   连通分量:非连通图的极大连通子图。

9.强连通图:有向图中,任意两个顶点之间均有路径。

  强连通分量:非强连通图的极大强连通子图。

图的抽象数据类型定义

顶点的有穷集合和顶点之间边的集合、图的建立、图的销毁、深度优先遍历、广度优先遍历。

图的遍历

从图中某顶点出发,对图中所有顶点访问一次且仅访问一次

图中没有确定的起始顶点,可以从图中任一顶点出发,从0开始对顶点编号。

在遍历的过程中区分顶点是否已经被访问:设置一个访问标志数组visited[n],初始值都为0表示没有被访问,若某顶点i被访问,那么visited[i]=1。

深度优先遍历

递归的方式

类似于树的前序遍历。

1.访问顶点v

2.从v中未被访问的邻接点选取一个顶点w,再从w出发进行深度优先遍历

3.重复1 2

广度优先遍历

类似于树的层序遍历。

1.访问顶点v

2.依次访问v的各个未被访问的邻接点w,x...

3.分别从w,x...出发依次访问他们未被访问的邻接点,直到图中与顶点v路径相通的点都被访问到。

图的存储结构

邻接矩阵&邻接表

邻接矩阵

(数组表示法)

用一个一维数组存储图中顶点

用一个二维数组存储图中的边(顶点之间的邻接关系) =邻接矩阵

n个顶点,邻接矩阵时nxn的方阵。

无向图的邻接矩阵一定是对称矩阵。

1.对于无向图,顶点i的度=邻接矩阵第i行非0元素的个数。

   对于有向图,顶点i的出度=邻接矩阵中第i行非0元素的个数

                                   入度=邻接矩阵中第i列非0元素的个数

2.判断两个顶点是否有边,只需测试邻接矩阵中相应位置元素是否为1

3.查找顶点i的所有邻接点,扫描邻接矩阵的第i行,若有非0元素,则找出邻接点即可。

#include<iostream>
using namespace std;
const int MaxSize = 10;  //图中最多的顶点个数
int visited[MaxSize] = { 0 };  //visited初始化
template <typename T>
class MGragh
{
	T vertex[MaxSize]; //存放顶点的数组
	int edge[MaxSize][MaxSize];  //存放边的数组
	int vertexNum, edgeNum;  //顶点数 边数
public:
	MGragh(T a[], int n, int e);//构造函数
	~MGragh() { }; //析构函数,邻接矩阵是静态存储非配,会自动释放
	void DFTraverse(int v);//深度优先
	void BFTraverse(int v);//广度优先
};
template<typename T>
MGragh<T>::MGragh(T a[], int n, int e)
{
	int i, j, k;
	vertexNum = n;
	edgeNum = e;
	for (int i = 0; i < vertexNum; i++)  //存储顶点
		vertex[i] = a[i];
	for (i = 0; i < vertexNum; i++)  //初始化邻接矩阵
		for (j = 0; j < vertexNum; j++)
			edge[i][j] = 0;
	for (k = 0; k < edgeNum; k++)  //依次输入每一条边
	{
		cout << "请输入第" << k + 1 << "条边";
		cin >> i >> j;
		edge[i][j] = 1;  //这里是无向图的创建
		edge[j][i] = 1;
	}
}
template <typename T>
void MGragh<T>::DFTraverse(int v)  //深度优先遍历
{
	cout << vertex[v];
	visited[v] = 1;
	for (int j = 0; j < vertexNum; j++)
	{
		if (edge[v][j] == 1 && visited[j] == 0)
			DFTraverse(j);
	}
}
template <typename T>
void MGragh<T>::BFTraverse(int v)  //广度优先遍历
{
	int w, j, Q[MaxSize];
	int front = -1;
	int rear = -1;
	cout << vertex[v];

	visited[v] = 1;
	Q[++rear] = v;
	while (front != rear)
	{
		w = Q[++front];
		for (j = 0; j < vertexNum; j++)
		{
			if (edge[w][j] == 1 && visited[j] == 0)
			{
				cout << vertex[j];
				visited[j] = 1;
				Q[++rear] = j;
			}
		}
	}
}
int main()
{
	int i;
	char ch[] = { 'A','B','C','D','E' };
	MGragh<char>MG(ch, 5, 6);
	for (i = 0; i < MaxSize; i++)
		visited[i] = 0;
	cout << "深度优先遍历序列:";
	MG.DFTraverse(0);
	for (i = 0; i < MaxSize; i++)
		visited[i] = 0;
	cout << "\n广度优先遍历序列:";
	MG.BFTraverse(0);

	return 0;
}

邻接表

顺序存储+链接存储。

类似于树的孩子表示法。

边表:对于每个顶点v,将v的所有邻接点链成一个单链表。

方便所有边表的头指针进行存取操作,采用顺序存储。

顶点表:存储边的表头指针和存储顶点的数组构成邻接表的表头数组。

邻接表中方两种结点结构:顶点表结点&边表结点

对于网图,边表结点增设info域存储边上的信息。

#include<iostream>
using namespace std;
const int MaxSize = 10;
int visited[MaxSize] = { 0 };
struct EdgeNode  //边表结点
{
	int adjvex;  //邻接点域
	EdgeNode* next;
};
template<typename T>
struct VertexNode  //顶点表结点
{
	T vertex;
	EdgeNode* firstEdge;
};
template<typename T>
class ALGraph
{
	VertexNode<T>adjlist[MaxSize];  //存放顶点表的数组
	int vertexNum, edgeNum;  //图的顶点数和边数
public:
	ALGraph(T a[], int n, int e);  //建立n个顶点e条边的图
	~ALGraph(); //析构函数
	void DFTraverse(int v);
	void BFTraverse(int v);
};
template<typename T>
ALGraph<T>::ALGraph(T a[], int n, int e)
{
	
	EdgeNode* s = nullptr;
	vertexNum = n;
	edgeNum = e;
	for (int i = 0; i < vertexNum; i++)  //初始化
	{
		adjlist[i].vertex = a[i];
		adjlist[i].firstEdge = nullptr;
	}
	for (int i = 0; i < edgeNum; i++)
	{
		cout << "输入边所依附的两个顶点编号:";
		int j, k;
		cin >> j >> k;
		s = new EdgeNode;  //生产边表结点s
		s->adjvex = k;
		s->next = adjlist[j].firstEdge;  //将结点s插入表头
		adjlist[j].firstEdge = s;
	}
}
template<typename T>
ALGraph<T>::~ALGraph()
{
	EdgeNode* p = nullptr, * q = nullptr;
	for (int i = 0; i < vertexNum; i++)
	{
		p = q = adjlist[i].firstEdge;
		while (p != nullptr)
		{
			p = p->next;
			delete q;
			q = p;
		}
	}
}
template<typename T>
void ALGraph<T>::DFTraverse(int v)
{
	int j;
	EdgeNode* p = nullptr;
	cout << adjlist[v].vertex;
	visited[v] = 1;
	p = adjlist[v].firstEdge;
	while (p!=nullptr)
	{
		j = p->adjvex;
		if (visited[j] == 0)
			DFTraverse(j);
		p = p->next;
	}
}
template<typename T>
void ALGraph<T>::BFTraverse(int v)
{
	int w, j, Q[MaxSize];  //采用顺序队列
	int front = -1, rear = -1;
	EdgeNode* p = nullptr;
	cout << adjlist[v].vertex;
	visited[v] = 1;
	Q[++rear] = v;
	while (front != rear)
	{
		w = Q[++front];
		p = adjlist[w].firstEdge;
		while (p != nullptr)
		{
			j = p->adjvex;
			if (visited[j] == 0)
			{
				cout << adjlist[j].vertex;
				visited[j] = 1;
				Q[++rear] = j;
			}
			p = p->next;
		}
	}
}
int main()
{
	char ch[] = "ABCDEF";
	ALGraph<char>ALG(ch,5,6);
	for (int i = 0; i < MaxSize; i++)
		visited[i] = 0;
	cout << "深度优先遍历的序列是:";
	ALG.DFTraverse(0);
	for (int i = 0; i < MaxSize; i++)
		visited[i] = 0;
	cout << "广度优先遍历的序列是:";
	ALG.BFTraverse(0);
	//ALG.TopSort();
	return 0;
}

邻接矩阵vs邻接表

1.空间性能比较

邻接矩阵空间代价O(n2),邻接表空间代价O(n+e)。

邻接矩阵存储所有可能的边,邻接表仅存储实际出现在图中的边。

邻接矩阵不需要指针的结构性开销。

图越稠密,邻接矩阵的空间效率更高。

2.时间性能比较

访问某顶点的所有邻接点:

邻接矩阵O(e/n)

邻接表O(n)

3.唯一性比较

邻接矩阵表示是唯一的;

邻接表表示不是唯一的,取决定边的输入次序以及结点在边表的插入算法。

4.对应关系

邻接表中顶点i的边表对应邻接矩阵的第i行。

整个邻接表可以看作邻接矩阵的带行指针的链接存储。

最小生成树

连通图的生成树:包含图中全部顶点的极小连通子图。

具有n个顶点的生成树有且仅有n-1条边。

最小生成树:代价最小的生成树。

Prim算法

时间复杂度O(n2),与网中边数无关,适用于求稠密网中的最小生成树。

Kruskal算法

还是上面的图

不能有回路!如果加入最短边产生回路则舍弃

Kruskal算法基于进行操作,可以采用边集数组存储。

连通分量的顶点所在的集合,由于涉及集合的查找和合并等操作,可以采用并查集来实现。

并查集:将集合中的元素组织成树的形式。

时间复杂度O(elog2e),与边数有关,适用于求稀疏网的最小生成树。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值