| 图与树搜索算法 |
| --- |
| * α–β
* A\*
* B\*（英语：）
* 回溯
* 集束（英语：）
* 贝尔曼-福特
* 最佳优先（英语：）
* 双向
* 布魯瓦卡（英语：）
* 分支限界
* BFS
* 大英博物馆
* D\*（英语：）
* DFS
* 深度限制（英语：）
* 迪杰斯特拉
* 愛德蒙斯（英语：）
* 弗洛伊德
* 边缘搜索
* 爬山
* IDA\*（英语：）
* 迭代加深
* 约翰逊（英语：）
* 跳点（英语：）
* 克鲁斯克尔
* 词典BFS（英语：）
* LPA\*（英语：）
* 普里姆
* SMA\*（英语：）
* 最短路径快速
 |
| 
分类 |
| * 图算法
* 搜索算法
* 算法列表（英语：）
 |
| 
相关主题 |
| * 动态规划
* 图的遍历
* 树的遍历
 |
| * 
* 论
* 编
 |

**普里姆算法**（英語：）是图论中的一种贪心算法，可在一个加权连通图中找到其最小生成树。意即由此算法搜索到的边子集所构成的树中，不但包括了连通图里的所有顶点，且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克（英语：）发现；并在1957年由美国计算机科学家羅伯特·C·普里姆独立发现；1959年，艾兹格·迪科斯彻再次发现了该算法。因此，在某些场合，普里姆算法又被称为**DJP算法**、**亚尔尼克算法**或**普里姆－亚尔尼克算法**。

描述
--

从单一顶点开始，普里姆算法按照以下步骤逐步扩大树中所含顶点的数目，直到遍及连通图的所有顶点。

1. 输入：一个加权连通图，其中顶点集合为V，边集合为E；
2. 初始化：{\displaystyle V\_{\text{new}}=\{x\}}，其中x为集合V中的任一节点（起始点），{\displaystyle E\_{\text{new}}=\{\}}；
3. 重复下列操作，直到{\displaystyle V\_{\text{new}}=V}：
	1. 在集合E中选取权值最小的边(u,v)，其中u为集合{\displaystyle V\_{\text{new}}}中的元素，而v则是V中没有加入{\displaystyle V\_{\text{new}}}的顶点（如果存在有多条满足前述条件即具有相同权值的边，则可任意选取其中之一）；
	2. 将v加入集合{\displaystyle V\_{\text{new}}}中，将(u,v)加入集合{\displaystyle E\_{\text{new}}}中；
4. 输出：使用集合{\displaystyle V\_{\text{new}}}和{\displaystyle E\_{\text{new}}}来描述所得到的最小生成树。

时间复杂度
-----

| 最小边、权的数据结构 | 时间复杂度（总计） |
| --- | --- |
| 邻接矩阵、搜索 | O(|V|^{2}) |
| 二叉堆（后文伪代码中使用的数据结构）、邻接表 | {\displaystyle O((|V|+|E|)\log |V|)=O(|E|\log |V|)} |
| 斐波那契堆、邻接表 | {\displaystyle O(|E|+|V|\log |V|)} |

通过邻接矩阵图表示的简易实现中，找到所有最小权边共需O(|V|^{2})的运行时间。使用简单的二叉堆与邻接表来表示的话，普里姆算法的运行时间则可缩减为{\displaystyle O(|E|\log |V|)}，其中|E|为连通图的边集大小，|V|为点集大小。如果使用较为复杂的斐波那契堆，则可将运行时间进一步缩短为{\displaystyle O(|E|+|V|\log |V|)}，这在连通图足够密集时（当|E|满足{\displaystyle \Omega (|V|\log |V|)}条件时），可较显著地提高运行速度。

例示
--

