第十九章 贪心算法、Prim算法和Kruskal算法

文章目录

  • 第十九章 贪心算法、Prim算法和Kruskal算法
  • 一、贪心算法
    • 1.介绍
    • 2.支付问题
  • 二、Prim算法
    • 1.最小生成树
    • 2.介绍
    • 3.代码实现
  • 三、Kruskal算法
    • 1.介绍
    • 2.代码实现

一、贪心算法

1.介绍

贪心算法(Greedy Algorithm)作为五大算法之一,在数据结构中的应用十分广泛。例如:在求最小生成树的 Prim 算法中,挑选的顶点是侯选边中权值最小的边的一个端点。在 Kruskal 算法中,每次选取权值最小的边加入集合。在构造哈夫曼树的过程中也是每次选取最小权值的结点构造二叉树。这种每次在执行子问题的求解时,总是选择当前最优的情形,恰好符合贪心的含义

贪心算法在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能的到整体最优解,但对范围相当广泛的许多问题,贪心算法能产生整体最优解或者是最优解的近似解

算法流程

  1. 建立数学模型来描述问题
  2. 把求解的问题分成若干个子问题
  3. 对每一个子问题求解,得到子问题的局部最优解
  4. 把子问题的局部最优解合成原来问题的一个解

伪代码

从问题的某一初始解出发while(能朝给定总目标前进){选择当前最优解作为可行解的一个解元素;}由所有解元素组合成问题的一个可行解

2.支付问题

题干

小明手中有 1,5,10,50,100 五种面额的纸币,每种纸币对应张数分别为 5,2,2,3,5 张。若小明需要支付 456 元,则最少需要多少张纸币

解题步骤

  1. 建立数学模型
    设小明每次选择纸币面额为 Xi,需要的纸币张数为 n 张,剩余待支付金额为 V,则有:
    X1 + X2 +…+ Xn = 456
  2. 问题拆分为子问题
    小明选择纸币进行支付的过程,可以划分为 n 个子问题:即每个子问题对应为:
    在未超过 456 的前提下,在剩余的纸币中选择一张纸币
  3. 制定贪心策略,求解子问题
    指定的贪心策略为:在允许的条件下选择面额最大的纸币
  4. 将所有解元素合并为原问题的解

代码实现

package com.sisyphus.greedy;/*** @Description: 贪心算法$* @Param: $* @return: $* @Author: Sisyphus* @Date: 7/29$*/
public class Notes {public static void main(String[] args) {int N = 5;int[] Count = {5,2,2,3,5};      //每张纸币的数量int[] Value = {1,5,10,50,100};  //每张纸币的面值int money = 456;int num = 0;for (int i = N - 1; i >= 0; i--) {int c = Math.min(money/Value[i],Count[i]);  //每一种面额需要的张数money -= c*Value[i];num += c;   //总张数}if (money > 0){System.out.println("无法支付当前金额!");}else{System.out.println("最少需要 " + num + " 张纸币");}}
}

二、Prim算法

1.最小生成树

在学习 Prim 算法前我们要先了解什么是最小生成树

  • 连通图:无向图中,若任意两个顶点与都有路径相通,则称该无向图为连通图
  • 强连通图:在有向图中,若任意两个顶点与都有路径相通,则称该有向图为强连通图
  • 连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网
  • 生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环
  • 最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树

2.介绍

Prim 算法是加权连通图里生成最小生成树的一种算法。该算法于 1930 年由捷克数学家沃伊捷赫·亚尔尼克发现。并在 1957年由美国计算机科学家罗伯特·普里姆独立发现。1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,Prim 算法又被称为 DJP 算法、亚尔尼克算法或普里姆-亚尔尼克算法

算法流程

  1. 对于一个加权连通图,其顶点集合为 V,边集合为 E。从集合 V 种任选一个顶点作为初始顶点,将该顶点标为已处理
  2. 已处理的所有顶点可以看成是一个集合 U,计算所有与集合 U 中相邻接的顶点的距离,选择距离最短的顶点,将其标记为已处理,并记录最短距离的边
  3. 不断计算已处理的顶点集合 U 和未处理的顶点的距离,每次选出距离最短的顶点标为已处理,同时记录最短距离的边,直至所有顶点都处理完
  4. 最终,所有记录的最短距离的边构成的树,即是最小生成树

