资源描述
第8章 图
8-1 画出1个顶点、2个顶点、3个顶点、4个顶点与5个顶点得无向完全图。试证明在n个顶点得无向完全图中,边得条数为n(n-1)/2。
【解答】
2个顶点得
无向完全图
1个顶点得
无向完全图
5个顶点得
无向完全图
4个顶点得
无向完全图
3个顶点得
无向完全图
【证明】
在有n个顶点得无向完全图中,每一个顶点都有一条边与其它某一顶点相连,所以每一个顶点有n-1条边与其她n-1个顶点相连,总计n个顶点有n(n-1)条边。但在无向图中,顶点i到顶点j与顶点j到顶点i就是同一条边,所以总共有n(n-1)/2条边。
D
A
8-2 右边得有向图就是强连通得吗?请列出所有得简单路径。
E
B
【解答】
F
C
判断一个有向图就是否强连通,要瞧从任一顶点出发就是否能够回到该顶点。右面得有向图做不到这一点,它不就是强连通得有向图。各个顶点自成强连通分量。
所谓简单路径就是指该路径上没有重复得顶点。
从顶点A出发,到其她得各个顶点得简单路径有A→B,A→D→B,A→B→C,A→D→B→C,A→D,A→B→E,A→D→E,A→D→B→E,A→B→C→F→E,A→D→B→C→F→E,A→B→C→F,A→D→B→C→F。
从顶点B出发,到其她各个顶点得简单路径有B→C,B→C→F,B→E,B→C→F→E。
从顶点C出发,到其她各个顶点得简单路径有C→F,C→F→E。
从顶点D出发,到其她各个顶点得简单路径有D→B,D→B→C,D→B→C→F,D→E,D→B→E,D→B→C→F→E。
从顶点E出发,到其她各个顶点得简单路径无。
从顶点F出发,到其她各个顶点得简单路径有F→E。
8-3 给出右图得邻接矩阵、邻接表与邻接多重表表示。
D
A
【解答】
(1) 邻接矩阵
E
B
F
C
∧
3
1
0 A
1 B
2 C
3 D
4 E
5 F
(2) 邻接表
∧
4
2
(出边表)
∧
5
∧
4
1
∧
∧
4
∧
0 A
1 B
2 C
3 D
4 E
5 F
(入边表)
∧
3
0
∧
1
∧
0
∧
5
3
1
∧
2
data fin fout
(3) 邻接多重表(十字链表)
i j ilink jlink
0 1 (A, B)
∧
0 A
1 B
2 C
3 D
4 E
5 F
∧
∧
0 3 (A, D)
∧
∧
1 2 (B, C)
1 4 (B, E)
∧
∧
∧
2 5 (C, F)
3 1 (D, B)
3 4 (D, E)
5 4 (F, E)
∧
∧
8-4 用邻接矩阵表示图时,若图中有1000个顶点,1000条边,则形成得邻接矩阵有多少矩阵元素?有多少非零元素?就是否稀疏矩阵?
【解答】
一个图中有1000个顶点,其邻接矩阵中得矩阵元素有10002 = 1000000个。它有1000个非零元素(对于有向图)或2000个非零元素(对于无向图),因此就是稀疏矩阵。
8-5 用邻接矩阵表示图时,矩阵元素得个数与顶点个数就是否相关?与边得条数就是否相关?
【解答】
用邻接矩阵表示图,矩阵元素得个数就是顶点个数得平方,与边得条数无关。矩阵中非零元素得个数与边得条数有关。
8-6 有n个顶点得无向连通图至少有多少条边?有n个顶点得有向强连通图至少有多少条边?试举例说明。
【解答】
n个顶点得无向连通图至少有n-1条边,n个顶点得有向强连通图至少有n条边。例如:
特例情况就是当n = 1时,此时至少有0条边。
8-7对于有n个顶点得无向图,采用邻接矩阵表示,如何判断以下问题: 图中有多少条边?任意两个顶点i与j之间就是否有边相连?任意一个顶点得度就是多少?
【解答】
用邻接矩阵表示无向图时,因为就是对称矩阵,对矩阵得上三角部分或下三角部分检测一遍,统计其中得非零元素个数,就就是图中得边数。如果邻接矩阵中A[i][j] 不为零,说明顶点i与顶点j之间有边相连。此外统计矩阵第i行或第i列得非零元素个数,就可得到顶点i得度数。
①
8-8对于如右图所示得有向图,试写出:
③
②
(1) 从顶点①出发进行深度优先搜索所得到得深度优先生成树;
(2) 从顶点②出发进行广度优先搜索所得到得广度优先生成树;
⑤
④
【解答】
(1) 以顶点①为根得深度优先生成树(不唯一):② ③ ④ ⑤ ⑥
③
②
①
或
①
③
②
④
⑤
④
⑤
(2) 以顶点②为根得广度优先生成树:
①
③
②
④
⑤
8-9 试扩充深度优先搜索算法,在遍历图得过程中建立生成森林得左子女-右兄弟链表。算法得首部为 void Graph::DFS ( const int v, int visited [ ], TreeNode<int> * t ) 其中,指针t指向生成森林上具有图顶点v信息得根结点。(提示:在继续按深度方向从根v得某一未访问过得邻接顶点w向下遍历之前,建立子女结点。但需要判断就是作为根得第一个子女还就是作为其子女得右兄弟链入生成树。)
【解答】
为建立生成森林,需要先给出建立生成树得算法,然后再在遍历图得过程中,通过一次次地调用这个算法,以建立生成森林。
te mplate<Type> void Graph<Type> :: DFS_Tree ( const int v, int visited [ ], TreeNode<Type> *t ) {
//从图得顶点v出发, 深度优先遍历图, 建立以t (已在上层算法中建立)为根得生成树。
Visited[v] = 1; int first = 1; TreeNode<Type> * p, * q;
int w = GetFirstNeighbor ( v ); //取第一个邻接顶点
while ( w != -1 ) { //若邻接顶点存在
if ( vosited[w] == 0 ) { //且该邻接结点未访问过
p = new TreeNode<Type> ( GetValue (w) ); //建立新得生成树结点
if ( first == 1 ) //若根*t还未链入任一子女
{ t->setFirstChild ( p ); first = 0; } //新结点*p成为根*t得第一个子女
else q->setNextSibling ( p ); //否则新结点*p成为*q得下一个兄弟
q = p; //指针q总指示兄弟链最后一个结点
DFS_Tree ( w, visited, q ); //从*q向下建立子树
}
w = GetNextNeighbor ( v, w ); //取顶点v排在邻接顶点w得下一个邻接顶点
}
}
下一个算法用于建立以左子女-右兄弟链表为存储表示得生成森林。
template<Type> void Graph<Type> :: DFS_Forest ( Tree<Type> & T ) {
//从图得顶点v出发, 深度优先遍历图, 建立以左子女-右兄弟链表表示得生成森林T。
T、root = NULL; int n = NumberOfVertices ( ); //顶点个数
TreeNode<Type> * p, * q;
int * visited = new int [ n ]; //建立访问标记数组
for ( int v = 0; v < n; v++ ) visited[v] = 0;
for ( v = 0; v < n; v++ ) //逐个顶点检测
if ( visited[v] == 0 ) { //若尚未访问过
p = new TreeNode<Type> ( GetValue ( v ) ); //建立新结点*p
if ( T、root == NULL ) T、root = p; //原来就是空得生成森林, 新结点成为根
else q-> setNextSibling ( p ); //否则新结点*p成为*q得下一个兄弟
q = p;
DFS_Tree ( v, visited, p ); //建立以*p为根得生成树
}
}
8-10 用邻接表表示图时,顶点个数设为n,边得条数设为e,在邻接表上执行有关图得遍历操作时,时间代价就是O(n*e)?还就是O(n+e)?或者就是O(max(n,e))?
【解答】
在邻接表上执行图得遍历操作时,需要对邻接表中所有得边链表中得结点访问一次,还需要对所有得顶点访问一次,所以时间代价就是O(n+e)。
⑧
⑦
⑨
8-11 右图就是一个连通图,请画出
②
⑥
①
⑩
(1) 以顶点①为根得深度优先生成树;
③
(2) 如果有关节点,请找出所有得关节点。
④
⑤
(3) 如果想把该连通图变成重连通图,至少在图中加几条边?
如何加?
【解答】
(1) 以顶点①为根得深度优先生成树:
⑧
⑨
⑦
①
②
⑥
⑩
③
⑤
④
(2) 关节点为 ①,②,③,⑦,⑧
(3) 至少加四条边 (1, 10), (3, 4), (4, 5), (5, 6)。从③得子孙结点⑩到③得祖先结点①引一条边,从②得子孙结点④到根①得另一分支③引一条边,并将⑦得子孙结点⑤、⑥与结点④连结起来,可使其变为重连通图。
⑨
⑧
⑦
⑥
②
①
⑩
⑤
④
③
8-12试证明在一个有n个顶点得完全图中,生成树得数目至少有2n-1-1。
【证明】略
7
11
10
7
②
④
5
③
8
⑤
6
⑥
①
9
8-13 编写一个完整得程序,首先定义堆与并查集得结构类型与相关操作,再定义Kruskal求连通网络得最小生成树算法得实现。并以右图为例,写出求解过程中堆、并查集与最小生成树得变化。
【解答】
求解过程得第一步就是对所有得边,按其权值大小建堆:
3 4 5
1 2 7
1 2 7
1 2 7
2 3 10
2 3 10
2 4 9
2 3 10
1 3 11
1 3 11
加(1, 2), (1, 3), (2,3)
2 4 9
1 3 11
加(3, 4)
加(2, 4)
3 4 5
3 4 5
3 5 7
1 2 7
1 2 7
3 5 7
3 6 8
2 3 10
2 4 9
1 3 11
1 3 11
2 3 10
2 4 9
加(3, 5)
加(3, 6)
3 4 5
3 5 7
5 6 6
加(5, 6)
3 6 8
2 3 10
2 4 9
1 2 7
1 3 11
求解过程中并查集与堆得变化:
1 2 7
⑥
④
5 6 6
③
3 6 8
3 5 7
⑤
③
3 5 7
1 2 7
④
2 3 10
2 4 9
1 3 11
选(5,6,6)
3 6 8
2 3 10
2 4 9
1 3 11
选(3,4,5)
3 6 8
③
①
3 5 7
⑤
③
①
2 3 10
3 6 8
2 4 9
2 3 10
⑥
②
④
⑥
④
②
1 3 11
选(3,5,7)
⑤
2 4 9
1 3 11
选(1,2,7)
2 3 10
③
①
③
2 4 9
①
1 3 11
⑤
④
④
②
2 3 10
1 3 11
⑤
②
选(2,4,9), 结束
⑥
选(3,6,8), 在同一连通分量上, 不加
⑥
最后得到得生成树如下
0 1 2 3 4 5 6
3 1 -6 3 3 5
①
⑤
7
③
5
6
7
并查集得存储表示
9
②
⑥
④
完整得程序如下:
#include <iostream、h>
template <class Type> class MinHeap {
public:
enum { MaxHeapSize = 50 };
MinHeap ( int Maxsize = MaxHeapSize );
MinHeap ( Type Array[ ], int n );
void Insert ( const Type &ele );
void RemoveMin ( Type &Min );
void Output ();
private:
void FilterDown ( int start, int end );
void FilterUp ( int end );
Type *pHeap;
int HMaxSize;
int CurrentSize;
};
class UFSets {
public:
enum { MaxUnionSize = 50 };
UFSets ( int MaxSize = MaxUnionSize );
~UFSets () { delete [ ] m_pParent; }
void Union ( int Root1, int Root2 );
int Find ( int x );
private:
int m_iSize;
int *m_pParent;
};
class Graph {
public:
enum { MaxVerticesNum = 50 };
Graph( int Vertices = 0) { CurrentVertices = Vertices; InitGraph(); }
void InitGraph ();
void Kruskal ();
int GetVerticesNum () { return CurrentVertices; }
private:
int Edge[MaxVerticesNum][MaxVerticesNum];
int CurrentVertices;
};
class GraphEdge {
public:
int head, tail;
int cost;
int operator <= ( GraphEdge &ed );
};
GraphEdge :: operator <= ( GraphEdge &ed ) {
return this->cost <= ed、cost;
}
UFSets :: UFSets ( int MaxSize ) {
m_iSize = MaxSize;
m_pParent = new int[m_iSize];
for ( int i = 0; i < m_iSize; i++ ) m_pParent[i] = -1;
}
void UFSets :: Union ( int Root1, int Root2 ) {
m_pParent[Root2] = Root1;
}
int UFSets :: Find ( int x ) {
while ( m_pParent[x] >= 0 ) x = m_pParent[x];
return x;
}
template <class Type> MinHeap<Type> :: MinHeap ( int Maxsize ) {
HMaxSize = Maxsize;
pHeap = new Type[HMaxSize];
CurrentSize = -1;
}
template <class Type> MinHeap<Type> :: MinHeap ( Type Array[], int n ) {
HMaxSize = ( n < MaxHeapSize ) ? MaxHeapSize : n;
pHeap = new Type[HMaxSize];
for ( int i = 0; i < n; i++ ) pHeap[i] = Array[i];
CurrentSize = n-1;
int iPos = ( CurrentSize - 1 ) / 2;
while ( iPos >= 0 ) {
FilterDown ( iPos, CurrentSize );
iPos--;
}
}
template <class Type> void MinHeap<Type> :: FilterDown ( int start, int end ) {
int i = start, j = 2 * start + 1;
Type Temp = pHeap[i];
while ( j <= end ) {
if ( j < end && pHeap[j+1] <= pHeap[j] ) j++;
if ( Temp <= pHeap[j] ) break;
pHeap[i] = pHeap[j];
i = j; j = 2 * j + 1;
}
pHeap[i] = Temp;
}
template <class Type> void MinHeap<Type> :: FilterUp ( int end ) {
int i = end, j = ( end - 1 ) / 2;
Type Temp = pHeap[i];
while ( i > 0 ) {
if ( pHeap[j] <= Temp ) break;
pHeap[i] = pHeap[j];
i = j; j = ( j - 1 ) / 2;
}
pHeap[i] = Temp;
}
template <class Type> void MinHeap<Type> :: Insert ( const Type &ele ) {
CurrentSize++;
if ( CurrentSize == HMaxSize ) return;
pHeap[CurrentSize] = ele;
FilterUp ( CurrentSize );
}
template <class Type> void MinHeap<Type> :: RemoveMin ( Type &Min ) {
if ( CurrentSize < 0 ) return;
Min = pHeap[0];
pHeap[0] = pHeap[CurrentSize--];
FilterDown ( 0, CurrentSize );
}
template <class Type> void MinHeap<Type> :: Output ( ) {
for ( int i = 0; i <= CurrentSize; i++ ) cout << pHeap[i] << " ";
cout << endl;
}
void Graph :: InitGraph( ) {
Edge[0][0] = -1; Edge[0][1] = 28; Edge[0][2] = -1; Edge[0][3] = -1; Edge[0][4] = -1; Edge[0][5] = 10;
Edge[0][6] = -1;
Edge[1][1] = -1; Edge[1][2] = 16; Edge[1][3] = -1; Edge[1][4] = -1; Edge[1][5] = -1; Edge[1][6] = 14;
Edge[2][2] = -1; Edge[2][3] = 12; Edge[2][4] = -1; Edge[2][5] = -1; Edge[2][6] = -1;
Edge[3][3] = -1; Edge[3][4] = 22; Edge[3][5] = -1; Edge[3][6] = 18;
Edge[4][4] = -1; Edge[4][5] = 25; Edge[4][6] = 24;
Edge[5][5] = -1; Edge[5][6] = -1;
Edge[6][6] = -1;
for ( int i = 1; i < 6; i++ )
for ( int j = 0; j < i; j ++ ) Edge[i][j] = Edge[j][i];
}
void Graph :: Kruskal( ) {
GraphEdge e;
int VerticesNum = GetVerticesNum ( );
int i, j, count;
MinHeap<GraphEdge> heap ( VerticesNum *VerticesNum );
UFSets set ( VerticesNum );
for ( i = 0; i < VerticesNum; i++ )
for ( j = i + 1; j < VerticesNum; j++ )
if ( Edge[i][j] > 0 ) {
e、head = i; e、tail = j; e、cost = Edge[i][j];
heap、Insert ( e );
}
count = 1;
while ( count < VerticesNum ) {
heap、RemoveMin ( e );
i = set、Find ( e、head );
j = set、Find ( e、tail );
if ( i != j ) {
set、Union ( i, j );
count++;
cout << "( " << e、head << ", " << e、tail << ", " << e、cost << " )" << endl;
}
}
}
8-14 利用Dijkstra算法得思想,设计一个求最小生成树得算法。
【解答】
计算连通网络得最小生成树得Dijkstra算法可描述如下:将连通网络中所有得边以方便得次序逐步加入到初始为空得生成树得边集合T中。每次选择并加入一条边时,需要判断它就是否会与先前加入T得边构成回路。如果构成了回路,则从这个回路中将权值最大得边退选。
① ② ③ ④ ⑤ ⑥
下面以邻接矩阵作为连通网络得存储表示,并以并查集作为判断就是否出现回路得工具,分析算法得执行过程。
16
②
①
①
②③④⑤⑥
5
19
21
9
26
14
⑥
③
6
11
18
④
⑤
①
①
②
16
①
②
16
①
´
21
5
21
⑤
②
⑥
③
14
19
②
⑤
⑥
③
14
⑥
9
并查集, 表明4个结点在同一连通分量上
⑥
③
④
④
⑤
⑤
④
①
16
①
16
②
⑤
´
②
①
5
⑤
②
19
②
①
5
´
19
14
⑥
③
9
⑥
14
③
③
11
⑥
③
6
④
6
④
⑤
⑤
④
⑥
④
16
①
②
最终得到得最小生成树为
5
⑥
③
6
14
11
④
⑤
实现算法得过程为
const int MaxNum = 10000;
void Graph :: Dijkstra ( ) {
GraphEdge e;
int VerticesNum = GetVerticesNum ( );
int i, j, p, q, k;
int disJoint[VerticesNum]; //并查集
for ( i = 0; i < VerticesNum; i++ ) disJoint[i] = -1; //并查集初始化
for ( i = 0; i < VerticesNum-1; i++ ) //检查所有得边
for ( j = i + 1; j < VerticesNum; j++ )
if ( Edge[i][j] < MaxNum ) { //边存在
p = i; q = j; //判结点i与j就是否在同一连通分量上
while ( disJoint[p] >= 0 ) p = disJoint[p];
while ( disJoint[q] >= 0 ) p = disJoint[q];
if ( p != q ) disJoint[j] = i; // i与j不在同一连通分量上, 连通之
} else { // i与j在同一连通分量上
p = i; //寻找离结点i与j最近得祖先结点
while ( disJoint[p] >= 0 ) { //每变动一个p, 就对q到根得路径检测一遍
q = j;
while ( disJoint[q] >= 0 && disJoint[q] == disJoint[p] )
q = disJoint[q];
if ( disJoint[q] == disJoint[p] ) break;
else p = disJoint[p];
}
k = disJoint[p]; //结点k就是i与j得最近得共同祖先
p = i; q = disJoint[p]; max = -MaxNum; //从i到k找权值最大得边(s1, s2)
while ( q <= k ) {
if ( Edge[q][p] > max ) { max = Edge[q][p]; s1 = p; s2 = q; }
p =q; q = disJoint[p];
}
p = j; q = disJoint[p]; max = -MaxNum; //从j到k找权值最大得边(t1, t2)
while ( q <= k ) {
if ( Edge[q][p] > max ) { max = Edge[q][p]; t1 = p; t2 = q; }
p =q; q = disJoint[p];
}
max = Edge[i][j]; k1 = i; k2 = j;
if ( max < Edge[s1][s2] ) { max = Edge[s1][s2]; k1 = s1; k2 = s2; }
if ( max < Edge[t1][t2] ) { max = Edge[t1][t2]; k1 = t1; k2 = t2; }
if ( max != Edge[i][j] ) { //当Edge[i][j] == max时边不改
if ( disJoint[k1] == k2 ) disJoint[k1] = -1;
else disJoint[k2] = -1; //删除权值最大得边
disJoint[j] = i; //加入新得边
Edge[j][i] = - Edge[j][i];
}
}
}
8-15以右图为例,按Dijkstra算法计算得到得从顶点①(A)到其它各个顶点得最短路径与最短路径长度。
A
18
10
【解答】
源点
终点
最短路径
最短路径长度
C
B
5
A
B
(A,B)
(A,B)
(A,B)
(A,B)
10
10
10
10
2
2
5
C
(A,C)
(A,C)
(A,C)
(A,C)
18
18
18
18
D
¾
(A,B,D)
(A,B,D)
(A,B,D)
¥
15
15
15
E
D
2
E
¾
¾
(A,B,D,E)
(A,B,D,E)
¥
¥
17
17
8-16 在以下假设下,重写Dijkstra算法:
(1) 用邻接表表示带权有向图G,其中每个边结点有3个域:邻接顶点vertex,边上得权值length与边链表得链接指针link。
(2) 用集合T = V(G) - S代替S (已找到最短路径得顶点集合),利用链表来表示集合T。
试比较新算法与原来得算法,计算时间就是快了还就是慢了,给出定量得比较。
【解答】
(1) 用邻接表表示得带权有向图得类定义:
const int DefaultSize = 10; //缺省顶点个数
class Graph; //图得前视类定义
struct Edge { //边得定义
friend class Graph;
int vertex; //边得另一顶点位置
float length; //边上得权值
Edge *link; //下一条边链指针
Edge ( ) { } //构造函数
Edge ( int num, float wh ) : vertex (num), length (wh), link (NULL) { } //构造函数
int operator < ( const Edge & E ) const { return length != E、length; } //判边上权值小否
}
struct Vertex { //顶点得定义
friend class Graph;
char data; //顶点得名字
Edge *adj; //边链表得头指针
}
class Graph { //图得类定义
private:
Vertex *NodeTable; //顶点表 (各边链表得头结点)
int NumVertices; //当前顶点个数
int NumEdges; //当前边数
int GetVertexPos ( const Type vertex ); //给出顶点vertex在图中得位置
public:
Graph ( int sz ); //构造函数
~Graph ( ); //析构函数
int NumberOfVertices ( ) { return NumVertices; } //返回图得顶点数
int NumberOfEdges ( ) { return NumEdges; } //返回图得边数
char GetValue ( int i ) //取位置为i得顶点中得值
{ return i >= 0 && i < NumVertices ? NodeTable[i]、data : ‘ ’; }
float GetWeight ( int v1, int v2 ); //返回边(v1, v2)上得权值
int GetFirstNeighbor ( int v ); //取顶点v得第一个邻接顶点
int GetNextNeighbor ( int v, int w ); //取顶点v得邻接顶点w得下一个邻接顶点
}
(2) 用集合T = V(G) - S代替S (已找到最短路径得顶点集合),利用链表来表示集合T。
8-17 试证明:对于一个无向图G = (V, E),若G中各顶点得度均大于或等于2,则G中必有回路。
【解答】
反证法:对于一个无向图G=(V,E),若G中各顶点得度均大于或等于2,则G中没有回路。此时从某一个顶点出发,应能按拓扑有序得顺序遍历图中所有顶点。但当遍历到该顶点得另一邻接顶点时,又可能回到该顶点,没有回路得假设不成立。
①
8-18 设有一个有向图存储在邻接表中。试设计一个算法,按深度优先搜索策略对其进行拓扑排序。并以右图为例检验您得算法得正确性。
②
③
【解答】
(1) 利用题8-16定义得邻接表结构。
④
⑤
增加两个辅助数组与一个工作变量:
w 记录各顶点入度 int indegree[NumVertices]。
w 记录各顶点访问顺序 int visited[NumVertices],初始时让visited[i] = 0, i = 1, 2, ¼, NumVertices。
w 访问计数int count,初始时为0。
(2) 拓扑排序算法
void Graph :: dfs ( int visited[ ], int indegree[ ], int v, int & count ) {
展开阅读全文