| 图例 | 说明 | 不可选 | 可选 | 已选 |
| --- | --- | --- | --- | --- |
|  | 此为原始的加权连通图。每条边一侧的数字代表其权值。 | - | - | - |
|  | 顶点**D**被任意选为起始点。顶点**A**、**B**、**E**和**F**通过单条边与**D**相连。**A**是距离**D**最近的顶点，因此将**A**及对应边**AD**以高亮表示。 | C, G | A, B, E, F | D |
|  | 下一个顶点为距离**D**或**A**最近的顶点。**B**距**D**为9，距**A**为7，**E**距**D**為15，**F**距**D**為6。因此，**F**距**D**或**A**最近，因此将顶点**F**与相应边**DF**以高亮表示。 | C, G | B, E, F | A, D |
|  | 算法继续重复上面的步骤。距离**A**为7的顶点**B**被高亮表示。 | C | B, E, G | A, D, F |
|  | 在当前情况下，可以在**C**、**E**与**G**间进行选择。**C**距**B**为8，**E**距**B**为7，**G**距**F**为11。**E**最近，因此将顶点**E**与相应边**BE**高亮表示。 | 无 | C, E, G | A, D, F, B |
|  | 这里，可供选择的顶点只有**C**和**G**。**C**距**E**为5，**G**距**E**为9，故选取**C**，并与边**EC**一同高亮表示。 | 无 | C, G | A, D, F, B, E |
|  | 顶点**G**是唯一剩下的顶点，它距**F**为11，距**E**为9，**E**最近，故高亮表示**G**及相应边**EG**。 | 无 | G | A, D, F, B, E, C |
|  | 现在，所有顶点均已被选取，图中绿色部分即为连通图的最小生成树。在此例中，最小生成树的权值之和为39。 | 无 | 无 | A, D, F, B, E, C, G |

证明
--

已知图G的边数量为numEdge, 顶点数量为numVert, prim生成的树为T0, 最小生成树（MST）为Tmin

则有，cost(Tmin)<=cost(T0)

设：
T0 的 numVert-1 条边按照权重由小到大排列依次为：ek1, ek2, ek3, ... ekn

Tmin 的 numVert-1 条边按照权重由小到大排列依次为：eg1, eg2, eg3, ... egn

其中n=numVert-1

两棵树的边从小到大权重比较，设第一个属于 T0 但不属于 Tmin 的边为 ed1, 连接该边的两个顶点为 (vs, ve1)

同时存在第一个属于 Tmin 但不属于 T0 且以vs为顶点的边，记为 ed2, 连接该边的两个顶点为 (vs, ve2)。

两个边的起点相同。由Prim算法性质可知，w(ed2) >= w(ed1)

此时，在 Tmin 中删除 ed2 ，添加 ed1，边的数量和顶点数量均不变，且不存在环，因此得到新的生成树Tnew,且cost(Tmin)>=cost(Tnew)

又因为 Tmin 是MST 所以 cost(Tmin)=cost(Tnew)。

以此类推，cost(Tmin)=cost(T0)

T0是最小生成树， 得证.

各語言程序代码
-------

### Pascal語言程序

部分主程序段：

```
procedure prim(v0:integer);
var
 lowcost,closest:array[1..maxn] of integer;
 i,j,k,min,ans:integer;

 for i:=1 to n do
 begin
 lowcost[i]:=cost[v0,i];
 closest[i]:=v0;
 end;
 for i:=1 to n-1 do
 begin
 min:=maxint;
 for j:=1 to n do
 if (lowcost[j]<min) and (lowcost[j]<>0) then
 begin
 min:=lowcost[j];
 k:=j;
 end;
 inc(ans, lowcost[k]);
 lowcost[k]:=0;
 for j:=1 to n do
 if cost[k,j]<lowcost[j] then
 begin
 lowcost[j]:=cost[k,j];
 closest[j]:=k;
 end;
 end;
 writeln(ans);
end;

```

### C语言代码

