【數據結構】圖的遍歷


What is 遍歷

訪問圖中的每一個元素一次,僅僅一次。訪問,可以是輸出打印,改寫啊,這樣的,根據ADT使用者的回調函數而定。

圖的遍歷常用的有2種:深度優先搜索,廣度優先搜索。

 

 

 

深度優先搜索(Deepth First Search . DFS)

 深度優先搜索和樹的先序遍歷道理是一樣的。

需要考慮以下幾點:

1、為了避免重復訪問,我們需要用一個  bool類型的訪問標記數組(visited flag  array),來標記頂點是否已經被訪問。

2、要考慮到 非連通圖中的 “孤島”,他們是孤立的子圖,不能通過路徑可達,但也要遍歷到。

3、和樹的遍歷一樣,有2種方法:遞歸和非遞歸。

 

說到這里,我又要吐槽書了,對!就是嚴蔚敏的數據結構。將visited訪問標記數組定義為全局變量, 實在看不下去了。

我的改進是,用“殼子”函數 ArrayGraph_DFS來創建局部訪問標記數組,而真正完成遍歷的是ArrayGraph_DFS_traverse,

將visited作為ArrayGraph_DFS_traverse的參數傳遞,這樣遞歸的各層

函數就能共享這個數組了。細心的同學發現我將ArrayGraph_DFS_traverse聲明為static,其作用是隱藏ArrayGraph_DFS_traverse於此源文件。

 

可以將訪問操作定義為一個回調函數,讓API使用者決定如何訪問。但是我沒有這樣做,而是硬編碼,用printf打印作為訪問操作。想寫的簡單些。

 

深度優先搜索的遞歸實現:

#include<stdio.h>
#define MAX_VERTEX  4

typedef char DataType;                 //圖中元素的目標數據類型 


typedef struct    
{                  
    DataType vertexArr[MAX_VERTEX];        //頂點元素數組 

    int edgeArr[MAX_VERTEX][MAX_VERTEX];   //邊矩陣二維數組 
    

}ArrayGraph;



void ArrayGraph_init(ArrayGraph *pGraph);
void ArrayGraph_create(ArrayGraph *pGraph);
void ArrayGraph_DFS(ArrayGraph * pGraph,int n);
static void  ArrayGraph_DFS_traverse(ArrayGraph * pGraph,int n,bool*visited);

int main()
{
    ArrayGraph g;
    ArrayGraph_init(&g);       //初始化圖 
    ArrayGraph_create(&g);     //創建圖 
    ArrayGraph_DFS(&g,3);       //遍歷 ,從索引為3的頂點開始
return 0;
}



//初始化為一個無圈圖 ,也就是邊矩陣中,主對角線元素都是0 
void ArrayGraph_init(ArrayGraph *pGraph)
{
    
    for (int i = 0; i < MAX_VERTEX; i++)

        pGraph->edgeArr[i][i] = 0;

}


void ArrayGraph_create(ArrayGraph *pGraph)
{
    

    for (int i = 0; i < MAX_VERTEX; ++i)    //填充頂點數組,也就是輸入頂點元素 
    {
        printf("輸入第%d個頂點值\n",i+1);
        
        scanf(" %c",&(pGraph->vertexArr[i])); 

    }

    for (int j = 0; j <MAX_VERTEX; ++j)   //填充邊關系 
    {
        for (int i = j+1; i < MAX_VERTEX; ++i)
        {
            
            printf("若元素%c和%c有邊,則輸入1,否則輸入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]);
            
            scanf("%d",&( pGraph->edgeArr[j][i]));
            pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i];     //對稱 
        }
    }

}



static void  ArrayGraph_DFS_traverse(ArrayGraph * pGraph,int n,bool*visited)
{
    
    printf("%c\t",pGraph->vertexArr[n]);

    visited[n] = true;

    for(int i=0;i<MAX_VERTEX;++i)    //以當前已訪問的頂點為中心, 在其他所有的頂點中尋找
    {
        if(pGraph->edgeArr[n][i]!=0 && visited[i]==false)  //如果和當前頂點有邊,且他們沒有被訪問過。則訪問他們。
        {
            
            ArrayGraph_DFS_traverse(pGraph,i,visited);
        }
    }
    
    
    for(int i=0;i<MAX_VERTEX;++i)  //對圖中可能出現的“孤島”做一次清查 
      if(visited[i]==false)    //如果有孤島存在,則用同樣的方法,遍歷他們。 
      {
           ArrayGraph_DFS_traverse(pGraph,i,visited);
      }

} 

void ArrayGraph_DFS(ArrayGraph * pGraph,int n)
{

    bool visited[MAX_VERTEX];      //訪問標記數組,
    for(int i=0;i<MAX_VERTEX;++i)   //局部變量初始化
       visited[i] = false;         
      
      ArrayGraph_DFS_traverse(pGraph,n,visited);

}

 

 

 

