最短路徑

在這裏插入圖片描述

要求最短的路徑,首先得把這些邊都存儲起來,邊的存儲有鄰接表和鄰接矩陣兩種,這個題數據最大20萬個,用鄰接矩陣肯定不行,只能使用鄰接表來存儲。
求最短路的算法 Dijkstra,Floyd,Bellman,spfa;
這個題想要100%通過測試用例,只能使用效率高但是不是很穩定的spfa算法。
需要注意的是,初始化給所有路徑的長度定義的無窮大這個數值,不能太大,也不能太小,比如說0x3fffffff,具體大小根據題中所給的邊權的範圍來定。

Dijkstra(迪傑斯特拉)算法

  • Dijkstra算法所求的是單源的最短路徑,從起點開始,每一次都找到當前起點的最短可到達的點,然後再進行鬆弛。
  • 該算法在使用時要求所包括的邊中不能有負權邊。

這可以看出,原本A -> B最短路徑應該是 1 ,如果存在負權邊的話就變成了2

#define MAXN 1000 //最大頂點數
#define INF 0x3fffffff //一個很大的數 

int n = 0, m = 0, s = 0;//n爲頂點數 m爲邊數 s爲起點 
int d[MAXN] = {0}; //起點到各點最短路徑的長度
int visit[MAXN] = {0};//記錄已經找到的最短路徑的點 0--》未找到  1--》表示找到
int arr[MAXN][MAXN] = {0}; //暫時用鄰接矩陣來存儲各個邊

void Dijkstra()
{
	int i = 0;
	//初始化一步可以到達的路徑長度 
	for(i = 0;i < n;i++)
	{
		if(arr[s][i])
		{
			d[i] = 	arr[s][i];
		}
		else
		{
			d[i] = INF;
		}
	}
	visit[s] = 1;//起點寫進數組中
	d[s] = 0; //自己到自己的路徑設置爲0 

	for(i = 0;i < n; i++)
	{
		//找到一條最短的路 
		int j = 0,u = -1,min = INF;
		for(j = 0; j < n; j++)
		{
			//如果存在一條最短路徑,並且該點沒有被訪問,更新min 和 u
			if(!visit[j] && d[j] < min)
			{
				u = j;
				min = d[j];
			}
		}
		//如果 u 的值還是初值,就說明已經找完了
		if(u == -1)
		{
			return ;
		}

		visit[u] = 1;
		d[u] = min;

		//更新路徑 
		for(j = 0;j < n;j++)
		{
			if(!visit[j] && arr[u][j] && d[u] + arr[u][j] < d[j])
			{
				d[j] = d[u] + arr[u][j];
			}
		}
	}
}

Floyd(弗洛伊德)算法

  • 可以解決多源的最短路徑問題,可以正確處理有向圖或者負權邊的最短路徑問題。
  • 問題就是,該算法的時間複雜度爲O(n 3),空間複雜度是O(n 2)。
  • 尋找從節點i 到 j 的最短路徑有兩種情況,1是直接從i到j,2是從i開始,經過若干個點,最後到j。如果說dis[i][j] 是i到j目前的最短路徑,那麼對於每一個非i,j的節點k,都要檢查dis[i][k] + dis[k][j] < dis[i][j] 是否成立,如果成立,則對該邊進行鬆弛,這樣的話,遍歷完所有結點,dis[i][j] 就是最短的路徑了,就不用擔心負權邊的問題。
#define MAX 10005

int arc[1000][1000]={0};

void Floyd(int n)
{
	int i=0;	
	int j=0;
	int k=0;
	for(k=0;k<n;k++)
	{
		for(i=0;i<n;i++)
		{
			for(j=0;j<n;j++)
			{
				if(arc[i][k]!=MAX && arc[k][j]!=MAX)
				{
					if(arc[i][j] > arc[i][k]+arc[k][j])
					{
						arc[i][j] = arc[i][k]+arc[k][j];
					}
				}
				
			}
		}
	}
	
}

Bellman算法

  • 該算法是求含負權圖的單元最短路徑的一種算法,效率較低(O(nm))。
  • 原理就是連續進行鬆弛,在每次鬆弛是把每一條邊都更新一下,若在n-1次鬆弛後還能更新,則說明圖中含有負環,所以就無法完成求最短路徑的結果。所以說可以判斷圖中是否含有負環的一種算法。
#define MAX 0xffff
#define MAX_ARR 200005

typedef struct Side
{
	int _from;//邊的起點
	int _to;//邊的終點
	int _key;//邊的權值
}Side;

