【數據結構】圖的存儲和代碼實現


鄰接矩陣存儲法

回顧:圖G = <V,E>

鄰接矩陣存儲法的主要思想如下

1、用一個數組存儲所有頂點,代表集合V中的元素

2、用一個二維數組存邊,代表集合E中的元素

 

 

無向圖的鄰接矩陣存儲

 我們通過具體的例子來講解,以下圖為例

 

 

                          

 

 

1、邊使用矩陣來構建模型,這使得每一個頂點和其它頂點之間都有邊的有無 的 表示的機會。若有邊,則他們交點 為1 ,否則為0。

2、無向圖的邊的矩陣一定是一個對稱矩陣,因為無向圖只關心邊是否存在,而不關心方向,V0和V1有邊,那么V1和V0也有邊。

3、因為這里不研究有圈圖,所以主對角線都是0

 先牛刀小試 一下,構建數據類型,寫出API。     :)

 

代碼都很好理解,唯一 一個技巧就是create函數中的二重for循環,利用了選擇排序中的思想,避免了邊信息的重復填充,也就是例如,輸入V0和V1邊的關系后,就不必輸入V1和V0的關系了。

在上一篇文章中提到:

  頂點數為n的圖,最多有條邊,這里的二重for循環的時間復雜度恰好也是O() = O(n2)

這個技巧后面都會用到。

 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];     //對稱 
        }
    }

 

 

代碼:

#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_show(ArrayGraph *pGraph);



int main()
{
    ArrayGraph g;
    ArrayGraph_init(&g);       //初始化圖 
    ArrayGraph_create(&g);     //創建圖 
    ArrayGraph_show(&g);       //打印圖 



    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_show(ArrayGraph *pGraph)
{


    printf("\n\n頂點元素如下\n");
    for (int i = 0; i < MAX_VERTEX; ++i)
    {
        
        printf("%-5c", pGraph->vertexArr[i]);
    }
    printf("\n\n");


    
    puts("邊矩陣如下\n\n"); 
    printf("%-2c",' ');
    for(int i=0;i<MAX_VERTEX;++i)
      printf("%-5c",pGraph->vertexArr[i]);
    putchar('\n');  
    
    
    
    for (int j = 0; j <MAX_VERTEX; ++j)
    {
        printf("%-2c",pGraph->vertexArr[j]);
        for (int i = 0; i < MAX_VERTEX; ++i)
        {
            printf("%-5d",pGraph->edgeArr[i][j]);

        }
        putchar('\n');
    }
    
    printf("\n\n");
}
View Code

 

 

有向圖的鄰接矩陣存儲

 

 

使用鄰接矩陣呢存儲時,有向圖和無向圖的區別在與 邊和弧矩陣的差別。因為弧是有方向的,所以我們 以對角線為界,將矩陣划分為2個區域:

左下區域表示出弧標記區域,坐上區域代表入弧標記區域(當然也可以交換,看個人習慣)

如  若代表弧的矩陣為arcArr

   arcArr[V2][V3] 為1,且在出弧標記區域,則說明 V3<------V2

   arcArr[V3][V2] 為0,且在入弧標記區域,則說明 V2---\--->V3

 

代碼:

#include<stdio.h>

#define  MAX_VERTEX  4

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


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

    int arcArr[MAX_VERTEX][MAX_VERTEX];   //弧矩陣二維數組 
    

}ArrayGraph;

void ArrayGraph_init(ArrayGraph *pGraph);
void ArrayGraph_create(ArrayGraph *pGraph);
void ArrayGraph_show(ArrayGraph *pGraph);



int main()
{
    ArrayGraph g;
    ArrayGraph_init(&g);
    ArrayGraph_create(&g);
    ArrayGraph_show(&g);



    return 0;
}



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

        pGraph->arcArr[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[i],pGraph->vertexArr[j]);
            scanf("%d",&( pGraph->arcArr[j][i]));
            
            printf("若元素%c有指向%c的弧,則輸入1,否則輸入0\t",pGraph->vertexArr[j],pGraph->vertexArr[i]);
            scanf("%d",&( pGraph->arcArr[i][j]));
        }
    }






}


