1. 程式人生 > >圖論--最小生成樹和最短路1

圖論--最小生成樹和最短路1

圖論的兩個經典問題。

1、先介紹樹的概念:

樹的概念挺簡單的,一個祖先,一個兒子只能有一個父親節點,不能形成環。n個節點只能有n-1條邊,要不然會形成環。(易得知)

2、再來講講我用來存圖的兩種方式:

1、鄰接矩陣(使用於稠密圖)

map[b][a] = map[a][b] = c;代表的意思是a到b的距離為c.

如圖(網上找的圖):


2、鄰接表(適用於稀疏圖)

struct Edge {///陣列的下標代表邊的另一個端點
	int v; //邊端點,另一端點已知
	int w; //邊權值
	Edge(int v_ = 0, int w_ = INFINITE): v(v_), w(w_) { }
	
};
vector< vector <Edge> > G(110); //圖的鄰接表
如圖(網上找的圖):

開始進入正題。(有些題目我會用兩種存圖方式寫,有些只能用其中一種)

一、最小生成樹

1、概念:一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。

我們先介紹prim演算法,然後再介紹kruskal演算法

prim演算法的思路很簡單。就是從一個起點開始進行連通始終尋找沒有訪問過且最小的邊來進行連通。

題意很簡單就是根據n個點求一顆最小生成樹。

prim程式碼如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#define maxn 100 + 15
#define inf 200000005
int dis[maxn], vis[maxn];
int map[maxn][maxn];
int n, sum;
using namespace std;
void prim() {
	memset(vis, 0, sizeof(vis));
	for(int i = 0; i < n; ++i)  dis[i] = map[i][0];
	vis[0] = 1;
	dis[0] = 0;
	for(int i = 0; i < n - 1; ++i) { ///n-1條邊
		int k, temp = inf; ///temp用來找最小的邊,k儲存最小邊的對應的點
		for(int j = 0; j < n; ++j) {
			if(!vis[j] && dis[j] < temp) temp = dis[j], k = j;
		}
		vis[k] = 1;  ///標記找到的點
		sum += dis[k]; ///最小邊加入到最小生成樹裡面
		for(int j = 0;j <n;++j)
            if(!vis[j]&&dis[j] > map[k][j])  dis[j] = map[k][j]; ///儲存小的
	}
	cout << sum << endl;
}
int main() {
	while(scanf("%d", &n) != EOF) {
		for(int i = 0; i < n; ++i)
			for(int j = 0; j < n; ++j) {
				int value;
				scanf("%d", &value);
				map[i][j] = value; ///鄰接矩陣存圖
			}
		sum = 0;
		prim();
	}
	return 0;
}

         kruskal演算法的思路:把邊按從小到大排序,然後運用並查集的知識按照邊的兩個端點進行合併,判斷是不是同一個連通分量如果不是進行合併,如果是的話則跳過。讀到n-1條邊合併之後則是最小生成樹。

程式碼如下:(POJ1258)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#define maxn 100 + 15
int father[maxn];
using namespace std;
///鄰接矩陣存圖
struct Edge {
	int s, e, v;
	Edge(int ss, int ee, int vv): s(ss), e(ee), v(vv) {}
	Edge() {}
};
///按邊從大到小排序
bool cmp(Edge a,Edge b)
{
    return a.v < b.v;
}
vector<Edge>vp;
///並查集
int Find(int x) {
	if(x == father[x]) return x;
	else    return father[x] = Find(father[x]);
}
void Union(int a, int b) {
	int aa = Find(a);
	int bb = Find(b);
	if(aa == bb) return ;
	else  father[bb] = aa;
}
void Clear(int n) {
	for(int i = 0; i < n; ++i) father[i] = i;
}
int main() {
	int n;
	while(scanf("%d", &n) != EOF) {
		Clear(n);
		vp.clear();
		for(int i = 0; i < n; ++i)
			for(int j = 0; j < n; ++j) {
				int value;
				scanf("%d", &value);
				vp.push_back(Edge(i,j,value));
			}
        sort(vp.begin(),vp.end(),cmp); ///按邊排序
		int sum = 0;
		int num = 0;
		for(int i = 0; i < vp.size(); ++i) {
			int a = vp[i].s, b = vp[i].e, c = vp[i].v;
			if(Find(a) != Find(b)) { ///是不是同一個祖先
				num++;
				sum += c;
				Union(a, b);        ///合併成統一個連通分量
			}
			if(num == n - 1) break; ///找到n-1條邊,數已經生成,退出。
		}
		cout << sum << endl;
	}
	return 0;
}