Side side[MAX_ARR] = { 0 };//結構體數組來存放每一條邊
int dis[MAX_ARR] = { 0 };//存放最短的路徑

//n是頂點數目 m是邊的數目
void BellMan(int n, int m)
{
	int i = 0, j = 0;
	int key = 0;
	int temp = 0;
	Side p;
	
	for (i = 2; i <= n; i++)
	{
		dis[i] = MAX;
	}
	dis[1] = 0;

	for (i = 1; i < n; i++)
	{
		for (j = 0; j < m; j++)
		{
			p = side[j];
			temp = dis[p._from] + p._key;
			//對可以優化的路徑進行鬆弛
			if (temp < dis[p._to])
			{
				dis[p._to] = temp;
			}
		}
	}
	//判斷有無負環
	for (j = 0; j < m; j++)
	{
		p = side[j];
		temp = dis[p._from] + p._key;
		if (temp < dis[p._to])
		{
			printf("存在負環\n");
			return ;
		}
	}
}

spfa算法

  • 適用的範圍: 給定的圖存在負權邊,這時Dijkstra算法便不能使用,題目對時間複雜度有所要求,這時Bellman-ford的時間複雜度又很高,這時就可以使用spfa算法。
  • 算法思想: 用一個數組來記錄每個節點的最短路徑,鄰接表來存儲圖(鄰接表的鏈式前向星)。遍歷時採用的方式類似於廣度優先搜索法,使用動態逼近的形式,在遍歷時如果一條邊的路徑最小,需要鬆弛,並且邊的終點沒有在隊列中,就將該邊的終點入隊列,直到隊列空爲止,算法的平均時間複雜度爲O(km),k爲所有頂點的平均入隊列次數,所以說spfa算法在求最短路徑時不穩定。
  • 判斷有無負環 :如果一個點入隊列次數超過N次,則爲存在負環。
#define MAX 200100
#define MAX_VAL 99999999

struct edge
{
	int to; //邊的終點 
	int val;//邊的權值 
	int next;//上一條相同起點的邊的編號 
}e[MAX];//e[i] 邊的起點 i

int m;//邊的數目 
int n;//頂點數目 
int head[MAX]; //表示以i爲起點的邊的編號 
int dis[MAX]; //表示1號點到i號點的距離

//邊的添加 
void add(int from,int to,int val,int len)
{
	e[len].to = to;
	e[len].val = val;
	e[len].next = head[from];//head[from] 表示上一條起點爲from 的邊 的編號 
	head[from] = len;//添加這條邊後,最新的以from爲起點的邊的編號是len 
}

//初始化路徑數組的值,不能使用memset 
void Init(int* a,int len,int val)
{
	int i = 0;
	for(i = 0; i <= len; i++)
	{
		a[i] = val;
	}
}

void spfa()
{
	int s;
	queue<int>q;
	Init(dis,n,MAX_VAL);
	int visit[MAX];//查看該邊是否在隊列中 0-->不在  1-->在 
	int judge[MAX];//判斷有無負環 
	memset(judge,0,sizeof(judge));
	memset(visit,0,sizeof(visit));
	dis[1] = 0;//初始化起點到起點的路徑爲0 
	q.push(1);
	visit[1] = 1;
	while(!q.empty())
	{
		int from = q.front();//得到隊頭的需要判斷的邊的起點 
		q.pop();
		visit[from] = 0;
		int i = 0;
		//遍歷該起點可以到達的所有邊 
		for(i = head[from];i != -1; i = e[i].next)
		{
			int to = e[i].to;
			//進行以from爲起點的所有路徑的鬆弛 
			if(dis[to] > dis[from] + e[i].val)
			{
				dis[to] = dis[from] + e[i].val;
				//如果成功鬆弛之後,把不在隊列中的邊加入隊列中 
				if(visit[to] == 0)
				{
					q.push(to);
					visit[to] = 1;
					judge[to]++;
					if(judge[to] > n)
					{
						printf("存在負環\n");
						return ;
					}
				}
			}
		}
	}
}

spfa的兩種優化思路

  1. SLF : 假如從源點到要入隊的節點的距離爲 j,而從源點到隊列隊頭節點的距離爲 i ,要是j < i ,就把該節點插入隊首。
  2. LLL :設從源點到隊列隊頭節點的距離爲 i ,隊列中所有節點 的最短路徑平均值爲dis,如果dis < i,就把i插入到隊尾,查找下一元素,直到找到某一個i 使得 dis >= i,就將i 出隊進行鬆弛操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章