图:存储结构、深度优先遍历以及广度优先遍历

图:记录关系、联系,由数个点Vertex、数条边Edge组成。
同构 Isomorphism 英[ˌaɪsəʊ’mɔ:fɪzəm] :两张图的连接方式一样。

图的存储结构:

阵列

记录所有的点与所有的边。直观、节省空间、但不适于计算
《图:存储结构、深度优先遍历以及广度优先遍历》
如图,记录(0,1) (0,3) (0,4)

邻接矩阵 adjacency matrix

《图:存储结构、深度优先遍历以及广度优先遍历》

《图:存储结构、深度优先遍历以及广度优先遍历》

无向图的边数组是个对称矩阵(aij=aji)。
顶点vi的度:vi所在行的元素之和

有向图讲究入度和出度。顶点vi的入度是vi所在列的元素之和,出度是所在行的元素之和

矩阵的值也可以是边的权重。

typedef struct{
    VertexType vexes[MAX]; //顶点表
    EdgeType arc[MAX][MAX]; //邻接矩阵
    int numVertexes,numEdges; //顶点数,边数
} MGraph;

邻接表 adjacency lists

把一張圖上的點依序標示編號。每一個點,後方串連所有相鄰的點
《图:存储结构、深度优先遍历以及广度优先遍历》

typedef char VertexType;   //顶点类型
typedef int EdgeType;  //边上的权值类型

typedef struct EdgeNode{    //边表结点
    int adjvex;
    EdgeType weight;
    struct EdgeNode *next;
} EdgeNode; 

typedef struct VertexNode{    //顶点表结点
    VertexType data;
    EdgeNode *firstedge;
} VertexNode, AdjList[MAX];

typedef struct{
    AdjList adjList;
    int numVertexes,numEdges;
} GraphAdjList;

图的遍历:

两种遍历算法:深度优先遍历 Depth First Search,广度优先遍历 Breadth First Search,。

深度优先遍历

从图中某个顶点出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直到图中所有和v有路径相同的顶点都被访问到。

以下图为例。从A点出发,做上表示走过的记号。我们给自己定一个原则,在没有碰到重复顶点的情况下,始终往右手边走。于是走向B,接着C、D、E、F。继续往右手边走,发现A点已经访问过了,此时退回F点,走向从右数的第二个通道到达顶点G,G有三条通道,B和D都走过,所以走向H,H的两条通道D和E都走过。
此时是否已经遍历了所有顶点呢?没有。所以按原路返回,回到G,没有未走过的通道,回到F,没有未走过的通道,回到E,有一条通往H,验证后也是走过的。回到D,有三条道未走过,一条条来。走到I,这是个未标记过的顶点,标记下来。继续返回直到返回顶点A,确认完成遍历任务,找到了所有的9个顶点。

《图:存储结构、深度优先遍历以及广度优先遍历》

《图:存储结构、深度优先遍历以及广度优先遍历》

将这个过程化为上图。这其实就是一个递归的过程。这里以连通图为例,如果是非连通图,需要对它的连通分量分别进行深度优先遍历。

图的存储方式是邻接矩阵:

typedef int Boolean;
Boolean visited[MAX];
void DFS(MGraph G,int i){
    int j;
    visited[i]=TRUE;
    printf("%c ",G.vexs[i]); //打印顶点,也可以进行其他操作
    for(j=0;j<G.numVertexes;j++){
        if(G.arc[i][j]==1 && !visited[j]) DFS(G,j); //对未访问的邻接顶点递归调用
    }
}

void DFSTraverse(MGraph G){
    int i;
    for(i=0;i<G.numVertexes;i++){
        visited[i]=FALSE;   //初始化所有顶点状态为未访问
    }
    for(i=0;i<G.numVertexes;i++){
        if(!visited[i]){   //对未访问过的顶点调用DFS,如果是连通图,只会执行一次
            DFS(G,i);
        }
    }
}

图的存储方式是邻接表

void DFS(GraphAdjList GL, int i){
    EdgeNode *p;
    visited[i]=TRUE;
    printf("%c ",GL->adjList[i].data);
    p=GL->adjList[i].firstedge;
    while(p!=NULL){
        if(!visited[p->adjvex]){
            DFS(GL,p->adjvex);
        }
        p=p->next;
    }
}
void DFSTraverse(GraphAdjList GL){
    int i;
    for(i=0;i<GL->numVertexes;i++){
        visited[i]=FALSE;   //初始化所有顶点状态为未访问
    }
    for(i=0;i<GL->numVertexes;i++){
        if(!visited[i]){   //对未访问过的顶点调用DFS,如果是连通图,只会执行一次
            DFS(G,i);
        }
    }
}

对于n个顶点e条边的图来说,邻接矩阵由于是二维数组,要查找每个顶点的邻接点需要访问矩阵中的所有元素,因此都需要O(n2)的时间。
而邻接表做存储结构时,找邻接点所需时间取决于顶点和边的数量,所以是O(n+e),显然对于点多边少的稀疏图,邻接表结构使得算法在时间效率上大大提高。

广度优先遍历

(依照編號順序)不斷找出尚未遍歷的點當作起點,進行下述行為:
 一、把起點放入queue。
 二、重複下述兩步驟,直到queue裡面沒有東西為止:
  甲、從queue當中取出一點。
  乙、找出跟此點相鄰的點,並且尚未遍歷的點,
    通通(依照編號順序)放入queue。

只觀察離開 queue 的時刻,可以發現 BFS 優先走遍距離起點最近之處,優先讓 BFS Tree 變得寬廣,因而得名 Breadth-first Search 。這個遍歷順序能夠解決許多圖論問題!

时间复杂度:使用的資料結構為 adjacency matrix 的話,可以以 O(V^2) 時間遍歷整張圖;使用 adjacency lists 的話,可以以 O(V+E) 時間遍歷整張圖。 V 是圖上的點數, E 是圖上的邊數。

bool adj[9][9]; // 一張圖,資料結構為adjacency matrix。
bool visit[9];  // 記錄圖上的點是否遍歷過,有遍歷過為true。

void BFS()
{
    // 建立一個queue。
    queue<int> q;
    // 全部的點都初始化為尚未遍歷
    for (int i=0; i<9; i++)
        visit[i] = false;
    for (int k=0; k<9; k++)
        if (!visit[k])
        {
            一、把起點放入queue。
            q.push(k);
            visit[k] = true;
            // 二、重複下述兩點,直到queue裡面沒有東西為止:
            while (!q.empty())
            {
                // 甲、從queue當中取出一點。
                int i = q.front(); q.pop();
                // 乙、找出跟此點相鄰的點,並且尚未遍歷的點,
                // 依照編號順序通通放入queue。
                for (int j=0; j<9; j++)
                    if (adj[i][j] && !visit[j])
                    {
                        q.push(j);
                        visit[j] = true;
                    }
            }
        }
}
    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/sunlilan/article/details/69829195
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