数据结构基础--图

一、图的基本概念

 1.图的定义

图是由顶点集合V和边集合E组成的,记为G=(G,V)。图可以只有点没有边,但不能只有边没有点。边:用(x,y)表示为xy之间的一条无向边;用<x,y>表示xy之间的一条有向边,x为有向边的起点,y为有向边的终点

2.图的基本术语 

邻接:有边相连的两个顶点之间的关系。存在(x,y)则称xy互为邻接点;存在<x,y>则称x邻接到y,y邻接于x

顶点的度、入度和出度:度是与该顶点相关联的边的数目。入度是该顶点作为终点的有向边的条数,出度是该顶点作为始点的有向边的条数

路径和路径长度:路径是接续的边构成的顶点序列。路径长度是路径上权值的和或者数目之和

完全图:具有n个顶点的无向图有最多的边数,即有n(n-1)/2条边;具有n个顶点的有向图有最多的边数,即有n(n-1)条边

子图:即是原本图的子集

(强)连通图:任意两个顶点u,v之间都存在从u到v的路径

(强)连通分量:无(有)向图的极大连通子图即为连通分量,极大连通子图顶点数目最多,再增加顶点子图不再连通。

生成树:包含图所有顶点的极小连通子图。极小连通子图删除任何一条边子图就不在连通

生成森林:对非连通图,由各个连通分量的生成树的集合

权和网:权是指图的每条边上的某种意义的数值,网是指每条边都有权值的图。

3.图的类型定义

图的数据包括点和边

ADT Graph
{
数据对象V:具有相同特性的数据元素的集合,称为顶点集
数据关系R:是多对多的运算
};

二、图的存储结构

1.邻接矩阵的表示法

图没有顺序存储结构,但可以借助二维数组来表示元素间的关系。

建立一个顶点表(记录各个顶点信息)和一个邻接矩阵(表示各个顶点之间关系)

矩阵的行数与列数取决与图中的顶点个数

无向图的邻接矩阵是对称的,顶点i的度=第i行(列)中1的个数

有向图中顶点的出度=第i行的元素个数和。顶点的入度=第i列元素之和

在带权无向图中,w(u,v)表示边(u,v)或边(v,u)的权值;在带权有向图,w(u,v)表示边<u,v>的权值。∞表示一个计算机允许的、大于所有边上权值的数。

完全图的邻接矩阵中,对角元素为0,其余为1

邻接矩阵中的元素只有0和1。1代表两点之间由连接,0代表两顶点之间无连接。

2.邻接矩阵的实现

邻接矩阵有两种:不带权图的和网的邻接矩阵。不带权图的邻接矩阵元素取值为0或1,网的邻接矩阵的元素取值为0,∞或者权值。

(1)使用一个三元组(u,v,w)代表一条边,u和v是边上的两个顶点,w是边的权

(2)对于两种邻接矩阵的主对角线元素的三元组(u,u,w)都有w=0

(3)对于无向图的每条无向边(u,v),需存储两条边(u,v)和(v,u)

typedef int MGDataType;
typedef struct mGraph
{
	MGDataType nodege;//两顶点间无边时的值
	int n;//顶点个数
	int e;//边数
	MGDataType** a;//二维数组,邻接矩阵
}MGraph;

头文件:

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<stdbool.h>
#include<assert.h>

typedef int MGDataType;
typedef struct mGraph
{
	MGDataType noEdge;//两顶点间无边时的值
	int vertex;//顶点个数
	int edge;//边数
	MGDataType** a;//二维数组,邻接矩阵
}MGraph;


void MGInit(MGraph* mg, int nSize, MGDataType noEdgeValue);//矩阵初始化
void MGDestory(MGraph* mg);//矩阵销毁
bool MGFind(MGraph* mg, int u, int v);//边的搜索
bool MGInsert(MGraph* mg, int u, int v, MGDataType w);//边的插入
bool MGDelete(MGraph* mg, int u, int v);//边的删除

源文件:

#include"MGraph.h"

