最小生成树 —— Prim 和 Kruskal 算法

最小生成树

定义

生成树:连通图包含全部顶点的一个极小连通子图

最小生成树:对于带权无向连通图 G=(V, E),G的所有生成树当中边的权值之和最小的生成树为 G 的最小生成树(MST)

性质

  1. 最小生成树不一定唯一,即最小生成树的树形不一定唯一。当带权无向连通图G的各边权值不等时或G只有节点数减1条边时,MST唯一
  2. 最小生成树的权值是唯一的,且是唯一的
  3. 最小生成树的边数为顶点数减1

算法

Prim 算法适用于稠密图,Kruskal算法适用于稀疏图

Prim 算法
  • 初始化:向空的结果树 T = ( V T , E T ) T=(V_T,E_T) T=(VT,ET) 中添加图 G=(V, E) 的任意顶点 u 0 u_0 u0 ,使 V T = { u 0 } V_T=\{u_0\} VT={u0} E T E_T ET 为空集;
  • 循环(直到 V T = V V_T=V VT=V):从 G 中选择满足 { ( u , v ) ∣ u ∈ V T , v ∈ V − V T } \{(u,v)|u\in V_T, v\in V-V_T\} {(u,v)uVT,vVVT} 且具有最小权值的边 (u,v),并置 V T = V T ∪ { v } , E T = E T ∪ { ( u , v ) } V_T=V_T \cup \{v\}, E_T=E_T\cup \{(u,v)\} VT=VT{v},ET=ET{(u,v)}
# 伪代码
void Prim(G, T){
    T = 空集;
    U = {w};
    while(V-U)!=空集){
        找到(u,v)使 u∈U 与 v∈(V-U),且权值最小的边;
        T=T∪{(u,v)};
        U=U∪{v};
    }
}

C++ 代码

void MST_Prim(Graph G){
    int min_weight[G.vexnum];	//存储最小权重 索引代表节点
    int adjvex[G.vexnum];		//存储边的节点 u
    for(int i=0;i<G.vexnum; i++){
        min_weight[i]=G.Edge[0][i];
        adjvex[i] = 0;
    }
    int min_arc;	//当前挑选的最小的边
    int min_vex;    //挑选的最小的权重边的节点 v
    for(int i=1; i<G.vexnum; i++){
        min_arc = MAX;
        for(int j=1; j<G.vexnum; j++)
            if(min_weight[j] != 0 && min_weight[j] < min_arc){
                min_arc = min_weight[j];
                min_vex = j;
            }
        min_weight[min_vex] = 0;
        for(int j=0; j<G.vexnum; j++){
            if(min_weight[j] != 0 && G.Edge[min_vex][j] < min_weight[j]){
                min_weight[j] = G.Edge[min_vex][j];
                adjvex[j] = min_vex;
            }
        }
    }
}

Python 代码

def mst_prim(graph):
    vex_num = len(graph)
    min_weight = []		# 存储最小权重 索引代表节点
    adjvex = []			# 存储边的节点 u
    for i in graph[0]:
        min_weight.append(i)
        adjvex.append(0)
    min_vex = 0
    for i in range(1, vex_num):
        min_arc = MAX
        for j in range(vex_num):
            if min_weight[j] != 0 and min_weight[j] < min_arc:
                min_arc = min_weight[j]
                min_vex = j
        min_weight[min_vex] = 0
        for k in range(vex_num):
            if min_weight[k] != 0 and min_weight[k] > graph[min_vex][k]:
                min_weight[k] = graph[min_vex][k]
                adjvex[k] = min_vex
	return adjvex

以这个图为例

最小生成树 —— Prim 和 Kruskal 算法_第1张图片

Python示例

MAX = 10e6

if __name__ == '__main__':
    # 上图的邻接矩阵表示法, A-E 分别用索引 0-4 表示
    graph = [
        [0, 3, 1, MAX, 4],
        [3, 0, 2, MAX, MAX],
        [1, 2, 0, 5, 6],
        [MAX, MAX, 5, 0, MAX],
        [4, MAX, 6, MAX, 0]
    ]
    adjvex = mst_prim(graph)
    print(adjvex)
'''
结果:
	[0, 2, 0, 2, 0]
'''

结果表示最小生成树的边有:1-2, 2-0, 3-2, 4-0 即 (B-C, C-A, D-C, E-A)即下图

最小生成树 —— Prim 和 Kruskal 算法_第2张图片