  1. 首先,选取顶点 A 作为起始点,标记 A,并将顶点 A 添加至集合 U 中
  2. 集合 U 中只有一个顶点 A,与 A 邻接的顶点有 B 和 C,B、C 距 A 的距离分别为 6、3.选择距离最短的边(A,C),将 C 标记,并将 C 添加至集合 U 中
  3. 集合 U 中顶点为 A 和 C。与顶点 A 邻接的有 B、C,对应距离为 6、3.与 C 邻接的顶点有 B、F、E,对应的距离为 4、7、8.由于顶点 A、C 均被标记,故不能选择距离为 3 的路径。此时应选择距离最短边(C,B)。标记 B 并将 B 添加至集合 U 中
  4. 集合 U 新加入顶点 B。与顶点 B 邻接顶点有 A、C、D、F。A、C 已经在集合内,不能再被选取。顶点 B 到顶点 D、F 的距离分别为 2、3。顶点 C 到顶点 E、F 的距离分别为 7、8.因此选择距离最短边(B,D),将 D 标记,并将 D 添加至集合 U 中
  5. 集合 U 中顶点有 A、B、C、D。顶点 A 无可选顶点。顶点 B 可选顶点有 F,距离为 3.顶点 C 可选顶点有 E、F,对应距离分别为 8、7。顶点 D 可选顶点为 F,对应距离为 6。因此选取距离最短的边(B,F),标记 F,并将 F 添加至集合 U 中
  6. 集合 U 中顶点有 A、B、C、D、F。顶点 A、B、D 均无可选顶点。顶点 C 可选顶点为 E,对应距离为 8。顶点 F 可选顶点为 E,对应距离为 7。选取距离最短的边(F,E),标记 E,将 E 添加至集合 U 中
  7. 集合 U 中顶点有 A、B、C、D、E、F,但是均没有可选顶点,树的生成过程结束

3.代码实现

package com.sisyphus.prim;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** @Description: Prim 算法$* @Param: $* @return: $* @Author: Sisyphus* @Date: 7/29$*/
public class Prim {final static int INF = Integer.MAX_VALUE;public static void main(String[] args) {int[][] matrix = new int[][]{{0,6,3,INF,INF,INF},{6,0,4,2,INF,3},{3,4,0,INF,8,7},{INF,2,INF,0,INF,6},{INF,INF,8,INF,0,7},{INF,3,7,6,7,0},};int[] parents = prim(matrix);//0 ~ (parents.length-1)对应 A~F ,存放最小生成树 A~F 的父结点System.out.println(Arrays.toString(parents));}private static int[] prim(int[][] matrix){List<Integer> reachedVertexList = new ArrayList<>();//选择顶点 0 为初始顶点,放入已触达顶点集合中reachedVertexList.add(0);//创建最小生成树数组,首元素设为 -1,代表生成树的根结点int[] parents = new int[matrix.length];parents[0] = -1;//边的权值int weight;//源顶点下标int fromIndex = 0;//目标顶点下标int toIndex = 0;while(reachedVertexList.size() < matrix.length){weight = INF;//在已触达的顶点中,寻找到达新顶点的最短边for (Integer vertextIndex : reachedVertexList){for (int i = 0; i < matrix.length; i++) {if (!reachedVertexList.contains(i)){if (matrix[vertextIndex][i] < weight){fromIndex = vertextIndex;toIndex = i;weight = matrix[vertextIndex][i];}}}}//确定了权值最小的目标顶点,放入已触达顶点集合reachedVertexList.add(toIndex);//放入最小生成树的数组parents[toIndex] = fromIndex;}return parents;}
}

时间复杂度为 O(n^2)

三、Kruskal算法

1.介绍

Kruskal 算法的时间复杂度为 O(edge),所以适合于求边稀疏的网的最小生成树

算法流程

  1. 把图中的所有边按距离从小到大排序
  2. 把图中的 n 个顶点看成独立的 n 棵树组成的森林
  3. 按权值从小到大选择边,如果这条边连接的两个顶点不属于同一棵树,则这条边符合条件
  4. 重复步骤 3 直到所有顶点都在同一棵树内或者有 n-1 条边为止

