【圖】最小路徑

最小路徑問題描述:node

在一個(有向/無向)圖中,每一個結點間存在或不存在直接路徑(不通過其餘結點直接到達結點的路徑),每條路徑上擁有其權值,尋找一條路徑,使得v0->vk所通過的路徑權值之和最小。算法


圖1數組

上面是一個無向圖,對於上面的圖,能夠肯定一個圖的二維矩陣(即鄰接矩陣),行首爲起始結點,列首爲目標結點。spa

起始/目標 v0 v1 v2 v3 v4 v5
v0 0 2 1 5 -1 -1
v1 2 0 2 3 1 -1
v2 1 2 0 3 1 2
v3 5 3 3 0 1 5
v4 -1 1 1 1 0 2
v5 -1 -1 2 5 2 0

注意:無向圖是一個對稱矩陣,有向圖大多都不是對稱矩陣,0表示起始結點與目標結點相同,-1表示沒法到達。.net

分析:code


(1).迪傑斯特拉算法-Dijkstrablog

圖片摘自http://blog.csdn.net/todd911/article/details/9347053遞歸







圖2圖片

①全部結點爲集合v的元素,假設當前已知最小路徑的結點集合爲s,m[j]表示從v0->vi->vj的最短路徑,表示爲min{v0->vi->vj}(注意:v0->v0->vj=v0->vj),其中vi∈s,vj∈v-s,那麼將m[x]中最小值對應的vj加入集合s。ci

如圖1所示v={v0,v1,v2,v3,v4,v5},s={v0},計算min{v0->vj}m={0,2,1,∞,5,∞},

此時將v2加入集合s,s={v0,v2},比較m與min{v0->v2->vj}相應結點的值,並將m中的值替換成較小值,此時m={0,2,1,2,4,3};

此時將v1加入集合s,s={v0,v2,v1},比較m與min{v0->v1->vj}相應結點的值,並將m中的值替換成較小值,此時m={0,2,1,2,4,3};

此時將v3加入集合s,s={v0,v2,v1,v3},比較m與min{v0->v3->vj}相應結點的值,並將m中的值替換成較小值,此時m={0,2,1,2,3,3};

此時將v4加入集合s,s={v0,v2,v1,,v4},比較m與min{v0->v4->vj}相應結點的值,並將m中的值替換成較小值,此時m={0,2,1,2,3,3};

此時將v5加入集合s,完畢。

②爲什麼min{v0->vi->vj}中vi必須∈s?假設vp∉s,若v0->vp->vx<min{v0->vi->vj} (vi∈s,vj∉s且j=0,1,2...n,vx爲vj的其中一個),那麼因爲v0->vp<v0->vp->vx<min{v0->vi->vj},因此此時,vp必將加入集合s中。因此與假設不符。

③使用path記錄vj的前驅結點爲vi,則算法結束後能夠由path還原出具體路徑。

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void perror(char *str){
	printf("%s", str);
	system("pause");
	exit(0);
}

void input_graph_array(int **data, int *n){
	int i,j;

	scanf("%d", n);
	*data = (int*)malloc(sizeof(int)*(*n)*(*n));
	if(*data==NULL)
		perror("malloc failed\n");
	
	for(i=0; i<*n; i++){
		for(j=0; j<*n; j++){
			if(j==0)
				scanf("%d", *data+i*(*n));
			else
				scanf(" %d", *data+i*(*n)+j);
		}
	}
}

//6
//0 2 1 5 -1 -1
//2 0 2 3 -1 -1
//1 2 0 3 1 -1
//5 3 3 0 1 5
//-1 -1 1 1 0 2
//-1 -1 -1 5 2 0