void MGInit(MGraph* mg, int nSize, MGDataType noEdgeValue)
{
	int i, j;
	mg->vertex = nSize;
	mg->edge = 0;
	mg->noEdge = noEdgeValue;
	mg->a = (MGDataType**)malloc(sizeof(MGDataType*) * nSize);
	if (!mg->a)
	{
		printf("malloc fail \n");
		exit(-1);
	}
	for (int i = 0; i < mg->vertex; i++)
	{
		mg->a[i] = (MGDataType*)malloc(sizeof(MGDataType) * nSize);
		for (int j = 0; j < mg->vertex; j++)
		{
			mg->a[i][j] = mg->noEdge;//权值初始默认为0或∞
		}
		mg->a[i][i] = 0;
	}
}

void MGDestory(MGraph* mg)
{
	assert(mg);
	int i;
	for (int i = 0; i < mg->vertex; i++)
	{
		free(mg->a[i]);
	}
	free(mg->a);
}

bool MGFind(MGraph* mg, int u, int v)
{
	assert(mg);
	if (u<0 || v<0 || u>mg->vertex - 1 || v>mg->vertex - 1 || u == v || mg->a[u][v] == mg->noEdge)
	{
		return false;
	}
	return true;
}


bool MGInsert(MGraph* mg, int u, int v, MGDataType w)
{
	if (u<0 || v<0 || u>mg->vertex - 1 || v>mg->vertex - 1 || u == v )
	{
		return false;
	}
	if (mg->a[u][v] != mg->noEdge)//u,v两点之间有边
	{
		printf("already alive\n");
		exit(-1);
	}
	mg->a[u][v] = w;
	mg->edge + 1;
	return true;
}


bool MGDelete(MGraph* mg, int u, int v)
{
	if (u<0 || v<0 || u>mg->vertex - 1 || v>mg->vertex - 1 || u == v )
	{
		return false;
	}
	if (mg->a[u][v] == mg->noEdge)//u,v两点之间无边
	{
		printf("no vertex\n");
		exit(-1);
	}
	mg->a[u][v] = mg->noEdge;
	mg->edge--;
	return true;
}

邻接矩阵不利于增加和删除结点

空间复杂度为O(N^2),对于稀疏图浪费空间,但对于稠密图特别是完全图十分合算

统计稀疏图中一共有多少边,需要遍历很浪费时间

3.邻接表表示法

邻接表表示法时图的另一种常用的存储表示法。邻接表为图的每一个顶点建立一个单链表。单链表中的每个结点代表一条边,称为边节点

按照编号顺序将顶点数据存储在一维数组中

关联同一顶点的边(以顶点为尾的弧):用线性链表存储

无向图的邻接表不唯一,边结点的顺序可以改变

若无向图中有n个顶点,e条边,则其邻接表需n个头结点和2e个表结点。适合存储稀疏图。

有向图顶点的出度为单链表中结点的个数

4.邻接表的实现

头文件:

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<stdbool.h>
#include<assert.h>

typedef int LGDataType;
typedef struct eNode
{
	int adjVex;//相连的顶点
	LGDataType w;//边的权值
	struct eNode* next;
}ENode;

typedef struct lGraph
{
	int vertex;//顶点个数
	int edge;//边数
	ENode** a;
}LGraph;


void LGInit(LGraph* lg,int nSize);//矩阵初始化
void LGDestory(LGraph* lg);//矩阵销毁
bool LGFind(LGraph* lg, int u, int v);//边的搜索
bool LGInsert(LGraph* lg, int u, int v, LGDataType w);//边的插入
bool LGDelete(LGraph* lg, int u, int v);//边的删除

源文件:

#include"LGraph.h"

void LGInit(LGraph* lg, int nSize)//表初始化
{
	lg->vertex = nSize;
	lg->edge = 0;
	lg->a = (ENode**)malloc(sizeof(ENode*) * nSize);
	if (!lg->a)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	for (int i = 0; i < lg->vertex; i++)
	{
		lg->a[i] = NULL;//将指针数组a置空
	}
}

void LGDestory(LGraph* lg)
{
	ENode* front, *behind;
	for (int i = 0; i < lg->vertex; i++)
	{
		front = lg->a[i];
		behind = front;
		while (front)
		{
			front = front->next;
			free(behind);
			behind = front;
		}
	}
	free(lg->a);
}

