数据结构(c语言版)第六章答案,数据结构(C语言版) 第 六 章 图 知识梳理 + 习题详解...

本系列博客为《数据结构》(C语言版)的学习笔记(上课笔记),仅用于学习交流和自我复习html

4ae89be0ceddd5dad61e2eebb85b81f9.png

1、 图的基本定义和术语

f1123adfe6997e15ec9474bea4e84786.png

9a50a8ef80ab5d636df6d38545b550ed.png

彻底图:任意两个点都有一条边相连ios

无向彻底图web

n个结点,一共有C

(

n

,

2

)

C(n,2)C(n,2)条边算法

有向彻底图数组

n

(

n

1

)

n(n-1)n(n−1)网络

cefe7613caa77bbefc2022942481b2b7.png

5f0d118a76006ee784e7c9c4a158efe7.png

066b741fb6eecce945fdc213693ded32.png

65174c416a1e3dff6252c5c63c5efe48.png

f7455ffc90cbca32ca2ff490a4e0e330.png

141e5b8b150d9bf79079b1aa2e520292.png

565db4fb51d5a43c1d62893bb29a8414.png

6fe49a03ba96b655f5e81f3aca444dc9.png

2、图的三种存储结构

1.邻接矩阵表示法

所谓邻接矩阵存储结构就每一个顶点用一个一维数组存储边的信息,这样全部点合起来就是用矩阵表示图中各顶点之间的邻接关系。所谓矩阵其实就是二维数组。数据结构

int g[N][N];

int main() {

int n, m; //n个点 m条边

scanf("%d%d", &n, &m);

int u, v; //从u到v

for (int i = 0; i < m; ++i) {

scanf("%d%d", &u, &v);

g[u][v] = 1;

//g[v][u] = 1;//无向图要建双边

//g[u][v] = w; //带权图

}

}

32a9a6116e1b14dc6e0761f2d7f289e9.png

918518c6569ed96573ce3d8b54aa831c.png

892ab56441b6731c4247ea4606e3389c.png

787eaa586a34f4b9fb34be4fad49dbdb.png

2.邻接表(链式)表示法

1c5c7adb8ac0f6bdc60185750246304a.png

8d48832291e2ca9731dabc3208093837.png

c9b683a8f87e4e03a7eecb5a80ce7b44.png

93ea14fe787c66aa567312a12859cd42.png

#define MVNum 100//最大顶点数

typedef struct ArcNode{ //边结点

int adjvex; //该边所指向的顶点的位置

struct ArcNode * nextarc; //指向下一条边的指针

OtherInfo info; //和边相关的信息

}ArcNode;

typedef struct VNode{

VerTexType data; //顶点信息

ArcNode * firstarc; //指向第一条依附该顶点的边的指针

}VNode, AdjList[MVNum]; //AdjList表示邻接表类型

typedef struct{

AdjList vertices; //邻接表

int vexnum, arcnum; //图的当前顶点数和边数

}ALGraph;

优势 :空间效率高,容易寻找顶点的邻接点;app

缺点 :判断两顶点间是否有边或弧,需搜索两结点对应的单链表,没有邻接矩阵方便。svg

3.邻接矩阵和邻接表的区别

b8402841511a8e45376bd9b176d3ade1.png

68a5f38e4389d6f2a51115d83fc6eb1b.png

4.链式前向星

若是说邻接表是很差写但效率好,邻接矩阵是好写但效率低的话,前向星就是一个相对中庸的数据结构。前向星当然好些,但效率并不高。而在优化为链式前向星后,效率也获得了较大的提高(主要是看着舒服)。

struct node

{

int v,nex,val,u;

}e[N];

int head[N],cnt;

inline void add(int u,int v,int val)//从u到v,从父节点到子节点

{

e[++cnt].nex=head[u];

e[cnt].val=val;//无关紧要

e[cnt].v=v;

e[cnt].u=u;//无关紧要

head[u]=cnt;

}

遍历全部结点方法:

for(int i=head[u];i;i=e[i].nex)

{

int v=e[i].v;

---------------

}

//这样咱们就能够遍历所有的点了!!

3、图的遍历

搜索引擎的两种基本抓取策略 —深度优先/广度优先

两种策略结合=先广后深 +权重优先

先把这个页面全部的连接都抓取一次再根据这些URL的权重来断定URL的权重高,就采用深度优先,URL权重低,就采用宽度优先或者不抓取 。

我把我以前写的博客的内容所有直接搬过来啦 ,下面的可能会有点难度

0x21.搜索 - 树与图的遍历、拓扑排序

注:如下图的创建都是使用链式前向星建图。

int head[N],ver[N],nex[N],edge[N],tot;

void add(int u,int v,int val){//链式前向星建图

ver[++tot] = v;

edge[tot] = val;

nex[tot] = head[u];

head[u] = tot;

}

1.)树与图的深度优先遍历及树的一些性质

1.树与图的深度优先遍历

深度优先遍历,就是在每一个点x上面的的多条分支时,任意选择一条边走下去,执行递归,直到回溯到点x后再走其余的边

