图的存储 – 十字链表法

注:十字链表是有向图的链式存储结构

有向图用邻接表法存储的时候,计算出度容易,计算入度难;用逆邻接表存则相反。

如何存储能使得有向图计算出度和入度都容易呢?

最容易想到的就是把邻接表和逆邻接表都存一份,此外有什么方式能将两个表合并在一起呢? 十字链表。

如图:

顶点表结点和边表结点中均存在两个指针(前半部分组成逆邻接表,后半部分组成邻接表)

存储类型表示

上图可看出,图有顶点表结点和边表结点组成

顶点表结点数据成员:data(顶点信息)+ first_RAdjhead(逆邻接表表头指针) + first_Adjhead(邻接表表头指针)

边表结点: tailvex(弧尾顶点下标)+ headvex(弧头顶点下标)+ RAdjPointer(逆邻接表指针)+ AdjPointer(邻接表指针)

// 边表结点
typedef struct ArcNode
{
    /*data*/
    // 弧尾顶点下标
    int tailvex;

    // 弧头顶点下标
    int headvex;

    // 逆邻接表指针
    struct ArcNode *RAdjPointer;

    // 邻接表指针
    struct ArcNode *AdjPointer;

    // 若为网,则添加权值
    // double ArcVal;
}ArcNode;


typedef char VextexType;

// 顶点表结点
typedef struct
{
    /*data*/
    // 顶点信息
    VextexType Info;

    // 逆邻接表表头指针
    ArcNode *First_RAdjhead;

    // 邻接表表头指针
    ArcNode *First_Adjhead;

}VexNode;


#define MaxVexNode 100

// 图的十字链表存储
typedef struct
{
    /* data */
    //顶点表数组
    VexNode Vex[MaxVexNode];

    // 顶点和弧的个数
    int Vexnum, ArcNum;

}MGraph;

建图过程

阿Q自行完成的十字链表建图过程:

此种方式下邻接表和逆邻接表的维护均为尾插法

其实本质即为同时维护邻接表和逆邻接表。以下为建立有向图的十字链表(邻接表和逆邻接表的维护均为头插法)

阿Q:看着很难,其实实现起来特别快。

int FindVex(OrthLinkGraph G, VextexType v){
    for(int i = 0; i < G.Vexnum; i++)
        if(G.Vex[i].Info == v) return i;
    return -1;
}

// 判断<u, v> 存在弧 
bool Exist(OrthLinkGraph G, int u, int v){
    // 找邻接表(出度)
    ArcNode *ptr = G.Vex[u].First_Adjhead;

    for(; ptr != nullptr; ptr = ptr->AdjPointer)
        if(ptr->headvex == v) return true; // <tailvex, headvex>
    
    return false;
}

// 建图过程
#include<iostream>
// 十字链表是有向图(网)的存储结构,此处以建立有向图为例
// 手动输入顶点信息和边信息
void CreateGraph(OrthLinkGraph &G){
    std::cout << "Input VexNum: ";
    std::cin >> G.Vexnum;

    std::cout << "Input ArcNum: ";
    std::cin >> G.ArcNum;

    // 初始化十字链表 并 输入顶点信息
    std::cout << "Input VexInfo(char): ";
    for(int i = 0; i < G.Vexnum; i++){
        std::cin >> G.Vex[i].Info;
        G.Vex[i].First_Adjhead = nullptr;
        G.Vex[i].First_RAdjhead = nullptr;
    }

    // 输入边信息,建立所需图的十字链表
    int i = 0;
    while(i < G.ArcNum){
        // <vex1,vex2> 弧边
        VextexType vex1, vex2;
        std::cout << "Input <vex1,vex2>(char): ";
        std::cin >> vex1 >> vex2;

        int index1 = FindVex(G, vex1);
        int index2 = FindVex(G, vex2);

        // 有这俩顶点 且 该边没有存值 才能存入该边的值
        if(index1 == index2 || index1 == -1 || index2 == -1 || Exist(G, index1, index2)){
            std::cout << "Input same Vex / No such Vex / This edge already have value" << std::endl;
            continue;
        }else{
            i++;
            // 维护邻接表和逆邻接表(重点!)
            
            // 新建边表结点
            ArcNode *newArc = new ArcNode;

            // <index1, index2>
            newArc->tailvex = index1;
            newArc->headvex = index2;

            newArc->AdjPointer = nullptr;
            newArc->RAdjPointer = nullptr;

            // 维护vex1的邻接表
            // 头插法(后半部分指针 - 邻接表)
            newArc->AdjPointer = G.Vex[index1].First_Adjhead;
            G.Vex[index1].First_Adjhead = newArc;

            // 维护vex2的逆邻接表
            // 头插法(前半部分指针 - 逆邻接表)
            newArc->RAdjPointer = G.Vex[index2].First_RAdjhead;
            G.Vex[index2].First_RAdjhead = newArc;
        }
    }

}
测试代码
int main(){
    OrthLinkGraph G;
    CreateGraph(G);

    // 依次计算 各顶点的度
    for(int i = 0; i < G.Vexnum; i++){
        // 入度,出度,度
        int InCount, OutCount, Count;
        InCount = OutCount = Count = 0;

        // 出度
        for(ArcNode *ptr = G.Vex[i].First_Adjhead; ptr != nullptr; ptr = ptr->AdjPointer)
            OutCount++;
        
        // 出度
        for(ArcNode *ptr = G.Vex[i].First_RAdjhead; ptr != nullptr; ptr = ptr->RAdjPointer)
            InCount++;

        Count = InCount + OutCount;

        std::cout << "vextex: " << G.Vex[i].Info << "   --- Indegree: " << InCount << "\tOutdegree:" << OutCount << "\tdegree:" << Count << std::endl;
    }

    return 0;
}
/*
输入:
Input VexNum: 4
Input ArcNum: 7
Input VexInfo(char): ABCD
Input <vex1,vex2>(char): AB
Input <vex1,vex2>(char): AC
Input <vex1,vex2>(char): CA
Input <vex1,vex2>(char): CD
Input <vex1,vex2>(char): DA
Input <vex1,vex2>(char): DB
Input <vex1,vex2>(char): DC
*/
/*
输出:
vextex: A   --- Indegree: 2     Outdegree:2     degree:4
vextex: B   --- Indegree: 2     Outdegree:0     degree:2
vextex: C   --- Indegree: 2     Outdegree:2     degree:4
vextex: D   --- Indegree: 1     Outdegree:3     degree:4
*/

建立如下图:


适用及优劣性

适用:有向图(网)

优点

  • 出度和入度都容易计算
  • 适合存储稀疏的有向图

阿Q:有向图的十字链表存储本质就是将邻接表和逆邻接表整合在一起;因此在建立边<u,v>时既要维护顶点u的邻接表,也要维护顶点v的逆邻接表

暂无评论

发送评论 编辑评论


上一篇
下一篇