二、最短路

1、概念:若網路中的每條邊都有一個數值(長度、成本、時間等),則找出兩節點(通常是源節點和阱節點)之間總權和最小的路徑就是最短路問題。

這裡我介紹兩種個演算法。floyd,dijkstra

裸裸的最短路。

2、dijkstra程式碼:

#include<cstdio>
#include<iostream>
#include<cstring>
#define MIN(a,b) a>b?b:a
#define maxn 201
#define INF 200000005
int map[maxn][maxn];
int dis[maxn];
bool vis[maxn];
using namespace std;
int main() {
	int n, m;
	while(scanf("%d%d", &n, &m) != EOF) {
		if(n == 0 && m == 0)
			break;
		memset(vis, 0, sizeof(vis));
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= n; j++)
				if(i == j)
					map[i][j] = 0; ///自己到自己的距離為0
				else
					map[i][j] = INF; ///初始到其他點的距離為無窮大
		}
		for(int i = 1; i <= m; i++) {
			int a, b, c;
			scanf("%d%d%d", &a, &b, &c);
			map[a][b] = map[b][a] = c; ///雙向距離相等
		}
		for(int i = 1; i <= n; i++)
			dis[i] = map[1][i];
		vis[1] = true;
		for(int i = 1; i < n; i++) {
			int temp = INF;
			int k;
			for(int j = 1; j <= n; j++) {
				if(vis[j]) continue;
				if(temp > dis[j]) {
					temp = dis[j];
					k = j;
				}
			}
			vis[k] = true;
			for(int j = 1; j <= n; j++) {
				if(vis[j]) continue;
				dis[j] = MIN(dis[j], dis[k] + map[k][j]);
			}
		}
		cout << dis[n] << endl;
	}
	return 0;
}
     3、floyd演算法(更新了所有點的最短路時間複雜度為O(n^3)):
///HDU 2544.
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100 + 15
#define inf 100000005
int a[maxn][maxn], dis[maxn], vis[maxn];
int n, m;
using namespace std;
int main() {
	while(scanf("%d%d", &n, &m) != EOF && (n && m)) {
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= n; j++) {
				a[i][j] = 0;
				if(i != j) a[i][j] = inf;
			}
		for(int i = 1; i <= m; i++) {
			int x, y, z;
			scanf("%d%d%d", &x, &y, &z);
			///更新最小值
			if(a[x][y] > z) a[x][y] = a[y][x] = z;
		}
		///更新了所有點的最短路
		for(int k = 1; k <= n; k++)
			for(int i = 1; i <= n; i++)
				for(int j = 1; j <= n; ++j) {
					a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
				}
		printf("%d\n", a[1][n]);
	}
	return 0;
}

    4、dijkstra(鄰接表存圖):

POJ3159

