数据结构笔记目录:
1. 绪论、时间复杂度
2. 线性表
3. 树
4. 图
5. 查找
6. 排序
不存在空图
顶点集一定非空
边集可以为空
图和树是逻辑上的区别,都是 逻辑结构
{
无向图
∑
i
=
1
n
T
D
(
v
i
)
=
2
e
有向图
∑
i
=
1
n
T
D
(
v
i
)
=
∑
i
=
1
n
I
D
(
v
i
)
+
∑
i
=
1
n
O
D
(
v
i
)
∑
i
=
1
n
I
D
(
v
i
)
=
∑
i
=
1
n
O
D
(
v
i
)
=
e
{
无向图
n
(
n
−
1
)
2
条边
有向图
n
(
n
−
1
)
条边
顶点集为图G的子集
无向图连通:顶点 v → w v\rightarrow w v→w 直接有路径
图 G 中任两顶点间都连通
- 一点可以访问全部
- 若边数小于n-1,则一定是非连通图
v p → v q v_p \rightarrow v_q vp→vq 之间的顶点序列
回路、环:起点和终点 ( v p = = v q v_p == v_q vp==vq) 相同的路径
已知有n个顶点,确保连通的的最小边数为:n-1个顶点的完全图的边数+1, ( n − 1 ) ( n − 2 ) 2 + 1 \frac{(n-1)(n-2)}{2}+1 2(n−1)(n−2)+1
已知有n条边,组成非连通图的顶点数为:k个顶点组成完全图+1, k ( k − 1 ) 2 + 1 ⇒ ⌈ 2 n ⌉ + 1 \frac{k(k-1)}{2}+1\Rightarrow \lceil \sqrt{2n} \rceil+1 2k(k−1)+1⇒⌈2n⌉+1
有向图连通: v ⇄ w v\rightleftarrows w v⇄w ,双向连通
每对顶点间有 路径
强连通分量
有向图中的极大强连通图
有向完全图一定是强连通图
{
无权图
取
0
或
1
带权图
w
i
j
或
0
或
∞
无向图
邻接矩阵对称且唯一
可进行压缩存储,只存放上(下)三角矩阵元素
对称矩阵默认为无向图
第i行(列)非零元素个数= v i v_i vi 的出度OD( v i v_i vi )或者入度ID( v i v_i vi )
有向图
适用于 稠密图 存储
空间复杂度为 O ( ∣ v ∣ 2 ) O(\mid v\mid^2) O(∣v∣2)
确定两点之间是否有边 O ( 1 ) O(1) O(1) ——数组的随机存取特性
确定图中边数 O ( ∣ v ∣ 2 ) O(\mid v\mid^2) O(∣v∣2)
某个顶点出度越大,则存储矩阵的行非零元素越多
图G的邻接矩阵为A,则 A n [ i ] [ j ] A^n[i][j] An[i][j] 表示从 v i → v j v_i \rightarrow v_j vi→vj 的长度为n的路径数量
typedef struct{
VexType Vex[MaxVertexNum];//顶点集
EdgeType Edge[MaxVertexNum][MaxVertexNum];//邻接矩阵表示边集
int vexnum,arcnum;
}MGraph;
有向无环图:非零元素集中在上三角或下三角区域,其对称区域全为0
上三角:出度大编号小
下三角:入度大编号小
邻接表法:只存出度
逆邻接表法:只存入度
邻接表:每个顶点 v 建立相应的边表
顶点头指针(顺序存储) :顶点头指针为边链表的头指针,指向与 v i v_i vi 关联的首条边
边信息(单链表)
无向图:依附于 v i v_i vi 的边
有向图:以 v i v_i vi 为尾的弧
适用于 稀疏图
邻接表 不唯一
同一顶点的边结点连接顺序不唯一,取决于建立边表的算法及边的输入序列
存储空间
无向图: O ( ∣ v ∣ + 2 ∣ e ∣ ) O(\mid v\mid+2\mid e\mid) O(∣v∣+2∣e∣)
有向图: O ( ∣ v ∣ + ∣ e ∣ ) O(\mid v\mid+\mid e\mid) O(∣v∣+∣e∣)
时间复杂度
找所有邻边:读顶点的边表—— O ( n ) O(n) O(n)
确定边是否存在:扫一个端点的边表—— O ( n ) O(n) O(n)
有向图某顶点的度
出度:邻接表中结点个数
入度:需遍历全部邻接表
typedef struct{
VexType data;
ArcNode *firstArc;
}VNode,AdList[MaxVertexNum];//邻接表
typedef struct{
int adjvex;//邻接点域
double weigh;//边表权值
struct ArcNode *nextArc;//邻接顶点
}ArcNode;//边表结点
typedef struct{
int vexnum;
int arcnum;
AdList vertices;//顶点头指针
}AGraph;

无向图的存储结构

有向图的存储结构

独立于存储结构
参数相同;实现不同,性能不同
Adjacent(G,x,y);//x与y之间是否存在边
Neighbors(G,x);//x的邻接边
FirstNeighbor(G,x);//G中x的第一个邻接点
NextNerghbor(G,x,y);//G中除x的下一个邻接点
InsertVertex(G,x);//插入点
DeleteVertex(G,x);//删除点
AddEdge(G,x,y);//新增边
RemoveEdge(G,x,y);//移除边
已知n个顶点的图,其邻接矩阵表示为MGraph,邻接表表示为LGraph
判别图中边数 n(e)
n(x):表示x的个数
| 无向图 | 有向图 | |
|---|---|---|
| MGraph | n ( e ) = n ( 1 ) 2 n(e)=\frac{n(1)}{2} n(e)=2n(1) | n ( e ) = n ( 1 ) n(e)=n(1) n(e)=n(1) |
| LGraph | n ( e ) = n ( e N o d e ) 2 n(e)=\frac{n(eNode)}{2} n(e)=2n(eNode) | n ( e N o d e ) n(eNode) n(eNode) |
判断两点是否连通
| MGraph | M G r a p h [ i ] [ j ] = ? 1 MGraph[i][j] \overset{?}{=}1 MGraph[i][j]=?1 | O ( 1 ) O(1) O(1) |
|---|---|---|
| LGraph | 遍历顶点i的邻接表 | O ( e ) O(e) O(e) |
度的计算
| 无向图 | 有向图 | |
|---|---|---|
| MGraph | 2 ∗ 第 i 行 ′ 1 ′ 的个数 2*第 i 行 '1' 的个数 2∗第i行′1′的个数 | 出度:第 i 行 ‘1’ 的个数 入度:第 i 列 ‘1’ 的个数 |
| LGraph | n(eNode) |
出度:表头为
i
的单链表中
e
N
o
d
e
的个数
出度:表头为 i 的单链表中 eNode 的个数
出度:表头为i的单链表中eNode的个数 入度:边表中 i 的个数 入度:边表中 i 的个数 入度:边表中i的个数 |
从某一顶点出发,沿图中的边对图中所有顶点访问且只访问一次
遍历与经过的区别
从某一点出发经过图中所有结点 ≠ \neq = 遍历:每个结点不重复的访问一次
对每个顶点查找邻接点的过程取决于存储结构
矩阵: O ( ∣ v ∣ 2 ) O(\mid v\mid^2) O(∣v∣2)
邻接表: O ( ∣ e ∣ ) O(\mid e\mid) O(∣e∣)
树是一种特殊的图
从某一顶点 v 开始,由近到远访问和 v 有路径长度为1,2,3…的顶点
- 逐层访问,故需要借助队列
bool visted[MAX_VERTEX_NUM];
bool BFSTraverse(Graph G){
//初始化
for(int i = 0;i < G.vernum;++i)
visited[i] = FALSE;
InitQueue(Q);
//BFS
for(int i = 0;i < G.vexnum;++i)
if(!visited[i])
BFS(G,i);//使用for循环确保每个连通分量都被访问
}
void BFS(Graph G,int v){//从顶点v出发,广度优先遍历图G
visit(v);
visited[v] = TRUE;
EnQueue(Q,v);//顶点v入队
while(!isEmpty(Q)){
DeQueue(Q,v);
for(w = FirstNeighbor(G,v);w >= 0;
w=NextNeighbor(G,v,w)){
//检测v的所有邻接点w
if(!visited[w]){//w为v尚未访问的邻接点
visit(w);
visited[w] = TRUE;
EnQueue(Q,w);
}
}
}
}
空间复杂度
O ( ∣ v ∣ ) O(\mid v\mid) O(∣v∣)
时间复杂度[与采取的存储方式有关]
邻接矩阵 O ( ∣ v ∣ 2 ) O(\mid v\mid^2) O(∣v∣2)
邻接表 O ( ∣ v ∣ + ∣ e ∣ ) O(\mid v\mid+\mid e\mid) O(∣v∣+∣e∣)
顶点入队 O ( ∣ v ∣ ) O(\mid v\mid) O(∣v∣)
搜索所有邻接点,访问边 O ( ∣ e ∣ ) O(\mid e\mid) O(∣e∣)
求 u → v u \rightarrow v u→v 路径长度最小的路径
无权图求单源点最短路径
void BFS_Min_Distance(Graph G,int u){
//d[i]表示从u到i的最短路径
for(i = 0;i < G.vexnum;++i)
d[i] = INFINITE;
visited[u] = TRUE;
d[u] = 0;
EnQueue(Q,u);
while(!isEmpty(Q)){
DeQueue(Q,u);//BFS算法主过程
for(w = FirstNeighbor(G,u);w >= 0;
w = NextNeighbor(G,u,w)){
if(!visited[w]){
visited[w] = TRUE;//设已访问标记
d[w] = d[u]+1;
EnQueue(Q,w);
}
}
}
}
广度优先生成树
邻接矩阵:表示法唯一 ⟹ \Longrightarrow ⟹ 生成树唯一
邻接表:表示法不唯一 ⟹ \Longrightarrow ⟹ 生成树不唯一
{
图的邻接矩阵唯一
⟹
基于邻接矩阵的
D
F
S
、
B
F
S
序列唯一
图的邻接表不唯一
⟹
基于邻接表的
D
F
S
、
B
F
S
序列不唯一
DFS——递归算法,用一个递归工作栈,空间复杂度 O ( ∣ v ∣ ) O(\mid v\mid) O(∣v∣)
性能分析
时间复杂度
邻接矩阵 O ( ∣ v ∣ 2 ) O(\mid v\mid^2) O(∣v∣2)
邻接表 O ( ∣ v ∣ + ∣ e ∣ ) O(\mid v\mid+\mid e\mid) O(∣v∣+∣e∣)
空间复杂度
O ( ∣ v ∣ ) O(\mid v\mid) O(∣v∣)
bool visited[MAX_VERTEX_NUM];//访问标记数组
void DFSTraverse(Graph G){
for(v = 0;v < G.vexnum;++v) //初始化
visited[v] = FALSE;
for(v = 0;v < G.vexnum;++v)//遍历所有连通分量
if(!visited[v])
DFS(G,v);
}
void DFS(Graph G,int v){
visit(v);
visited[v] = TRUE;//访问v并做标记
for(w = FirstNeighbor(G,v);w >= 0;
w = NextNeighbor(G,v,w)){
if(!visited[w])
DFS(G,w);
}
}
连通
连通分量个数
DFSTraverse / BFSTraverse 中调用 DFS / BFS 次数为连通分量数DFSTraverse 的退栈序列就是一个拓扑序列
边数 = 顶点数 - 1
带权连通图权值和最小
最小权值和唯一,但树形不唯一
若各边权值互不相等,则树形唯一
Generate_MST(Graph G){
T = NULL;
while T未形成树
do 找最小代价边(u,v)且加入T后不形成回路
T = T ∪ (u,v);
}

选点:Prime——Point
手动模拟

实现
void Prim(G,T){
T = ∅;//初始化为空集
U = {'a'};//MST的顶点集
while((V-U) != ∅){
设(u,v)是使 u∈U与v∈V,且权值最小的边;
T = T∪{(u,v)};
U = U∪{v};
}
}
时间复杂度: O ( ∣ v ∣ 2 ) O(\mid v\mid^2) O(∣v∣2) ,故不依赖于边集,适用于稠密图

void Kruskal(G,T){
T = V;//初始化树,仅含顶点
numS = n;//连通分量数
while(numS > 1){//若连通分量数大于1
从E中取出权值最小的边(v,w);
if(v和u属于不同连通分量){
T = T∪{(v,u)};//将此边加入生成树中
numS--;
}
}
}
时间复杂度: O ( ∣ e ∣ l o g ∣ e ∣ ) O(\mid e\mid log\mid e\mid) O(∣e∣log∣e∣) ,适用于点多边少
带权路径长度:路劲上权值的和
- 满足连通的 u → v u \rightarrow v u→v
- 找所有 u → v u \rightarrow v u→v 中权值最小的路径
区别:
Dijkstra : 单源点最短路径
Floyd : 多源点最短路径

void Dijkstra(MGraph G,int v0,Patharc P,ShortPathTable D){
int flag[MAXVEX];//访问标记数组 0-未访问 1-已访问
for(int i = 0;i < G.numVertices;++i){
flag[i] = 0;
D[i] = G.arc[v0][i];//D[]为距离数组
P[i] = 0;//P[i]为到达vi的路径长度
}
D[v0] = 0,flag[v0] = 1,P[0] = 0;
for(int i = 0;i < G.numVertices;++i){
int min = INFINITY;//记录当前距离最小值
int idx;//记录距离最小值下标
for(int j = 0;j < G.numVertices;++j)//查找距离v0最近的点
if(!flag[j] && D[j] < min){//vj未被访问且距离最小
idx = j;
min = D[j];
}
flag[idx] = 1;
for(int w = 0;w < G.numVertices;++w)//更新各顶点最小距离
if(!flag[w] && (min + G.arc[idx][w] < D[w]))
D[w] = min + G.arc[idx][w];
P[i+1] = idx;
}
}

A
(
0
)
=
[
0
6
13
10
0
4
/
19
5
15
/
∞
0
]
=
[
0
6
13
10
0
4
5
15
0
]
P
A
T
H
(
0
)
=
[
a
b
a
c
b
a
b
a
c
/
b
c
c
a
c
a
b
/
c
b
]
=
[
a
b
a
c
b
a
b
c
c
a
c
a
b
]
A
(
1
)
=
[
0
6
10
/
13
10
0
4
5
/
∞
15
0
]
=
[
0
6
13
10
0
4
5
15
0
]
P
A
T
H
(
1
)
=
[
a
b
a
b
c
/
a
c
b
a
b
c
c
b
a
/
c
a
c
a
b
]
=
[
a
b
a
c
b
a
b
c
c
a
c
a
b
]
A
(
2
)
=
[
0
∞
/
6
13
9
/
10
0
4
5
15
0
]
=
[
0
6
13
9
0
4
5
15
0
]
P
A
T
H
(
2
)
=
[
a
c
b
/
a
b
a
c
b
c
a
/
b
a
b
c
c
a
c
a
b
]
=
[
a
b
a
c
b
c
a
b
c
c
a
c
a
b
]
有向无环图:一个有向图中无环,简称DAG
用于描述公共子式

在一个有向无环图的顶点组成的序列中
- 每个顶点出现且只出现一次
- 若顶点A在序列中排在顶点B前面,则在图中不存在B->A的路径
对有向无环图的一种排序,若存在一条顶点A到顶点B的路径,则在排序中顶点B出现在顶点A的后面。
AOV(用顶点表示活动):若用DAG表示一个工程,顶点表示活动,有向边 < v i , v j >
<vi,vj> 表示活动 v i v_i vi 必须先于活动 v j v_j vj 发生的关系。

每次从AOV中选一个没有前驱(入度=0)的顶点输出
从AOV网中删除该顶点和以该顶点为起点的所有边
重复 1. 2. 直至当前的AOV为空或当前网中不存在无前驱的顶点
若AOV为空,则该图存在拓扑序列
若AOV不空,则该图不存在拓扑序列

bool TopologicalSort(Graph G){
InitStack(S);//初始化栈,存储入度为0的顶点
for(int i = 0;i < G.vexnum;++i)
if(indegree[i] == 0)
Push(S,i);//将所有入度为0的顶点入栈
int count = 0;//表示已经输出的顶点数
while(!IsEmpty(S)){
Pop(S,i);//栈顶元素出栈
print[count++] = i;//输出顶点i
for(p = G.vertices[i].firstarc;p;p = p->nextarc){
//将所有i指向的顶点入度减1并将入度为0的顶点压栈
v = p->adjvex;//v是p的邻接点
if(!(--indegree[v]))
Push(S,v);//入度为0则入栈
}
}
if(count < G.vexnum)
return false;//排序失败,有向图有回路
else
return true;//存在拓扑序列
}
时间复杂度: O ( ∣ v ∣ + ∣ e ∣ ) O(\mid v\mid+\mid e\mid) O(∣v∣+∣e∣)
入度为零的顶点,即没有前驱活动的或前驱活动全部完成的顶点,工程可以从这个顶点所代表的活动开始或继续
若一个顶点有多个直接后继,则拓扑序列不唯一
AOV网中各个顶点地位相同,故可重新编号,若是DAG则其邻接矩阵是三角矩阵
有向无环图中,顶点表示事件,有向边表示活动,边上权值表示完成该活动的开销。用边表示活动的图为AOE。

拓扑序列: v 1 , v 2 , v 3 , v 4 , v 5 , v 5 , v 6 , v 7 , v 8 , v 9 v_1,v_2,v_3,v_4,v_5,v_5,v_6,v_7,v_8,v_9 v1,v2,v3,v4,v5,v5,v6,v7,v8,v9
从源点到汇点的路径中,具有最大路径长度的路径;关键路径上的活动为关键活动

活动最早开始时间 = 活动的起点事件最早开始时间
活动的最晚开始时间 = 活动的终点事件最晚开始时间-活动的时长
活动的最晚开始时间-活动的最早开始时间 = 0 的活动为关键活动

即关键路径为( v 1 , v 4 , v 7 , v 8 , v 10 , v 11 v_1,v_4,v_7,v_8,v_{10},v_{11} v1,v4,v7,v8,v10,v11)