数据结构与算法--贪婪算法

贪婪算法

  • 贪婪算法分阶段地工作。在每个阶段,可以认为所做决定是最好的,而不考虑将来的后果。通常这意味着选择的是某个局部最优。这种“当前能获得的最优就拿”的策略是这类算法的名字来源。
  • 当算法终止时候,我们希望的到累积的局部最优解就等于全局最优,如果是这样的,那么算法就是正确的;否则算法得到的是一个次优解(suboptimal solution)。如果不需要绝对的最佳答案,那么有时候使用简单的贪婪算法生成近似的答案能得到一个时间复杂度更优的解。
案例分析
  • 举几个现实的贪婪算法的例子,明显的是货币找零问题。

    • 使用RMB找零钱,我们重复的配发最大额货币。于是为了找十七块六毛,
    • 我们拿出一张10元,一张5元,2张1元(2元货币现在少见了),加一个5毛硬币,两个一毛硬币。这么做,我们能保证使用最少的钞票和硬币,这个算法不是对每种货币通用,但是RMB是有效的
  • 交通问题

  • 以下会详细讲解两个案例,来说明贪婪算法的应用,第一个模拟调度问题,第二个模拟文件压缩算法,他是计算机科学最早的成果之一。

一个简单的调度问题
  • 假设我们计算机系统由任务j1,j2,j3 …jN,已知对应的运行时间分别是t1,t2,t3…tN,而处理器只有一个,为了把作业平均完成的时间最小化,调度这些作业的最好方式是什么???
  • 假设我们讨论的都是非预占调度(nonpreemptive scheduling):一旦开始一个任务就必须吧任务执行完。
  • 我们用以下案例简化,四个作业,已经运行时间,如下表格,一个可能的调度在图一中表示:
任务时间
j115
j28
j33
j410

在这里插入图片描述

  • 上图中j1 用时15个时间单位运行结束,j2用时23=(15+j2时间) 个时间单位,j3,用26,j4用36,所以平均完成时间是25。但是一个更好的调度完成时间是17.75 ,如下图中:
    在这里插入图片描述
  • 上图中给出的调度是按照最短的作业先完成来进行的。我们可以证明这种模式总会产生一个最优的调度,
  • 令调度表中的作业是j1,j2,j3,j4,…jn。第一个作业完成时间t_1,第二个作业t_1+t_2后完成,第三个作业依次类推,由此可以得到调度的总代价C有如下推论:
C = t_1 + (t_1+t_2) + (t_1+t_2+t_3) + ......+(t_1+t_2+t_3+......+t_n)
C = nt_1 + (n-1)t-2 + (n-2)t_3 + (n-3)t_4 + ....+ 2t_(n-1) + t_(n-1)
C = (n-1+1)t_1 + (n-2+1)t_2 + ... + (n-(n-1)+1)t_(n-1) + (n-n+1)t_n
  • 如上累加:∑k=1n\sum_{k=1}^nk=1n(n-k+1)tk = ∑k=1n\sum_{k=1}^nk=1n(n+1)tk - ∑k=1n\sum_{k=1}^nk=1nk*tk
  • 如上表达是可以看成,调度总时间和∑k=1n\sum_{k=1}^nk=1nk*tk 成反比,k表示第几个位置执行,tk表示指定第k次执行的时间,当这个数值越大,那么总的时间就越少,那么我们将权重越大的往后放就能得到最小值。
多处理器情况
  • 我们可以把问题扩展到多个处理器的情况,我们还是这些作业j1,j2,j3,j4,…jn。,时间t1,t2,t3…tN,另外有处理器P个。假设作业是有序的,最短的运行时间最先处理,加入P = 3.如下表格
任务时间
j13
j25
j36
j410
j511
j614
j715
j818
j920

在这里插入图片描述

  • 如上图他平均完成时间优化到了最小,作业1,4,7在处理器1 上运行,处理器二在2,5,8 上,处理器3 处理其余作业,总完成时间是165,平均是165/9 = 18.33
  • 解决多处理器情形的算法是按顺序开始作业,处理器之间轮换分配罪业,不难证明没有那个其他顺序能做到更好。但是还有其他顺序的最优解,如下图:
    在这里插入图片描述
  • 如上,还是最小的先执行,只是我们将是哪个处理器处理的任务换了换而已
