数据结构之图的创建

本文介绍了图的定义,包括有向图的入度和出度概念。接着讨论了图的五种存储结构:邻接矩阵、邻接表、十字链表、邻接多重表和边集数组,分析了它们的优缺点。重点讲解了邻接矩阵和邻接表,并给出了C/C++实现邻接表创建图的代码片段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.图的定义

图(Graph):是由顶点的又穷非空集合和定点之间的边集组成,通常表示为G(V,E),其中G表示一个图,V是其顶点集合,E是其边集合。

有向图(Directed graphs): 任意两个点之间的边都是有向边。

入度(InDegree):以顶点为v为头的弧的数目称为入度。

出度(OutDegree):以顶点v为尾的弧的数目成为出度。


2.图的存储结构

常见的存储结构有五种:

  • 邻接矩阵
  • 邻接表
  • 十字链表
  • 邻接多重表
  • 边集数组

邻接矩阵:图的定义G(V,E),顶点可以用一维数组来存储type vex[n],边可以理解为线,当然就可以用二维数组
type arc[n][n]进行存储。对于无向表该矩阵一定是对称矩阵,如果顶点i,j之间存在边那么arc[i][j],arc[j][i]=1否则arc[i][j]=0
实例图

存在的边:V0—-V1,V1—-V2 ,V2—V3 ,V3—V0即:

arc[0][1]=arc[1][0]=1;
arc[1][2]=arc[2][1]=1;
arc[2][3]=arc[3][2]=1;
arc[3][0]=arc[0][3]=1;

其矩阵应该为:

这里写图片描述

某个顶点i所在行或者所在列元素之和即其度。
对于有向表,矩阵不再是对称的,可以定义列顶点作为起点,行作为终点。那么顶点i所在行的元素和即为出度,列元素和即为入度。
这里写图片描述
由图可知:
arc[0][3]=1;
arc[1][0]=1;
arc[1][2]=1;
arc[2][0]=1;
arc[2][1]=1;
其矩阵为:
这里写图片描述
这种存储结构优缺点都十分明显,优点在于结构简单,能分清楚看出顶点的出度和入度,简单判断两个顶点间是否存在边。
但是创建是遍历数组复杂都n^2,动态扩展困难,很多的存储空间的浪费。


邻接表
基于邻接矩阵,顶点依然用一维数据存放,但不仅仅存放顶点数据,还存放一个存储由以该定点为弧头邻接点的一维链表。
这里写图片描述
这种数据结构下,图就由一个链表数组构成。链表数据结构为:
这里写图片描述
在例子图中
这里写图片描述
依然可以轻松看出每一个顶点的出度或者入度(如果链表存放的是以顶点Vi为弧头的邻接点则是出度,为弧尾则是入度)。在有向表的情况下也是一样。只关注一个方向进行记录就好。
虽然解决了邻接矩阵难以动态扩展和空间浪费的问题,但是依然存在一个问题,不能同时反应顶点的出度和入度,链表和数组相比,链表始终需要进行遍历(当然有算法可以减少查找次数),导致效率上始终比不上数组毕,竟数组只是一串连续的存储空间,很轻松就可以获取到目标值。

边集数组
这个办法思路更简单,用一个一维数组(或者链表)来存放所有边的信息,其结构是:
begin end weight(权,如果有用到的话)
这里写图片描述
这种方法会使得出度入度及顶点信息都不是很好获得所以不常用。

十字链表和多重链接表
克服了邻接表不能同时显示入度和出度的问题,实现比前几个都复杂所以留到后面再写,不然这篇就太长了= =b。



3.图的建立

是时候敲敲代码了,这里的建立方法使用邻接表的方式,本来我想用Python来做图这种数据类型,但是数据结构本来的目的就是接近底层,所以这里还是使用C/C++来实现尽管要麻烦一些 = =。

class Graph
{
public:
    typedef struct item
    {
        int index;
        int power;
        item*next=NULL;
    }NodeList,*Node;
    int num;
    Graph();
    ~Graph();
private:
    NodeList vexs[20];//最多支持20个顶点
};
Graph::Graph()
{
    int temp=0;
    int counts = 0;
    cout << "输入顶点个数:" << endl;
    cin >> num;
    for (int i = 1; i < num + 1; i++)
    {
        Node ptr = &vexs[i-1];
        cout << "输入第"<<i<<"个顶点信息" << endl;
        counts = 0;
        while (1)
        {
            cout << "输入链接下标" << endl;
            cin >> temp;
            if (temp == -1)
            {
                ptr->next = NULL;
                cout << "第" << i << "个节点录入完成" << endl;
                break;
            }
            else
            {
                if (counts > 0)
                {
                    ptr->next = (Node)malloc(sizeof(NodeList));
                    ptr = ptr->next;
                }
                ptr->index = temp;
                cout << "输入该边的权" << endl;
                cin >> temp;
                ptr->power = temp;
                counts++;
            }
        }
    }
}

Graph::~Graph()
{

}

这个版本很早以前写的,但还是可以用。
这里又存在了一个数组,又可以进行抉择,追求速度可以就是用数组,想更高效的利用存储空间就可以利用链表。即顶点链表中每一个节点都包含着一个边链表,实现方法如下:
首先因为是两个链表,直接整逻辑会显得很复杂,所以这里将它拆分为两个类,各自实现添加操作。