//6
//0 5 -1 -1 5 1
//5 0 5 -1 -1 5
//-1 5 0 1 1 -1
//-1 -1 1 0 -1 10
//5 -1 1 -1 0 1
//1 5 -1 10 1 0
void cal_min_path(int *data, int n, int **path, int **m){
	int *s, sk;
	int nk;
	int mintemp, min_node;
	
	s = (int*)malloc(sizeof(int)*n);
	*path = (int*)malloc(sizeof(int)*n);    //path記錄結點相鄰前驅結點
	*m = (int*)malloc(sizeof(int)*n);       //m記錄結點v0通過集合s的一個結點到達vj的最小路徑,m[j]表示從v0到vj路徑上,通過或不通過集合s中結點的最小路徑長度,因此當集合s加入新結點,只須要計算v0通過新節點vp到達vp相鄰節點的路徑長度,更新m便可。

	if(s==NULL||*path==NULL||*m==NULL)
		perror("malloc failed\n");
	//initialization
	for(sk=0; sk<n; sk++){
		*(s+sk)=0;
		*(*path+sk)=0;
		if(*(data+sk)!=-1)
			*(*m+sk)=*(data+sk);  //m記錄集合S中元素與周邊結點最小路徑長度。
		else
			*(*m+sk)=INT_MAX;
	}
	*s=1;

	for(nk=0; nk<n; nk++){
		for(sk=0, mintemp=INT_MAX; sk<n; sk++){
			if(!s[sk]&&(*(*m+sk)!=INT_MAX)&&(*(*m+sk)<mintemp)){
				min_node = sk;
				mintemp = *(*m+sk);
			}
		}
		s[min_node] = 1;

		//min_node加入集合S後,更新m(須要比較v0->v_min_node->vj,vj∉S)
		for(sk=0; sk<n; sk++){
			if(!s[sk]&&*(data+min_node*n+sk)!=-1&&(*(*m+sk)>mintemp+*(data+min_node*n+sk))){
				*(*m+sk) = *(*m+min_node)+*(data+min_node*n+sk);
				*(*path+sk) = min_node;
			}
		}
	}
}

void dis_minpath(int n, int *path, int i){
	if(*(path+i)==0){
		printf("v0");
		return;
	}
	else{
		dis_minpath(n, path, *(path+i));
	}
	printf("->v%d", *(path+i));
}

int main(void){
	int *data, n, k;
	int *path, *min_len;

	input_graph_array(&data, &n);
	cal_min_path(data, n, &path, &min_len);

	for(k=1; k<n; k++){
		dis_minpath(n, path, k);
		printf("->v%d\n", k);
	}

	system("pause");
	return 0;
}


(2).佛洛伊德算法-Floyd

佛洛伊德算法並非用於單獨求出兩個結點的最小路徑,而是用於求任意兩個不一樣節點間的最小路徑。固然,也可使用上面所介紹的迪傑斯特拉算法對任意結點進行計算,但Floyd算法在代碼上要更簡潔些。

佛洛伊德算法使用的是動態規劃的方法,因此咱們應該定義出輔助空間具體含義,將問題分爲子問題,並努力尋求所求目標的遞推公式。

①假設m(k)[i,j]表示結點vi到vj通過一系列結點vp,p=1,2,3,...,k,vp就是所謂的中間結點,m(k)[i,j]可描述爲結點vi通過若干個序號不超過k的中間結點到達結點vj的最小路徑;

②m(-1)[i,j]因爲結點序號不可能爲-1或更小,因此m(-1)[i,j]表示圖中結點間的直接路經長度;

③m(k)[i,j]即結點vi通過若干個序號不超過k的中間結點到達結點vj的最小路徑無非有兩種狀況:1)結點vi到達結點vj的最小路徑上不含vk;2)結點vi結點vj的最小路徑上包含vk。

若vi->vj的最小路徑上不含結點vk,則m(k)[i,j]=m(k-1)[i,j];若vi->vj的最小路徑上含有結點vk,那麼vi->vj=vi->vk->vj,其中vi->vk的路徑上通過了若干個序號不超過k-1的中間結點,vk->vj的路徑上也通過了若干個序號不超過k-1的中間結點。所以m(k)[i,j]=min{m(k-1)[i,j] or m(k-1)[i,k]+m(k-1)[k,j]};

④咱們最終須要求出任意結點vi,vj在通過若干個中間結點(vp,p=1,2,3,...,n,p≠i or j),因此咱們就是求解m(n)[i,j]的值。

 

//佛洛伊德算法-Floyd(動態規劃)
//①m[i,j]_k表示vi到vj通過不大於序號k的節點的最小路徑,即vi->...->vj,其中...表示vp,p=1,2,3,...,k;
//②m[i,j]_k=min{m[i,j]_(k-1), m[i,k]_(k-1)+m[k,j]_(k-1)},即取vi->vp->vj(vp,p=1,2,3,...,k-1)與vi->vp1->vk->vp2->vj(vp1,vp2,p1=1,2,3,...,k-1;p2=1,2,3,...,k-1)之間的較小值;
//③m[i,j]_n即爲目標所求。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void perror(char *str){
	printf("%s", str);
	system("pause");
	exit(0);
}