```
//来源：严蔚敏 吴伟民《数据结构(C语言版)》

void MiniSpanTree\_PRIM (MGraph G, VertexType u) {
 /\* 用普利姆算法從第u個頂點出發構造網G 的最小生成樹T,輸出T的各條邊。
 記錄從頂點集U到V-U的代價最小的邊的輔助數組定義：
 struct
 {
 VertexType adjvex;
 VRtype lowcost;
 }closedge[MAX\_VERTEX\_NUM];
 \*/
 
 k = LocateVex(G, u);
 for (j = 0 ; j < G.vexnum; j++) { //輔助數組初始化
 if (j != k)
 closedge[j] = {u, G.arcs[k][j].adj}; //{adjvex, lowcost}
 }
 closedge[k].lowcost = 0; //初始，U={u}
 for (i = 1; i < G.vexnum ; i++) { //選擇其餘G.vexnum -1 個頂點
 k = minimum(closedge); //求出T的下個結點：第k結點
 // 此时 closedge[k].lowcost = MIN{ closedge[Vi].lowcost|closedge[Vi].lowcost>0,Vi∈V-U}
 printf(closedge[k].adjvex, G.vexs[k]); //輸出生成樹的邊
 closedge[k].lowcost = 0; //第k條邊併入U集
 for (j = 0; j < G.vexnum; j++) {
 
 //新頂點併入U後重新選擇最小邊
 if (G.arcs[k][j].adj < closedge[j].lowcost && closedge[j].lowcost!=0) 
 closedge[j] = {G.vex[k], G.arcs[k][j].adj};
 }
 }
}

```

```
//来源： 浙大-陈越 《数据结构》

#define ERROR -1
Vertex FindMinDist( MGraph Graph, WeightType dist[] )
{
 /\* 返回未被收录顶点中dist最小者 \*/
 
 Vertex MinV, V;
 WeightType MinDist = INFINITY;

 for (V=0; V<Graph->Nv; V++) {
 if ( dist[V]!=0 && dist[V]<MinDist) {
 /\* 若V未被收录，且dist[V]更小 \*/
 MinDist = dist[V]; /\* 更新最小距离 \*/
 MinV = V; /\* 更新对应顶点 \*/
 }
 }
 if (MinDist < INFINITY) /\* 若找到最小dist \*/
 return MinV; /\* 返回对应的顶点下标 \*/
 else return ERROR; /\* 若这样的顶点不存在，返回-1作为标记 \*/
}

int Prim( MGraph Graph, LGraph MST )
{ /\* 将最小生成树保存为邻接表存储的图MST，返回最小权重和 \*/
 WeightType dist[MaxVertexNum], TotalWeight;
 Vertex parent[MaxVertexNum], V, W;
 int VCount;
 Edge E;

 /\* 初始化。默认初始点下标是0 \*/
 for (V=0; V<Graph->Nv; V++) {
 /\* 这里假设若V到W没有直接的边，则Graph->G[V][W]定义为INFINITY \*/
 dist[V] = Graph->G[0][V];
 parent[V] = 0; /\* 暂且定义所有顶点的父结点都是初始点0 \*/
 }
 TotalWeight = 0; /\* .

 ..........初始化权重和 \*/
 VCount = 0; /\* 初始化收录的顶点数 \*/
 /\* 创建包含所有顶点但没有边的图。注意用邻接表版本 \*/
 MST = CreateGraph(Graph->Nv);
 E = (Edge)malloc( sizeof(struct ENode) ); /\* 建立空的边结点 \*/

 /\* 将初始点0收录进MST \*/
 dist[0] = 0;
 VCount ++;
 parent[0] = -1; /\* 当前树根是0 \*/

 while (1) {
 V = FindMinDist( Graph, dist );
 /\* V = 未被收录顶点中dist最小者 \*/
 if ( V==ERROR ) /\* 若这样的V不存在 \*/
 break; /\* 算法结束 \*/

 /\* 将V及相应的边<parent[V], V>收录进MST \*/
 E->V1 = parent[V];
 E->V2 = V;
 E->Weight = dist[V];
 InsertEdge( MST, E );
 TotalWeight += dist[V];
 dist[V] = 0;
 VCount++;

 for( W=0; W<Graph->Nv; W++ ) /\* 对图中的每个顶点W \*/
 if ( dist[W]!=0 && Graph->G[V][W]<INFINITY ) {
 /\* 若W是V的邻接点并且未被收录 \*/
 if ( Graph->G[V][W] < dist[W] ) {
 /\* 若收录V使得dist[W]变小 \*/
 dist[W] = Graph->G[V][W]; /\* 更新dist[W] \*/
 parent[W] = V; /\* 更新树 \*/
 }
 }
 } /\* while结束\*/
 if ( VCount < Graph->Nv ) /\* MST中收的顶点不到|V|个 \*/
 TotalWeight = ERROR;
 return TotalWeight; /\* 算法执行完毕，返回最小权重和或错误标记 \*/
}

```