bool LGFind(LGraph* lg, int u, int v)
{
	ENode* p;
	if (u<0 || v<0 || u>lg->vertex - 1 || v>lg->vertex - 1 || u == v)
	{
		return false;
	}
	p = lg->a[u];
	while (p && p->adjVex != v)
	{
		p = p->next;
	}
	if (!p)
	{
		return false;
	}
	return true;
}

bool LGInsert(LGraph* lg, int u, int v, LGDataType w)
{
	ENode* p;
	if (u<0 || v<0 || u>lg->vertex - 1 || v>lg->vertex - 1 || u == v)
	{
		return false;
	}
	if (LGFind(lg, u, v))
	{
		printf("already alive\n");
		exit(-1);
	}
	p = (ENode*)malloc(sizeof(ENode));
	p->adjVex = v;
	p->w = w;
	p->next = lg->a[u];//将新的边界点插入单链表的最前面
	lg->a[u] = p;
	lg->edge++;
	return true;
}

bool LGDelete(LGraph* lg, int u, int v)
{
	ENode* p, * q;
	if (u<0 || v<0 || u>lg->vertex - 1 || v>lg->vertex - 1 || u == v)
	{
		return false;
	}
	p = lg->a[u], q = NULL;
	while (p && p->adjVex != v)
	{
		q = p;
		p = p->next;
	}
	if (!p)
	{
		printf("no vertex\n");
		exit(-1);
	}
	if (q)
	{
		q->next = p->next;
	}
	else
	{
		lg->a[u] - p->next;
	}
	free(p);
	lg->edge--;
	return true;
}

方便找任一顶点的所有“邻接点”

节约稀疏图的空间:需要N个头指针+2E个结点(每个结点至少2个域)

方便计算任一顶点的度,但对于有向图只能计算出度

5.邻接矩阵域邻接表的关系

(1)联系:

邻接表中每个链表对应于邻接矩阵中的一行,链表中结点个数等于一行中非零元素的个数

(2)区别:

对于任一确定的无向图,邻接矩阵是唯一的,但是邻接表不唯一,链表的链接次序与顶点编号无关

邻接矩阵的空间复杂度为O(N^2),而邻接表的空间复杂度为O(N+e)

三、图的遍历

从图中任一顶点v出发,按照某种次序访问图中的所有顶点,且每个顶点仅访问一次的过程称为图的遍历。

图中可能存在回路,为了避免重复访问设置辅助数组visited,用于标记访问状态,防止被多次访问

1.深度优先遍历(DFS)

 连通图的深度优先遍历类似于树的先根遍历,对于同一种图可有多种不同的访问次序

 深度优先遍历图的过程本质上是对每个顶点搜索其邻接点的过程。此过程中,每个顶点仅被访问一次,其所消耗的时间取决于图所采用的存储结构。

设图的顶点数为N,边数为e

当采用邻接表表示图,虽然有2e个表结点,但是只需要扫描e个结点即可完成遍历时,DFS算法的时间复杂度为O(N+e)

而采用邻接矩阵表示图时,遍历图中每个顶点都要从头扫描该顶点所在行,DFS算法的时间复杂度为O(N^2)

对于非连通图,一次优先遍历之后,图中必定还有顶点未被访问,需从图中另一个未访问顶点出发再次深度优先遍历,直达图中所有的顶点均被访问为止

 代码实现: 

#include"LGraph.h"


void DFS(int v, int visited[], LGraph lg)
{
	ENode* neighbor;
	printf("%d ", v);
	visited[v] = 1;
	for (neighbor = lg.a[v]; neighbor; neighbor = neighbor->next)
	{
		if (!visited[neighbor->adjVex])//如果neighbor尚未访问,则递归调用DFS
		{
			DFS(neighbor->adjVex, visited, lg);
		}
	}
}

void DFSGraph(LGraph lg)
{
	int i;
	int* visited = (int*)malloc(sizeof(int) * lg.vertex);
	for (int i = 0; i < lg.vertex; i++)//初始化visited数组
	{
		visited[i] = 0;
	}
	for (int i = 0; i < lg.vertex; i++)//逐步遍历检查每一个结点
	{
		if (!visited[i])//如果未被访问则调用DFS
		{
			DFS(i, visited, lg);
		}
	}
	free(visited);
}

2.广度优先遍历(BFS)

类似于树的层次遍历过程