  1. 首先将所有的边按照距离从小到大进行排序,排序结果为:(B,D)、(B,F)、(A,C)、(B,C)、(A,B)、(D,F)、(E,F)、(C,E)
  2. 距离最短的边为 (B,D),且顶点 B、D 不属于同一棵树,将顶点 B、D 合并到同一棵子树
  3. 距离最短的边为 (A,C),且顶点 A、C 不属于同一棵树,将顶点 A、C 合并到同一棵子树
  4. 距离最短的边为 (B,F),且顶点 B、F 不属于同一棵树,将顶点 B、F 合并到同一棵子树
  5. 距离最短的边为 (B,C),且顶点 B、C 不属于同一棵树,将顶点 B、C 合并到同一棵子树
  6. 距离最短的边为 (A,B),但是顶点 A、B 属于同一棵树,因此不能选择此边
  7. 距离最短的边为 (E,F),但是顶点 E、F 属于同一棵树,因此不能选择此边
  8. 距离最短的边为 (E,F),且顶点 E、F 不属于同一棵树,将顶点 E、F 合并到同一棵子树
  9. 所有顶点均在同一棵树内,生成过程完毕

2.代码实现

package com.sisyphus.kruskal;import java.util.ArrayList;
import java.util.Scanner;/*** @Description: Kruskal 算法$* @Param: $* @return: $* @Author: Sisyphus* @Date: 7/29$*/
public class Kruskal {private int edgeNum;private char[] vertexs; //顶点数组private int[][] matrix; //邻接矩阵//使用INF代表两个顶点不能连通private static final int INF=Integer.MAX_VALUE;//构造器public Kruskal(char[] vertexs,int[][] matrix){this.vertexs=vertexs;this.matrix=matrix;//统计边的条数 邻接矩阵右上角for (int i = 0; i < vertexs.length; i++) {for (int j = i+1; j < vertexs.length; j++) {if (this.matrix[i][j]!=INF){this.edgeNum++;}}}}public static void main(String[] args) {char[] vertexs= {'A','B','C', 'D', 'E','F'};//克魯斯卡尔算法的邻接矩阵int matrix[][]= {{0,6,3,INF,INF,INF},{6,0,4,2,INF,3},{3,4,0,INF,8,7},{INF,2,INF,0,INF,6},{INF,INF,8,INF,0,7},{INF,3,7,6,7,0},};//创建 KruskalCase 对象实例Kruskal kruskal = new Kruskal( vertexs, matrix);//输出构建的 kruskalCase.kruskal();kruskal.kruskal();}public void kruskal(){int index=0;//用于保存"已有最小生成树”中的每个顶点在最小生成树中的终点int[] ends=new int[edgeNum];//存放选取的边集合EData[] rets=new EData[edgeNum];//获取到图中的边EData[] eData=getEdges();//将所有的边按权值进行排序sortEdges(eData);//遍历edges数组,将边添加到最小生成树中时,判断是准备加入的边否形成了回路,如果没有,就加入rets,否则不能加入for (int i = 0; i < edgeNum; i++) {//获取到第i条边的第一个顶点(起点)int p1=getPosition(eData[i].start);//获取到第i条边的第2个顶点int p2=getPosition(eData[i].end);//获取pl这个顶点在已有最小生成树中的终点int m=getEnd(ends,p1);//获取p2这个顶点在已有最小生成树中的终点int n=getEnd(ends,p2);//是否构成回路if (m!=n){ends[m]=n; //设置m在"已有最小生成树"中的终点rets[index++]=eData[i];}}//打印最小生成树for (int i = 0; i < index; i++) {System.out.println(rets[i]);}}/***获取下标为 i 的顶点的终点(),用于后面判断两个顶点的终点是否相同* @param ends  数组 就是记录了各个顶点对应的终点是哪个,ends数组是在遍历过程中,逐步形成* @param i     表示传入的顶点对应的下标* @return      返回的就是下标为i的这个顶点对应的终点的下标*/public int getEnd(int[] ends,int i){while (ends[i]!=0){i=ends[i];}return i;}//获取顶点间所有的边(实例)集合public EData[] getEdges(){EData[] edges = new EData[edgeNum];int index = 0; //边的下标for (int i = 0; i < this.vertexs.length; i++) {for (int j = i+1; j < this.vertexs.length; j++) {//找到一条边if (this.matrix[i][j]!=INF){edges[index++]=new EData(this.vertexs[i],this.vertexs[j],this.matrix[i][j]);}}}return edges;}//对边集合进行排序(按照权值weight) 冒泡排序public void sortEdges(EData[] edges){for (int i = 0; i < edges.length-1; i++) {for (int j = 0; j < edges.length-i-1; j++) {if (edges[j].weight>edges[j+1].weight){EData tempEdge=edges[j];edges[j]=edges[j+1];edges[j+1]=tempEdge;}}}}//获得顶点对应的下标public int getPosition(char vertex){for (int i = 0; i < this.vertexs.length; i++) {if (this.vertexs[i]==vertex){return i;}}return -1;}
}//该类实例代表一条边
class EData{char start; //边的一端(顶点)char end;   //边的另一端(另一个顶点)int weight; //距离(权值)public EData(char start,char end,int weight){this.start=start;this.end=end;this.weight=weight;}@Overridepublic String toString() {return "EData[<"+start+","+end+">,"+"weight:"+weight+"]";}
}

【Java数据结构与算法】第十九章 贪心算法、Prim算法和Kruskal算法相关推荐

  1. 【数据结构】最小生成树问题(Prim算法和Kruskal算法)