int vis[N];//标记每个点的状态

void dfs(int u){

vis[u] = 1;

for(int i = head[u];i;i = nex[i]){

int v = ver[i];

if(vis[v])

continue;

dfs(v);

}

}

注:下面的2,3,4,5,6小节的内容不要求掌握,我就是看着有关联就放到这里的,都是竞赛相关的内容,有兴趣能够看一下,都比较简单

2.时间戳

按照上述的深度优先遍历的过程,以每个结点第一次被访问的顺序,依次赋值1~N的整数标记,该标记就被称为时间戳。

标记了每个结点的访问顺序。

3.树的DFS序(树链剖分前驱知识)

通常来讲,咱们在对树的进行深度优先时,对于每一个节点,在刚进入递归时和回溯前各记录一次该点的编号,最后会产生一个长度为2

N

2N2N的序列,就成为该树的D

F

S

DFSDFS序。

int a[N],cnt;

int dfs(int u){

a[++cnt] = u;//用a数组存DFS序

vis[u] = 1;

for(int i = head[u]; i;i = nex[i]){

int v = ver[i];

if(vis[v])

continue;

dfs(v);

}

a[++cnt] = u;

}

D

F

S

DFSDFS序的特色时:每一个节点的x

xx的编号在序列中刚好出现两次。设这两次出现的位置时L

[

x

]

,

R

[

x

]

L[x],R[x]L[x],R[x],那么闭区间[

L

[

x

]

,

R

[

x

]

]

[L[x],R[x]][L[x],R[x]]就是以x

xx为根的子树的D

F

S

DFSDFS序。

dfs序能够把一棵树区间化,便可以求出每一个节点的管辖区间。

对于一棵树的dfs序而言,同一棵子树所对应的必定是dfs序中连续的一段。

a457ce7bd7621c567ec13d3ca959735f.png

3925097b2e18605c6c959f0f8a136525.png

放一个博客。

dfs序的七个基本问题

4.树的深度

树中各个节点的深度是一种自顶向下的统计信息

起初,咱们已知根节点深度是0

00.若节点x的深度为d

[

x

]

d[x]d[x],则它的子结点 y

yy 的深度就是d

[

y

]

=

d

[

x

]

+

1

d[y]=d[x]+1d[y]=d[x]+1

int dep[N];

void dfs(int u){

vis[u] = 1;

for(int i = head[u];i;i = nex[i]){

int v = ver[i];

if(vis[v])

continue;

dep[v] = dep[u]+1;//父结点 u 到子结点 v 递推

dfs(v);

}

}

5.树的重心与s

i

z

e

sizesize

树的重心是自底向上统计的

树的重心也叫树的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。

int vis[N];

int Size[N];

int ans = INF;

int id;

void dfs(int u){

vis[u] = 1;

Size[u] = 1;//子树的大小

int max_part = 0;

for(int i = head[u];i;i = nex[i]){

int v = ver[i];

if(vis[v])

continue;

dfs(v);

Size[u] += Size[v];

max_part = max(max_part,Size[v]);//比较儿子的size由于这里是假设以u为重心

}

max_part = max(max_part,n-Size[u]);//n为整棵树的结点数

if(max_part

ans = max_part;//记录重心对应的max_part的值

id = u;//记录重心位置

}

}

6.图的连通块划分

若在一个无向图中的一个子图中任意两个点之间都存在一条路径(能够相互到达),而且这个子图是“极大的”(不能在扩展),则称该子图是原图的一个联通块

以下代码所示,cnt是联通块的个数,v记录的是每个点属于哪个联通块

通过连通块划分,能够将森林划分出每一颗树,或者将图划分为各个连通块。

int cnt;

void dfs(int u){

vis[u] = cnt;//这里存的是第几颗树或者是第几块连通图

for(int i = head[u];i;i = nex[i]){

int v = ver[i];

if(vis[v])

continue;

dfs(v);

}

}

int main()

{

for(int i = 1;i<=n;++i){

if(!vis[i])//若是是颗新树就往里面搜

++cnt,dfs(i);

}

}

DFS算法效率分析

用邻接矩阵来表示图,遍历图中每个顶点都要从头扫描该顶点所在行,时间复杂度为O

(

n

2

)

O(n^2)O(n2)。

用邻接表来表示图,虽然有 2e 个表结点,但只需扫描 e 个结点便可完成遍历,加上访问 n个头结点的时间,时间复杂度为O

(

n

+

e

)

O(n+e)O(n+e)。

结论:

稠密图适于在邻接矩阵上进行深度遍历;

稀疏图适于在邻接表上进行深度遍历。

2.)树与图的广度优先搜索

树与图的广度优先遍历,顺便求d数组(树结点的深度/图结点的层次)。

void bfs(){

memset(d,0,sizeof d);

queueq;

q.push(1);

d[1] = 1;

while(q.size()){

int u = q.front();

q.pop();

for(int i = head[u];i;i = nex[i]){

int v = ver[i];

if(d[v])continue;

d[v] = d[u]+1;

q.push(v);

}

}

}

