图的存储和基本操作

1.邻接矩阵法:数组实现的顺序存储,空间复杂度高,不适合存储稀疏图。

#define MaxVertexnum 100
//邻接矩阵
typedef char VertexType;//顶点的数据类型
typedef int EdgeType;//顶点权值的数据类型
typedef struct {
    VertexType Vex[MaxVertexnum];//顶点表
    EdgeType Edge[MaxVertexnum][MaxVertexnum];//邻接矩阵,边表
    int vexnum, arcnum;//当前顶点数和弧数
}MGraph;

注:(1)无向图的邻接矩阵一定是一个对称矩阵且唯一,对于规模特大的邻接矩阵可采用压缩存储。

(2)对于无向图,第i行或第i列,非零元素个数正好是顶点i的度

(3)对于有向图,第i行非零元素的个数正好是顶点i的出度,第i列非零元素的个数正好是顶点i的入度

(4)用邻接矩阵存储图,很容易确定图中任意两个顶点之间是否有边相连,但要确定有多少条边,所花时间代价很大。

(5)稠密图适合使用邻接矩阵的存储表示

(6)邻接矩阵表示法的空间复杂度为O(n2),其中n为图的顶点数|V|,只和顶点有关,和实际的边数无关


 

2.邻接表法:结合了顺序存储和链式存储方法