/*******************边集链表*********************/
class Node//边信息
{
public:
    char date;
    int wight;//权 暂未使用
    Node*next;
};

class LinkList
{
public:
    Node * head;//头指针
    Node * tail;//尾部指针
    int lenth=0;//链表长度

    LinkList(char *s);//用一个char数组初始化链表
    Node* add_one(char s);//返回一个当前指针
    void display();
    bool del(int index);
};
LinkList::LinkList(char*s)
{
    if (s == NULL)
    {
        cout << "create a empty LinkList!" << endl;
        head = NULL;
        tail = NULL;
    }
    else
    {
        head = (Node*)malloc(sizeof(Node));
        Node *ptr = head;
        while (*s!='\0')
        {
            lenth++;
            ptr->date = *s++;
            ptr->next = (Node*)malloc(sizeof(Node));
            ptr = ptr->next;
        }
        ptr->date = '#';
        ptr->next = NULL;
        tail = ptr;
    }
}

Node* LinkList::add_one(char s)
{
    Node *ptr;
    Node*curr;
    if (lenth == 0)//链表未曾进行过添加
    {
        head = (Node*)malloc(sizeof(Node));
        ptr = head;
        ptr->date = s;
        ptr->next= (Node*)malloc(sizeof(Node));
        ptr = ptr->next;
        ptr->date = '#';
        ptr->next = NULL;
        tail = ptr;
        lenth++;
        return head;
    }
    else
    {
        ptr = tail;
        ptr->date = s;
        curr = ptr;
        ptr->next = (Node*)malloc(sizeof(Node));
        ptr = ptr->next;
        ptr->date = '#';
        ptr->next = NULL;
        tail = ptr;
        lenth++;
        return curr;
    }
}

bool LinkList::del(int index)
{
    if (index > lenth || index < 0)return false;
    Node*ptr=head;
    if (index == 0){
        head = head->next;
        free(ptr);
    }
    else {
        for (int i = 0; i < index - 1; i++)
        {
            ptr = ptr->next;
        }
        Node*temp = ptr->next;
        ptr->next = ptr->next->next;
        free(temp);
    }
    lenth--;
    return true;
}

void LinkList::display()
{
    Node*ptr = head;
    for (int i = 0; i < lenth; i++)
    {
        cout << "LinkList[" << i << "]:" << ptr->date<<"  ";
        ptr = ptr->next;
    }
    cout << endl;
}

这个链表就用于存放顶点集,其构造方法和display方法是用于测试该表链是否有问题,构造图主要使用的是add_one()方法
自动增加一个节点。

/****************顶点集链表******************/
class vex//顶点信息
{
public:
    LinkList *current;
    vex *next;
    char date;
};

class VexLinkList
{
public:
    vex*head;
    vex*tail;
    int lenth = 0;
    VexLinkList(char *s);
    vex* add_one(char s);
};

VexLinkList::VexLinkList(char *s)//顶点集
{
    if (s == NULL)
    {
        head = NULL;
        tail = NULL;
        cout << "create a empty VexLinkList!" << endl;
    }
    else
    {
        head = (vex*)malloc(sizeof(vex));
        vex*ptr = head;
        while (*s!='\0')
        {
            lenth++;
            ptr->date = *s++;
            ptr->next = (vex*)malloc(sizeof(vex));
            ptr = ptr->next;
        }
        ptr->date = '#';
        ptr->next = NULL;
        tail = ptr;
    }
}
vex* VexLinkList::add_one(char s)
{
    vex*curr;
    if (lenth == 0)
    {
        head = (vex*)malloc(sizeof(vex));
        head->date = s;
        head->next = (vex*)malloc(sizeof(vex));
        tail = head->next;
        tail->date = '#';
        tail->next = NULL;
        lenth++;
        return head;
    }
    else
    {
        tail->date = s;
        curr = tail;
        tail->next = (vex*)malloc(sizeof(vex));
        tail = tail->next;
        tail->date = '#';
        tail->next = NULL;
        lenth++;
        return curr;
    }
}

其实链表结构就那样,所以这两个链表其实差不多只是节点类型上有区别;add_one()方法依然自动添加一个节点。


图类型结构

class Graph
{
public:
    VexLinkList *t;//顶点集
    int sum;//顶点总数
    Graph();
};
Graph::Graph()
{
    char temp;
    t = (VexLinkList*)malloc(sizeof(VexLinkList));
    t->lenth = 0;
    while (1)
    {
        cout << "input the vex # means none" << endl;
        cin >> temp;
        fflush(stdin);
        if (temp == '#')break;
        vex*cur=t->add_one(temp);
        cout << "now input the date in the vex" << endl;
        cur->current = (LinkList*)malloc(sizeof(LinkList));
        cur->current->lenth = 0;
        while (1)
        {
            cin >> temp;
            if (temp == '#')break;
            cur->current->add_one(temp);
        }
    }
}

这里设定了一个默认的终止字符’#’碰到’#’字符就认为当前的顶点集链表或者边集链表输入完毕。
数据结构图:

这里写图片描述

最后

相对于树的建立,感觉上图的建立还算比较简单,只是相关的概念非常多,遍历(深度,广度)相较二叉树稍显复杂,这里立一个flag,深度遍历,广度遍历,十字链表和多重邻接表下周来写,本来以为写这些应该很快,结果发现。。。好慢啊= =。最后吐槽一下markdown编辑器,真是帮人学习HTML CSS。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值