数据结构基础--图

news2025/1/11 1:38:04

一、图的基本概念

 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");
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/75340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据开发适合哪类人群?

有不少应届大学毕业生和0基础人群选择学大数据&#xff0c;但是要选择零基础的大数据培训班&#xff0c;从Java基础开始学习&#xff0c;由浅入深掌握离线数据分析、实时数据分析和内存数据计算等重要内容。 应届大学生缺乏工作经验和技能&#xff0c;对未来没有明确的规划&am…

VTK- PointLocator

欢迎大家加入VTK社区雪易VTK社区-CSDN社区云 小结&#xff1a;本博文主要针对VTK中的PointLocator的分类及各接口的用途进行讲解&#xff0c;PointLocator主要用途为点的位置计算&#xff0c;希望能为各位小伙伴有所帮助。 vtk中关于Locator的关系图 目录 vtkLocator vtkAbs…

软件测试基础理论体系学习4-单元测试的目的?概念是什么?过程是什么?

4-单元测试的目的&#xff1f;概念是什么&#xff1f;过程是什么&#xff1f;1 单元测试目的1.1 单元测试的错误认识1.2 单元测试的重要性1.2.1 时间方面1.2.2 测试效果1.2.3 测试成本1.2.4 产品质量1.3 单元测试的优点1.3.1 它是一种验证行为1.3.2 它是一种设计行为1.3.3 它是…

CPU是什么

CPU&#xff08;Central Processing Unit&#xff09;是计算机系统的运算和控制核心&#xff0c;是信息处理、程序运行的最终执行单元&#xff0c;相当于系统的“大脑”。当 CPU 过于繁忙&#xff0c;就像“人脑”并发处理过多的事情&#xff0c;会降低做事的效率&#xff0c;严…

Postman安装和运行

下载安装 Postman是一个方便用于构造请求的软件.可以以简单的方式来构造请求. 要下载软件,还是同样的话,要去官网下载.这里我们直接将官网地址放在这里. https://www.postman.com/downloads/ 进入官网以后,点击windows 64-bit(图中圈起来的部分)即可下载. 下载好以后双击安…

java小技能:JWT认证实现

文章目录 引言I. 预备知识1.1 关键字去空格处理II token组成2.1 头部(Header)2.2 有效载荷(Playload)2.3 签名(Signature)2.4 代码实现:生成tokenIII 验证token3.1 网关验证token3.2 使用拦截器验证token引言 认证流程 I. 预备知识 1.1 关键字去空格处理

前端复制粘贴方式上传图片

最近在做一个论坛的项目&#xff0c;发布评论的时候&#xff0c;很多时候会用到截图上传的功能&#xff0c;通过微信截图&#xff0c;QQ截图&#xff0c;直接将截取的图片通过Ctrlv 复制到输入框里&#xff0c;自动上传将图片渲染到页面上&#xff0c;今天就来实现一个这样的功…

BOS金蝶云星空:表单插件设置单据体背景色

一.效果图&#xff1a; 备注&#xff1a;只适用于只读列 二.代码案例&#xff1a; 自定义单据提附加背景色方法&#xff1a; /// /// 设置单据体背景颜色 /// /// 实体 /// 行 /// 字段 /// 颜色代码 private void SetEntityBackgoundColor(string entityKey, int row, st…

【面试题】大厂面试题分享:如何让(a===1a===2a===3)的值为true?

大厂面试题分享 面试题库 前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 当我第一次看到这一题目的时候&#xff0c;我是比较震惊的&#xff0c;分析了下很不合我们编程的常理&#xff0c;并认为不大可能&#…

面试官:断网了,还能 ping 通 127.0.0.1 吗?

你女神爱不爱你&#xff0c;你问她&#xff0c;她可能不会告诉你。 ‍ 但网通不通&#xff0c;你 ping 一下就知道了。 可能看到标题&#xff0c;你就知道答案了&#xff0c;但是你了解背后的原因吗&#xff1f; 那如果把 127.0.0.1 换成 0.0.0.0 或 localhost 会怎么样呢&…

Win10用命令行编译带有cuda的opencv