void ArrayGraph_show(ArrayGraph *pGraph)
{


    printf("\n\n頂點元素如下\n");
    for (int i = 0; i < MAX_VERTEX; ++i)
    {
        
        printf("%-5c", pGraph->vertexArr[i]);
    }
    printf("\n\n");


    
    puts("弧矩陣如下\n\n"); 
    printf("%-2c",' ');
    for(int i=0;i<MAX_VERTEX;++i)
      printf("%-5c",pGraph->vertexArr[i]);
    putchar('\n');  
    
    
    
    for (int j = 0; j <MAX_VERTEX; ++j)
    {
        printf("%-2c",pGraph->vertexArr[j]);
        for (int i = 0; i < MAX_VERTEX; ++i)
        {
            printf("%-5d",pGraph->arcArr[i][j]);

        }
        putchar('\n');
    }
    putchar('\n');  
}
View Code

 

 

無向網的鄰接矩陣存儲

無向網的邊是有權值的,這個值可以是任何一個合法的值,什么樣的值是合法的呢?這需要根據圖的具體用途來定。所以,我們不能用簡單的0,1來代表邊。

如果2個頂點無關聯,他們也不能用0表示,因為0也可能是一個合法的wieght值。可有類比一下:如何地球上2個地方之間不可互通,那么他們之間的車程費是不是無窮大呢?

所以,我們來要根據圖權值類型定義一個相應類型的最大值,來代表2個頂點之間不關聯。

同樣用一個例子。

 

V0 V1之間的權值為12

V0 V2之間的權值為1

V0 V3之間的權值為5

V2 V3之間的權值為7

 

       

 

 

 代碼:

#include<stdio.h>


#define MAX_VERTEX 4     

#define INFINITY  65535

typedef char DataType;     //存儲的元素類型 
typedef int WeightType;    //權值的類型 

typedef struct
{
    DataType vertexArr[MAX_VERTEX];             //存儲頂點的數組 

    WeightType edgeArr[MAX_VERTEX][MAX_VERTEX]; //存儲邊的二維數組 

}UArrayNet;     //數據結構類型:無向網 


void UArrayNet_init(UArrayNet*pGraph);
void UArrayNet_create(UArrayNet*pGraph);
void UArrayNet_show(UArrayNet *pGraph);

int main()
{
    UArrayNet net;
    UArrayNet_init(&net);

    UArrayNet_create(&net);
    UArrayNet_show(&net);



    return 0;
}

void UArrayNet_init(UArrayNet*pGraph)
{
    for (int i = 0; i < MAX_VERTEX; ++i)
    {
        pGraph->edgeArr[i][i] = INFINITY;
    }


}


void UArrayNet_create(UArrayNet*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有邊,則輸入權值,否則輸入無效值%d\t", pGraph->vertexArr[j], pGraph->vertexArr[i], INFINITY);

            scanf("%d", &(pGraph->edgeArr[j][i]));
            pGraph->edgeArr[i][j] = pGraph->edgeArr[j][i];     //對稱 
        }
    }


}

void UArrayNet_show(UArrayNet *pGraph)
{


    printf("\n\n頂點元素如下\n");
    for (int i = 0; i < MAX_VERTEX; ++i)
    {

        printf("%-5c", pGraph->vertexArr[i]);
    }
    printf("\n\n");



    puts("邊矩陣如下");
    printf("%-2c", ' ');
    for (int i = 0; i<MAX_VERTEX; ++i)
        printf("%-5c", pGraph->vertexArr[i]);
    putchar('\n');



    for (int j = 0; j <MAX_VERTEX; ++j)
    {
        printf("%-2c", pGraph->vertexArr[j]);
        for (int i = 0; i < MAX_VERTEX; ++i)
        {
            if(pGraph->edgeArr[i][j]==INFINITY)
            {
                printf("%-5c", '#');
                
            } 
            else
                printf("%-5d", pGraph->edgeArr[i][j]);

        }
        putchar('\n');
    }
}
View Code

 

 

有向網的鄰接矩陣存儲

有向網和有向圖的原理是一樣,這里不再擴充。

 

 

 

鄰接表儲存實現

 

 

鄰接矩陣存儲很好理解,但是,有時候太浪費空間了,特別是對於頂點數多,但是關聯關系少的圖

