22计算机408考研—数据结构—图

Posted 发呆哥o_o ....

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了22计算机408考研—数据结构—图相关的知识,希望对你有一定的参考价值。

手把手教学考研大纲范围内树定义,遍历,Huffman,并查集
22考研大纲数据结构要求的是C/C++,笔者以前使用的都是Java,对于C++还很欠缺,
如有什么建议或者不足欢迎大佬评论区或者私信指出
初心是用最简单的语言描述数据结构

Talk is cheap. Show me the code.
理论到处都有,代码加例题自己练习才能真的学会

一、图的基本概念

官方解释:
	图G由集合V和集合E组成,记作G = (V,E)
	V(G)代表图G的顶点集合 (有穷非空集合)
	E(G)代表图G的边集合(如果图G为空,则图只有顶点没有边)
歪理:
	树是一个结点对应多个结点,图就是多个结点对应多个结点
	图可以想象成一个乱七八糟的树
	线性表和树都可以看作是一种特殊的图

图分为有向图无向图

1、有向图:

顾名思义,有向图是有方向的图
1->2 但是 2不能指向1

2、无向图:

没有方向的图
只要有边存在就可以互相走,a  -> c , c -> a 两种都存在 

3、子图:

官方解释:

G = (V,E)和G' = (V', E')
V'属于V, E'属于E
称G'为G的子图

当前图的子图有:
1 -> 2
1 -> 2 -> 4
3 -> 4
等等很多,无向图也是一样的

4、完全图:

对于无向图:

​ 若具有n(n-1)条边,则称为无向完全图。

对于有向图:

​ 若具有n(n-1)条边,则称为有向完全图。

5、稀疏图和稠密图:

​ 边的数量相对顶点很少的图称为稀疏图,反之称为稠密图。

6、权和网:

​ 在一些情况下,每条边可以标上具有某种含义的数值,该数值称为该边上的

​ 这些权表示从 一个顶点到另一个顶点 的距离或者耗费,这种带权的图通常称为

7、邻接点:

​ 对于无向图G,如果图的边(a,b)属于 E ,则称 a,b 互为邻接点,即 a,b 相邻接,边(a,b)依附于顶点 ab ,边(a,b)与顶点a,b相关联。

8、度,入度和出度:

​ 顶点 v 的度是:与 v 相关联的边的数目,记作 TD(v)

​ 对于有向图,顶点 v 的度分为入度和出度:

​ 入度是指向 v 的边的数目,记为 ID(v)

​ 出度是从 v 指出来的边的数目,记作 OD(v)

例子:

​ 以顶点 2 为例子:

​ 顶点 2 的入度ID(v)= 1

​ 顶点 2 的出度OD(v)= 2

​ 顶点 2 的度为TD(v)= ID(v)+ OD(v)= 3

边和度之间的关系:

​ 顶点 vi 的度记为 TD(v),那么一个有 n 个顶点,e 条边的图,满足如下关系

e = (TD(v1) + TD(v2) + …… +TD(vn))/ 2

7、路径和路径长度:

​ 顶点 Va 到顶点 Vb 之间的 路径: 是指顶点序列 Va,Vi1,Vi2,……,Vim,Vb

路径长度: 路径中边的数目

回路或环: 第一个顶点和最后一个顶点 相同的路径 称为回路或环(也就是 ab 相同)

简单路径: 路径中顶点不重复出现的路径的路径为简单路径

简单回路 或 简单环: 除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路 或 简单环

8、连通、连通图和连通分量:

在无向图中

​ 如果从顶点 A 到顶点 B 有路径,则称 AB连通

​ 如果图中任意两个两个顶点 AB 都是连通的,则称图为 连通图

在无向图中

​ 上图不是连通图

​ 下面是上图的三个连通分量

连通分量也称作无向图中的极大连通子图

​ 也就是说,把一个非连通图完全分成多个连通图,这些连通图就叫做连通分量

9、强连通图和强连通分量:

在有向图中 如果图中任意两个两个顶点 AB 都是连通的,则称图为 强连通图

在有向图中: G4不是强连通图,但是他有三个强连通分量

10、连通图的生成树、有向树和生成森林:

**连通图的生成树:**是包含图中全部顶点的一个极小连通子图。若图中顶点数为 n ,则它的生成树含有 n − 1 条边。

对生成树而言,若砍去一条边,则会变成非连通图

加上一条边则会形成一个回路

在非连通图中,连通分量的生成树构成了非连通图的生成森林

有向树: 有一个顶点的入度为0,其余顶点的入度均为1的有向图称为有向树

换句话说,一个有向图的生成森林是由若干棵有向树组成。

二、图的存储结构

​ 图的结构相对于其他来说比较复杂,任何两个顶点之间都可能存在联系,无法用顺序存储的方式存储图之间的关系(无法用一维数组来保存图

​ 虽然图无法用顺序存储的方式,但是可以用二维数组的方式表示元素之间的关系,即 邻接矩阵 表示法。

​ 由于图的任意两个结点都可能存在关系,用链式存储图也很常见。

​ 图的链式存储有:邻接表、十字链表和邻接多重表。(应该根据实际需要选择不通的存储结构)

2.1、邻接矩阵

邻接矩阵: 顶点之间相邻关系的矩阵。

​ 图G(V,E)是具有 n 个顶点的图(A[ i,j] = 1表示顶点 i 和顶点 j 有一条边联系,反之则没有边联系)

​ **对角线上的都是0:**都是对应当前点(V0,V0 V1,V1 ……)顶点是无法自身与自身相连接的

​ 对于无向图来说,其实他们是关于对角线对称的ij相连,那么ji肯定也是相连的)

​ 对于有向图来说,没有这个规律 ( ij相连,那么ji不一定相连)

​ 网G,邻接矩阵可以定义为如下(其实和图是一样的,不过就是把顶点有关系变成了顶点直接的权值)

​ A[ i,j] = 3 表示顶点 i 和顶点 j 有一条边,边的权值为3

​ 和图一样,对角线上一定为0,顶点无法与自身相连接

邻接矩阵示例代码

代码:

(无向图也可以这么创建,无向图就不需要输入权值了,把相关联的两个点在矩阵中置1)

#include <iostream>

using namespace std;

#define None 0  //顶点之间没有关系用0表示
#define MAXNum 100  //设图中顶点数量最多为100

typedef struct     //图的结构体:顶点列表,邻接矩阵,图的点数和边数
    int nodes[MAXNum];
    int arcs[MAXNum][MAXNum];
    int vewnum, arcnum;
 AMGraph;

    //取顶点在顶点表中的下标
int getAMGraphIndex(AMGraph G, int node) 
    for (int i = 0; i < G.vewnum; i++)     //循环顶点表,找到顶点,返回下标
        if (G.nodes[i] == node) 
            return i;
        
    
    return -1;


    //邻接矩阵法创建无向图
bool createAMGraph (AMGraph &G) 
    cout << "请输入顶点数量和边的数量" << endl;
    cin >> G.vewnum >> G.arcnum;    //输入 顶点数 和 边数
    for (int i = 0; i < G.vewnum; i++)     //输入顶点
        cout << "请输入顶点的值" << endl;
        cin >> G.nodes[i];
    
    for (int i = 0; i < G.vewnum; i++)     //邻接矩阵,初始化顶点间的边(关系)都为空
        for (int j = 0; j < G.vewnum; j++) 
            G.arcs[i][j] = None;
        
    
    int v1, v2, w;
    int i1, i2;
    for (int i = 0; i < G.arcnum; i++)     //接收两个顶点和顶点间边的权值
        cout << "请输入新建边相邻的两个结点,以及这条边的权重" << endl;
        cin >> v1 >> v2 >> w;
        i1 = getAMGraphIndex(G, v1);    //找到两个顶点的下标
        i2 = getAMGraphIndex(G, v2);
        G.arcs[i1][i2] = G.arcs[i2][i1] = w;    //在邻接矩阵中建立关系
    
    return true;


    //输出图
void printAMGraph(AMGraph G) 
    cout << "顶点数:" << G.vewnum << "边数:" << G.arcnum << "\\n";
    cout << "顶点表:";
    for (int i = 0; i < G.vewnum; i++) 
        cout << G.nodes[i] << " ";
    
    cout << "\\n";

    cout << "邻接矩阵关系(A[i,j] = 3 表示顶点 i 和顶点 j 有一条边,边的权值为3):\\n";
    for (int i = 0; i < G.vewnum; i++) 
        for (int j = 0; j < G.vewnum; j++) 
            cout << G.arcs[i][j] << " ";
        
        cout << "\\n";
    
    cout << "\\n";