广度优先遍历需要借助队列来实现,与层序遍历思路类似 

 代码实现:

#include"LGraph.h"
#include"Queue.h"


void BFS(int v, int visited[], LGraph lg)
{
	ENode* neighbor;
	Queue q;
	QueueInit(&q);
	visited[v] = 1;//给顶点v打上访问标记
	printf("%d ", v);
	QueuePush(&q, v);//放入队列
	while (!QueueEmpty(&q))
	{
		QueueFront(&q,v);
		QueuePop(&q);//队首顶点出列
		for (neighbor = lg.a[v]; neighbor; neighbor = neighbor->next)//循环遍历所有邻接点
		{
			if (!visited[neighbor->adjVex])//如果没有被访问则放入队列中
			{
				visited[neighbor->adjVex] = 1;//将放入队列中的标记
				printf("%d ", neighbor->adjVex);
				QueuePush(&q, neighbor->adjVex);
			}
		}
	}
}

void BFSGraph(LGraph lg)
{
	int* visited = (int*)malloc(sizeof(int) * lg.vertex);
	for (int i = 0; i < lg.vertex; i++)
	{
		visited[i] = 0;//初始时数组visited都为0
	}
	for (int i = 0; i < lg.vertex; i++)
	{
		if (!visited[i])
		{
			BFS(i, visited, lg);
		}
	}
	free(visited);
}

3.DFS与BFS算法效率比较

 空间复杂度相同,都是O(N)

时间复杂度只与存储结构(邻接矩阵或者邻接表)有关,与搜索路径无关

四、拓扑排序

1.AOV网

用一个有向图表示一个工程的各子工程及其相互制约的关系,其中以顶点表示活动,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称AOV网(Activity On Vertex network)

若从i到j有一条有向路径,则i是j的前驱;j是i的后继。

若<i,j >是网中有向边,则i是j的直接前驱;j是i的直接后继。

AOV网中不允许有回路,如果有回路存在,则表明某项活动以自己为前提条件,这是不符合逻辑的

2.AOV网的拓扑排序

在AOV网没有回路的前提下,我们将全部活动排列成一个线性序列,使得若AOV 网中有弧<i,j>存在,则在这个序列中,i一定排在j的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序的算法称为拓扑排序。

 拓扑排序步骤:①在图中选择一个入度为0的顶点②从图中删除此顶点,以及该顶点的所有出边③重复前两个步骤,直至不存在入度为0的顶点

一个AOV网的拓扑序列不是唯一的

检测AOV网中是否存在环的方法:对有向图构造其顶点的拓扑有序序列,如果网中所有顶点都在它的拓扑有序序列中,则该AOV网必定不存在环。

代码实现:

#include"LGraph.h"
#include"Stack.h"

//degree计算顶点入度
void Degree(int* inDegree, LGraph* lg)
{
	ENode* p;
	for (int i = 0; i < lg->vertex; i++)//初始化degree数组
	{
		inDegree[i] = 0;
	}
	for (int i = 0; i < lg->vertex; i++)
	{
		for (p = lg->a[i]; p; p = p->next)//检查以顶点i为尾的所有邻接点
		{
			inDegree[p->adjVex]++;//将顶点i的邻接点p->adjVex的入度加1
		}
	}
}

//AOV网拓扑排序
void TopoSort(int* topo, LGraph* lg)
{
	ENode* p;
	ST s;
	int* inDegree = (int*)malloc(sizeof(int) * lg->vertex);
	StackInit(&s);
	Degree(inDegree, lg);//计算顶点的入度
	for (int i = 0; i < lg->vertex; i++)
	{
		if (!inDegree[i])//入度为0的进栈
		{
			StackPush(&s, i);
		}
	}
	for (int i = 0; i < lg->vertex; i++)
	{
		if (StackEmpty(&s))
		{
			printf("loop in Graph\n");
			exit(-1);
		}
		else
		{
			int j = StackTop(&s);//顶点出栈
			StackPop(&s);
			topo[i] = j;//将顶点j输出到拓扑序列中
			printf("d ", j);
			for (p = lg->a[j]; p; p = p->next)//检查顶点j为尾的所有邻接点
			{
				int k = p->adjVex;
				inDegree[k]--;
				if (!inDegree[k])//顶点入度为0,入栈
				{
					StackPush(&s, k);
				}
			}
		}
	}
}