舉個極端的栗子。

下圖中,5個頂點都是孤立的,然而為了存儲邊的信息,要分配一個5X5的矩陣。本來一條邊都沒有,沒必要用存儲空間來存儲邊,但還要分配額外的空間。

           

 

 

 

無向圖與鄰接表

用鄰接表則可以避免這種浪費。鄰接表用動態表結構存儲邊,更加靈活:有一個邊就分配一個空間去存儲,沒有就不分配。

我們仍然用鄰接矩陣中那個圖的例子來說明鄰接表的構建思想。

鄰接表只需要一個數組,但是這個數組有點復雜,需要解剖一下。如下圖。

 

 

 

可以發現,用鄰接表存儲時,圖中的每一個節點 不僅要存儲本頂點的數據data,好要存儲一個表,這個表存儲了此頂點的所有臨接點的索引。2點確定一線,那么就能存儲此節點相關的所有的邊了。

 

 

代碼:

 

注:代碼中,我並沒有手動編寫鏈表,而是用了C++標准庫中的vector模板,它是一個線性表,具體說是在堆中的動態數組。push_back()就是向表中添加新的元素。

這樣做的目的是為了讓代碼更簡潔,思路更清晰。我不想把書抄一遍,這樣我寫這篇隨便也就失去了意義。

而且,書上的很多例子中,將多種數據結構的構建混雜在一起,根本不知道降低耦合度,如果我以前學過了鏈表,而且也自己實現了,為什么不直接拿來用呢?吐槽完畢。

 

#include<stdio.h>
#include<vector>

#define MAX_VERTEX 4
using std::vector;        //使用C++標准庫中的vector來作為list,它實質就是一個順序表 


typedef char DataType;

typedef struct node
{
    DataType data;            //頂點數據 
    vector<int> indexList;    //存儲頂點鄰接點索引的 表 

}Node;


typedef Node* UListGraph;


/********************************/
void UListGraph_init(UListGraph*pGraph);
void UListGraph_create(UListGraph  graph);
void UListGraph_show(UListGraph graph);
void UListGraph_destroy(UListGraph*pGraph);

int main(void)
{

    UListGraph g;

    UListGraph_init(&g);
    UListGraph_create(g);
    UListGraph_show(g);

    UListGraph_destroy(&g);



    return 0;
}


void UListGraph_init(UListGraph*pGraph)
{

    (*pGraph) = new Node[MAX_VERTEX];


}


void UListGraph_create(UListGraph  graph)
{

    
    
    for (int i = 0; i < MAX_VERTEX; ++i)  //填充頂點數組
    {
        printf("輸入第%d個頂點值\n",i+1);
        
        scanf(" %c",  &(graph[i].data)); 

    }

    for(int i=0;i<MAX_VERTEX;++i)
        for(int j=i+1;j<MAX_VERTEX;++j)
        {
            printf("如果元素%c和%c之間有邊,請輸入1,否則輸入0",graph[i].data,graph[j].data);

            int opt;
            scanf("%d",&opt);

            if(opt==1)
            {
                graph[i].indexList.push_back(j);
                graph[j].indexList.push_back(i);
            }


        }



}



void UListGraph_show(UListGraph graph)
{
    
    printf("頂點元素如下:\n\n"); 
    for(int i=0;i<MAX_VERTEX;i++)
    {
        printf("%c\t",graph[i].data);
    }
    printf("\n\n");



    for(int i=0;i<MAX_VERTEX;i++)
    {
        printf("元素%c的鄰接點 :",graph[i].data);

        Node tnode = graph[i];

        for(int k=0;k<tnode.indexList.size();++k)
        {
            printf("%c\t",graph[tnode.indexList[k]].data);
        }

        putchar('\n');
    }


}


void UListGraph_destroy(UListGraph*pGraph)
{
    delete (*pGraph);
    *pGraph = NULL;        
       

}
View Code

 

 

 

 

無向網的鄰接表

無向網的鄰接表,因為網是帶權值的,所以,還要為邊附加權值信息。

確切的說,就是IndexList表 中存儲的是一個個的結構體,這個結構體不僅保存鄰接點的索引,還用一個成員保存了本頂點和他的鄰接點之間邊的權值。

 