typedef struct ArcNode {//边表结点
    int adjvex;//该弧所指向的顶点的位置
    struct ArcNode* next;//指向下一条弧的指针
    //InfoType info;//网的边权值
}ArcNode;
typedef struct Vnode {//顶点表结点
    VertexType data;//顶点信息
    ArcNode* first;//指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexnum];
typedef struct {
    AdjList vertices;//邻接表
    int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;//ALGraph是以邻接表存储的图的类型

注:

(1)无向图所需的存储空间为O(|V|+2|E|),有向图所需的存储空间为O(|V|+|E|),无向图中的倍数2是由于无向图中,每条边在邻接表中出现了两次

(2)对于稀疏图,采用邻接表极大地节省存储空间

(3)计算一个顶点出度只需计算其邻接表中的结点个数,但求其顶点的入度则需要遍历整个邻接表

(4)邻接表的表示并不唯一,因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法以及边的输入次序。

(5),计算入度、度不方便

3.十字链表:只能用于存储有向图,表示不唯一

4.邻接多重表:只能存储有向图,表示不唯一

图的遍历

1.广度优先遍历:首先访问起始顶点v,由v出发,依次访问v的各个未被访问过的邻接结点w1,w2,...,然后依次访问w1,w2,.....未被访问过的邻接顶点,再从这些访问过的顶点出发,访问它们所有未被访问过的邻接顶点,直至所有顶点都被访问为止。若此时图中尚有顶点未被访问,则另选图中一个未被访问的顶点作为始点,重复上述过程,直至图中所有顶点都被访问到为止。

广度优先搜索是一种分层的查找过程,每向前走一步可能访问一批顶点,不想深度优先搜索那样有回退的情况,因此它不是一个递归的算法。为了实现逐层的访问,算法必须借助一个辅助队列,以记忆正在访问的顶点的下一层顶点。

#define true 1
#define false 0
int visited[MaxVertexnum];
void BFS(MGraph G, int v) {
    visit(v);//从顶点v出发
    visited[v] = true;//对v做已访问标记
    EnQueue(Q,v);//顶点v入队列Q
    while (!IsEmpty(Q)) {
        DeQueue(Q, v);//顶点v出队列
        for (w = FirstNeighbor(G, v); w >= 0; w = NextNeightbor(G, v, w));//检查v所有邻接点
        if (!visited[w]) {//w为v的未被访问过的邻接顶点
            visit(w);//访问顶点w
            visited[w] = true;//对w做已访问标记
            EnQueue(Q, w);//顶点w入队列
        }
    }
}
void BFStraverse(MGraph G) {//对图进行广度优先遍历
    for (int i = 0; i < G.vexnum; ++i)
        visited[i]= false;//访问标记数组初始化
    InitQueue(&Q);//初始化辅助队列
    for (int i = 0; i < G.vexnum; ++i)//从0号顶点开始遍历
        if (!visited[i])//对每个连通分量调用一次BfS
            BFS(G, i);//对于未被访问的顶点,以该顶点为始点,调用BFS
}

注:

(1)对于无向图,调用BFS函数的次数=连通分量的个数

(2)广度优先搜索遍历算法是二叉树的层次遍历算法的扩展

(3)BFS需借助一个队列,最坏情况下,空间复杂度为O(|V|)

(4)邻接表存储,访问v个顶点,需要O(|V|)的时间,查找各个顶点的邻接点共需O(|E|)的时间,时间复杂度为O(|V|+|E|);邻接矩阵存储:访问|V|个顶点需要O(|V|)的时间,查找每个顶点的邻接点都需要O(|V|)的时间,总共有|v|个顶点,时间复杂度为O(|V|)+O(|V|2)=O(|V|2)

2.深度优先遍历:首先访问图中某一起始顶点v,然后由v出发,访问与v邻接且未被访问的任一顶点w1,再访问与w1邻接且未被访问的任一顶点w2......重复上述过程。当v不能再向下访问时,依次退回到最近被访问的顶点,若它还有邻接点未被访问过,则从该点开始继续上述搜索过程,直至图中左右顶点均被访问过为止。

int visited[MaxVertexnum];
void DFS(MGraph G, int v) {
    visit(v);//访问顶点v
    visited[v] = true;//设已访问标记
    for (w = FirstNeighbor(G, v); w >= 0; w = NextNeightbor(G, v, w))
        if (!visited[w]) {//w为v的尚未访问的邻接顶点
            DFS(G, w);
        }
}
void DFSTraverse(MGraph G) {
    for (int v = 0; v < G.vexnum; ++v)
        visited[v] = false;//访问标记数组初始化
    for (int v = 0; v < G.vexnum; ++v)//从v=0开始遍历
        if (!visited[v])
            DFS(G, v);
}

注:

(1)深度优先搜索类似于树的先序遍历,这种搜索算法所遵循的搜索策略是尽可能“深”地搜索一个图。

(2)DFS算法是一个递归算法,需要借助一个递归工作栈,空间复杂度为O(|V|)

(3)邻接矩阵表示时,查找每个顶点的邻接点所需要的时间为O(|V|),故总的时间复杂度为O(|V|2);

         以邻接表表示时,查找顶点的邻接点是O(|E|),访问顶点所需的时间为O(|V|)总的时间复杂度为O(|V|+|E|)

3.无向图G是否为一棵树:若是树,则该树为有n-1条边的连通图

采用深度优先搜索遍历算法,在遍历过程中统计访问到的结点数和边数,若一次就能遍历到n个顶点和n-1条边,则可判定是一棵树

int isTree(MGraph G) {
    for (int i = 0; i < G.vexnum; i++)
        visited[i] = false;//访问标记数组初始化
    int Vnum=0, Enum=0;//记录顶点数和边数
    DFS1(G, 1, Vnum, Enum, visited);
    if (Vnum == G.vexnum && Enum == 2 * (G.vexnum - 1))
        return true;//符合树的条件
    else
        return false; //不符合树的条件

}
void DFS1(MGraph G, int v, int Vnum, int Enum, int visited[]) {
    visited[v] = true;//作访问标记
    Vnum++;//顶点计数
    int w = FirstNeighbor(G, v);//取v的第一个邻接点
    while (w != -1) {//当邻接点存在
        Enum++;//边存在,边计数
        if (!visited[w])//当该邻接点未被访问过
            DFS1(G, w, Vnum, Enum, visited);
        w = NextNeightbor(G, v, w);
    }
}

4.图的深度优先搜索非递归算法:借助栈实现

(1)先将v0入栈(2)只要栈不空,则栈顶元素出栈,如果未被访问,则访问并置访问标志,然后将该结点所有未被访问过的邻接点入栈

void DFSfd(MGraph G, int v) {
    int w,k;
    InitStack(&S);
    for (int i = 0; i < G.vexnum; i++)
        visited[i];
    Push(S, v);
    visited[v] = true;
    while (!IsEmpty(S)) {
        k = Pop(S);
        visit(k);
        for (w = FirstNeighbor(G, k); w >= 0; w = NextNeightbor(G, k, w));
        if (!visited[w]) {
            Push(S, w);
            visited[w] = true;
        }
    }
}

 

posted @ 2021-10-17 22:26  #Lorraine#  阅读(113)  评论(0)    收藏  举报