int main() 
    AMGraph G;
    //输入例子
    //5 6 1 2 3 4 5 1 5 8 1 3 6 1 4 9 2 3 8 5 4 9 3 4 6
    createAMGraph(G);
    printAMGraph(G);
    return 0;


输入输出样例:
/home/a1439775520/CLionProjects/Graph/cmake-build-debug/Graph
请输入顶点数量和边的数量
5 6
请输入顶点的值
1
请输入顶点的值
2
请输入顶点的值
3
请输入顶点的值
4
请输入顶点的值
5
请输入新建边相邻的两个结点,以及这条边的权重
1 5 8
请输入新建边相邻的两个结点,以及这条边的权重
1 3 6
请输入新建边相邻的两个结点,以及这条边的权重
1 4 9
请输入新建边相邻的两个结点,以及这条边的权重
2 3 8
请输入新建边相邻的两个结点,以及这条边的权重
5 4 9
请输入新建边相邻的两个结点,以及这条边的权重
3 4 6
顶点数:5边数:6
顶点表:1 2 3 4 5 
邻接矩阵关系(A[i,j] = 3 表示顶点 i 和顶点 j 有一条边,边的权值为3):
0 0 6 9 8 
0 0 8 0 0 
6 8 0 6 0 
9 0 6 0 9 
8 0 0 9 0 


Process finished with exit code 0

2.2、邻接表

​ 当一个图中,边的数量少 时(也叫做稀疏图),用邻接矩阵会很 浪费空间 ,这时候 邻接表 就出来了

邻接表结构:

头结点包含一个 顶点 和一个指向 边(表结点) 的指针

表结点包含一个 顶点 ,一个指向 下一条边(表结点) 的指针 和 边的权值(图没有权值)

Tips: 这里指向的边,都是 以当前 头结点 顶点 直接联系 的边

​ eg:

​ 比如这个以 V1 直接联系的点是 V4 和 V2 ,所以表结点是 V4 和 V2

无向图

有向图

解释:

0-4 代表 a-e

​ 邻接矩阵中,是以顶点为起点的关系(逆邻接矩阵,以顶点为终点的关系)

​ 0 -> 1 (a->b) 的边,权重 e1

​ 0 -> 2 (a->c) 的边,权重 e2

​ 0没有指向其他方向的边了

多分析分析这几个图,其实很容易懂得

邻接表示例代码

代码:
#include "iostream"

using namespace std;

#define MAXNum 100  //顶点数最大为100

typedef struct ArcNode     //定义边结点结构体:顶点的邻点adjvex,边的权重,下一个边结点的指针
    int adjvex;
    int info;
    struct ArcNode *nextarc;
 ArcNode;

typedef struct VNode   //定义表头结点结构体:顶点data,指向边结点的指针
    int data;
    ArcNode *firstarc;
 VNode, AdjList[MAXNum];   //AdjList 是表头结点的数组类型

typedef struct     //定义邻接表结构体:表头结点数组,顶点数量和边数量
    AdjList vertices;
    int vexnum, arcnum;
 ALGraph;
    //找到顶点在表头结点的下标
int getGraphIndex(ALGraph G, int targetnode) 
    for (int i = 0; i < G.vexnum; i++) 
        if (G.vertices[i].data == targetnode) 
            return i;
        
    
    return -1;


    //创建邻接表