程式碼:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
#define maxn 30000 + 5
#define inf 200000005
int vis[maxn];
int n, m;
using namespace std;
///鄰接表存圖
struct edge {
	int e, cc; ///本身自帶一個節點,e代表另一個節點 v代表權值
};
bool operator < (const edge &a, const edge &b) {
	return a.cc > b.cc;
}
///優先佇列
priority_queue<edge>pq;
///鄰接表
vector< vector<edge> >v;
int main() {
	while(scanf("%d%d", &n, &m) != EOF) {
		edge d, z;
		v.clear();
		v.resize(n + 1);
		for(int i = 0; i < m; ++i) {
			int a, b, c;
			scanf("%d%d%d", &a, &b, &c);
			d.e = b;
			d.cc = c;
			v[a].push_back(d);///鄰接表存圖
		}
		memset(vis,0,sizeof(vis));
		d.e = 1;
		d.cc = 0;
		pq.push(d);
		while(!pq.empty()) {
			d = pq.top();///不能使用pq.front();
			pq.pop();
			if(vis[d.e]) continue; ///表示已經走過
			vis[d.e] = 1;
			if(d.e == n) break; ///找到該點
			for(int i = 0 ; i < v[d.e].size(); ++i) {
				z.e = v[d.e][i].e;
				if(vis[z.e]) continue; ///鄰接表裡的點有沒有訪問過
				z.cc = v[d.e][i].cc + d.cc;
				pq.push(z);
			}
		}
		cout << d.cc << endl;
	}
	return 0;
}

相關推薦

--小生成樹短路1

圖論的兩個經典問題。 1、先介紹樹的概念: 樹的概念挺簡單的,一個祖先,一個兒子只能有一個父親節點,不能形成環。n個節點只能有n-1條邊,要不然會形成環。(易得知) 2、再來講講我用來存圖的兩種方式:

小生成樹短路徑的總結

1.求最小生成樹有兩種方法: ①克魯斯卡爾演算法:這個演算法是以邊為單位(包括邊的所有的資訊:兩個端點+權值)進行儲存的,然後將邊按照權值的從小到大的順序進行排序,然後將第一條邊連線起來,第二條邊連線起來,就這樣一直迴圈,直到所有的邊都被連線起來為止,在這期間,你需要判斷

小生成樹的遍歷

Prim演算法 1.概覽 普里姆演算法(Prim演算法),圖論中的一種演算法,可在加權連通圖裡搜尋最小生成樹。意即由此演算法搜尋到的邊子集所構成的樹中,不但包括了連通圖裡的所有頂點(英語:Vertex (graph theory)),且其所有邊的權值之

[轉載]有向小生成樹小樹形圖

轉載: 有固定根的最小樹形圖求法O(VE): 首先消除自環,顯然自環不在最小樹形圖中。然後判定是否存在最小樹形圖,以根為起點DFS一遍即可。 之後進行以下步驟。 設cost為最小樹形圖總權值。 0.置cost=0。 1.求最短弧集合Ao (一條弧就是

小生成樹 VS 短路徑

圖的應用問題 note 僅作入門參考的記錄 1. 網路架設之路徑最短問題 參考給出了兩個演算法:Prim 演算法和Kruskal演算法,前者針對鄰接矩陣,後者針對邊集陣列。

51nod 1212 無向小生成樹小生成樹

 收藏  關注 N個點M條邊的無向連通圖,每條邊有一個權值,求該圖的最小生成樹。 Input 第1行:2個數N,M中間用空格分隔,N為點的數量,M為邊的數量。(2 &

小生成樹小成本):Prim演算法

最小成本:n 個頂點,用 n−1 條邊把一個連通圖連線起來,並且使得權值的和最小。 最小生成樹:構造連通網的最小代價生成樹。 根據原來寫的部落格:【圖】圖的定義,裡面提到一個連通圖的生成樹是一

(Graph)——小生成樹短路徑、Kruskal、Dijkstra、Floyd

4. 最小生成樹 4.1 生成樹 (1)定義:所有頂點均由邊連線在一起,但不存在迴路的圖叫該圖的生成樹 (2)深度優先生成樹與廣度優先生成樹 (3)     一個圖可以有許多棵不同的生成樹    所有

小生成樹小成本):克魯斯卡爾(Kruskal)演算法