### Python语言实现

此份源码使用了堆优化

```
from queue import PriorityQueue as priority\_queue
from math import inf
class Node:
    def \_\_init\_\_(self,id,\*\*kwargs):
        self.id = id
        self.fst = self.lst = None

    def \_\_iter\_\_(self):
        return NodeIterator(self)

    def \_\_repr\_\_(self):
        return "Node(%d)"%self.id

class NodeIterator:
    def \_\_init\_\_(self,Node):
        self.prst = Node.fst

    def \_\_next\_\_(self):
        if self.prst == None:
            raise StopIteration()
        ret = self.prst
        self.prst = self.prst.nxt
        return ret

class Edge:
    def \_\_init\_\_(self,fr,to,\*\*kwargs):
        if fr.fst == None:
            fr.fst = self
        else:
            fr.lst.nxt = self
        fr.lst = self
        self.to = to
        self.nxt = None
        self.w = 1 if 'w' not in kwargs else kwargs['w']

    def \_\_repr\_\_(self):
        return "Edge({},{},w = {})",format(self.fr,self.to,self.w)

class Graph:
    def \_\_init\_\_(self,V):
        self.nodecnt = V
        self.nodes = [Node(i) for i in range(V)]
        self.edges = []

    def add(self,u,v,\*\*kwargs):
        self.edges.append(Edge(self.nodes[u],self.nodes[v],\*\*kwargs))

    def MST\_prim(self,begin):
 '''
 prim algorithm on a graph(with heap),
 returns the weight sum of the tree
 or -1 if impossible
 '''
        q = priority\_queue()
        vis = [False for \_ in range(self.nodecnt)]
        q.put((0,begin))
        ret = 0
        while not q.empty():
            prst = q.get()
            if vis[prst[1]]:
                continue
            vis[prst[1]] = True
            ret += prst[0]
            for i in self.nodes[prst[1]]:
                if not vis[i.to.id]:
                    q.put((i.w,i.to.id))
        if all(vis):
            return ret
        else:
            return -1

```

### Java语言实现

