【算法】歐拉路

學長(姐?)昨晚講了歐拉路,寫個博加深一下印象ios

歐拉路

定義與性質:

歐拉路是一種路(滑稽)。c++

一種不重不漏的一筆畫走完全部路徑,且每一條路都至少走一遍,也只能走一遍。數組

大約是這樣的:函數

其中 1 -> 2 -> 3 -> 5 -> 10 -> 3 -> 4 -> 8 -> 9 -> 11 -> 8 -> 7 -> 6 就是一條歐拉路。優化

固然 1 -> 2 -> 3 -> 10 -> 5 -> 3 -> 4 -> 8 -> 11 -> 9 -> 8 -> 7 -> 6 也是一條歐拉路。spa

而後 6 -> 7 -> 8 -> 11 -> 9 -> 8 -> 4 -> 3 -> 10 -> 5 -> 3 -> 2 -> 1 因此反過來仍是歐拉路。code

由此能夠看出,一個圖的歐拉路並非惟一的。blog

而歐拉回路就是起點與終點相同的歐拉路,能夠在歐拉路的起點與終點之間連一條邊構成歐拉回路:get

代碼實現:

思路io

由歐拉路的性質可知,若是一個圖(無向圖)爲歐拉路,那麼這個圖的全部點只有兩個點的度數爲奇數,或者沒有點的度數爲奇數,

其餘全部點的度數爲偶數。

而歐拉路的起點或終點就是那兩個度數爲奇數的點。(歐拉回路隨便找個點當起點就行)

因此能夠經過一個 dfs 來找出整個歐拉路。

  1. 先統計每一個點的度數;

  2. 再判斷有幾個點的度數爲奇數:

    • 若是有兩個點或者沒有點的度數爲奇數,隨便找個點做爲起點開始 dfs;
    • 若是有多個點的度數爲奇數,輸出 No answer
  3. dfs 過程當中用棧來維護歐拉路的路徑;

  4. 最後輸出路徑。

完整代碼 1(用鄰接矩陣來存圖)

#include <iostream>
#include <cstdio>
#define MAXN 1050
#define F1(i, a, b) for (int i = a; i <= b; ++i)
#define F2(i, a, b) for (int i = a; i >= b; --i)
using namespace std;

int f[MAXN][MAXN]; // f[i][j] 存儲從 i 到 j 之間有幾條邊
int du[MAXN], sta[MAXN]; // du[i] 存儲 i 點的度;sta 數組爲棧用來記錄歐拉路的路徑
int top = 0, n, m;

void dfs(int t) {
	F1(i, 1, n) { // 挨個尋找與 t 鏈接的邊
		while (f[t][i] || f[i][t]) { // 若是這裏有邊,while 防止多邊鏈接兩點相同
			--f[t][i]; // 該邊數量減一,表明該邊走過了
			--f[i][t]; // 無向圖
			dfs(i);
		}
	}
	sta[++top] = t; // 回溯時把 t 點放進棧裏
}

int main() {
	int x, y, k = 1, cnt = 0;
	scanf("%d %d", &n, &m);
	F1(i, 1, m) {
		scanf("%d %d", &x, &y);
		++f[x][y]; // 存邊
		++f[y][x];
		++du[x]; // 統計度數
		++du[y];
	}
	F2(i, 1, n) if (du[i] % 2) ++cnt;
	if (cnt == 2 || cnt == 0) dfs(k);
	else {
		printf("No answer\n");
		return 0;
	}
	F2(i, top, 1) printf("%d\n", sta[i]);
	return 0;
}

完整代碼 2(鏈式前向星存圖)

前置:

毫無疑問鏈式前向星存圖必定是比鄰接矩陣存圖更優的,特別是在稀疏圖中

但不少人不用鏈式前向星是由於歐拉路大部分是無向圖,

標記一條道路被走過須要對這條道路的反向道路也標記爲走過,

而這一點不太好實現,因此不少人直接用鄰接矩陣來存圖。

而這個時候就須要對鏈式前向星的理解力了:

鏈式前向星存的是邊,同時用 head 數組來記錄上一條邊的序號,而遍歷的時候用 head 數組和 nxt 來遍歷,

存圖用 add 函數來實現。

如今再回到歐拉路上:

歐拉路一般爲無向圖,若是用鏈式前向星來存,那麼就會用 add 正着存一遍,再倒着存一遍,因此兩條邊的序號是連着的,

若是序號爲奇數(我是從一開始存),那麼這條邊就是正着存的,那它的下一條邊就是這條邊的反邊;

若是序號爲偶數,那麼這條邊就是倒着存的,那它的上一條邊就是這條邊的正邊。

代碼:

#include <iostream>
#include <cstdio>
#define MAXN 1001
#define F1(i, a, b) for (int i = a; i <= b; ++i)
#define F2(i, a, b) for (int i = a; i >= b; --i)
using namespace std;

int js = 0, n, m, top = 0;
int head[MAXN], du[MAXN], sta[MAXN];

struct edge {
	int v, nxt;
	bool book; // book 記錄該邊是否被走過
}e[MAXN << 1];

inline int read() { // 讀入優化
	int sto = 0, fg = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') fg = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		sto = (sto << 1) + (sto << 3) + (ch ^ 48);
		ch = getchar();
	}
	return sto * fg;
}

void add(int u, int v) { // 存邊函數
	e[++js].v = v;
	e[js].nxt = head[u];
	head[u] = js;
}

void dfs(int t) {
	for (int i = head[t]; i; i = e[i].nxt) {
		if (!e[i].book) { // 用鏈式前向星遍歷
			if (i % 2) e[i + 1].book = 1;
			else e[i - 1].book = 1; // 將反向邊標記
			e[i].book = 1; // 正向邊標記(好像沒用)
			dfs(e[i].v);
		}
	}
	sta[++top] = t; // 存路
}

int main() {
	int x, y, k = 1, cnt = 0;
	n = read(); m = read();
	F1(i, 1, m) {
		x = read(); y = read();
		add(x, y); // 存正邊
		add(y, x); // 存反邊
		++du[x]; ++du[y];
	}
	F1(i, 1, n) if (du[i] % 2) ++cnt, k = i;
	if (cnt == 2 || cnt == 0) dfs(k);
	else {
		printf("No answer\n");
		return 0;
	}
	F2(i, top, 1) printf("%d ", sta[i]);
	return 0;
}

不知道之前有沒有人這麼幹的

相關文章
相關標籤/搜索