給出一個連通網: 克魯斯卡爾(Kruskal)演算法 基本思想 假設 N=(V,{E}) 是連通網: 令最小生成樹的初始狀態為只有 n 個頂點並且沒有邊的非連通圖 T={V,{}}

資料結構:——的遍歷、小生成樹短路徑演算法

前言 在這裡,如果大家對圖或者資料結構還不太熟悉,想找一個動態的生成過程來參考,這是一個不錯的網站. 知識框架 圖的定義 線上性結構中,資料元素之間滿足唯一的線性關係,每個資料元素(除第一個和最後一個外)只有一個直接前趨和一個直接後繼; 在樹形結構中,資料元素之間有著明顯的層次關係,

小生成樹倍增法求lca(Uva11354Bond)

#include<bits/stdc++.h> #define maxn 600000 #define inf (1124984) using namespace std; int head[maxn],book[maxn],deep[maxn],pre[maxn],fa[maxn],mxcos

小生成樹切分定理

本文提綱 最小生成樹 切分定理 證明 1.最小生成樹 最小生成樹問題,針對帶權無向圖,就是在一個V個結點的連通圖裡面尋找V-1條邊,使得這個圖連通,並且權值之和最小的問題。 2.切分定理(Cut Property) 定義一:把圖中的結點分

的遍歷、小生成樹短路徑

這一篇我們要總結的是圖(Graph),圖可能比我們之前學習的線性結構和樹形結構都要複雜,不過沒有關係,我們一點一點地來總結,那麼關於圖我想從以下幾點進行總結: 1,圖的定義? 2,圖相關的概念和術語? 3,圖的建立和遍歷? 4,最小生成樹和最短路徑? 5,演算法實現? 回到頂部一,圖的定義 什麼

bzoj 2561: 小生成樹小割】

inf front ostream ring pos clu clas 要求 || 看錯題了以為多組詢問嚇得不行…… 其實還挺好想的,就是數據範圍一點都不網絡流。把U作為s,V作為t,以最小生成樹為例,(U,V,L)要在最小生成樹上,就要求所有邊權比L小的邊不能連通(U,V

POJ-2485 Highways---小生成樹大邊

style ble include void ack return 最小生成樹 color spa 題目鏈接: https://vjudge.net/problem/POJ-2485 題目大意: 求最小生成樹中的最大邊 思路: 是稠密圖,用prim更好,但是規模不大,kru

PKUACM 2018 D Chocolate 小生成樹 Kruskal 長公共子序列

size and this put first test case ren The was $ \rightarrow $ 戳我進POJ原題 D:Chocolate 總時間限制: 1000ms $ \quad $ 內存限制: 65536kB   描述 Vincent is

BZOJ 2561 小生成樹小割)

任重而道遠  給定一個邊帶正權的連通無向圖G=(V,E),其中N=|V|,M=|E|,N個點從1到N依次編號,給定三個正整數u,v,和L (u≠v),假設現在加入一條邊權為L的邊(u,v),那麼需要刪掉最少多少條邊,才能夠使得這條邊既可能出現在最小生成樹上,也可能出現在最大生成樹上? Inp

POJ 2485 - Highways(求小生成樹大權值-Kruskal演算法)

題目 Language:Default Highways Time Limit: 1000MS

小生成樹大比率 UVALive - 5713

題目連結:https://vjudge.net/problem/UVALive-5713 題意:給出t組資料,每組資料第一行給出一個n,表示點的數量,接下來n行,每行有三個數字,分別是點的座標x,y和點的值w。現在我們要用n-1條邊來連線這n個點,秦始皇希望這n-1條邊的權值之和最小,現在徐福說他可以讓其中

BZOJ 2177: 曼哈頓小生成樹 曼哈頓小生成樹

2177: 曼哈頓最小生成樹 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 281  Solved: 117 [Submit][Status][Discuss] Description 平面座標系xOy內,給定n個頂點