【JS】寻找欧拉回路三种实现(回溯\栈\递归)
来源:10-4 求解欧拉回路的三种算法
Potter520
2023-05-10
将老师课程提供的图,改成这样方便理解。
- 根据欧拉理论,图必须是联通的,联通分量必须是1,否则找不到欧拉回路,所以需要一个CC类
class CC {
constructor(graph) {
this._graph = graph;
this._cccount = 0;
this._visited = Array(this._graph.V()).fill(-1);
for (let v = 0; v < this._graph.V(); v++) {
if (this._visited[v] === -1) {
this.dfs(v);
this._cccount++;
}
}
}
dfs(v) {
this._visited[v] = this._cccount;
for (const w of this._graph.adj(v)) {
if (this._visited[w] === -1) {
this.dfs(w);
}
}
}
cccount() {
return this._cccount;
}
isConnected(v, w) {
this._graph.validateVertex(v);
this._graph.validateVertex(w);
return this._visited[v] === this._visited[w];
}
/**
* 获取各联通分量对应的元素列表
*/
components() {
let res = Array(this._cccount);
for (let v = 0; v < this._graph.V(); v++) {
if (!res[this._visited[v]]) {
res[this._visited[v]] = [];
}
res[this._visited[v]].push(v);
}
return res;
}
}
- 由于是采用回溯寻找路径,根据老师提供的思路,遍历时需要删除边,回溯时需要恢复对应的边。所以需要添加给graph添加两个方法,deleteEdge、addEdge
class Graph {
constructor(filePath) {
this._set = new Set();
this._V = 0;
this._E = 0;
let content = fs.readFileSync(filePath, { encoding: "utf8" });
let arr = content.trim().split("\r\n");
if (arr.length <= 0) {
throw Error("file content is empty,filepath:", filePath);
}
let firstRow = arr.shift();
[this._V, this._E] = firstRow.split(" ").map((it) => Number(it));
if (arr.length !== this._E) {
throw Error("graph data is invalid !");
}
//1.初始化顶点
this._adj = new Array(this._V);
for (let i = 0; i < this._adj.length; i++) {
this._adj[i] = new Set();
}
//2.初始化边
for (let i = 0; i < arr.length; i++) {
let [v, w] = arr[i].split(" ").map((it) => Number(it));
if (!this._adj[v].has(w)) {
this._adj[v].add(w);
//! 说明:由于是无向图,所以需要v->w是一条边,w->v也是一条边,需要互相包含
this._adj[w].add(v);
}
}
}
deleteEdge(v, w) {
this.validateVertex(v);
this.validateVertex(w);
this._adj[v].delete(w);
this._adj[w].delete(v);
this._E -= 1;
}
addEdge(v, w) {
this.validateVertex(v);
this.validateVertex(w);
this._adj[v].add(w);
//! 说明:由于是无向图,所以需要v->w是一条边,w->v也是一条边,需要互相包含
this._adj[w].add(v);
this._E += 1;
}
toString() {
let sb = "";
sb += `V:${this._V},E:${this._E}\n`;
for (let i = 0; i < this._adj.length; i++) {
let row = [];
for (const value of this._adj[i].values()) {
row.push(value);
}
sb += `${i}: ${row.join(",")}\n`;
}
return sb;
}
V() {
return this._V;
}
E() {
return this._E;
}
validateVertex(v) {
if (v < 0 || v > this._V) {
throw new Error(`vertex ${v} is invalid !`);
}
}
adj(v) {
this.validateVertex(v);
return this._adj[v];
}
degress(v) {
return this.adj(v).size();
}
hasEdge(v, w) {
this.validateVertex(v);
this.validateVertex(w);
return this._adj[v].has(w);
}
}
- 遍历边,以前都是遍历顶点,这会来个边,这咋记录边呢?一个父节点一个父节点的相邻顶点,不就构成了一条边了嚒。
方式1:回溯法
function findEulerLoop(graph) {
let cc = new CC(graph);
if (cc.cccount() != 1) {
return [];
}
let E = graph.E();
let res = [];
let count = 0;
function print(...args) {
// console.log("-".repeat(count), ...args);
}
function dfs(v, path) {
count++;
path.push(v);
//! 说明:当前的边数全部遍历完,说明找到解
if (graph.E() == 0) {
res = path.slice(0);
print("find solution: ", res);
return true;
}
//! 说明:需要把当前顶点的相邻顶点copy出来,如果不copy出来就会导致for循环的是循环。for里面不能边删除又添加
let edges = [...graph.adj(v).values()];
for (const w of edges) {
//! 遍历操作
graph.deleteEdge(v, w);
print("push:", path);
if (dfs(w, path)) {
return true;
}
//! 回溯操作
graph.addEdge(v, w);
path.pop();
print("pop:", path);
}
count--;
return false;
}
dfs(0, []);
return res;
}
let res = findEulerLoop(new Graph(path.resolve(__dirname, "./assets/g0.txt")));
console.log(res.join("->"));
输出结果
0,1->1,3->3,4->4,1->1,2->2,0
方式2:利用栈实现
function findEulerLoop(graph) {
function hasEulerLoop(graph) {
let cc = new CC(graph);
if (cc.cccount() != 1) {
return false;
}
for (let v = 0; v < graph.V(); v++) {
if (graph.degree(v) % 2 != 0) {
return false;
}
}
return true;
}
if (!hasEulerLoop(graph)) {
return [];
}
let g = graph.clone();
let res = [];
let stack = [];
let curv = 0;
stack.push(curv);
while (stack.length != 0) {
if (g.degree(curv) != 0) {
let w = g.adj(curv).values().next().value;
/**
* ! 注意:此处容易写成如下代码,这是错误的。
curv = w;
stack.push(curv);
g.deleteEdge(curv, w);
*
*/
stack.push(curv);
g.deleteEdge(curv, w);
curv = w;
} else {
res.push(curv);
curv = stack.pop();
}
}
return res;
}
let res = findEulerLoop(new Graph(path.resolve(__dirname, "./assets/g0.txt")));
console.log(res.join("->"));
输出结果:
0->2->1->4->3->1->0
方式3:递归实现
function findEulerLoop(graph) {
function hasEulerLoop(graph) {
let cc = new CC(graph);
if (cc.cccount() != 1) {
return false;
}
for (let v = 0; v < graph.V(); v++) {
if (graph.degree(v) % 2 != 0) {
return false;
}
}
return true;
}
if (!hasEulerLoop(graph)) {
return [];
}
let g = graph.clone();
let res = [];
function dfs(v, path) {
for (const w of graph.adj(v)) {
if (graph.degree(w) != 0) {
graph.deleteEdge(v, w);
dfs(w, path);
}
}
path.push(v);
}
dfs(0, res);
return res;
}
let res = findEulerLoop(new Graph(path.resolve(__dirname, "./assets/g0.txt")));
console.log(res.join("->"));
输出结果:
0->2->1->4->3->1->0
写回答
1回答
-
liuyubobobo
2023-05-13
大赞!感谢分享!继续加油!:)
00
相似问题