将最后完成时间最小化
  • 还需要考虑一个非常类似情况,假设我们只关注最后的作业的结束时间,在上面的两个例子中,他们的完成时间分别是40 与38,第二种最后完成时间是更小的,我们能找出更小的最后完成时间
  • 在上面二个方法中,整个序列完成时间更早是更可取的方法,我们有如下算法实现:
算法分析
  • 首先获取任务,并且从小到大排序
  • 每次分配统计每个处理器处理需要的总时间
  • 每次取出最小的一个任务,给定处理器列表中总时间最小的那个,也就先处理完的优先分配任务
  • 依次处理任务,直到任务都处理完结
代码实现
/*** 贪心算法,处理器调度问题** @author liaojiamin* @Date:Created in 16:26 2021/1/12*/
public class ProcessorScheduling {class Task implements Comparable<Task> {private String taskName;private Integer taskTime;public String getTaskName() {return taskName;}public void setTaskName(String taskName) {this.taskName = taskName;}public Integer getTaskTime() {return taskTime;}public void setTaskTime(Integer taskTime) {this.taskTime = taskTime;}@Overridepublic int compareTo(Task o) {return this.taskTime - o.taskTime;}}/*** 获取需要处理的业务数据*/public Task[] getTask(Integer num) {Task[] tasks = new Task[num];Random random = new Random();for (int i = 0; i < num; i++) {Task task = new Task();task.setTaskName("t" + i);task.setTaskTime(random.nextInt(10));tasks[i] = task;}return tasks;}/*** 对数据进行从小到大排序*/public Task[] sortTask(Task[] tasks) {if (tasks == null) {return null;}quickSort(tasks, 0, tasks.length - 1);return tasks;}public Task[] quickSort(Task[] tasks, Integer left, Integer right) {if (left < right) {Integer temp = swap(tasks, left, right);quickSort(tasks, left, temp - 1);quickSort(tasks, temp + 1, right);}return tasks;}/*** 挖坑法快排*/public Integer swap(Task[] tasks, Integer left, Integer right) {if (left < right) {Task position = tasks[left];while (left < right) {while (left < right && tasks[right].compareTo(position) > 0) {right--;}if (left < right) {tasks[left] = tasks[right];left++;}while (left < right && tasks[left].compareTo(position) < 0) {left++;}if (left < right) {tasks[right] = tasks[left];right--;}}tasks[left] = position;}return left;}/*** 贪心算法* 给P个处理器分配任务*/public void finishTask(Integer p) {Task[] tasks = sortTask(getTask(8));for (int i = 0; i < tasks.length; i++) {System.out.println(tasks[i].getTaskName() + ":" + tasks[i].getTaskTime());}//存储每个列表总时长int[] countP = new int[p];//存储每个cpu的任务存储位置int[] posicition = new int[p];//存储p个cpu的任务列表Task[][] taskResult = new Task[p][tasks.length - 1];for (int i = 0; i < tasks.length; i++) {Integer min = getMinId(countP);countP[min] += tasks[i].getTaskTime();taskResult[min][posicition[min]] = tasks[i];posicition[min] += 1;}for (Task[] tasks1 : taskResult) {for (Task task : tasks1) {if (task != null) {System.out.print(task.getTaskName() + ":" + task.getTaskTime());System.out.print(" ");}}System.out.println();}}/*** 获取最小值id*/public int getMinId(int[] countP) {if (countP.length <= 0) {return 0;}int min = countP[0];int minNum = 0;for (int i = 0; i < countP.length; i++) {if (min > countP[i]) {min = countP[i];minNum = i;}}return minNum;}public static void main(String[] args) {ProcessorScheduling fi = new ProcessorScheduling();fi.finishTask(3);}
}

哈夫曼编码

  • 第二个案例我们来研究一下贪婪算法的文件压缩处理(file compression)

  • 在现实中,文件是非常大的,许多大文件是某个程序的输出,而在使用评率最大和最小的字符之间存在很大差别,例如文件大多包含的空格和newline多,q,x这些字符少。如果我们在非高效性传输场景下我们更希望文件大小越小越好,其实却是是有这样的方案囊使文件街上25%甚至更多

