若无向图G=(V,E)中含7个顶点,则保证图G在任何情况下都是连通的,则需要的边数最少是( ) c
A :6 B:15 C:16 D:21
分析:要想保证在任何情况下都连通,其中6个顶点组成完全图。包含6个顶点的完全图中包含6*5/2=15个条边。第7个顶点和其他6个顶点之间有一条边,就能保证图是联通的。因此,至少需要16条边
图的抽象数据类型定义
ADT Graph
Data
顶点的有穷非空集合和边的集合
Operation
InitGraph
前置条件:图不存在
输入:无
功能:图的初始化
输出:无
后置条件:构造一个空的图
DFSTraverse
前置条件:图已存在
输入:遍历的起始顶点v
功能:从顶点v出发深度优先遍历图
输出:图中顶点的一个线性排列
后置条件:图保持不变
BFSTraverse
前置条件:图已存在
输入:遍历的起始顶点v
功能:从顶点v出发广度优先遍历图
输出:图中顶点的一个线性排列
后置条件:图保持不变
DestroyGraph
前置条件:图已存在
输入:无
功能:销毁图
输出:无
后置条件:释放图所占用的存储空间
GetVex
前置条件:图已存在
输入:顶点v
功能:在图中查找顶点v的数据信息
输出:顶点v的数据信息
后置条件:图保持不变
endADT
图的遍历操作
图的遍历是从图中某一顶点出发,对图中所有顶点访问一次且仅访问一次。
抽象操作,可以是对结点进行的各种处理,这里简化为输出结点的数据
需要解决的关键问题:
-
在图中,如何选取遍历的起始顶点?
从编号小的顶点开始
在线性表中,数据元素在表中的编号就是元素在序列中的位置,因而其编号是唯一的;
在树中,将结点按层序编号,由于树具有层次性,因而其层序编号也是唯一的;
在图中,/任何两个顶点之间都可能存在边,顶点是没有确定的先后次序的,所以,顶点的编号不唯一。/
为了定义操作的方便,将图中的顶点按任意顺序排列起来,比如,按顶点的存储顺序。
2.从某个起点始可能到达不了所有其它顶点,怎么办?
多次调用从某顶点出发遍历图的算法
下面仅讨论从某顶点出发遍历图的算法。
3.因图中可能存在回路,某些顶点可能会被重复访问,那么如何避免遍历不会因回路而陷入死循环?
附设访问标志数组visited[n]
4.在图中,一个顶点可以和其它多个顶点相连,当这样的顶点访问过后,如何选取下一个要访问的顶点?
深度优先遍历和广度优先遍历 -
深度优先遍历 (DFS:Depth First Search)
基本思想:
⑴ 访问顶点v;
⑵ 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
⑶ 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。 -
广度优先遍历 (BFS:Broad First Search ;FIFO: First In First Out)
基本思想:
⑴ 访问顶点v;
⑵ 依次访问v的各个未被访问的邻接点v1, v2, …, vk;
⑶ 分别从v1,v2,…,vk出发依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。直至图中所有与顶点v有路径相通的顶点都被访问到。
6.2 图的存储结构及实现
是否可以采用顺序存储结构存储图?
图的特点:顶点之间的关系是m:n,即任何两个顶点之间都可能存在关系(边),无法通过存储位置表示这种任意的逻辑关系,所以,图无法采用顺序存储结构。
如何存储图?
考虑图的定义,图是由顶点和边组成的,分别考虑如何存储顶点、如何存储边
邻接矩阵(数组表示法)
基本思想:
用一个一维数组存储图中顶点的信息
用一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系
假设图G=(V,E)有n个顶点,则邻接矩阵是一个n×n的方阵,定义为:
arc[i][j]= 1 若(vi, vj)∈E(或<vi, vj>∈E)
0 其它
无向图的邻接矩阵
特点:主对角线为 0 且一定是对称矩阵
如何求顶点i的度?
邻接矩阵的第i行(或第i列)非零元素的个数
如何判断顶点 i 和 j 之间是否存在边?
测试邻接矩阵中相应位置的元素arc[i][j]是否为1
如何求顶点 i 的所有邻接点
将数组中第 i 行元素扫描一遍,若arc[i][j]为1,则顶点 j 为顶点 i 的邻接点
有向图的邻接矩阵一定不对称吗
不一定,例如有向完全图
有向图的邻接矩阵
如何求顶点 i 的出度
邻接矩阵的第 i 行元素之和
如何求顶点 i 的入度?
邻接矩阵的第 i 列元素之和
如何判断从顶点 i 到顶点 j 是否存在边?
测试邻接矩阵中相应位置的元素arc[i][j]是否为1
网图的邻接矩阵
arc[i][j]=权重 若(vi, vj)∈E(或<vi, vj>∈E)
0 若i==j
∞ 其他
邻接矩阵存储无向图的类
const int MaxSize=10;
template <class T>
class Mgraph{
public:
MGraph(T a[ ], int n, int e );
~MGraph( )
void DFSTraverse(int v);
void BFSTraverse(int v);
……
private:
T vertex[MaxSize];
int arc[MaxSize][MaxSize];
int vertexNum, arcNum;
};
网图:带权的图
邻接矩阵中图的基本操作——构造函数MGraph(T a[ ], int n, int e );
1.确定图的顶点个数和边的个数;
2. 输入顶点信息存储在一维数组vertex中;
3. 初始化邻接矩阵;
4. 依次输入每条边存储在邻接矩阵arc中;
4.1 输入边依附的两个顶点的序号i, j;
4.2 将邻接矩阵的第i行第j列的元素值置为1;
4.3 将邻接矩阵的第j行第i列的元素值置为1
template <class T>
MGraph::MGraph(T a[ ], int n, int e) {
vertexNum=n; arcNum=e;
for (i=0; i<vertexNum; i++)
vertex[i]=a[i];
for (i=0; i<vertexNum; i++) //初始化邻接矩阵
for (j=0; j<vertexNum; j++)
arc[i][j]=0;
for (k=0; k<arcNum; k++) {
cin>>i>>j; //边依附的两个顶点的序号
arc[i][j]=1; arc[j][i]=1; //置有边标志
}
}
理解:初始化 将所有值都置为一,然后自己输入不为一的边的两个顶点
n:顶点 e:边
深度优先访问:
⑴ 访问顶点v;
⑵ 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
⑶ 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。
递归定义
int visited[MaxSize];
template <class T>
void MGraph::DFSTraverse(int v)
{
cout<<vertex[v]; visited [v]=1;
for (j=0; j<vertexNum; j++)
if (arc[v][j]==1 && visited[j]==0)
DFSTraverse( j );
}
⑴ 访问顶点v;
⑵ 依次访问v的各个未被访问的邻接点v1, v2, …, vk;
⑶ 分别从v1,v2,…,vk出发依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。直至图中所有与顶点v有路径相通的顶点都被访问到。
int visited[MaxSize];
template <class T>
void MGraph::BFSTraverse(int v){
front=rear=-1; //假设采用顺序队列且不会发生溢出
int Q[MaxSize]; cout<<vertex[v]; visited[v]=1; Q[++rear]=v;
while (front!=rear) {
v=Q[++front];
for (j=0; j<vertexNum; j++)
if (arc[v][j]==1 && visited[j]==0 ) {
cout<<vertex[j]; visited[j]=1; Q[++rear]=j;
}
}
}
//不太理解 还要回头再看一看
增加一个顶点
在存储顶点的一维数组中插入该顶点的信息
在邻接矩阵中插入一行、一列
删除一个顶点
在存储顶点的一维数组中删除该顶点的信息
在邻接矩阵中删除一行、一列
增加一条边
修改相应的矩阵元素的值
删除一条边
修改相应的矩阵元素的值
增加一个顶点:
template <class T>
void MGraph<T>::InsertVex(int num,T name) {
if ( num<0|| num>vertexNum) throw "位置";
int row, col, numv;
numv = vertexNum-1;
vertexNum++;
for(int i=numv;i>=num;i--) vertex[i++]=vertex[i];
vertex[num]=name;
for(row=numv;row>=0;row--) {所有行上num列之后的列后移,增加一列,
for(col=numv;col>=num;col--) arc[row][col+1]=arc[row][col];
arc[row][num]=0;
}
for(row=numv;row>=num;row--)
for(col=0;col<=numv+1;col++) arc[row+1][col]=arc[row][col];
for(col=0;col<vertexNum;col++) arc[num][col]=0;
}
删除一个顶点:
template <class T> void MGraph<T>::DeleteVex(int pos){
if ( pos<0|| pos>MaxSize) throw "位置";
int row, col;
int numv=vertexNum;
for(int i=pos;i<numv;i++) vertex[i]=vertex[i+1];
vertexNum--;
for(row=0;row<numv;row++) { //删除一列
for(col=pos;col<numv;col++) arc[row][col]=arc[row][col+1];
}
for(row=pos;row<numv;row++)
for(col=0;col<numv;col++)
arc[row][col]=arc[row+1][col];
}
}
插入一条边:
template <class T>
void MGraph<T>::InsertArc(int i, int j)
{
if ( i>MaxSize|| j>MaxSize) throw "位置";
arc[i][j]=1;
arc[j][i]=1;
}
删除一条边:
template <class T>
void MGraph<T>::DeleteArc(int i, int j)
{
if ( i>MaxSize|| j>MaxSize) throw "位置";
arc[i][j]=arc[j][i]=0;
}
邻接表:
图的邻接矩阵存储结构的空间复杂度?
如果为稀疏图则会出现什么现象?
假设图G有n个顶点e条边,则存储该图需要O(n2)
邻接表存储的基本思想:
对于图的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表(对于有向图则称为出边表)
所有边表的头指针和存储顶点信息的一维数组构成了顶点表