typedef struct edge
{
    int otherVerTex;          //本頂點和索引為otherVertex的頂點形成一條邊
    WeightType weight;        //邊的權值


}Edge


//使用方法如下:用V0和V1來舉例

// Edge edge = {1 , 12};
// V0.IndexList.push_back(edge );
//

 

 

 

 

有向圖和十字鏈表

 

 鄰接表對於無向圖來說很適用,但是對於有向圖來說就不適用了,因為鄰接表中,每一個節點的IndexList只能存儲一種狀態的弧,出弧或者入弧(逆鄰接表),那怎么將一個頂點的出入弧都存儲起來呢?

那就是將鄰接表和逆鄰接表都用起來,也就是一個節點需要存儲:①data,②inArcList,入弧記錄表,③outArcList,出弧記錄表

 

 

 

 

可以看出,十字表比鄰接表更復雜,因為十字表要存儲更多的數據。

 

 代碼:

 

#include<stdio.h>
#include<vector>

using std::vector;

#define MAX_VERTEX 4 

typedef char DataType;


typedef struct arc{ 

    int headVertex;         //此條弧的頭尾結點的index
    int tailVertex;         //此條弧的頭尾結點的index

    //WeightType weight;    //此弧的附加信息,比如權重,這里是有向圖,不是網,所以不需要weight

}Arc;

typedef struct node{

    DataType data;   //頂點數據域

    vector<Arc> outArc;     //此頂點的所有 出度 弧 表
    vector<Arc> inArc;      //此頂點的所有 入度 弧 表 

}Node;


typedef Node* Graph;

void Graph_init(Graph* pG);
void Graph_create(Graph g);
void Graph_show(Graph g);
void Graph_destroy(Graph*pg);


int main(void)
{


    Graph g;

    Graph_init(&g);
    Graph_create(g);
    Graph_show(g);

    return 0;
}



void Graph_init(Graph* pG)
{

    (*pG) = new Node[MAX_VERTEX];
    if(*pG == NULL)
    {
        exit(-1);
    }

}

void Graph_create(Graph g)
{

    int opt;
    
    for (int i = 0; i < MAX_VERTEX; ++i)  //填充頂點數組
    {
        printf("輸入第%d個頂點值\n",i+1);
        
        scanf(" %c",  &(g[i].data)); 

    }

    for(int j=0;j<MAX_VERTEX;++j)
        for(int i=j+1;i<MAX_VERTEX;++i)
        {


            printf("若元素%c有指向%c的弧,則輸入1,否則輸入0\t",g[j].data,g[i].data);
           
            scanf("%d",&opt);

            if(opt==1)
            {
                Arc* parc = new Arc;
                parc->headVertex = i;
                parc->tailVertex = j;
                
                g[j].outArc.push_back(*parc);
                g[i].inArc.push_back(*parc);

            }

            
            printf("若元素%c有指向%c的弧,則輸入1,否則輸入0\t",g[i].data,g[j].data);
           
            scanf("%d",&opt);

            if(opt==1)
            {
                
                Arc* parc = new Arc;
                parc->headVertex = j;
                parc->tailVertex = i;

                g[i].outArc.push_back(*parc);
                g[j].inArc.push_back(*parc);
                     

            }

        }

}

void Graph_show(Graph g)
{


    for (int j = 0; j <MAX_VERTEX; ++j)
    {
        printf("頂點數據%c\n",g[j].data);
       
        printf("發射:");

        
        for(int i=0;i<g[j].outArc.size();++i)
        {
            printf("%c\t",g[g[j].outArc[i].headVertex].data);
        }


        putchar('\n');
        printf("接收:");

        for(int i=0;i<g[j].inArc.size();++i)
        {
            printf("%c\t",g[g[j].inArc[i].tailVertex].data);
        }

        printf("\n\n");

    }
    


}



void Graph_destroy(Graph*pg)
{
    //once you use new or malloc in your C/C++ project,you must remember
    //to delete or free the memory you allocated
    //I have no time to complete this code
    //you can try yourself
    //:)
}
View Code

 

 

鄰接多重表

 沒時間了~以后補!

 

 

 

下一篇:圖的遍歷

 


免責聲明!

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



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