bool createGraph(ALGraph &G) 
    cout << "请输入顶点数量和边的数量" << endl;
    cin >> G.vexnum >> G.arcnum;    //输出顶点数量和边数量
    for (int i = 0; i < G.vexnum; i++)     //输出顶点,并且设置指向的边结点为空
        cout << "请输入顶点的值" << endl;
        cin >> G.vertices[i].data;
        G.vertices[i].firstarc = NULL;
    
    int v1, v2, i1, i2;
    for (int i = 0; i < G.arcnum; i++)     //输入两个相邻的顶点
        cout << "请输入新建边相邻的两个结点" << endl;
        cin >> v1 >> v2;
        i1 = getGraphIndex(G, v1);  //找到两个顶点的下标
        i2 = getGraphIndex(G, v2);

        ArcNode *p1 = new ArcNode;  //创建p1边结点,让v1表头结点指向p1边结点
        p1->adjvex = v2;    //p1设置邻接点为v2
        p1->nextarc = G.vertices[i1].firstarc;  //采用头插法,如果采用尾插,每次都要循环到当前表头结点的最后一个末尾边结点才能插入
        G.vertices[i1].firstarc = p1;

        ArcNode *p2 = new ArcNode;  //与上面同理
        p2->adjvex = v1;
        p2->nextarc = G.vertices[i2].firstarc;
        G.vertices[i2].firstarc = p2;
    
    return true;


    //输出邻接表
void printGraph(ALGraph G) 
    for (int i = 0; i < G.vexnum; i++) 
        cout << G.vertices[i].data << " :  ";
        ArcNode *temp = G.vertices[i].firstarc;
        while (temp != NULL) 
            cout << "下一个顶点:" <<  temp->adjvex << "  ";
            temp = temp->nextarc;
        
        cout << "下一个顶点为空\\n";
    


int main() 
    ALGraph G;
    //输入样例:4 3 1 2 3 4 1 2 1 3 2 3
    createGraph(G);
    printGraph(G);


输入输出样例:
/home/a1439775520/CLionProjects/Graph/cmake-build-debug/Graph
请输入顶点数量和边的数量
4 3
请输入顶点的值
1
请输入顶点的值
2
请输入顶点的值
3
请输入顶点的值
4
请输入新建边相邻的两个结点
1 2
请输入新建边相邻的两个结点
1 3
请输入新建边相邻的两个结点
2 3
1 :  下一个顶点:3  下一个顶点:2  下一个顶点为空
2 :  下一个顶点:3  下一个顶点:1  下一个顶点为空
3 :  下一个顶点:2  下一个顶点:1  下一个顶点为空
4 :  下一个顶点为空

Process finished with exit code 0

2.3、十字链表

十字链表特点:

​ 十字链表是有向图的另一种链式结构,可以看作邻接表和逆邻接表结合起来得到的一种链表。

​ 十字链表可以快速得到与某点相关联的边,可以高效存取。

​ 大致理解为:每一行第一个为顶点结点,顶点结点的指针根据规律指向对应的边结点。

十字链表结构:

顶点表结点:

data:表示顶点的值