五、关键路径

1.AOE网

用一个有向图表示一个工程的各子工程及其相互制约的关系,以弧表示活动,以顶点表示活动的开始或结束事件,称这种有向图为边表示活动的网,简称为AOE网(Activity On Edge)

AOE网中只有一个出度为0的顶点,用来表示工程的结束,称为汇点。

AOE网中同样不存在回路

2.AOE网的关键路径

对于AOE网有两个问题:①完成整个工程至少需要多少时间②那些活动是影响工程进度的关键

关键路径:路径长度最长的路径

路径长度:路径上各个活动持续时间的总和

关键活动:关键路径上的活动,即l(i)==e(i)的活动

(1)最早发生时间

w为权值,最早发生时间取决于其直接前驱时间用时最久的边

最早发生时间从源点向汇点计算

(2)最迟发生时间

 最迟发生时间从汇点向源点计算

求关键路径的步骤:

①求最早发生时间ve②求最迟发生时间vl③求最早开始时间e(i)④求最晚开始时间l(i)⑥求时间余量l(i)-e(i)

以下图为例求关键路径

代码实现:

//AOE网Eearly函数
void Eearly(int* eearly, int* topo, LGraph lg)
{
	ENode* p;
	for (int i = 0; i < lg.vertex; i++)//初始化数组eearly
	{
		eearly[i] = 0;
	}
	for (int i = 0; i < lg.vertex; i++)
	{
		int k = topo[i];//获取拓扑排序中的顶点序号k
		for (p = lg.a[k]; p; p = p->next)
		{
			if (eearly[p->adjVex] < eearly[k] + p->w)//更新eearly
			{
				eearly[p->adjVex] = eearly[k] + p->w;
			}
		}
	}
}

//AOE网Elate函数
void Elate(int* elate, int* topo, int longest, LGraph lg)
{
	ENode* p;
	for (int i = 0; i < lg.vertex; i++)
	{
		elate[i] = longest;
	}
	for (int i = lg.vertex - 2; i > -1; i--)
	{
		int j = topo[i];
		for (p = lg.a[j]; p; p = p->next)
		{
			if (elate[j] > elate[p->adjVex] - p->w)
			{
				elate[j] = elate[p->adjVex] - p->w;
			}
		}
	}
}

六、最小代价生成树

1.最小代价生成树的基本概念

生成树:所有顶点均由边连接在一起,并且不存在回路的图

一个图可以有许多棵不同的生成树

生成树的顶点个数与图的顶点个数相同;生成树是图的极小连通子图 ;一个有n个顶点的连通图的生成树一定有n-1条边;生成树中增加任意一条边则必然形成回路

最小生成树:给定一个无向网,在该网的所有生成树中,各边权值之和最少的生成树为最小生成树,也叫最小代价生成树

MST性质:

最小生成树(Minimum Spanning Tree, MST)是一种特殊的图

①在生成树的构造过程中,图中n个顶点分属两个集合:已落在生成树上的顶点集U;尚未落在生成树上的顶点集V-U。V为全集

②在所有连通U的顶点和V-U的顶点的边中选取权值最小的边

以上图为例:假设V1属于集合U中,V2-V6属于集合V-U中。使两个集合连接的顶点包括{(V1,V2),(V1,V3),(V1,V4)},其(V1,V3)顶点的边权值为1,最小。

2.普利姆算法

 每次找一个顶点,这个顶点与U集合之间相连的边有最小的权值

每一个顶点都要找其他的所有顶点

代码实现:

①一维数组closeVex[v]存放与v距离最近的顶点编号u,距离最近是指边(u,v)是所有与顶点v关联的边中权值最小的边

②一维数组lowWeight[v]存放边(clowVex[v],v)的权值

③一维数组isMark[v]用于标记顶点v是否在生成树中,如果isMark[v]=0表示未加入生成树,否则表示已经加入生成树中