0. 环境 笔记本win10 NVIDIA GeForce GTX 1660 Ti 1. 准备x64 Native Tools Command Prompt 1.1 准备Visual Studio Installer 需要安装visual studio 2019 1.2 安装工作负荷 为了安装x64 Native Tools Command Prompt&#xff0c;勾上使用C的桌面开发 安装完毕后&#xf…

Latent Class Modeling lca

潜类别模型&#xff08;Latent Class Modeling&#xff09; 潜在类别分析&#xff08;LCA&#xff09;数据分析流程&#xff08;详细版&#xff09; - 简书 (jianshu.com) R数据分析&#xff1a;用R语言做潜类别分析LCA - 知乎 (zhihu.com) About Latent Class Modeling -…

Postman(六): postman定义公共函数

Postman(11): postman定义公共函数 postman定义公共函数 在postman中&#xff0c;如下面的代码&#xff1a; 1、返回元素是否与预期值一致 var assertEqual(name,actual,expected)>{tests[${name}&#xff1a;实际结果&#xff1a; ${actual} &#xff0c; 期望结果&…

PDF转Excel怎么转?这些方法值得收藏

在我们的工作生活中&#xff0c;避免不了Excel表格的使用&#xff0c;当我们遇到想要将PDF文件中的信息转换制作成表格的时候&#xff0c;要怎么做呢&#xff1f;毕竟&#xff0c;PDF文件是一个不易编辑的格式&#xff0c;我们想复制其中的内容就较为的麻烦。一般这种时候&…

何止一个惨字形容,水滴 Java 面试一轮游,壮烈了,问啥啥不会,数据库血崩

static 关键字是用来干什么的&#xff0c;static 修饰的方法里面可以使用非静态的成员变量吗&#xff0c;为什么呢 private 修饰的方法是否可以被子类覆盖 覆盖和重载有什么区别 进程跟线程的区别 Java 中创建线程有几种方式 a. 反思&#xff1a;讲完三种方式之后&#xff…

Python——文件

文件 概念 我们常见的txt,jpg,mp4等等都是文件&#xff0c;存储在硬盘中的内容&#xff0c;就是文件&#xff0c;而文件夹是一种特殊的文件——目录文件 路径 一层一层文件夹组成的字符串就是路径&#xff0c;每一个文件的路径都是唯一的&#xff0c;相当于身份证号&#x…

卷积、自相关函数、功率谱密度

文章目录1、自相关函数和卷积2、自相关函数的傅里叶变换最近我在思考为什么&#xff1a; 为什么随机过程的自相关函数和其功率谱密度是一对傅里叶变换&#xff1f;1、自相关函数和卷积 这俩跟孪生兄弟似的&#xff0c;经常一起出现&#xff0c;我们先来看看自相关函数和卷积的…

【读书笔记】曾国藩的正面与侧面(二)

本书为全集的第二册&#xff0c;针对曾国藩的整个家族进行了介绍。包括他的兄弟&#xff0c;父母&#xff0c;和子女。 曾国藩的兄弟&#xff1a; 曾国潢&#xff1a;比曾国藩小9岁 是几个兄弟中读书天分最差的一个&#xff0c;但是有一个优点就是勤奋实在&#xff0c;所以在…

案例故事丨老虎国际 x TiDB ,降低架构复杂性,保障全球用户安全可靠投资

券商是一个古老的行业&#xff0c;发展至今已经历了三个时代&#xff1a;第一代券商为传统券商&#xff0c;在线下交易大厅进行买卖&#xff1b;第二代券商开始了电子化进程&#xff0c;从线下到线上进行了浅层服务的转移&#xff0c;改善了用户体验&#xff0c;提高了金融服务…

复习计算机网络——第四章习题记录

1、一台交换机具有24个100Mbps的全双工端口和2个1000Mbps的全双工端口&#xff0c;如果所有的端口都工作在全双工状态&#xff0c;那么交换机总带宽等于: 交换机知识点&#xff1a; &#xff08;1&#xff09;交换机具有24个10或者100Mbps全双工端口连接一般的用户计算机&…