  • 我们一般的策略是让代码的长度从字符到字符是变化不等的,同时保证经常出现的字符其代码要短。

  • 如果所有字符都相同频率出现那么无法优化

  • 我们可以用二进制代码来标识字符,并且我们用二叉树来标识二进制

  • 标准的ASCII字符集大约也就100个“可打印”字符组成。我们设一个文件,质保函字符a,e,i,s,t,加上一些空格和newline(换行)。进一步假设有10个a,15个e,12个i,3个s,4个t,13个空格以及一个newline如下图中标识
    在这里插入图片描述
    -如上二叉树所有数据只在叶子节点上,每个字符通过根节点开始用0 指定左边分支,用1 标识右边,如a节点 000 占用3个比特位置, e节点001占用3个比特位置,i节点010,如下表格统计

字符编码频率比特数
a0001030
e0011545
i0101236
s01139
t100412
空格1011339
nl11013
  • 如下表格中这个文件我们用二叉树标识可以只永174个比特位就能标识,因为有58个字符,而每个字符3个比特位。
  • 这种数据结构叫做trie树(trie)如果字符 c_i 在深度 d_i 出出现 f_i 次那么这种编码的值就等于∑i=1n\sum_{i=1}^ni=1nd_i*f_i
  • 更优的解法如下图:

在这里插入图片描述

  • 如上二叉树的构成并不是最优解,而且我们注意到这个二叉树是一颗满树 (full tree):所有的节点要么是树叶,要么有两个儿子。一种最优的编码总是有这种性质,否则正如我们看到的,具有一个儿子的节点可以向上移动一层。
  • 如上,我们需要解决的基本问题在于找到总价值最小的满二叉树,其中所有字符都必须在叶子节点,我们需要解决的是如何构造这颗二叉树,1952年Huffman给出来一个算法。因此这种编码系统通常称为哈夫曼编码(Hufman code)
哈夫曼算法
  • 加上字符个数为C。哈夫曼算法(Huffman’s algorithm)可以如下描述:
    • 算法对应由树组成的一个森林。一棵树的权重等于它的树叶出现的评率,此处是字符在文件中出现的次数。
    • 任意选取两颗权重最小的树T1, T2,并任意以T1,T2 为子树组成一颗新树
    • 将上一步骤过程进行C-1次。
  • 在算法开始存在C课单节点树—每棵树都代表一个字符。在算法结束得到一棵树,这棵树就是最优哈夫曼编码树
具体案例分析
  • 如下图的初始森林,每棵树的权在根节点处以数字表示。

在这里插入图片描述

  • 将两颗权最低的树合并到一起,得到以下森林,我们将根节点命名成T1,命名可以任意,此处只是为了以下的表述更加方便,我们可以看到我们令s是左儿子,这次令其为左儿子还是右儿子是任意的;

  • 我们可以观察到哈夫曼算法描述中两个任意性:

    • 新树的总权值是那些老树权的和,当然也就很容易计算
    • 由于建立新树只需得出一个新节点,建立左右链接并将权重记录,因此建立新树叶简单
      在这里插入图片描述
  • 经以上步骤,我们得到六棵树,接着在选取两颗权重最小的树,这两颗数是T1 和t,然后将他们合并成另一颗新树,树根节点是T2,权重是8,如下图:
    在这里插入图片描述

  • 在将T2 与a 节点合并建立T3,权重是10+8 = 18,如下图:

在这里插入图片描述

  • 接着继续选取最小权重两个节点,这次是i 节点和空格节点,将这两棵树合并成根节点T4,:
    在这里插入图片描述

  • 继续合并根节点为e 和T3 的树,得到下图:

在这里插入图片描述

  • 最后将两个剩下的树合并得到最优树,根节点是T6.

在这里插入图片描述

  • 如上算法可以看出哈夫曼树的一些特性:

    • 哈夫曼树必然是满的
    • 其次两个权重最小的节点A,B必然是最深的节点
    • 如果权重最小的节点A,B不是最深的节点,那么必然存在某个C是最深的节点,如果A的权重小于C,那么我们可以通过交换他们在树上的位置而改进总权值
    • 相同深度上任意两个节点处的字符可以交换而不影响最优性,这说明总可以找到一颗最优树,他含有两个进程不出现的符号作为兄弟
  • 以上算方分析是贪婪算法的原因在于,每一节点我们都进行一次合并,而没有进行全局的考虑我们只是每个步骤选取最小权重的树

算法分析
  • 按照上述C个元素按顺序保存在一个优先队列,那么我们在队列上进行一次buildHeap,2C-2次deleteMin,和C-2次insert,因此运行时间为O(ClogC)
  • 如果使用链表简单实现该队列,我们给出一个O(C^2)时间复杂度算法
  • 优先队列实现方法的选择取决于C的大小,在ASCII字符集典型情况下,C是足够小的,这使得二次的运行时间是可以接受的,这样的应用中实际上所有的运行时间都讲话费在读取输入文件和写入压缩文件所需的磁盘IO上。
算法实现
//哈夫曼树节点定义
/*** @author liaojiamin* @Date:Created in 11:47 2021/1/13*/
public class HufmanNode implements Comparable<HufmanNode>{//节点权重private Integer weight;//节点名称private String nodeName;private HufmanNode left;private HufmanNode right;public Integer getWeight() {return weight;}public void setWeight(Integer weight) {this.weight = weight;}public String getNodeName() {return nodeName;}public void setNodeName(String nodeName) {this.nodeName = nodeName;}public HufmanNode getLeft() {return left;}public void setLeft(HufmanNode left) {this.left = left;}public HufmanNode getRight() {return right;}public void setRight(HufmanNode right) {this.right = right;}@Overridepublic int compareTo(HufmanNode o) {return this.weight - o.weight;}
}
/*** 哈夫曼算法  模拟 文件压缩问题* @author liaojiamin* @Date:Created in 11:46 2021/1/13*/
public class HufmanCode {/*** 初始化文件字符信息* */public static String getRandomString(int length){String str="abcdefghabc";Random random=new Random();StringBuffer sb=new StringBuffer();for(int i=0;i<length;i++){int number=random.nextInt(10);sb.append(str.charAt(number));}System.out.println(sb);return sb.toString();}/*** 哈夫曼编码问题实现* */public static HufmanNode hufmanCode(){String hufmanBase = getRandomString(20);if(hufmanBase == null || hufmanBase.length() <= 0){return null;}Map<String, Integer> nameToWeight = new HashMap<>();for (int i = 0; i < hufmanBase.length(); i++) {String key = String.valueOf(hufmanBase.charAt(i));Integer value = nameToWeight.get(key);nameToWeight.put(key, value == null ? 1 : value + 1);}LinkedList<HufmanNode> linkedList = buildHufmanNode(nameToWeight);quickSort(linkedList, 0, linkedList.size() -1);if(linkedList.size() == 1){return linkedList.get(0);}while (!linkedList.isEmpty()){if(linkedList.size() == 1){return linkedList.removeFirst();}HufmanNode hufmanNode =  buildNewNode(linkedList.removeFirst(), linkedList.removeFirst());insertLinkedList(linkedList, hufmanNode);}return linkedList.removeFirst();}/*** 按weight顺序插入哈夫曼节点* */public static void insertLinkedList(LinkedList<HufmanNode> linkedList, HufmanNode hufmanNode){if (linkedList.size() <= 0){linkedList.add(hufmanNode);return;}Integer temp = linkedList.size() - 1;for (int i = 0; i < linkedList.size(); i++) {if(linkedList.get(i).getWeight() > hufmanNode.getWeight()){temp = i;}}linkedList.add(temp, hufmanNode);}/*** 构造节点信息* */public static HufmanNode buildNewNode(HufmanNode left, HufmanNode right){HufmanNode hufmanNode = new HufmanNode();hufmanNode.setLeft(left);hufmanNode.setRight(right);hufmanNode.setNodeName("node" + System.currentTimeMillis());hufmanNode.setWeight(left.getWeight() + right.getWeight());return hufmanNode;}/*** 快排权重从小到大* */public static void quickSort(LinkedList<HufmanNode> linkedList, Integer left, Integer right){if(left <  right){Integer temp = swap(linkedList, left, right);quickSort(linkedList, left, temp -1);quickSort(linkedList, temp + 1, right);}}/*** 挖坑法实现快排* */public static Integer swap(LinkedList<HufmanNode> linkedList, Integer left, Integer right){if(left < right){HufmanNode position = linkedList.get(left);while (left < right){while (left < right && linkedList.get(right).compareTo(position) > 0){right --;}if(left < right){linkedList.set(left, linkedList.get(right));left ++;}while (left < right && linkedList.get(left).compareTo(position) < 0){left ++;}if(left < right){linkedList.set(right, linkedList.get(left));right--;}}linkedList.set(left, position);}return left;}/*** 构造树节点* */public static LinkedList<HufmanNode> buildHufmanNode(Map<String, Integer> nameToWeight){LinkedList<HufmanNode> linkedList = new LinkedList<>();for (String nodeName : nameToWeight.keySet()) {HufmanNode hufmanNode = new HufmanNode();hufmanNode.setNodeName(nodeName);hufmanNode.setWeight(nameToWeight.get(nodeName));linkedList.addFirst(hufmanNode);}return linkedList;}public static void main(String[] args) {HufmanNode hufmanNode = hufmanCode();}
}

上一篇:数据结构与算法–图论-深度优先搜索及其应用
下一篇:数据结构与算法–贪婪算法2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310461.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[C++STL]C++ 实现map容器和set容器

代码如下: #pragma once #include <iostream> using namespace std;enum COLOR {BLACK, RED };template<class V>//迭代器声明&#xff0c;定义在后面 struct RBTreeIterator;template<typename V> struct RBTreeNode {RBTreeNode<V> * _parent;RBTre…

多角度让你彻底明白yield语法糖的用法和原理及在C#函数式编程中的作用

如果大家读过dapper源码&#xff0c;你会发现这内部有很多方法都用到了yield关键词&#xff0c;那yield到底是用来干嘛的&#xff0c;能不能拿掉&#xff0c;拿掉与不拿掉有多大的差别&#xff0c;首先上一段dapper中精简后的Query方法&#xff0c;先让大家眼见为实。private s…

C++泛型编程实现哈希表(闭散列---线性探测)

代码如下: #include <iostream> #include <vector> using namespace std;enum STATE {EXIST,DELETE,EMPTY };template<typename K,typename V> struct HashNode {pair<K, V> _kv;STATE _state EMPTY; };template<typename K,typename V> class…

哪种开源许可证最适合商业化?

选择最佳开源许可证是为新项目所做的最重要的决定之一。大多数开发者会选用 MIT、BSD 或 Apache 等流行的宽松许可证&#xff08;permissive license&#xff09;。对于商业项目而言&#xff0c;这种选择不错&#xff0c;因为这能减少用户对项目的抵触情绪。当应用于开源项目时…

C++泛型编程实现哈希表(开散列法)

代码如下: #include <iostream> #include <vector> using namespace std;template<typename K> struct HashNode {typedef HashNode<K> Node;K _val;Node * _next;HashNode(const K & val):_val(val),_next(nullptr){} };template<typename K&…

数据结构与算法--分治算法-最大子序列和问题

分治算法 用于设计算法的一种常用技巧–分治算法&#xff08;divide and conquer&#xff09;。分治算法由两部分组成&#xff1a; 分(divide)&#xff1a;递归然后借机较小的问题&#xff08;基础情况除外&#xff09;治(conquer)&#xff1a;然后从子问题的解构建原问题的解…

请把我不会,换成我可以学

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份有位读者跟我说起自己的烦恼&#xff1a;“我到公司已经接近四年了&#xff0c;领导经常让我做一些岗位职责以外的事情。这些东西我都不会&#xff0c;还非让我做。并且一直没有职位上的改变&#xff0c;我怎…

[C++STL]C++实现unordermap容器和unorderset容器

代码如下: #include <iostream> #include <vector> using namespace std;template<typename K,typename V,typename KeyOfValue> class HashTable;//声明template<typename V> struct HashNode {typedef HashNode<V> Node;V _val;Node * _next;…

还不会docker+k8s?2020年,就要面对现实了...

docker的前世今生2010年&#xff0c;几个年轻人&#xff0c;在美国旧金山成立了一家名叫“dotCloud”的公司。这家公司主要提供基于PaaS的云计算技术服务。具体来说&#xff0c;是和LXC有关的容器技术。后来&#xff0c;dotCloud公司将自己的容器技术进行了简化和标准化&#x…

数据结构与算法--重建二叉树

二叉树 树在实际编程中经常遇到地一种数据结构。上一篇中我们解释了二叉树及其原理&#xff0c;从中可以知道&#xff0c;树地操作会涉及到很多指针地操作&#xff0c;我们一般遇到地树相关地问题差不多都是二叉树。二叉树最重要地莫过于遍历&#xff0c;即按照某一顺序访问树…

3分钟掌握Quartz.net分布式定时任务的姿势

长话短说&#xff0c;今天聊一聊分布式定时任务&#xff0c;我的流水账笔记&#xff1a;ASP.NET CoreQuartz.Net实现web定时任务AspNetCore结合Redis实践消息队列细心朋友稍一分析&#xff0c;就知道还有问题&#xff1a;水平扩展后的WebApp的Quartz.net定时任务会多次触发&…

数据结构与算法--利用栈实现队列

利用栈实现队列 上一节中说明了栈的特点 后进先出&#xff0c;我们用数组的方式实现了栈的基本操作api&#xff0c;因此我们对栈的操作是不考虑排序的&#xff0c;每个api的操作基本都是O(1)的世界&#xff0c;因为不考虑顺序&#xff0c;所以找最大&#xff0c;最小值&#x…

ASP.NET Core 配置源:实时生效

在之前的文章 ASP.NET Core 自定义配置源 和 ASP.NET Core etcd 配置源 中主要是介绍如何实现自定义的配置源&#xff0c;但不论内置的和自定义的配置源&#xff0c;都会面临如何使配置修改后实时生效的问题&#xff08;修改配置后在不重启服务的情况下能马上生效&#xff09;。…

分布式事务理论模型

分布式事务 事务的概念&#xff0c;我们第一想到的应该是数据库的事务。所谓数据库事务就是只作为单个逻辑工作单元执行多个数据库操作的时候&#xff0c;数据库需要保证要么都成功&#xff0c;要么都失败&#xff0c;它必须满足ACID特性&#xff0c;即&#xff1a; 原子性&…

[MySQL基础]数据库的相关概念

DB: 数据库(database):存储数据的“仓库”&#xff0c;它保存了一系列有组织的数据。 DBMS: 数据库管理系统(Database Management System):数据库是通过DBMS创建和操作的容器。 SQL: 结构化查询语言(Structure Query Language):专门用来与数据库通信的语言。 SQL的优点: 1.几…

Linq下有一个非常实用的SelectMany方法,很多人却不会用

在平时开发中经常会看到有些朋友或者同事在写代码时会充斥着各种for&#xff0c;foreach&#xff0c;这种程式代码太多的话阅读性特别差&#xff0c;而且还显得特别累赘&#xff0c;其实在FCL中有很多帮助我们提高阅读感的方法&#xff0c;而现实中很多人不会用或者说不知道&am…

.NET Core前后端分离快速开发框架(Core.3.1+AntdVue)

引言时间真快&#xff0c;转眼今年又要过去了。回想今年&#xff0c;依次开源发布了Colder.Fx.Net.AdminLTE(254Star)、Colder.Fx.Core.AdminLTE(335Star)、DotNettySocket(82Star)、IdHelper(47Star)&#xff0c;这些框架及组件都是本着以实际出发&#xff0c;实事求是的态度&…

数据结构与算法--查找与排序另类用法-旋转数组中的最小数字

查找与排序 查找 查找与排序都在程序设计中常被用到的算法。查找相对而言简单&#xff0c;一般都是顺序查找&#xff0c;二分查找&#xff0c;哈希表查找&#xff0c;和二叉排序树查找。其中二分查找是我必须熟悉的一种。哈希表和二叉排序树主要点在于他的数据结构而不是算法…

[MySQL基础]MySQL常见命令介绍

show databases; use 库名; show tables; show tables from 库名 select database(); create table 名字( id int, name varchar(20)); desc 表名; select * from 表名; insert into 表名 (a,b,…,f) values(1,2,3,…,7); update 库名 set name‘lilei’ where id1; delete f…