firstin:指向第一个以当前结点为终止点的边(first 第一个 in 进入 第一个进入该结点的边

firstout:指向第一个以当前结点为起始点的边(first 第一个 out 出去 第一个从该结点出去的边

边结点:

tailvex:存储边的起始点。(tailvex代表的是弧尾,也就是起始点 A -> B 弧尾是A,这条边从A开始)

headvex:存储边的终止点(headvex 代表的是弧头)

hlink:指向上一条以 headvex 为终止点的边。

tlink:指向上一条以 tailvex 为起始点的边。

十字链表例图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Q0QFhY0-1637418026309)(https://tse1-mm.cn.bing.net/th/id/R-C.a10051fc3c5919b3a3e16936e4f38f90?rik=p9TqaLD3G%2f1cfQ&riu=http%3a%2f%2fdata.biancheng.net%2fuploads%2fallimg%2f190107%2f2-1Z10H11122456.gif&ehk=%2ffw8m0g04yWye0qjikwIzKthZfijC%2bAo0j0TBpva6Rw%3d&risl=&pid=ImgRaw&r=0)]

V1 点为例子,

找从V1起始的边:

V1firstout(指向第一个以当前结点为起始点的边)指向边 0->1 ,(这里存储的是结点下标)

​ 此边的 tlink (指向和当前边的起始结点相同的上一条边)指向 0->2

​ 可一直通过边的 tlink 找到相同起始点的边

找以 V1 结束的边:

V1firstin (指向第一个以当前结点为终止点的边)指向边 3->0

​ 此边的 hlink (指向和当前边终止结点相同的上一条边) ,不存在终止结点相同上一条边,此时为NULL

​ 可一直通过边的 hlink 找相同终止点的边

十字链表的最大优点:

把邻接表和逆邻接表整合在一起了,很容易找到以 V1 为起始点的边,也容易找到以 V1 为终止点的边。

十字链表示例代码

代码:
#include "iostream"

using namespace std;

#define MAXSize 100  //顶点数最大为100

typedef struct ArcNode     //定义边结点结构体:(边采用头插法)
    int info;   //边的权重,
    int tailvex;    //边的尾结点的下标(一条边的起始点下标)
    int headvex;    //边的头结点的下标(一条边的终止点下标)
    struct ArcNode *hlink;  //指向上一条 与边的头结点相同的边 (指向上一条有相同起始点的边)
    struct ArcNode *tlink;  //指向上一条 与边的尾结点相同的边 (指向上一条有相同终止点的边)
 ArcNode;

typedef struct VNode   //定义表头结点结构体:
    int data;   //结点的数据
    ArcNode *firstin;   //指向最后一条以当前顶点为结尾的边(最后一条以当前点的下标为终止点的边)
    ArcNode *firstout;  //指向最后一条以当前顶点为起始的边(最后一条以当前点的下标为起始点的边)
 VexNode;

typedef struct     //定义十字链表结构体:表头结点数组,结点数量和边数量
    VexNode vexlist[MAXSize];
    int vexnum, arcnum;
 ALGraph;
//找到顶点在表头结点的下标
int getGraphIndex(ALGraph G, int targetnode)   //通过结点的值,找到在图中表头结点的下标(如果不存在,返回-1)
    for (int i = 0; i < G.vexnum; i++) 
        if (G.vexlist[i].data == targetnode) 
            return i;
        
    
    return -1;


    //创建边
bool createArc(ALGraph &G)     //(边采用头插法)
    int vex1, vex2, index1, index2; //起始点,终止点,起始点下标,终止点下标
    cout << "输入边的起始点和终止点" << endl;
    cin >> vex1 >> vex2;
    index1 = getGraphIndex(G, vex1);  //找到两个顶点的下标
    index2 = getGraphIndex(G, vex2);
    if (index1 == -1 && index2 == -1) 
        cout << "起始顶点和终止顶点不存在" << endl;
        return false;
     else if (index1 == -1) 
        cout << "起始顶点不存在" << endl;
        return false;
     else if (index2 == -1) 
        cout << "终止顶点不存在" << endl;
        return false;
    
    ArcNode *newArc = new ArcNode;  //创建新边结点,新边的起始点指向index1,终止点指向index2
    newArc->tailvex = index1;
    newArc->headvex = index2;

    ArcNode *oldtail = G.vexlist[index1].firstout;  //找到当前index1为起始点的第一条边
    ArcNode *oldhead = G.vexlist[index2].firstin;   //找到当前index2为终止点的第一条边

    newArc->hlink = oldhead;    //新边的index2为终止点的边指向oldhead(oldhead就成了上一个以index2为终止点的边)
    newArc->tlink = oldtail;    //新边的index2为起始点的边指向oldtail(oldtail就成了上一个以index2为起始点的边)

    G.vexlist[index1].firstout = newArc;    //以index1为起始点的第一条边指向新边(头插法)
    G.vexlist[index2].firstin = newArc;     //以index2为终止点的第一条边指向新边


    cout << vex1 << "->" << vex2 << "  创建成功" << endl;
    return true;


    //删除边
bool deleteArc(ALGraph &G) 
    int vex1, vex2, index1, index2;
    cout << "输入边的起始点和终止点" << endl;
    cin >> vex1 >> vex2;
    index1 = getGraphIndex(G, vex1);
    index2 = getGraphIndex(G, vex2);
    if (index1 == -1 && index2 == -1) 
        cout << "起始顶点和终止顶点不存在" << endl;
        return false;
     else if (index1 == -1) 
        cout << "起始顶点不存在" 22计算机408考研—数据结构—图

22计算机408考研—数据结构—图

22计算机408考研—数据结构—排序(详解加例题)

22计算机408考研—数据结构—线性表栈队列数组

22计算机408考研—数据结构—线性表栈队列数组

22计算机408考研—数据结构—线性表栈队列数组