【Java数据结构与算法】第十九章 贪心算法、Prim算法和Kruskal算法
第十九章 贪心算法、Prim算法和Kruskal算法
文章目录
- 第十九章 贪心算法、Prim算法和Kruskal算法
- 一、贪心算法
- 1.介绍
- 2.支付问题
- 二、Prim算法
- 1.最小生成树
- 2.介绍
- 3.代码实现
- 三、Kruskal算法
- 1.介绍
- 2.代码实现
一、贪心算法
1.介绍
贪心算法(Greedy Algorithm)作为五大算法之一,在数据结构中的应用十分广泛。例如:在求最小生成树的 Prim 算法中,挑选的顶点是侯选边中权值最小的边的一个端点。在 Kruskal 算法中,每次选取权值最小的边加入集合。在构造哈夫曼树的过程中也是每次选取最小权值的结点构造二叉树。这种每次在执行子问题的求解时,总是选择当前最优的情形,恰好符合贪心的含义
贪心算法在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能的到整体最优解,但对范围相当广泛的许多问题,贪心算法能产生整体最优解或者是最优解的近似解
算法流程
- 建立数学模型来描述问题
- 把求解的问题分成若干个子问题
- 对每一个子问题求解,得到子问题的局部最优解
- 把子问题的局部最优解合成原来问题的一个解
伪代码
从问题的某一初始解出发while(能朝给定总目标前进){选择当前最优解作为可行解的一个解元素;}由所有解元素组合成问题的一个可行解
2.支付问题
题干
小明手中有 1,5,10,50,100 五种面额的纸币,每种纸币对应张数分别为 5,2,2,3,5 张。若小明需要支付 456 元,则最少需要多少张纸币
解题步骤
- 建立数学模型
设小明每次选择纸币面额为 Xi,需要的纸币张数为 n 张,剩余待支付金额为 V,则有:
X1 + X2 +…+ Xn = 456 - 问题拆分为子问题
小明选择纸币进行支付的过程,可以划分为 n 个子问题:即每个子问题对应为:
在未超过 456 的前提下,在剩余的纸币中选择一张纸币 - 制定贪心策略,求解子问题
指定的贪心策略为:在允许的条件下选择面额最大的纸币 - 将所有解元素合并为原问题的解
代码实现
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 算法、亚尔尼克算法或普里姆-亚尔尼克算法
算法流程
- 对于一个加权连通图,其顶点集合为 V,边集合为 E。从集合 V 种任选一个顶点作为初始顶点,将该顶点标为已处理
- 已处理的所有顶点可以看成是一个集合 U,计算所有与集合 U 中相邻接的顶点的距离,选择距离最短的顶点,将其标记为已处理,并记录最短距离的边
- 不断计算已处理的顶点集合 U 和未处理的顶点的距离,每次选出距离最短的顶点标为已处理,同时记录最短距离的边,直至所有顶点都处理完
- 最终,所有记录的最短距离的边构成的树,即是最小生成树
- 首先,选取顶点 A 作为起始点,标记 A,并将顶点 A 添加至集合 U 中
- 集合 U 中只有一个顶点 A,与 A 邻接的顶点有 B 和 C,B、C 距 A 的距离分别为 6、3.选择距离最短的边(A,C),将 C 标记,并将 C 添加至集合 U 中
- 集合 U 中顶点为 A 和 C。与顶点 A 邻接的有 B、C,对应距离为 6、3.与 C 邻接的顶点有 B、F、E,对应的距离为 4、7、8.由于顶点 A、C 均被标记,故不能选择距离为 3 的路径。此时应选择距离最短边(C,B)。标记 B 并将 B 添加至集合 U 中
- 集合 U 新加入顶点 B。与顶点 B 邻接顶点有 A、C、D、F。A、C 已经在集合内,不能再被选取。顶点 B 到顶点 D、F 的距离分别为 2、3。顶点 C 到顶点 E、F 的距离分别为 7、8.因此选择距离最短边(B,D),将 D 标记,并将 D 添加至集合 U 中
- 集合 U 中顶点有 A、B、C、D。顶点 A 无可选顶点。顶点 B 可选顶点有 F,距离为 3.顶点 C 可选顶点有 E、F,对应距离分别为 8、7。顶点 D 可选顶点为 F,对应距离为 6。因此选取距离最短的边(B,F),标记 F,并将 F 添加至集合 U 中
- 集合 U 中顶点有 A、B、C、D、F。顶点 A、B、D 均无可选顶点。顶点 C 可选顶点为 E,对应距离为 8。顶点 F 可选顶点为 E,对应距离为 7。选取距离最短的边(F,E),标记 E,将 E 添加至集合 U 中
- 集合 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),所以适合于求边稀疏的网的最小生成树
算法流程
- 把图中的所有边按距离从小到大排序
- 把图中的 n 个顶点看成独立的 n 棵树组成的森林
- 按权值从小到大选择边,如果这条边连接的两个顶点不属于同一棵树,则这条边符合条件
- 重复步骤 3 直到所有顶点都在同一棵树内或者有 n-1 条边为止
- 首先将所有的边按照距离从小到大进行排序,排序结果为:(B,D)、(B,F)、(A,C)、(B,C)、(A,B)、(D,F)、(E,F)、(C,E)
- 距离最短的边为 (B,D),且顶点 B、D 不属于同一棵树,将顶点 B、D 合并到同一棵子树
- 距离最短的边为 (A,C),且顶点 A、C 不属于同一棵树,将顶点 A、C 合并到同一棵子树
- 距离最短的边为 (B,F),且顶点 B、F 不属于同一棵树,将顶点 B、F 合并到同一棵子树
- 距离最短的边为 (B,C),且顶点 B、C 不属于同一棵树,将顶点 B、C 合并到同一棵子树
- 距离最短的边为 (A,B),但是顶点 A、B 属于同一棵树,因此不能选择此边
- 距离最短的边为 (E,F),但是顶点 E、F 属于同一棵树,因此不能选择此边
- 距离最短的边为 (E,F),且顶点 E、F 不属于同一棵树,将顶点 E、F 合并到同一棵子树
- 所有顶点均在同一棵树内,生成过程完毕
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算法相关推荐
- 【数据结构】最小生成树问题(Prim算法和Kruskal算法)
相关概念 连通图与它的生成树 连通图的生成树是包含图中全部顶点的一个极小连通子图.若图的顶点数为n,则它的生成树含有n-1条边.一个连通图可能拥有多个生成树. 最小生成树(Minimum-Spanni ...
- 求的带权图最小生成树的Prim算法和Kruskal算法
求的带权图最小生成树的Prim算法和Kruskal算法 最小生成树的概念 最小生成树其实是最小权重生成树的简称. 一个连通图可能有多个生成树.当图中的边具有权值时,总会有一个生成树的边的权值之和小于或 ...
- 加权无向图与最小生成树(Prim算法和Kruskal算法)
目录 0 引入 1 图的最小生成树定义及相关约定 2 最小生成树原理 2.1 性质 2.2 切分定理 3 贪心思想 4 Prim算法 4.1 算法步骤 4.2 API设计 4.3 Java代码演示 5 ...
- 最小生成树之Prim算法和Kruskal算法
一个连通图可能有多棵生成树,而最小生成树是一副连通加权无向图中一颗权值最小的生成树,它可以根据Prim算法和Kruskal算法得出,这两个算法分别从点和边的角度来解决. Prim算法 输入:一个加权连 ...
- Prim算法和Kruskal算法
Prim算法和Kruskal算法都能从连通图找出最小生成树.区别在于Prim算法是以某个顶点出发挨个找,而Kruskal是先排序边,每次选出最短距离的边再找. 一.Prim(普里姆算法)算法: ...
- matlab实现prim算法,Prim算法和Kruskal算法的Matlab实现
Prim算法和Kruskal算法的Matlab实现 <计算机仿真>期末大作业 Prim算法和Kruskal算法的Matlab实现 05605刘禹050697(30) 连线问题应用举例: 欲 ...
- 【最小生成树】Prim算法和Kruskal算法的区别对比
Prim算法和Kruskal算法都是从连通图中找出最小生成树的经典算法- 从策略上来说,Prim算法是直接查找,多次寻找邻边的权重最小值,而Kruskal是需要先对权重排序后查找的- 所以说,Krus ...
- 作业1-采用Prim算法和Kruskal算法构造最小生成树
采用Prim算法和Kruskal算法构造最小生成树 实验报告 1.问题 2.解析 (1)Prim算法 (2)Kruskal算法 3.设计 (1)Prim算法 (2)Kruskal算法 4.分析 (1) ...
- 最小生成树-Prim算法和Kruskal算法
转载自:https://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html Prim算法 1.概览 普里姆算法(Prim算法),图论 ...
最新文章
- 决策树-特征属性选择划分
- Scala算术运算符细节说明
- Traveling on the Axis (The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online)
- openlayers地图旋转_OpenLayers的使用---- 一个完全免费开源的地图JS库
- c 调用openoffice word转pdf_批量 Word 转 PDF 方法
- 老员工在线“黑”华为:早期手机难看丢人 习惯另外带苹果三星
- php object 对象不存在。增加对象_《相亲者女》:找一个匹配的对象,但永远不存在...
- sql 的 where 和 having 的区别和用法
- H264的码率控制方法(CBR, VBR, CVBR,ABR)
- 计算机管理用户拒绝访问,win10系统管理员账户拒绝访问怎么办
- C#开发工控上位机编程 csdn_机器视觉软件开发新人入门必看 --机器视觉软件开发学习路径...
- android 更新相册,Android 图片存入系统相册更新显示实例详解
- java加按钮_剪辑大神都在用的加字幕神器,你知道嘛
- Java华氏度与摄氏度之间的转换
- java 投票防重复_关于防止重复刷投票的个人观点
- 京东/淘宝的手机销售榜(前4名 -- 手机品牌 --手机型号*3 --手机分辨率 -- 手机操作系统 --安卓版本号)
- 弘辽科技:拼多多类目选错了有什么影响?怎么办?
- Android无法通过浏览器观看HLS直播的问题
- 功率谱和频谱的区别、联系(自用)
- linux 光功率 模块_光模块及调整光模块输入光功率的方法