写代码是一种艺术,甚于蒙娜丽莎的微笑!

不拼搏,枉少年!

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

图的遍历

图的遍历主要有两种方法:广度遍历和深度遍历,它们也叫做广度优先搜索和深度优先搜索。由遍历所经过的路径可以形成一个树,分别叫做广度优先搜索生成树和深度优先搜索生成树。

深度优先搜索

深度优先搜索如树的先根遍历类似,如下图1:

图1

其搜索过程,如图2所示:

图1

假设从顶点v1 出发进行搜索,在访问了顶点v1 之后,选择邻接点v2。因为v2 未曾访问,则从v2 出发进行搜索。依次类推,接着从v4 、v8 、v5 出发进行搜索。在访问了v5 之后,由于v5 的邻接点都已被访问,则搜索回到v8。由于同样的理由,搜索继续回到v4,v2 直至v1,此时由于v1 的另一个邻接点未被访问,则搜索又从v1 到v3,再继续进行下去由此,得到的顶点访问序列为:

从上面的搜索过程,我们可以看出深度优先搜索是一个递归的过程。

邻接矩阵存储图的深度优先搜索算法

//邻接矩阵
struct MGraph{
    int edges[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
    int n;
    int e;
};

void MDfs(const MGraph& mGraph,int v0)
{
    cout << v0 << ' ';
    visited[v0] = true;
    for (int i = 1; i <= mGraph.n; i++)
    {
        if (visited[i] == false && mGraph.edges[v0][i] != -1)
        {
            MDfs(mGraph, i);
        }
    }
}

int main()
{
      int n, m;
    int v1, v2,c;
    MGraph mG;
    cin >> n >> m;
    //初始化
    for (int i = 1; i < MAX_VERTEX_NUM; i++)
        visited[i] = false;
    for (int i = 1; i < MAX_VERTEX_NUM; i++)
    for (int j = 1; j < MAX_VERTEX_NUM; j++)
            mG.edges[i][j] = -1;
    mG.n = n;
    mG.e = m;
    for (int i = 1; i <= m; i++)
    {
        cin >> v1 >> v2 >> c;
        mG.edges[v1][v2] = mG.edges[v2][v1] = c;
    }

    MDfs(mG, 1);  
}
View Code

测试

邻接链表存储图的优先搜索算法

bool visited[MAX_VERTEX_NUM];

//邻接链表
struct ArcNode{
    int vertex;
    int len;
    int cost;
    ArcNode *nextarc;
};

typedef struct VertexNode{
    ArcNode *firstArc;
    int size;
}VNode[100];
//分配边结点空间
void allocateArcNode(ArcNode **arcNode)
{
    *arcNode = (ArcNode *)malloc(sizeof(ArcNode));
    (*arcNode)->nextarc = NULL;
}
//头插法
void insertArcNode(LGraph &lGraph, ArcNode *arcNode,int v)
{
    arcNode->nextarc = lGraph.nodes[v].firstArc;
    lGraph.nodes[v].firstArc = arcNode;
}

//图的初始化
void initeGraph(LGraph &lGraph)
{
    for (int i = 0; i < MAX_VERTEX_NUM; i++)
        lGraph.nodes[i].firstArc = NULL;
    lGraph.n = 0;
    lGraph.e = 0;
}

void LDfs(const LGraph& lGraph, int v0)
{
    cout << v0 << ' ';
    visited[v0] = true;
    ArcNode* arcptr = lGraph.nodes[v0].firstArc;
    while (arcptr != NULL)
    {
        int v = arcptr->vertex;
        if (!visited[v])
            LDfs(lGraph, v);
        arcptr = arcptr->nextarc;
    }
}

void freeLGraph(LGraph &lGraph)
{
    int size = lGraph.n;
    ArcNode *arcPtr = NULL;
    for (int i = 1; i <= size; i++)
    {
        arcPtr = lGraph.nodes[i].firstArc;
        while (arcPtr != NULL)
        {
            ArcNode *t = arcPtr;
            arcPtr = arcPtr->nextarc;
            delete t;
        }
    }
}

void main()
{
    int n, m;
    int v1, v2,c;


    LGraph lG;
    initeGraph(lG);
    cin >> n >> m;
    lG.n = n;
    lG.e = m;
    for (int i = 1; i < MAX_VERTEX_NUM; i++)
        visited[i] = false;
    ArcNode *arc1 = NULL;
    ArcNode *arc2 = NULL;

    for (int i = 1; i <= m; i++)
    {
        cin >> v1 >> v2 >> c;
        allocateArcNode(&arc1);
        allocateArcNode(&arc2);
        arc1->cost = c;
        arc1->vertex = v2;
        insertArcNode(lG, arc1, v1);

        arc2->cost = c;
        arc2->vertex = v1;
        insertArcNode(lG, arc2, v2);
    }
    LDfs(lG, 1);
    freeLGraph(lG);
}
View Code

测试

算法分析

分析上述算法,在遍历时,对图中每个顶点至多调用一次DFS 函数,因为一旦某个顶点被标志成已被访问,就不再从它出发进行搜索。因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程。其耗费的时间则取决于所采用的存储结构。当用二维数组表示邻接矩阵图的存储结构时,查找每个顶点的邻接点所需时间为O(n2) ,其中n 为图中顶点数。而当以邻接表作图的存储结构时,找邻接点所需时间为O(e),其中e 为无向图中边的数或有向图中弧的数。由此,当以邻接表作存储结构时,深度优先搜索遍历图的时间复杂度为O(n+e) 。

广度优先搜索

广度优先搜索类似与树的层次遍历,如上图1显示的无向图。广度搜索过程如下图3所示:

图3

首先访问v1 和v1 的邻接点v2 和v3,然后依次访问v2 的邻接点v4 和v5 及v3 的邻接点v6 和v7,最后访问v4 的邻接点v8。由于这些顶点的邻接点均已被访问,并且图中所有顶点都被访问,由些完成了图的遍历。得到的顶点访问序列为:v1→v2 →v3 →v4→ v5→ v6→ v7 →v8。

与深度优先搜索类似,在遍历的过程中也需要一个访问标志数组。并且,为了顺次访问路径长度为2、3、…的顶点,需附设队列以存储已被访问的路径长度为1、2、… 的顶点。

同样的给出两种存储结构的广度优先搜索算法。

邻接矩阵存储图的广度优先搜索算法

bool visited[MAX_VERTEX_NUM];
struct Quenue{
    int vertexs[MAX_VERTEX_NUM];
    int front;
    int rear;
};

Quenue quenue;
quenue.front = quenue.rear = 0;
void MBfs(const MGraph& mGrpah, int v0)
{
    quenue.vertexs[quenue.rear++] = v0;
    visited[v0] = true;
    while (quenue.front != quenue.rear)
    {
        int v = quenue.vertexs[quenue.front];
        cout << v << ' ';
        quenue.front++;
        for (int i = 1; i <= mGrpah.n; i++)
        {
            if (visited[i] == false && mGrpah.edges[v][i] != -1)
            {
                quenue.vertexs[quenue.rear++] = i;
                visited[i] = true;

            }
        }
    }

}
View Code

邻接表存储图的广度优先搜索算法

void LBfs(const LGraph& lGraph, int v0)
{

    quenue.vertexs[quenue.rear++] = v0;
    visited[v0] = true;
    while (quenue.front != quenue.rear)
    {
        int v = quenue.vertexs[quenue.front];
        cout << v << ' ';
        quenue.front++;
        ArcNode*arcPtr = lGraph.nodes[v0].firstArc;
        while (arcPtr != NULL)
        {
            if (visited[arcPtr->vertex] == false)
            {
                quenue.vertexs[quenue.rear++] = arcPtr->vertex;
                visited[arcPtr->vertex] = true;

            }
        }
    }

}
View Code

两种算法的测试输出如下

算法分析

每个顶点至多进一次队列。遍历图的过程实质是通过边或弧找邻接点的过程,因此广度优先搜索遍历图的时间复杂度和深度优先搜索遍历相同,两者不同之处仅仅在于对顶点访问的顺序不同。

posted on 2015-12-08 22:11  RunningSnail  阅读(2561)  评论(0)    收藏  举报