时间复杂度 O ( ∣ V ∣ 2 ) O(|V|^2) O(V2) 适用于稠密图

Kruskal 算法
  • 初始化: V T = V , E t = 空 集 V_T=V,E_t=空集 VT=V,Et= 。每个顶点构成一棵独立的树,T 是一个仅含 |V| 个顶点的森林
  • 循环(直到T为树):按图 G 的边的权值递增的顺序依次从 E − E T E-E_T EET 中选择一条边,若这条边加入后不构成回路,则将其加入 E T E_T ET ,否则舍弃
# 伪代码
void Kruskal(V, T){
    T=V;
    nums=n;
    while(nums>1){
        从E中取出权值最小的边(v, u);
        if(v和u属于T中不同的连通分量){
            T=T∪{(v,u)};
            nums--;
        }
    }
}

使用排序+并查集 (文末) 的方法(排序可以使用快排或堆排序)

C++ 代码

#define SIZE 100
int UFSets[SIZE];

void Initial(int S[]){
    for(int i=0;i<SIZE;i++)
        S[i]=-1;
}

int Find(int s[], int x){
    while(S[x]>=0)
        x=S[x];
    return x
}

void Union(int S[], int Root1, int Root2){
    S[Root2] = Root1;
}
typedef struct Edge{
    int a,b;
    int weight;
};
void MST_Kruskal(Graph G, Edge* edges, int* parent){
    heap_sort(edges);
    Initial(parent);
    for(int i=0, i<G.arcnum; i++){
        int a_root = Find(parent, edges[i].a);
        int b_root = Find(parent, edges[i].b);
        if(a_root != b_root)
            Union(parent, a_root, b_root);
    }
}
class UFSet:
    def __init__(self, length):
        self.set = []
        for i in range(length):
            self.set.append(-1)

    def find(self, x):
        while self.set[x] >= 0:
            x = self.set[x]
        return x

    def union(self, root1, root2):
        self.set[root2] = root1


def mst_kruskal(graph):
    res = []            # 存储最终选择的边
    nums = len(graph)
    edge_list = []      # 存储所有的边 [(节点,节点,权重)]
    # 对所有的边存储排序
    for i in range(nums):
        for j in range(i, nums):
            if graph[i][j] < MAX:
                edge_list.append((i, j, graph[i][j]))
    # 使用排序对边排序(可以自己使用快速排序,堆排序)
    edge_list = sorted(edge_list, key=lambda x:x[2]) 
    parent = UFSet(nums)            # 并查集初始化
    for i in range(len(edge_list)):
        a_root = parent.find(edge_list[i][0])
        b_root = parent.find(edge_list[i][1])
        if a_root != b_root:
            parent.union(a_root, b_root)
            res.append(edge_list[i])
    return res

还是以上个图为例

最小生成树 —— Prim 和 Kruskal 算法_第3张图片

if __name__ == '__main__':
    graph = [
        [0, 3, 1, MAX, 4],
        [3, 0, 2, MAX, MAX],
        [1, 2, 0, 5, 6],
        [MAX, MAX, 5, 0, MAX],
        [4, MAX, 6, MAX, 0]
    ]
    res = mst_kruskal(graph)
    print(res)
'''
结果:
	[(0, 2, 1), (1, 2, 2), (0, 4, 4), (2, 3, 5)]
'''

最小生成树 —— Prim 和 Kruskal 算法_第4张图片

时间复杂度 O ( ∣ E ∣ log ⁡ ∣ E ∣ ) O(|E| \log|E|) O(ElogE) 适用于稀疏图

并查集

一种简单的集合表示。通常用树的双亲表示法作为并查集的存储结构。通常用数组元素的下标代表元素名,用根节点的下标代表集合名,根节点的双亲节点为负数。

Initial(S) 将集合 S 中的每个元素都初始化为只有一个单元素的子集合

Union(S, Root1, Root2) 把集合 S 中的子集合(互不相交)Root2 并入子集合 Root1

Find(S, x) 查找集合 S 中单元素 x 所在子集合,并返回该子集合的名字

#define SIZE 100
int UFSets[SIZE];

void Initial(int S[]){
    for(int i=0;i<SIZE;i++)
        S[i]=-1;
}

int Find(int s[], int x){
    while(S[x]>=0)
        x=S[x];
    return x
}

void Union(int S[], int Root1, int Root2){
    S[Root2] = Root1;
}

你可能感兴趣的:(数据结构与算法,数据结构与算法,最小生成树,Prim,Kruskal)