看見遞歸,全局變量,老司機都會鄒起眉頭。原因不多說啊。下面是非遞歸實現。

非遞歸實現強調一個回退動作,當遍歷到盡頭時,需要退回來,嘗試另一條支路,所以,在遞進到下一層之前,我們需要保存此刻的頂點的索引到棧中,為回退做准備。

這個和狗狗在路邊尿尿做標記很類似  :)

 

對於非遞歸實現,附上一張gif圖,便於理解非遞歸的方法  前進和回退 過程。這里畫成了樹結構,因為我覺得畫成圖了,看起來就很費勁了。樹也是一種圖,所以不影響的,道理是一樣的。

50幀啊,一幀一幀的畫,oh  my  god    (;′⌒` )    播放速度可能有點快,可以下載了用看圖王 看。

 

深度優先搜索的非遞歸實現:

#include<stdio.h>
#include<stack>

using std::stack;
#define MAX_VERTEX  4

typedef char DataType;                 //圖中元素的目標數據類型 


typedef struct    
{                  
    DataType vertexArr[MAX_VERTEX];        //頂點元素數組 

    int edgeArr[MAX_VERTEX][MAX_VERTEX];   //邊矩陣二維數組 
    

}ArrayGraph;


void ArrayGraph_init(ArrayGraph *pGraph);
void ArrayGraph_create(ArrayGraph *pGraph);
void ArrayGraph_DFS(ArrayGraph * pGraph,int n);


int main()
{
    ArrayGraph g;
    ArrayGraph_init(&g);       //初始化圖 
    ArrayGraph_create(&g);     //創建圖 
    ArrayGraph_DFS(&g,3);       //遍歷 



    return 0;
}



//初始化為一個無圈圖 ,也就是邊矩陣中,主對角線元素都是0 
void ArrayGraph_init(ArrayGraph *pGraph)
{
    
    for (int i = 0; i < MAX_VERTEX; i++)

        pGraph->edgeArr[i][i] = 0;

}


void ArrayGraph_create(ArrayGraph *pGraph)
{
    

    for (int i = 0; i < MAX_VERTEX; ++i)    //填充頂點數組,也就是輸入頂點元素 
    {
        printf("輸入第%d個頂點值\n",i+1);
        
        scanf(" %c",&(pGraph->vertexArr[i])); 

    }

    for (int j = 0; j <MAX_VERTEX; ++j)   //填充邊關系 
    {
        for (int i = j+1; i < MAX_VERTEX; ++i)
        {
            
            printf("若元素%c和%c有邊,則輸入1,否則輸入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]);
            
            scanf("%d",&( pGraph->edgeArr[j][i]));
            pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i];     //對稱 
        }
    }


}



void ArrayGraph_DFS(ArrayGraph * pGraph,int n)
{
    
    bool visited[MAX_VERTEX];       //局部訪問標記數組 
    for(int i=0;i<MAX_VERTEX;++i)   //局部變量需要手動初始化哦! 
       visited[i] = false;          //局部變量是一次性數據,調用完,就回收。 
    
    
    bool  complete  = false;       //是否真正遍歷完成?,主要用來對付非連通圖
    
    stack<int> backStack;          //回退記錄棧 

    int peekVertexIndex ;         //回退棧的棧頂存儲的頂點的索引 
    int i;

    do{
    

        printf("%c\t",pGraph->vertexArr[n]);    //訪問當前子圖的源點,first blood!!!
        visited[n] = true;
        backStack.push(n);
    
        while(!backStack.empty())     //當回退棧為空時,說明已經無路可退,頂點遍歷完了 
        {
            peekVertexIndex = backStack.top();  //從先前訪問過的頂點開始
    
            for(i=0;i<MAX_VERTEX;++i)
            {
                                              //尋找他的一個未被訪問的鄰接點
                if(pGraph->edgeArr[peekVertexIndex][i]!=0 && visited[i]== false)   
                {
                    printf("%c\t",pGraph->vertexArr[i]);   //一旦找到一條可行的支路連接的新頂點,則訪問它 
    
                    visited[i] = true;
                    backStack.push(i);                     //訪問過后,將他入棧,作為新的棧頂元素。 
                    break;              
    
                }
                
            }
    
            if(i==MAX_VERTEX)       //一個前進的路徑也沒找到,說明到了某條支路的盡頭,或者此頂點的鄰結點都被訪問過了 
            {
                backStack.pop();    //回退,嘗試先前訪問過的頂點的另一條支路 
            }
    
        
    
         } //end of while
         
         complete = true;    //當遍歷完一個連通子圖后,假定完成了所有的遍歷 
         
         for(i = 0;i<MAX_VERTEX;++i) //對所有的頂點清查, 
         {
             if(visited[i]==false)    //發現還有頂點沒訪問到,出現了孤島 
             {
                 complete = false;    //將完成標記 置為false 
                 n = i;               //記下這個孤島的源頂點點索引 
                 break;
            }  
             
         }
     
    }while(!complete);        //沒有真正完成所有的遍歷,則再來一次 
}

 

 

 記得看過一本書上說:過多的注釋是對自己代碼的不自信,我覺得這話有道理。但是注釋就是代碼的筆記,多一點總比少一點好吧。

 

 

 

 

廣度優先搜索(Breadth First Search . BFS)

廣度優先搜索和數的層遍歷也是一個道理。

它對有向圖和無向圖都適用。

它同樣需要訪問標記數據,同樣需要考慮非連通圖的問題。

 

同樣我也用了gif圖來宏觀的描述這個過程。(我懷疑我是個圖形極客  - - ! )

可以發現,當發現一個可以訪問的頂點后,廣度優先搜索不會像深度優先搜索那么立刻遞進到下一層,而是再去找同層的“兄弟”頂點,直到同層再也找不到更多的可訪問的“兄弟”了,才進入下一層。這個差異在代碼中就是 break的有無體現出來的。

從這點差異我們也可以體會到,為什么一個叫深度優先,一個叫廣度優先。深度優先搜索是先縱向伸展開,而廣度優先則是先橫向拉伸。

 

 同一層的頂點是用隊列來存儲的。我用了C++ STL中的queue模版類。

 

 

 

 

 

 

 

 

代碼:

#include<stdio.h>
#include<queue>

using std::queue;
#define MAX_VERTEX  4

typedef char DataType;                 //圖中元素的目標數據類型 


typedef struct    
{                  
    DataType vertexArr[MAX_VERTEX];        //頂點元素數組 

    int edgeArr[MAX_VERTEX][MAX_VERTEX];   //邊矩陣二維數組 
    

}ArrayGraph;


void ArrayGraph_init(ArrayGraph *pGraph);
void ArrayGraph_create(ArrayGraph *pGraph);
void ArrayGraph_BFS(ArrayGraph * pGraph,int n);


int main()
{
    ArrayGraph g;
    ArrayGraph_init(&g);       //初始化圖 
    ArrayGraph_create(&g);     //創建圖 
    ArrayGraph_BFS(&g,0);       //遍歷 



    return 0;
}



//初始化為一個無圈圖 ,也就是邊矩陣中,主對角線元素都是0 
void ArrayGraph_init(ArrayGraph *pGraph)
{
    
    for (int i = 0; i < MAX_VERTEX; i++)

        pGraph->edgeArr[i][i] = 0;

}


void ArrayGraph_create(ArrayGraph *pGraph)
{
    

    for (int i = 0; i < MAX_VERTEX; ++i)    //填充頂點數組,也就是輸入頂點元素 
    {
        printf("輸入第%d個頂點值\n",i+1);
        
        scanf(" %c",&(pGraph->vertexArr[i])); 

    }

    for (int j = 0; j <MAX_VERTEX; ++j)   //填充邊關系 
    {
        for (int i = j+1; i < MAX_VERTEX; ++i)
        {
            
            printf("若元素%c和%c有邊,則輸入1,否則輸入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]);
            
            scanf("%d",&( pGraph->edgeArr[j][i]));
            pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i];     //對稱 
        }
    }


}



void ArrayGraph_BFS(ArrayGraph * pGraph,int n)
{
    
    bool visited[MAX_VERTEX];       
    for(int i=0;i<MAX_VERTEX;++i)   
       visited[i] = false;         
    
    
    bool  complete  = false;       
    
    queue<int> layerQueue;        //層隊列   

    int frontVertexIndex ;         
    int i;

    do{
    

        printf("%c\t",pGraph->vertexArr[n]);    
        visited[n] = true;
        layerQueue.push(n);
    
        while(!layerQueue.empty())     
        {
            frontVertexIndex = layerQueue.front();  //獲取隊列 隊首頂點 
            layerQueue.pop();
            
            for(i=0;i<MAX_VERTEX;++i)
            {
                                              //將 隊首頂點 的鄰結點全部訪問,並全部入棧 
                if(pGraph->edgeArr[frontVertexIndex][i]!=0 && visited[i]== false)   
                {
                    printf("%c\t",pGraph->vertexArr[i]);   
    
                    visited[i] = true;
                    layerQueue.push(i);               
                                                       
    
                }
                
            }
    
          
         } //end of while
         
         complete = true;    //當遍歷完一個連通子圖后,假定完成了所有的遍歷 
         
         for(i = 0;i<MAX_VERTEX;++i) //對所有的頂點清查, 
         {
             if(visited[i]==false)    //發現還有頂點沒訪問到,出現了孤島 
             {
                 complete = false;    //將完成標記 置為false 
                 n = i;               //記下這個孤島的源頂點點索引 
                 break;
            }  
             
         }
     
    }while(!complete);        //沒有真正完成所有的遍歷,則再來一次 
}

 

 

 

下一篇:圖的最小生成樹

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM