如何判断欧拉回路:
打印欧拉回路时,是逆序输出得到的序列,而不是正序输出。
这个题是求欧拉回路。在求欧拉回路时,我们判重是对边进行判重而不是对点进行判重,而且必须是用过一条边就必须把它删除了。对于有向图来说很好做,但是对于无向图,它可以看作两条相反的有向边,因此我们删除一条边的时候,还要考虑到它的反向边,所以就显得有些ex。
下面解释一下,为什么代码中使用的是引用:
假设只有一个点,但是有 m m m个自环,即 m m m条边都是指向了这个节点,然后使用bool数组存储每条边有没有被遍历过。根据上面伪码描述,第一次运行到dfs中会遍历 m m m条边,递归进入dfs后还需要遍历 m m m条边,一共需要遍历 m m m次,因此 m m m个 m m m相加等于 m × m = m 2 m\times m=m^2 m×m=m2,会超时。因此需要进行优化:
优化的方式是每次遍历过这条边后将其从邻接表中删除,保证之后不会再遍历到这条边。这样就可以保证每条边只会被遍历一次,时间复杂度变为线性。
参考如下图解释:
或者:
边的转化:
我们在建图时,无向图中边的编号是 0 0 0到 2 m − 1 2m-1 2m−1,但是题目中的编号是 1 1 1到 m m m,因此需要转化一下:
这里还需要处理入度和出度,我们设立了din[]
和dout[]
分别表示入度和出度:
因此,可以发现,无向图和有向图处理入度和出度的方式都是一样的。
第一次错误的修改:
void dfs(int u)
{
for(int i = h[u] ; ~i ; i = ne[i])
{
if(used[i])
{
h[u] = ne[i];
continue;
}
used[i] = true;
if(type == 1)used[i ^ 1] = true;
int t;
if(type == 1)
{
t = i / 2 + 1;
if(i & 1)t = -t;
}
else t = i + 1;
int j = e[i];
h[u] = ne[i];
dfs(j);
res[++ cnt] = t;
}
}
正确修改后的代码一:不使用 引用 的写法
void dfs(int u)
{
for(int i = h[u] ; ~i ; i = h[u])//区别
{
if(used[i])
{
h[u] = ne[i];//区别
continue;
}
used[i] = true;
if(type == 1)used[i ^ 1] = true;
int t;
if(type == 1)
{
t = i / 2 + 1;
if(i & 1)t = -t;
}
else t = i + 1;
int j = e[i];
h[u] = ne[i];//区别
dfs(j);
res[++ cnt] = t;
}
}
使用 引用 的写法:
void dfs(int u)
{
for (int &i = h[u]; ~i;)//区别
{
if (used[i])
{
i = ne[i];//区别
continue;
}
used[i] = true;
if (type == 1) used[i ^ 1] = true;
int t;
if (type == 1)
{
t = i / 2 + 1;
if (i & 1) t = -t;
}
else t = i + 1;
int j = e[i];
i = ne[i];//区别
dfs(j);
ans[ ++ cnt] = t;
}
}
写法1
#include
#include
#include
#include
using namespace std;
const int N = 1e5+10, M = 4e5+10;
int type;
int n, m;
int h[N], e[M], ne[M], idx;
//used[i]=true表示i这条边已经被用过了
bool used[M];
//ans表示欧拉路径,cnt表示路径上边的数量
int ans[M], cnt;
//点的出度与入度;
int din[N], dout[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
for(int &i=h[u];~i;)
{
if(used[i])//如果边i已经被用过了 则直接去下一条边看
{
i = ne[i];
continue;
}
//标记i这条边已经被用过了
used[i]=true;
int t;//点的编号 无向图时点编号=边编号/2+1 有向图时点编号=便编号+1
if(type==1)
{
used[i^1]=true;//如果无向边 则把反向边也标记
t=i/2+1;
if(i&1)t=-t;//奇数边为反向边 如果 pi 为正数表示从 ve 走到 ue,否则表示从 ue 走到 ve
}
else
t=i+1;
int j = e[i];//边上连接的邻点 即节点u的邻接点j
i = ne[i]; //边用过之后直接删了
dfs(j);// 一直走到底 把终点先压进栈 然后回溯往回走把中间及起点加进栈 最终输出结果 ans[cnt]为起点
ans[++cnt] = t;//从下往上将点输入到路径中,因为从上往下的过程中,可能边路有些环并没有被顾虑到
}
}
int main()
{
scanf("%d",&type);
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i=0;i
写法2:
#include
#include
#include
#include
using namespace std;
const int N = 1e5+10, M = 4e5+10;
int type;
int n, m;
int h[N], e[M], ne[M], idx;
//used[i]=true表示i这条边已经被用过了
bool used[M];
//ans表示欧拉路径,cnt表示路径上边的数量
int ans[M], cnt;
//点的出度与入度;
int din[N], dout[N];
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
for(int i = h[u] ; ~i ; i = h[u])//区别
{
if(used[i])
{
h[u] = ne[i];//区别
continue;
}
used[i] = true;
if(type == 1)used[i ^ 1] = true;
int t;
if(type == 1)
{
t = i / 2 + 1;
if(i & 1)t = -t;
}
else t = i + 1;
int j = e[i];
h[u] = ne[i];//区别
dfs(j);
ans[++ cnt] = t;
}
}
int main()
{
scanf("%d",&type);
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i=0;i
写法3:
#include
#include
using namespace std;
const int N = 100010, M = 400010;
int type; // 1代表无向图,2代表有向图
int n, m;
int h[N], e[M], ne[M], idx;
bool used[M]; // 记录每条边是否被使用了
int ans[M / 2], cnt; // 记录欧拉路径
int din[N], dout[N]; // 记录每个点的入度,出度,无向图的度等于入度和出度之和
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs(int u) {
while (~h[u]) { // 这样写是为了跳过应遍历的边
int i = h[u]; // 当前遍历第i条边
if (used[i]) { // 防止无向图的反向边被加入答案中
h[u] = ne[i];
continue;
}
h[u] = ne[i]; // 这里h[u]被更新,因为h是全局变量,下次dfs就不会遍历到当前考察的边
used[i] = true;
if (type == 1) used[i ^ 1] = true;
dfs(e[i]);
if (type == 1) {
int t = i / 2 + 1; // 无向图当前考察的是第t条边(从1开始)
if (i & 1) t *= -1;
ans[++cnt] = t;
} else ans[++cnt] = i + 1;
}
}
int main() {
scanf("%d", &type);
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
for (int i = 0; i < m; i++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b);
if (type == 1) add(b, a);
dout[a]++, din[b]++; // 一条边只会让两个点的度增加,因此对于无向图也是成立的
}
// 判断是否无解
if (type == 1) {
for (int i = 1; i <= n; i++)
if (din[i] + dout[i] & 1) { // 成立的话说明点i的度为奇数
puts("NO");
return 0;
}
} else {
for (int i = 1; i <= n; i++)
if (din[i] != dout[i]) {
puts("NO");
return 0;
}
}
// 求解欧拉路径
for (int i = 1; i <= n; i++)
if (~h[i]) { // 即h[i] != -1
dfs(i);
break; // 只能执行一次dfs函数,否则说明其他边在其他连通分量中
}
if (cnt < m) { // 说明所有边不连通
puts("NO");
return 0;
}
// 输出答案
puts("YES");
for (int i = cnt; i; i--) printf("%d ", ans[i]);
puts("");
return 0;
}