#include"LGraph.h"
#define INFTY 32767
bool Prim(int k, int* closeVex, LGDataType* lowWeight, LGraph lg)
{
	ENode* p;
	LGDataType min;
	int* isMark = (int*)malloc(sizeof(int) * lg.vertex);
	if (k<0 || k>lg.vertex - 1)
	{
		printf("k<0 k>lg.vertex\n");
		exit(-1);
	}
	for (int i = 0; i < lg.vertex; i++)
	{
		closeVex[i] = -1;
		lowWeight[i] = INFTY;
		isMark[i] = 0;
	}
	//源点加入生成树
	lowWeight[k] = 0;
	closeVex[k] = k;
	isMark[k] = 1;
	//选择其余n-1条边加入生成树
	for (int i = 1; i < lg.vertex; i++)
	{
		for (p = lg.a[k]; p; p = p->next)
		{
			int j = p->adjVex;
			if ((!isMark[j]) && (lowWeight[j] > p->w))//更新生成树外顶点的lowWeight值
			{
				lowWeight[j] = p->w;
				closeVex[j] = k;
			}
		}
		min = INFTY;
		for (int j = 0; i < lg.vertex; j++)//寻找生成树外顶点中,具有最小lowWeight值的顶点k
		{
			if((!isMark[j]) && (lowWeight[j] < min))
			{
				min = lowWeight[j];
				k = j;
			}
		}
		isMark[k] = 1;//将顶点k加入生成树
	}
	for (int i = 0; i < lg.vertex; i++)
	{
		printf("%d ", closeVex[i]);
		printf("%d ", i);
		printf("%d ", lowWeight[i]);
		printf("/n");
	}
	return true;
}

3.克鲁斯卡尔算法

 克鲁斯卡尔算法采用直接选取权值最小的边加入,是否选取该边的判断条件为加入边后是否会形成环,如果形成环那么就选择其他边

 

代码实现:

①一维数组edgeset:从邻接矩阵中获取所有边保存在数组edgeset中,并用排序算法对边按照权值进行递增排序

②一维数组vexset:用于表示个顶点所属的连通分量,若两个顶点属于不同的连通分量,则将这两个顶点关联的边加到生成树中时不会形成回路

#include"MGraph.h"

//定义结构体--边
typedef struct edge
{
	int u;
	int v;
	MGDataType w;
}Edge;


void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}


void SelectSort(int* a, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int mini = begin;
		int maxi = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
			if (a[i] < a[mini])
			{
				mini = i;
			}
		}
		Swap(&a[begin], &a[mini]);
		if (maxi == begin)
		{
			maxi = mini;
		}
		Swap(&a[end], &a[maxi]);
		begin++;
		end--;
	}
}

void Kruscal(MGraph mg)
{
	int* vexSet = (int*)malloc(sizeof(int) * mg.vertex);
	Edge* edgeset = (Edge*)malloc(sizeof(Edge) * mg.vertex);
	int k = 0;
	for (int i = 0; i < mg.vertex; i++)
	{
		for (int j = 0; j < i; j++)
		{
			if (mg.a[i][j] != 0 && mg.a[i][j] != mg.noEdge)
			{
				edgeset[k].u = i;
				edgeset[k].v = j;
				edgeset[k].w = mg.a[i][j];
			}
		}
	}
	SelectSort(edgeset, mg.edge / 2);
	for (int i = 0; i < mg.vertex; i++)
	{
		vexSet[i] = i;
	}
	k = 0;
	int j = 0;
	while (k < mg.vertex - 1)
	{
		int u1 = edgeset[j].u;
		int v1 = edgeset[j].v;
		int vs1 = vexSet[u1];
		int vs2 = vexSet[v1];
		if (vs1 != vs2)
		{
			printf("%d ,%d ,%d\n", edgeset[j].u, edgeset[j].v, edgeset[j].w);
			k++;
			for (int i = 0; i < mg.vertex; i++)
			{
				if (vexSet[i] == vs2)
				{
					vexSet[i] = vs1;
				}
				j++;
			}
		}
	}
}

 4.两种算法的比较

算法名普利姆算法克鲁斯卡尔算法
算法思想选择点选择边
时间复杂度O(N^2)        N为顶点数O(NlogN)        N为边数
适应范围稠密图稀疏图

七、最短路径

1.最短路径问题