void input_graph_array(int **data, int *n){
	int i,j;

	scanf("%d", n);
	*data = (int*)malloc(sizeof(int)*(*n)*(*n));
	if(*data==NULL)
		perror("malloc failed\n");
	
	for(i=0; i<*n; i++){
		for(j=0; j<*n; j++){
			if(j==0)
				scanf("%d", *data+i*(*n));
			else
				scanf(" %d", *data+i*(*n)+j);
		}
	}
}

//6
//0 6 1 5 -1 -1
//6 0 5 -1 3 -1
//1 5 0 5 6 4
//5 -1 5 0 -1 2
//-1 3 6 -1 0 6
//-1 -1 4 2 6 0
void cal_min_path(int *data, int n, int **path, int **m){
	int k,i,j;

	*path = (int*)malloc(sizeof(int)*n*n);
	*m = (int*)malloc(sizeof(int)*n*n);

	//initialization
	for(i=0; i<n; i++){
		for(j=0; j<n; j++){
			*(*m+i*n+j)=(*(data+i*n+j)<=-1)?INT_MAX:*(data+i*n+j);
			*(*path+i*n+j)=-1;
		}
	}

	for(k=0; k<n; k++){
		for(i=0; i<n; i++){
			for(j=0; j<n; j++){
				if(*(*m+i*n+k)!=INT_MAX&&*(*m+k*n+j)!=INT_MAX&&*(*m+i*n+j)>*(*m+i*n+k)+*(*m+k*n+j)){
					*(*m+i*n+j) = *(*m+i*n+k)+*(*m+k*n+j);
					*(*path+i*n+j) = k;
					//當須要有path推出最短路徑時,原理是vi->vj,path(i,j)通過結點vk,而後遞歸查詢path(i,k)=k',則可知vi->vk通過結點vk',繼續遞歸查詢path(i,k'),直至遇到-1結束。
				}
			}
		}
	}
}

void dis_minpath(int n, int *path, int i, int j){
	if(*(path+i*n+j)==-1)
		return;
	dis_minpath(n, path, i, *(path+i*n+j));
	printf("->v%d", *(path+i*n+j));
	dis_minpath(n, path, *(path+i*n+j), j);
}

void show_path(int n, int *path, int i, int j){
	printf("v%d", i);
	dis_minpath(n, path, i, j);
	printf("->v%d\n", j);
}

int main(void){
	int *data, n, k;
	int *path, *min_len;
	int i,j;

	input_graph_array(&data, &n);
	cal_min_path(data, n, &path, &min_len);

	for(i=0; i<n; i++)
		for(j=0; j<n; j++){
			if(i==j)
				printf("v%d\n", i);
			else
				show_path(n, path, i, j);
		}

	system("pause");
	return 0;
}

這裏使用遞歸輸出任意結點間的最小路徑,從path(i,j)=k可知,vi->...->vk->...->vj,而後可又path(i,k)=k'可知,vi->...->vk'->...->vk->...vj,以此類推動行輸出。

 

不知道你們有沒有這個疑惑?由於m(k)[i,j]與m(k-1)[i,k],m(k-1)[k,j]有關,那麼在嵌套中直接修改m這個輔助數組不會對後面的計算產生影響?

咱們假設前面計算了m(k)[i,j]=min{m(k-1)[i,k]+m(k-1)[k,j] or m(k-1)[i,j]},

當咱們須要計算m(k)[i',j']=min{m(k-1)[i',k]+m(k-1)[k,j'] or m(k-1)[i',j']}時,假設m(k-1)[i',k]在以前被m(k)[i,k]或m(k)[k,j]所覆蓋,那麼由m(k)[i',k]=min{m(k-1)[i',k]+m(k-1)[k,k] or m(k-1)[i',k]},因爲m(x)[k,k]=0,因此此時m(k)[i',k]=m(k-1)[i',k];m(k)[k,j]證實相似。

相關文章
相關標籤/搜索