广度优先遍历是一种按照层次顺序访问的方法。

它具备两个重要的性质:

在访问完全部的第i层结点后,才会访问第i+1层结点。

任意时刻,队列中只会有两个层次的结点,知足“两段性”和“单调性”。

BFS算法效率分析

若是使用邻接矩阵,则BFS对于每个被访问到的顶点,都要循环检测矩阵中的整整一行( n 个元素),总的时间代价为O

(

n

2

)

O(n^2)O(n2)。

用邻接表来表示图,虽然有 2e 个表结点,但只需扫描 e 个结点便可完成遍历,加上访问 n个头结点的时间,时间复杂度为O

(

n

+

e

)

O(n+e)O(n+e)。

练习

4f4b69c01c612e2eb8b01898db976fab.png

答案:

深度:3,6,5,1,2,4

广度:3,6,2,5,1,4

4、图的应用

本ACMer狂喜

1.最小生成树

极小连通子图:该子图是G 的连通子图,在该子图中删除任何一条边,子图再也不连通。

生成树:包含图G全部顶点的极小连通子图(n-1条边)。

88dd97282beaee6f8295abd9c4a2dee1.png

be65fd29289292562ae5ae786ea173f6.png

首先明确:

使用不一样的遍历图的方法,能够获得不一样的生成树

从不一样的顶点出发,也可能获得不一样的生成树。

按照生成树的定义,n 个顶点的连通网络的生成树有 n 个顶点、n-1 条边。

目标:

在网的多个生成树中,寻找一个各边权值之和最小的生成树。

K

r

u

s

k

a

l

KruskalKruskal算法能够简单理解为按边贪心。

P

r

i

m

PrimPrim算法是以更新过的节点的连边找最小值

1.K

r

u

s

k

a

l

KruskalKruskal算法

每次选择权值最小的边,若该边两点没有加入集合,就将他加入。

起初每一个点的都是一个独立的集合,把边权从小到达排序,按照边权枚举边,用并查集判断两个是否在同一个集合,若是在一个集合就跳过当前边,反之就联通这两个集合。

时间复杂度:O

(

m

l

o

g

m

)

O(mlogm)O(mlogm)

给出C++代码:

#include

#include

#include

#include

#include

#include

#define over(i,s,t) for(register int i = s;i <= t;++i)

#define lver(i,t,s) for(register int i = t;i >= s;--i)

//#define int __int128

#define lowbit(p) p&(-p)

using namespace std;

typedef long long ll;

typedef pair PII;

const int N = 2e5+7;

struct node{

int x,y,z;

bool operator

return z < t.z;

}

}edge[N];

int fa[N],n,m,ans;

int Find(int x){

if(x == fa[x])return x;

return fa[x] = Find(fa[x]);

}

int main()

{

cin>>n>>m;

over(i,1,m)

scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);

sort(edge + 1,edge + 1 + m);

over(i,1,n)

fa[i] = i;

over(i,1,m){

int x = Find(edge[i].x);

int y = Find(edge[i].y);

if(x == y)continue;

fa[x] = y;

ans += edge[i].z;

}

printf("%d\n",ans);

}

2.P

r

i

m

PrimPrim算法

每次选择当前点所连的边的最小值,而后把它连起来

有些相似D

i

j

k

s

t

r

a

DijkstraDijkstra

普通版本的时间复杂度为O

(

n

2

)

O(n^2)O(n2)

堆优化的算法时间复杂度为O

(

n

l

o

g

n

)

O(nlogn)O(nlogn)

给出C++代码:

#include

#include

#include

#include

#include

#include

#include

#define over(i,s,t) for(register int i = s;i <= t;++i)

#define lver(i,t,s) for(register int i = t;i >= s;--i)

//#define int __int128

#define lowbit(p) p&(-p)

using namespace std;

typedef long long ll;

typedef pair PII;

const int N = 4e5+7;

int ver[N],nex[N],edge[N],head[N],tot;

int n,m,ans;

int dis[N];

int vis[N],cnt;

void add(int u,int v,int val){

ver[++tot] = v;

edge[tot] = val;

nex[tot] = head[u];

head[u] = tot;

}

priority_queue,greater >q;

void prim(){

dis[1] = 0;

q.push({0,1});

while(q.size()&&cnt != n){

int d = q.top().first,u = q.top().second;

q.pop();

if(vis[u])continue;

cnt++;

ans += d;

vis[u] = 1;

for(int i = head[u];i;i = nex[i]){

int v = ver[i];

if(edge[i] < dis[v])

dis[v] = edge[i],q.push({dis[v],v});

}

}

}

int main()

{

memset(dis,0x3f,sizeof dis);

scanf("%d%d",&n,&m);

over(i,1,m){

int x,y,z;

scanf("%d%d%d",&x,&y,&z);

add(x,y,z);

add(y,x,z);

}

prim();

printf("%d\n",ans);

return 0;

}

2.最短路

3.拓扑排序

4.关键路径

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值