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)依附于顶点 a 和 b ,边(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
路径长度: 路径中边的数目
回路或环: 第一个顶点和最后一个顶点 相同的路径 称为回路或环(也就是 a 和 b 相同)
简单路径: 路径中顶点不重复出现的路径的路径为简单路径
简单回路 或 简单环: 除了第一个顶点和最后一个顶点之外,其余顶点不重复出现的回路,称为简单回路 或 简单环
8、连通、连通图和连通分量:
在无向图中
如果从顶点 A 到顶点 B 有路径,则称 A 和 B 是 连通 的
如果图中任意两个两个顶点 A ,B 都是连通的,则称图为 连通图
在无向图中
上图不是连通图
下面是上图的三个连通分量
连通分量也称作无向图中的极大连通子图
也就是说,把一个非连通图完全分成多个连通图,这些连通图就叫做连通分量
9、强连通图和强连通分量:
在有向图中 如果图中任意两个两个顶点 A ,B 都是连通的,则称图为 强连通图
在有向图中: G4不是强连通图,但是他有三个强连通分量
10、连通图的生成树、有向树和生成森林:
**连通图的生成树:**是包含图中全部顶点的一个极小连通子图。若图中顶点数为 n ,则它的生成树含有 n − 1 条边。
对生成树而言,若砍去一条边,则会变成非连通图
若加上一条边则会形成一个回路。
在非连通图中,连通分量的生成树构成了非连通图的生成森林
有向树: 有一个顶点的入度为0,其余顶点的入度均为1的有向图称为有向树
换句话说,一个有向图的生成森林是由若干棵有向树组成。
二、图的存储结构
图的结构相对于其他来说比较复杂,任何两个顶点之间都可能存在联系,无法用顺序存储的方式存储图之间的关系(无法用一维数组来保存图
)
虽然图无法用顺序存储的方式,但是可以用二维数组的方式表示元素之间的关系,即 邻接矩阵 表示法。
由于图的任意两个结点都可能存在关系,用链式存储图也很常见。
图的链式存储有:邻接表、十字链表和邻接多重表。
(应该根据实际需要选择不通的存储结构)
2.1、邻接矩阵
邻接矩阵: 顶点之间相邻关系的矩阵。
图
图G(V,E)是具有 n 个顶点的图(A[ i,j] = 1表示顶点 i 和顶点 j 有一条边联系,反之则没有边联系)
**对角线上的都是0:**都是对应当前点(V0,V0 V1,V1 ……)顶点是无法自身与自身相连接的
对于无向图来说,其实他们是关于对角线对称的
( i
与j
相连,那么j
与i
肯定也是相连的)
对于有向图来说,没有这个规律 ( i
与j
相连,那么j
与i
不一定相连)
网
网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
起始的边:
V1
的 firstout
(指向第一个以当前结点为起始点的边)指向边 0->1
,(这里存储的是结点下标)
此边的 tlink
(指向和当前边的起始结点相同的上一条边)指向 0->2
可一直通过边的 tlink
找到相同起始点的边
找以 V1
结束的边:
V1
的 firstin
(指向第一个以当前结点为终止点的边)指向边 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考研—数据结构—图