欧拉回路——Hierholzer算法
对于一个欧拉回路,我们分析其组成部分,一个欧拉回路必是由多个环连接在一起,才能符合所有的点入度与出度相等。
因此Hierholzer算法算法的核心就是依次寻找环,将所有的环拼合成一条欧拉回路。
过程
首先我的定义一个双端链表和一个队列,设链表为 T T T就是最后的欧拉回路的路径,队列 B B B类似于 B F S BFS BFS算法中的队列的作用。
队列中的元素是一个二元组 ( v , T v ) (v,T_{v}) (v,Tv),其中 v v v是图中节点的编号, T v T_{v} Tv是这个节点在链表 T T T的指针。
初始化队列中只有一个元素 ( 1 , N U L L ) (1,NULL) (1,NULL),一个任意节点(不一定是 1 1 1),在 T T T中的指针为 N U L L NULL NULL,因为此时 1 1 1未在 T T T中。
在Hierholzer算法中,我们依次取出队列的队首元素设为 ( v , p t r ) (v,ptr) (v,ptr),直到队列为空为止。
接下来,我将在图中搜索一个环,这个环以 v v v开始,以 v v v结束。计算出这个环之后,如果 p t r ptr ptr为空指针,那么就让 T T T等于这个环链表即可。如果 p t r ptr ptr不为空,则用这个环链表去替换 v v v在 T T T中的位置即可。如果这个环为空,即 v v v的出度为 0 0 0,那么就什么也不做。
代码
首先是链表定义:
struct Node
{
int idx;
Node *ptr;
Node *next;
Node *prev;
Node() : next(nullptr), prev(nullptr), ptr(nullptr)
{
}
};
class LinkedList
{
private:
Node *head;
Node *tail;
public:
LinkedList()
{
head = new Node;
tail = new Node;
head->next = tail;
tail->prev = head;
}
Node *add(Node n)
{
Node *c = new Node(n);
Node *prv = tail->prev;
prv->next = c;
c->prev = prv;
c->next = tail;
tail->prev = c;
return c->prev;
}
bool empty()
{
return head->next == tail;
}
Node pop()
{
Node *tag = head->next;
Node *nxt = head->next->next;
head->next = nxt;
nxt->prev = head;
Node ans(*tag);
delete tag;
return ans;
}
void replace(LinkedList c, Node *n)
{
if(c.empty()) return;
Node *prv = n->prev;
Node *nxt = n->next;
delete n;
prv->next = c.head->next;
c.head->next->prev = prv;
nxt->prev = c.tail->prev;
c.tail->prev->next = nxt;
}
void print()
{
Node * curr = head->next;
while (curr != tail)
{
cout << curr->idx;
curr = curr->next;
}
}
};
一个双端队列,添加了一个替换操作。
然后是环搜索算法:
LinkedList circle(int idx)
{
LinkedList c;
Node t;
t.idx = idx;
c.add(t);
int u = idx;
while (Deg[u] > 0)
{
int nxt = 0;
for (int i = 1; i <= n; i++)
if (Graph[u][i])
{
nxt = i;
break;
}
Graph[u][nxt] = Graph[nxt][u] = 0;
Deg[u]--;
Deg[nxt]--;
Node n;
n.idx = nxt;
Node *tag = c.add(n);
if (Deg[u] > 0)
{
Node k;
k.idx = u;
k.ptr = tag;
L.add(k);
}
u = nxt;
}
return c;
}
我们不断的沿着第一个边跑下去,并且边跑边把经过的边删除,直到出度为 0 0 0,并且,把路径中包含其他环的点继续加入到搜索队列中。
主函数搜索合并即可:
int main()
{
FR;
cin >> n >> m;
while (m--)
{
int u, v;
cin >> u >> v;
Graph[u][v] = Graph[v][u] = 1;
Deg[u]++;
Deg[v]++;
}
LinkedList T;
Node inital;
inital.idx = 1;
L.add(inital);
while (!L.empty())
{
Node curr = L.pop();
LinkedList c = circle(curr.idx);
if (curr.ptr == nullptr)
T = c;
else
T.replace(c, curr.ptr);
}
T.print();
return 0;
}
证明
搜索环函数的正确性,我们假设搜索之前的图存在欧拉回路(即所有点的出度=入度),搜索之后要么为空,要么仍存在欧拉回路。
对于起始点 i d x idx idx来说,经过 i d x idx idx的之后, i d x idx idx的度将变成奇数度,除了 i d x idx idx点,其他点必是偶数度,因此每经过一次其他点,其他点的度都要减 2 2 2,或者说,能进入其他点,就必能从其他点出来,奇数点只有 i d x idx idx,因此,while循环必将停在 i d x idx idx上。当搜索结束之后,点 i d x idx idx的度必为 0 0 0,并且 i d x idx idx所在的所有环都将并入到 T T T中。此时图要么仍存在欧拉回路,要么为空。