路径问题大概有以下几种:

  • 确定起点的最短路径问题:已知起始点,求起点到其他任意点最短路径的问题。即单源最短路径问题。

  • 确定终点的最短路径问题:与确定起点的问题相反,该问题是已知终点,求最短路径的问题。在无向图(即点之间的路径没有方向的区别)中该问题与确定起点的问题完全等同,在有向图(路径间有确定的方向)中该问题等同于把所有路径方向反转的确定起点的问题。

  • 确定起点终点的最短路径问题:已知起点和终点,求任意两点之间的最短路径。即多源最短路径问题。 

2.单源最短路径问题

①初始化:先找出源点Vo到各个终点Vk的直达路径(Vo,Vk),即通过一条边到达的路径

②选择:从这些路径中,找出一条长度最短的路径(Vo,U)

③更新:对其余各条路径进行适当调整

D[i]用于表示从Vo到Vi边的权值,如果不存在用∞表示

D[j]=Min{D[i]}

 

 

代码实现:

//迪杰斯特拉算法
#include"MGraph.h"

#define INFETY 32767

int Choose(int* d, int* s, int n)
{
	MGDataType min;
	min = INFETY;
	int minpos = -1;
	for (int i = 0; i < n; i++)
	{
		if (d[i] < min && !s[i])
		{
			min = d[i];
			minpos = i;
		}
	}
	return minpos;
}

bool Dijkstra(int v, MGDataType* d, int* path, MGraph mg)
{
	if (v<0 || v>mg.vertex - 1)
	{
		printf(" error\n");
		exit(-1);
	}
	int* s = (int*)malloc(sizeof(int) * mg.vertex);
	for (int i = 0; i < mg.vertex; i++)
	{
		s[i] = 0;
		d[i] = mg.a[v][i];
		if (1 != v && d[i] < INFETY)
		{
			path[i] = v;
		}
		else
		{
			path[i] = -1;
		}
	}
	s[v] = 1;
	d[v] = 0;
	for (int i = 0; i < mg.vertex - 1; i++)
	{
		int k = Choose(d, s, mg.vertex);
		if (k == 1)
		{
			continue;
		}
		s[k] = 1;
		printf("%d ", k);
		for (int w = 0; w < mg.vertex; w++)
		{
			if (!s[w] && d[k] + mg.a[k][w] < d[w])
			{
				d[w] = d[k] + mg.a[k][w];
				path[w] = k;
			}
		}
	}
	for (int i = 0; i < mg.vertex; i++)
	{
		printf("%d ", d[i]);
	}
	return true;
}

3.所有顶点之间的最短路径

方法一:每次以一个顶点为源点,重复执行迪杰斯特拉算法N次

方法二:弗洛伊德算法

 

代码实现:

//弗洛伊德算法
#include"MGraph.h"

#define INFTY 32767
void Floyd(MGraph mg)
{
	MGDataType** d = (MGDataType**)malloc(mg.vertex * sizeof(MGDataType*));
	int** p = (int**)malloc(sizeof(int) * mg.vertex);
	for (int i = 0; i < mg.vertex; i++)
	{
		d[i] = (MGDataType*)malloc(mg.vertex * sizeof(MGDataType));
		p[i] = (int*)malloc(mg.vertex * sizeof(int));
		for (int j = 0; j < mg.vertex; j++)
		{
			d[i][j] = mg.noEdge;
			p[i][j] = 0;
		}
	}
	for (int i = 0; i < i < mg.vertex; i++)
	{
		for (int j = 0; j < mg.vertex; j++)
		{
			d[i][j] = mg.a[i][j];
			if (i != j && mg.a[i][j] < INFTY)
			{
				p[i][j] = i;
			}
			else
			{
				p[i][j] = 0;
			}
		}
	}
	for (int k = 0; k < mg.vertex; k++)
	{
		for (int i = 0; i < mg.vertex; i++)
		{
			for (int j = 0; j < mg.vertex; j++)
			{
				if (d[i][k] + d[k][j] < d[i][j])
				{
					d[i][j] = d[i][k] + d[k][j];
					p[i][j] = p[k][j];
				}
			}
		}
	}
	for (int i = 0; i < mg.vertex; i++)
	{
		for (int j = 0; j < mg.vertex; j++)
		{
			printf("%d ", d[i][j]);
		}
		printf("\n");
	}
}

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值