    相关概念 连通图与它的生成树 连通图的生成树是包含图中全部顶点的一个极小连通子图.若图的顶点数为n,则它的生成树含有n-1条边.一个连通图可能拥有多个生成树. 最小生成树(Minimum-Spanni ...

  2. 求的带权图最小生成树的Prim算法和Kruskal算法

    求的带权图最小生成树的Prim算法和Kruskal算法 最小生成树的概念 最小生成树其实是最小权重生成树的简称. 一个连通图可能有多个生成树.当图中的边具有权值时,总会有一个生成树的边的权值之和小于或 ...

  3. 加权无向图与最小生成树(Prim算法和Kruskal算法)

    目录 0 引入 1 图的最小生成树定义及相关约定 2 最小生成树原理 2.1 性质 2.2 切分定理 3 贪心思想 4 Prim算法 4.1 算法步骤 4.2 API设计 4.3 Java代码演示 5 ...

  4. 最小生成树之Prim算法和Kruskal算法

    一个连通图可能有多棵生成树,而最小生成树是一副连通加权无向图中一颗权值最小的生成树,它可以根据Prim算法和Kruskal算法得出,这两个算法分别从点和边的角度来解决. Prim算法 输入:一个加权连 ...

  5. Prim算法和Kruskal算法

       Prim算法和Kruskal算法都能从连通图找出最小生成树.区别在于Prim算法是以某个顶点出发挨个找,而Kruskal是先排序边,每次选出最短距离的边再找. 一.Prim(普里姆算法)算法: ...

  6. matlab实现prim算法,Prim算法和Kruskal算法的Matlab实现

    Prim算法和Kruskal算法的Matlab实现 <计算机仿真>期末大作业 Prim算法和Kruskal算法的Matlab实现 05605刘禹050697(30) 连线问题应用举例: 欲 ...

  7. 【最小生成树】Prim算法和Kruskal算法的区别对比

    Prim算法和Kruskal算法都是从连通图中找出最小生成树的经典算法- 从策略上来说,Prim算法是直接查找,多次寻找邻边的权重最小值,而Kruskal是需要先对权重排序后查找的- 所以说,Krus ...

  8. 作业1-采用Prim算法和Kruskal算法构造最小生成树

    采用Prim算法和Kruskal算法构造最小生成树 实验报告 1.问题 2.解析 (1)Prim算法 (2)Kruskal算法 3.设计 (1)Prim算法 (2)Kruskal算法 4.分析 (1) ...

  9. 最小生成树-Prim算法和Kruskal算法

    转载自:https://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html Prim算法 1.概览 普里姆算法(Prim算法),图论 ...

最新文章

  1. 决策树-特征属性选择划分
  2. Scala算术运算符细节说明
  3. Traveling on the Axis (The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online)
  4. openlayers地图旋转_OpenLayers的使用---- 一个完全免费开源的地图JS库
  5. c 调用openoffice word转pdf_批量 Word 转 PDF 方法
  6. 老员工在线“黑”华为:早期手机难看丢人 习惯另外带苹果三星
  7. php object 对象不存在。增加对象_《相亲者女》:找一个匹配的对象,但永远不存在...
  8. sql 的 where 和 having 的区别和用法
  9. H264的码率控制方法(CBR, VBR, CVBR,ABR)
  10. 计算机管理用户拒绝访问,win10系统管理员账户拒绝访问怎么办
  11. C#开发工控上位机编程 csdn_机器视觉软件开发新人入门必看 --机器视觉软件开发学习路径...
  12. android 更新相册,Android 图片存入系统相册更新显示实例详解
  13. java加按钮_剪辑大神都在用的加字幕神器,你知道嘛
  14. Java华氏度与摄氏度之间的转换
  15. java 投票防重复_关于防止重复刷投票的个人观点
  16. 京东/淘宝的手机销售榜(前4名 -- 手机品牌 --手机型号*3 --手机分辨率 -- 手机操作系统 --安卓版本号)
  17. 弘辽科技:拼多多类目选错了有什么影响?怎么办?
  18. Android无法通过浏览器观看HLS直播的问题
  19. 功率谱和频谱的区别、联系(自用)
  20. linux 光功率 模块_光模块及调整光模块输入光功率的方法

热门文章

  1. WEB服务器技术名词
  2. 各种排序算法的总结和比较(转)
  3. ASP.NET操作Word的IIS权限设置
  4. Apache 服务器 参数设置
  5. JAVA Linux 排查CPU 过高的方法
  6. (76)时序分析基础(基本资源)
  7. (09)System Verilog 队列示例
  8. (28)FPGA面试题寄生效应
  9. FPGA控制AD7768采集
  10. FPGA UART总线协议简介