```
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Prim {
 public static List<Vertex> vertexList = new ArrayList<Vertex>();//结点集
 public static List<Edge> EdgeQueue = new ArrayList<Edge>();//边集
 public static List<Vertex> newVertex = new ArrayList<Vertex>();//已经 访问过的结点

 public static void main(String[] args) {
 primTree();
 }
 public static void buildGraph() {
 Vertex v1 = new Vertex("a");
 Prim.vertexList.add(v1);
 Vertex v2 = new Vertex("b");
 Prim.vertexList.add(v2);
 Vertex v3 = new Vertex("c");
 Prim.vertexList.add(v3);
 Vertex v4 = new Vertex("d");
 Prim.vertexList.add(v4);
 Vertex v5 = new Vertex("e");
 Prim.vertexList.add(v5);
 addEdge(v1, v2, 6);
 addEdge(v1, v3, 7);
 addEdge(v2, v3, 8);
 addEdge(v2, v5, 4);
 addEdge(v2, v4, 5);
 addEdge(v3, v4, 3);
 addEdge(v3, v5, 9);
 addEdge(v5, v4, 7);
 addEdge(v5, v1, 2);
 addEdge(v4, v2, 2);
 }
 public static void addEdge(Vertex a, Vertex b, int w) {
 Edge e = new Edge(a, b, w);
 Prim.EdgeQueue.add(e);
 }
 public static void primTree() {
 buildGraph();
 Vertex start = vertexList.get(0);
 newVertex.add(start);
 for (int n = 0; n < vertexList.size() - 1; n++) {
 Vertex temp = new Vertex(start.key);
 Edge tempedge = new Edge(start, start, 1000);
 for (Vertex v : newVertex) {
 for (Edge e : EdgeQueue) {
 if (e.start == v && !containVertex(e.end)) {
 if (e.key < tempedge.key) {
 temp = e.end;
 tempedge = e;
 }
 }
 }
 }
 newVertex.add(temp);
 }
 Iterator it = newVertex.iterator();
 while (it.hasNext()) {
 Vertex v = (Vertex) it.next();
 System.out.println(v.key);
 }
 }
 public static boolean containVertex(Vertex vte) {
 for (Vertex v : newVertex) {
 if (v.key.equals(vte.key))
 return true;
 }
 return false;
 }
}

class Vertex {
 String key;
 Vertex(String key) {
 this.key = key;
 }
}

class Edge {
 Vertex start;
 Vertex end;
 int key;
 Edge(Vertex start, Vertex end, int key) {
 this.start = start;
 this.end = end;
 this.key = key;
 }
}

```

參考
--

普林演算法與迪科斯彻演算法的策略相似。

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 

| * 
* 论
* 编
算法 |
| --- |
|  |
| 排序 | 

|  |  |
| --- | --- |
| 比较排序 | 
* 冒泡排序
* 选择排序
* 插入排序
* 希尔排序
* 快速排序
* 归并排序
* 堆排序
* 鸡尾酒排序
* 梳排序
* 侏儒排序
* 图书馆排序
* 内省排序
* 奇偶排序

 |
|  |
| 线性时间排序 | 
* 鸽巢排序
* 基数排序
* 計數排序
* 桶排序

 |
|  |
| 并行排序 | 
* 排序网络（英语：）
* Batcher归并网络

 |
|  |
| 不实用的 | 
* Bogo排序
* 臭皮匠排序

 |
|  |
| 图 | 
* 拓撲排序

 |

 |
|  |
| 搜索 | 

|  |  |
| --- | --- |
| 列表 | 
* 线性搜索
* 二分搜索
* 插值搜尋

 |
|  |
| 树・图 | 
* 广度优先搜索
	+ 最良優先搜索（英语：）
	+ 均一开销搜索
	+ A\*
* 深度优先搜索
	+ 迭代深化深度优先搜索
	+ 深度限制搜索（日语：）
	+ 双向搜索
* 分枝限定法（英语：）

 |
|  |
| 字符串 | 
* KMP算法
* 博耶-穆尔字符串搜索算法
* AC自动机算法
* 拉宾-卡普算法
* bitap算法

 |

 |
|  |
| 最短路问题 | 
* 戴克斯特拉算法
* 贝尔曼-福特算法
* A\*搜尋演算法
* Floyd-Warshall算法

 |
|  |
| 最小生成树 | 
* 普林姆算法
* 克鲁斯克尔演算法

 |
|  |
| 最大流最小割 | 
* 福特-富尔克森算法
* 埃德蒙兹-卡普算法
* 迪尼茨算法

 |
|  |
| 线性规划 | 
* 单纯形法
* 卡马卡尔算法（英语：）

 |
|  |
| 順序統計量 | 
* 选择算法
* 中位数的中位数（英语：）

 |
|  |
| 種類 | 
* 精确算法
* 近似算法
* 随机化算法

 |
|  |
| 其他 | 
* 分治法
* 动态规划
* 贪心算法

 |
|  |
| **Category:算法** |

 |