本节为图内容,回到总目录:点击此处
本部分目录
ADT Graph
{
数据对象
数据关系 R={VR}
VR = {<x,y>|P(x,y)^(x,y->V)}
基本操作:
1,CreateGraph(G)
2,DestoryGraph(G)
3,LocateVertex(G,v) //若图存在顶点,则返回顶点在图中的位置
4,GetVertex(G,i)//返回图G中的第i个顶点的值。若i大于图G的顶点数,则返回空
5,FirstAdjVertex(G,v) //返回图G中顶点v的第一个邻接点。若v无邻接点或G中无顶点v,则返回空
6,NextAdjVertex(G,v,w) //返回顶点v的下一个邻接点,紧跟在w的后面,若w是v的最后一个邻接点,则返回空。
7,InsertVertex(G,u) //在图G中增加一个顶点u
8,DeleteVertex(G,v) //删除图G的顶点v以及与顶点v相关联的弧
9,InsertArc(G,v,w) //在图G中增加一条从顶点v到顶点w点弧
10,DeleteArc(G,v,w) //删除图G中从顶点v到顶点w的弧
11,TraverseGraph(G) //按照某种次序,对图G的每个顶点访问且只访问一次
}ADT Graph;
基本术语
·完全图、稀疏图、稠密图
无向完全图:
有向完全图:**完全图:**若图中各个顶点都与除自身外的其他顶点有关系,这样的无向图称为完全图(如图 4a))。同时,满足此条件的有向图则称为 有向完全图(图 4b))。
稀疏图:对于有很少边的图(e < n l o g n nlogn nlogn) 反之,则为稠密图
稀疏和稠密的判断条件是:e< nlogn,其中 e 表示图中边(或弧)的数量,n 表示图中顶点的数量。如果式子成立,则为稀疏图;反之 为稠密图。
子图:
入度和出度 对于有向图中的一个顶点 V 来说,箭头指向 V 的弧的数量为 V 的入度(InDegree,记为 ID(V));箭头远离 V 的弧的数量为 V 的出 度(OutDegree,记为 OD(V))。拿图 2 中的顶点 V1 来说,该顶点的入度为 1,出度为 2(该顶点的度为 3)。
(V1,V2) 和 <V1 , V2> 的区别
无向图中描述两顶点(V1 和 V2)之间的关系可以用 (V1,V2) 来表示,而有向图中描述从 V1 到 V2 的"单向"关系用 <V1 , V2> 来表示。 由于图存储结构中顶点之间的关系是用线来表示的,因此 (V1,V2) 还可以用来表示无向图中连接 V1 和 V2 的线,又称为边;同样,<V1 , V2> 也可用来表示有向图中从 V1 到 V2 带方向的线,又称为弧。有向图中,无箭头一端的顶点通常被称为"初始点"或"弧尾",箭头直线的顶点被称为"终端点"或"弧头"。
邻接点:
度、入度和出度:
权与网:图的边或弧具有一定意义的数值,每一条边都有与他相
关的数——称为权。这些带权的图称为赋权图或网。路径和回路
无论是无向图还是有向图,从一个顶点到另一顶点途径的所有顶点组成的序列(包含这两个顶点),称为一条路径。如果路径中第一个 顶点和最后一个顶点相同,则此路径称为"回路"(或"环")。 并且,若路径中各顶点都不重复,此路径又被称为"简单路径";同样,若回路中的顶点互不重复,此回路被称为"简单回路"(或简单 环)。 拿图 1 来说,从 V1 存在一条路径还可以回到 V1,此路径为 {V1,V3,V4,V1},这是一个回路(环),而且还是一个简单回路(简单环)。连通图:
连通图中的生成树必须满足以下 2 个条件:
- 包含连通图中所有的顶点; 2. 任意两顶点之间有且仅有一条通路;
因此,连通图的生成树具有这样的特征,即生成树中边的数量 = 顶点数 - 1
。生成树是对应连通图来说,而生成森林是对应非连通图来说的。
不同类型的图,存储的方式略有不同,根据图有无权,可以将图划分为两大类:图和网 。
图的存储结构
图的数据结构:
#define MAX_VERtEX_NUM 20 //顶点的最大个数
#define VRType int //顶点之间的关系的变量类型
#define InfoType char //存储弧获知边额外信息的指针变量类型
#define VertexType int //图中顶点的数据类型
typedef enum{DG,DN,UDG,UDN}GraphKind; //枚举图的4种类型
typedef struct
{
VRType adj; //对于无权图,用1或0表示是否相邻,若为带权图,直接为权值
InfoType *info; //弧或边额外含有的信息指针
}ArcCell,AdjMatrix[MAX_VERtEX_NUM][MAX_VERtEX_NUM];
typedef struct
{
VertexType vexs[MAX_VERtEX_NUM]; //存储图中顶点数据
AdjMatrix arcs; //二维数组,记录顶点之间的关系
int vexnum,arcnum; //记录图的顶点数和弧边数
GraphKind kind; //记录图的种类
}MGraph;
· 邻接矩阵表示法
也叫数组表示法:采用两个数组来表示图
一个是用于存储顶点信息的一维数组
另一个是用于存储图中顶点之间关联关系的二维数组
无向图的邻接矩阵一定是对称的。
—— 采用邻接矩阵表示法创建有向图
int LocateVertex (AdjMatrix * G, VertexData v) //求顶点位置函数
{
int j = Error,k;
for(k = 0; k < G -> vexnum; k ++)
{
if(G -> vertex[k] == v)
{
j = k;
break;
}
}
return j;
}
//构造无向图
int CreateDN(MGraph *G)
{
cin >> G -> vexnum >> G -> arcnum;
for (int i = 0; i < G -> vexnum; i ++)
{
cin >> G -> vexs[i];
}
for (int i = 0; i < G -> vexnum; i ++)
{
for (int j = 0; j < G -> vexnum; j ++)
{
G -> arcs[i][j].adj = 0;
G -> arcs[i][j].info = NULL;
}
}
for (int i = 0; i < G -> arcnum; i++)
{
int v1,v2;
cin >> v1 >> v2;
int n = LocateVex(G,v1);
int m = LocateVex(G,v2);
if (m == -1 || n == -1 )
{
cout << "no this vertex";
return;
}
G -> arcs[n][m].adj = 1;
G -> arcs[m][n].adj = 1; //无向图的二阶矩阵的对称
}
}
·邻接表表示法
对于稀疏图来说如果使用邻接矩阵,这样会造成大量的空间浪费。邻接表的存储方法实际上是一种链式存储结构,能够克服邻接矩阵的弊病,基本思想是只存储关联的信息,对于图中存在的边信息则存储,而不相邻接的顶点则不保留信息。① 表头结点表:由所有表头结点以顺序结构的形式存储,以便可以随机访问任一顶点的边链表。
表头结点由两部分组成:数据域用于存储顶点的名或其他相关信息; 链域用于指向链表中第一个顶点(也就是与顶点vi邻接的第一个邻接点)②边表:由表示图中顶点间邻接关系的n个边链表组成。
由三部分组成:邻接点域 存放与顶点vi相邻接的顶点在图中的位置;链域用于指向与顶点vi相关联的下一条边或弧的结点;数据域用于存储边或弧的相关信息。
#define MAX_VERTEX_NUM 20 // 顶点最多数
typedef enum {DG,DN,UDG,UDN} GraphKind;
typedef struct ArcNode
{
int adjvex; //该弧指向顶点的位置
struct ArcNode *nextarc; //指向下一条弧的指针
OtherInfo info; //与弧相关的信息
}ArcNode;
typedef struct VertexNode
{
VertexData data; //顶点数据
ArcNode *firstarc //指向该顶点第一条弧的指针
}VertexNode;
typedef struct
{
VertexNode vertex{MAX_VERTEX_NUM};
int vexnum,arcnum;
GraphKind kind;
}AdjList;
分析:
· 存储空间:若采用邻接表作为存储结构,则需要n个表头结点和2e个表结点。
· 无向图的度:在无向图的邻接表中,顶点vi的度恰好就是第i个单链表上结点的个数。
· 有向图的度:出度–>第i个单链表上结点的个数是结点vi的出度。
入度–>可以采取逆邻接表法–>计算第i个顶点的边链表中结点个数即可。
·十字链表
–>是有向图的另一种链式存储结构,可以把它看成是将有向图的邻接表和逆邻接表结合起来的一种链表。
有向图中每一条弧对应十字链表中的一个弧结点,同时有向图中的每个顶点在十字链表中对应有一个结点,成为顶点结点。
#define MAX_VERTEX_NUM 20 //最多顶点数
typedef enum{DG,UDG,DN,UDN} GraphKind;
typedef struct ArcNode
{
int tailvex,headvex;
struct ArcNode *hlink,*tlink;
}ArcNode;
typedef struct VertexNode
{
VertexData data; //顶点信息
ArcNode *firstin, *firstout;
}VertexNode;
typedef struct
{
VertexNode vertex[MAX_VERTEX_NUM];
int vexnum,arcnum; //图的顶点数和弧数
GraphKind kind;
}OrthList;
*邻接多重表
前面讲过,无向图的存储可以使用邻接表,但在实际使用时,如果想对图中某顶点进行实操(修改或删除),由于邻接表中存储该顶点 的节点有两个,因此需要操作两个节点。
为了提高无向图操作顶点的效率,采用邻接多重表结构。
图的遍历
深度优先搜索
算法思想:
1)首先将 v 0 v_0 v0入栈
2)只要栈不为空,则重复下面的操作:栈顶顶点出栈,如果未访问,则访问并置访问标志;然后将该顶点所有未访问的邻接点入栈。
#define True 1
#define false 0
#define Error -1
#define OK 1
int visted[MAX_VERTEX_NUM];
void TraverseGraph(Graph g)
{
for(vi = 0; vi < g.vexnum; vi ++) visited[vi] = False;
for(vi = 0; vi < g.vexnum; vi ++) //循环调用深度优先遍历连通子图的操作,若图g是连通图,则此调用只执行一次
if(!visited[vi]) DepthFirstSearch(g,vi);
}
void DepthFirstSearch(Graph g, int v0)
{
//深度遍历v0所在的连通子图
visit(v0);
visited[v0] = True;
w = FirstAdjVertex(g,v0);
while(w != -1)
{
if(!visited[w]) DepthFirstSearch(g,w); //递归调用
w = NextAdjVertex(g,v0,w);//找下一个邻接点
}
}
//不同的图的存储方式,函数的运行方式也不同
//如用邻接矩阵方式
void DepthFirstSearch(Graph g, int v0)
{
visit(v0); visited[v0] = True;
for(vi = 0; vi < g.vexnum; v ++)
{
if(!visited[vi] && g.arcs[v0][vi].adj == 1)
{
DepthFirstSearch(g,vi);
}
}
}
//采用邻接表的方法
void DepthFirstSearch(Graph g,int v0)
{
visit(v0); visited[v0] = True;
p = g.vertex[v0].firstarc;
while(p != NULL)
{
if(!visited[p -> adjvex]) DepthFirstSearch(g,p->adjvex);
p = p->nextarc;
}
}
宽度优先搜索
类似于层次遍历
算法思想:
1)首先访问 v 0 v_0 v0并置访问标志,然后将 v 0 v_0 v0入队;
2)只要队不空,则重复下面的操作:
a.队头结点v出队
b.对v的所有邻接点w,如果w未访问,则访问w并置访问标志,然后将w入队。
void BreadthFirstSearch(Graph g, int v0)
{
//宽度搜索图g中v0所在的连通子图
visit(v0); visited[v0] = True;
InitQueue(&Q);
EnterQueue(&Q,v0);
while(!Empty(Q))
{
DeleteQueue(&Q,&v);
w = FirstAdjVertex(g,v); //求v的第一个邻接点
wgile(w != -1)
{
if(!visited[w])
{
visit(w); visited[w] = True;
EnterQueue(&Q,w);
}
w = NextAdjVertex(g,v,w); //求v相对于w的下一个邻接点
}
}
}
算法评价:若采用邻接表的方式存储,则时间复杂度为 O(n+e);
若采用邻接矩阵的方式存储,则时间复杂度为O(n^2);
图的应用
图的连通性问题
**1.**可以利用图的遍历过程来判断一个图是否连通,如果遍历过程不止调用一次搜索过程,则说明该图就是一个非连通图。
图的两个顶点之间的简单路径(?):
int *pre;
void one_path(Graph *G, int u, int v)
{
//在连通图G中找一条从第u个顶点到v个顶点的简单路径
int i;
pre = (int *)malloc(G -> vexnum *sizeof(int));
for(i = 0;i < G -> vexnum;i ++) pre[i] = -1;
pre[u] = -2; //表示初始顶点u已被访问,并且u没有前驱
DFS_path(G,u,v); //用深搜找一条从u到v的简单路径
free(pre);
}
int DFS_path(Graph *G, int u, int v)
{
int j;
for(j = firstadj(G,u);j>=0;j=nextadj(G,u,j))
{
if(pre[j] == -1)
{
pre[j] = u;
if(j == v)
{
print_path(pre,v) //从v开始,沿着pre[]中保留的前驱指针输出路径(直到-2)
return 1;
}
else if(DFS_path(G,j,v)) return 1;
}
return 0;
}
}
图的生成树与最小生成树
更适合稠密图
1)一个连通图的生成树是指一个极小连通子图,它含有图中的全部顶点,但只有足以构成一棵树的n-1条边
—注:有n-1条边的图并非一定连通,不一定存在生成树。
如果一个图有n个顶点且边数小于n-1条,则该图一定是非连通图
2)最小生成树
在一个图的所有生成树中,各边的代价之和最小的那颗生成树称为该连通图的最小代价生成树, 简称最小生成树
Prim算法 (顶点角度)
普里姆算法在找最小生成树时,将顶点分为两类,一类是在查找的过程中已经包含在树中的(假设为 A 类),剩下的是另一类(假设为 B 类)。
对于给定的连通网,起始状态全部顶点都归为 B 类。
在找最小生成树时,选定任意一个顶点作为起始点,并将之从 B 类移至 A 类;
然后找出 B 类中到 A 类中的顶点之间权值最小的顶点,将之从 B 类移至 A 类,如此重复,直到 B 类中没有顶点为止。所走过的顶点和边 就是该连通图的最小生成树。算法思想:
1、首先将初始顶点u加入到U中,对其余的每一个顶点i,将closedge[i]均初始化为i到u的边信息
2、循环n-1次,做如下处理:
a.从各组最小表closedge[]中选出最小的最小边closedge[v]
b.将v加入U中
c.更新剩余的每组最小边信息closedge[i] (i∈ V-U)
对于以i为中心的那组边,新增加了一条从v到i边,如果新边的权值比closedge[i].lowcost小,则将closedge[i].lowcost更新为新边的权值。
时间复杂度为:O(n2 )。
Kruskal算法 (边角度)
更适合稀疏图
思维流程:
对于任意一个连通网的最小生成树来说,在要求总的权值最小的情况下,最直接的想法就是将连通网中的所有边按照权值大小进行升序 排序,从小到大依次选择。
需要满足的要求:
生成树中任意顶点之间有且仅有一条通路,也就是说,生成树中不能存在回路;
对于具有 n 个顶点的连通网,其生成树中只能有 n-1 条边,这 n-1 条边连通着 n 个顶点。所以克鲁斯卡尔算法的具体思路是:将所有边按照权值的大小进行升序排序,然后从小到大一一判断,条件为:如果这个边不会与之前 选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出 来的边和所有的顶点构成此连通网的最小生成树。
重连通图和重连通分量
在无向图中,如果任意两个顶点之间含有不止一条通路,这个图就被称为重连通图。在重连通图中,在删除某个顶点及该顶点相关的边
后,图中各顶点之间的连通性也不会被破坏。
在一个无向图中,如果删除某个顶点及其相关联的边后,原来的图被分割为两个及以上的连通分量,则称该顶点为无向图中的一个关节 点(或者“割点”)。
重连通图的判定 (割点)
通过深搜或宽搜都能获得一棵生成树–》
1、首先判断整棵树的树根结点,如果树根有两条或者两条以上的子树,则该顶点肯定是关节点。因为一旦树根丢失,生成树就会变成森林。
2、然后判断生成树中的每个非叶子结点,以该结点为根结点的每棵子树中如果有结点的回边与此非叶子结点的祖宗结点相关联,那么此非叶子结点就不是关节点;反之,就是关节点。 回边就是图中的虚线
注意:必须是和该非叶子结点的祖宗结点(不包括结点本身)相关联,才说明此结点不是关节点。
割点的求法(tarjan算法)
如果u为割点,当且仅当满足下面的1/2
1、如果u为树根,那么u必须有多于1棵子树
2、如果u不为树根,那么(u,v)为树枝边,当Low[v]>=DFN[u]时。
tarjan用于求强连通分量
DFN[x] :表示在DFS深搜中,x实际被访问的时间点;x被访问的越早,DFN[x]越小
Low[x]: 表示在DFS深搜中,x通过无向边,可回溯到的最早时间点。
x如果是割点:
1、不是根结点&&有儿子结点&&low[x的儿子]>=DFN[x]
2、x是根结点且有两个儿子结点
|深度优先搜索的拓展|
void tarjan(int u)
{
DFN[u] = Low[u] = ++index;
isVisted[u] = true;
for (int i = 1; i <= n; ++ i)
{
if (map[u][i])
{
if (!isVisted[i])
{
tarjan(i);
Low[u] = Min(Low[u], Low[i]);
if (Low[i] >= DFN[u] && u != 1)//if it is not root
{
gPoint[u] ++; //删除u点后,分成的连通分量个数
}
else if (u == 1)//if it is root
{
root ++;
}
}
else
{
Low[u] = Min(Low[u], DFN[i]);
}
}
}
}
参考:https://blog.csdn.net/u010126535/article/details/20071273
有向无环图的应用
拓扑排序
拓扑排序是将有向无环图(DAG图)中的顶点按照图中指定的先后顺序进行排序
偏序:存在顶点没有明显的先后关系
全序:所有顶点都有明确的先后关系
->全序是偏序的一种特殊关系,通过拓扑排序得到的序列首先一定是偏序,如果任意两点都有前后顺序,那么此序列就是全序的。
遵守的原则:
1)在图中选择一个没有前驱的顶点V
2)从图中删除顶点V和所有以该顶点为尾的弧
AOV 网: 用来表示某种活动间的先后关系的有向图
算法的实现大致思路:
1)首先通过邻接表将AOV网进行存储,由于拓扑排序的整个过程中,都是以顶点的入度为依据进行排序,所以需要根据建立的邻接表统计出各顶点的入度
2)在得到各顶点的入度后,首先找到入度为0的顶点作为拓扑排序的起始点,然后查找以该顶点为起始点的所有顶点,如果入度为1,说明删除前一个顶点后,该顶点的入度为0,为拓扑排序的下一个对象。
关键路径
AOE网:在AOV网的基础上,其中每一个边都具有各自的权值,是一个有向无环网。其中权值表示活动持续时间
原文链接: “https://blog.csdn.net/fu_jian_ping/article/details/88962697”
确定关键路径,需要定义4个描述量:
1、ve(vj) — 表示事件vj的最早发生时间 (顶点表示)
ve(v1) = 0; ve(v2) = 30
2、vl(vj) — 表示vj的最迟发生时间 (顶点表示)
vl(v4) = 165
3、e(i) — 表示活动ai的最早开始时间 (边表示)
e(a3) = 30
4、l(i) — 表示活动ai的最晚开始时间 (边表示)
l(a3) = 120
l(i) - e(i) — 表示完成活动ai的时间余量
l(3) - e(3) = 90
关键活动: l(i) == e(i) 关键路径上的活动,没有时间余量
弧<j,k>
如何求活动的 l(i) e(i)
1、 e(i) = ve(j) (j表示弧尾)
2、 l(i) = vl(k) - w j k w_{jk} wjk (k弧头减去弧的权)|
如何计算ve(j) , vl(j)
1、从ve(1) = 0开始向前推进
ve(j) = Max{ve(i) + w}–> 通俗来讲,找每个点的最大值权 w i w_i wi + 前面一个的最大值权 w i w_i wi
2、从vl(n) = ve(n)开始向后推
vl(i) = Min{vl(j) - w}–>倒过来求解,从汇点开始往前求,汇点要求的时间 - 权中最大的值 —>最迟发生时间
具体算法实现:
1、利用拓扑排序可以求出 ve[N]; 在拓扑排序中,要注意将拓扑排序的结果用另外一个对战存储起来 最大化和值max
2、求vl的时候,先用ve[]的最后一个来初始化vl[]数组,然后依据拓扑排序获得的堆栈来求vl。最小化差值 min
3、利用公式求e和l,当 e == l的时候,所求的路径就是关键路径 <j,k> 权值 w j k w_{jk} wjk
最短路径问题
·求某一点到其他各定点的最短路径
Djiskstra 算法
Djiskstra算法计算的是有向网中的某个顶点到其他所有顶点的最短路径。
其中将没有两点直接没有连线的点之间用无穷大表示
dist[i] //用于存放某一点到点i的最短距离
dijistra算法
1、初始化:先找出从原点v0到各终点vk的直达路径(v0,vk),也就是通过一条弧到达的路径
2、选择:从这些路径中找出一条长度最短的路径(v0,u)
3、更新:然后对其余各条路径进行适当调整
若在图中存在弧(u,vk),且(v0,u) + (u, vk) < (v0,vk),
则以路径(v0,u,vk)代替(v0,vk)
Floyd 算法
Floyd算法计算的是有向网中任意两个顶点之间的最短路径
时间复杂度更高,但实现形式更加简单。
核心思想:对于网中的任意两个顶点(例如顶点A和顶点B)来说,之间的最短路径有两种情况:
1)直接从顶点A到顶点B的弧的权值为顶点A到顶点B的最短路径
2)从顶点A开始,经过若干个顶点,最终达到B,期间经过的弧的权值为顶点A到B的最短路径
算法思想:(动态规划的一种途径)
1、逐个顶点试探
2、从vi到vj的所有可能存在的路径中
3、选出一条长度最短的路径
初始化:
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
图(与离散数学的联系)
欧拉图
欧拉通路与欧拉回路:
欧拉通路: 通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路。(不需要回到原点)
欧拉回路: 通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路。(要能够回到原点)
半欧拉图:具有欧拉通路但不具有欧拉回路的无向图称为半欧拉图。
欧拉图: 具有欧拉回路的无向图称为欧拉图。
连通图
a. 对于无向图G,若从顶点vi到顶点vj有路径相连,则称vi和vj是连通的。如果在无向图G中,任意两点都是互通的,那么无向图G是连通的。如何用代码实现呢?其实从一个点出发,通过广度/深度优先遍历,能够遍历到每一个点,说明该图是连通的。
b. 对于有向图G,若从顶点vi到顶点vj有路径相连(图中所有的边是同向的),则称vi和vj是连通的。**如果在有向图G中,任意两点vi和vj,存在一条路径从vi到vj且存在一条路径从vj到vi,则有向图G是强连通的。**如果任意两点都是连通的,则称G是连通的。(必须遍历C(n, 2)个定点组合了,我现在能想到的办法。)
欧拉图的重要性质
-
对于无向图G
a. G 是欧拉图当且仅当 G 是连通的且没有奇度顶点。(我们首先可以对每个点的度数进行统计,判断是否符合条件。然后,进行深度优先遍历,如果能够遍历完所有的点,那么G是欧拉图)
b. G 是半欧拉图当且仅当 G 是连通的且 G 中恰有 2 个奇度顶点。(首先,对度数进行统计,判断是否符合条件。然后,进行一次深度优先遍历就好了,如果能够遍历完所有的点,那么G是欧拉图)
证明无向图是连通的,只需要进行深度/广度优先遍历,能够遍历所有的点就行。
-
对于有向图G
a. G是欧拉图当且仅当 G 的所有顶点属于同一个连通分量且每个顶点的入度和出度相同。(首先,对度数进行统计,判断是否符合条件。然后,进行一次深度优先遍历就好了,如果能够遍历完所有的点,那么G是欧拉图。)
b. G 是半欧拉图当且仅当 G 的所有顶点属于同一个连通分量且:1)恰有一个点的出度与入度相差1;2)恰有一个点的入度与出度相差1;3)其他顶点的入度与出度相等。(首先,对度数进行统计,判断是否符合条件。然后,从出度与入度相差1的顶点出发进行一次深度优先遍历就好了,如果能够遍历完所有的点,那么G是欧拉图。)