贪心算法和分治算法

栏目: 编程工具 · 发布时间: 5年前

内容简介:本文是数据结构与算法之美的学习笔记贪心算法是指在解决问题的时候,总是选择当前最好的,并比如我们有一个可以容纳100kg物品的背包,我们有5种豆子吗,每种豆子的总量和总价值都不一样,如何能让背包中的物品总价值最大呢?

本文是数据结构与算法之美的学习笔记

贪心算法的概念

贪心算法是指在解决问题的时候,总是选择当前最好的,并 希望 通过一系列的最优选择,能够产生一个问题的全局最优解。

比如我们有一个可以容纳100kg物品的背包,我们有5种豆子吗,每种豆子的总量和总价值都不一样,如何能让背包中的物品总价值最大呢?

豆子 总量(kg) 总价值(元)
黄豆 100 100
绿豆 30 90
红豆 60 120
黑豆 20 80
青豆 50 75

很简单:

  • 先算每个豆子的单价,黄豆1元,绿豆3元,红豆2元,黑豆4元,青豆1.5元
  • 按照单价由高到底依次往背包里装就可以了,结果就是20kg黑豆,30kg绿豆,50kg红豆

贪心算法的思路

  1. 满足约束条件,比如上面的例子限制值就是100kg,期望值是总价值最大
  2. 局部最优,每次选出当前步骤中的最优解,比如上面的例子中从单价最高的开始往下一个一个的加
  3. 前面的选择会影响后面的选择,选择之后就没法改变,上面的例子使用贪心思想可以找到最优解,有时候使用贪心思想不能找到最优解,比如下面的例子

贪心算法和分治算法

上面是一个有权图,从顶点S开始找一条道T的最短路径。如果使用贪心算法的思想,每次都是找权重最小的那么结果是S->A->E->T,长度是1+4+4=9

但是如果我们仔细看最短路径并不是这个,而是S->B->D->T长度是2+2+2=6

这就是前面的选择会影响后面的选择,一旦做出了选择,就不能改变了,上面的例子,我们一开始选择了最小权重1到达了A,那么A后面不管权重多大都得捏着鼻子往下走。

贪心算法实战

第一题:钱币找零

假如我们有1元,2元,5元,10元,20元,50元,100元面额的纸币,他们的张数分别为c1,c2,c5,c10,c20,c50,c100。现在需要支付K元,最少需要多少张纸币

很简单,使用贪心思想,先从最大的面值开始支付,如果不够就继续使用更小一点的面值,以此类推直到支付完成。

第二题:最大整数

有n个整数,把他们连成一排,组成一个最大的多位数

比如:n=3 整数位 5,16,876 连成的最大整数就是:876516

先把整数换成字符串,比较a+b和b+a,如果a+b>=b+a,就把a放在b的前面,反之把a放在b的后面

第三题:区间覆盖

给定一个长度为m的区间,在给出n条线段的起点和终点,从中选出尽量多的线段,要求每个线段都是独立的,不跟其他的线段有交集。

比如:m=10

线段:[6,8] [2,4] [3,5] [1,5] [5,9] [8,10]

结果:[2,4] [6,8] [8,10]

  1. 首先按照起始点 排序 为[1,5] [2,4] [3,5] [5,9] [6,8] [8,10]
  2. 然后选取左端点跟前面的已经覆盖的区间不重合,并且右端点尽量小这样可以让剩下的未覆盖的区间尽可能的大,也就可以放置更多的区间。

贪心算法不一定会给出最优解,但是它简单好理解,如果一个问题可以使用多种方法解决,那么使用贪心算法也是最好的选择之一。

分治算法

分治算法顾名思义就是分而治之,就是把一个复杂的问题分成若干个相同或者相似的子问题,在把小问题分成更小的问题,直到最后的小问题可以很简单的求解,然后把各个子问题的解合并起来形成原问题的解。

现在比较火的大数据,实际上就是使用了分治算法的思想,当数据大到一定的程度的时候,一台机器处理不了,那好就用很多台机器一起处理,把大数据分成若干个小数据分到每个机器上分别处理,然后把处理结果合并。当在处理不了的时候就加机器继续分。

还有比较常用的快速排序算法,归并 排序算法 都用了分治的思想。

分治算法比较适合使用递归来实现,每一层的递归一般涉及到3个操作

  1. 把问题分解成一系列的子问题
  2. 递归的求解各个子问题
  3. 将子问题的结果合并成原问题

根据上面的分治算法的思想,我们可以想象一下可以使用分治思想解决的问题的特点

  1. 原问题和分解成的小问题具有相同的模式
  2. 原问题分解成的子问题可以独立的求解,子问题之间没有相关性
  3. 具有分解终止的条件,条件就是子问题可以直接求解
  4. 可以将子问题的解合并成原问题,合并的操作复杂度不能太高,否则就不能起到见效算法总体复杂度的效果了。

例子:求一组数据的逆序对个数

有序对就是左边小于右边,逆序对就是左边大于右边,比如一组数2,4,1,5,6。逆序对就是(2,1) (4,3) (4,1) (3,1)

我们可以通过归并排序来实现,归并排序中有个操作是把两个有序的小组合并成一个有序数组,在合并的过程中我们就可以计算着两个小组的逆序对数了。

比如一组数据 1,5,6,2,3,4。

首先将其分成两部分分别排序 : 1,5,6和2,3,4,

这里分组已经排序完成,下面就是两组合并成一组

  1. 准备一个空数组
  2. 1和2比较,1比2小,1放入数组
  3. 5和2比较5比2大,2放入数组中,5和5后面的都比2大,说明可以组成逆序对,所以这里逆序对个数+2
  4. 5和3比较,5比3大,3放入数组中,5和5后面的都比2大,说明可以组成逆序对,所以这里逆序对个数+2
  5. 5和4比较,5比4大,4放入数组中,5和5后面的都比2大,说明可以组成逆序对,所以这里逆序对个数+2
  6. 最后5和6放入数组中排序完成
  7. 结果逆序对的个数就是2+2+2=6

代码:

private int num = 0; // 全局变量或者成员变量

public int count(int[] a, int n) {
  num = 0;
  mergeSortCounting(a, 0, n-1);
  return num;
}

private void mergeSortCounting(int[] a, int p, int r) {
  if (p >= r) return;
  int q = (p+r)/2;
  mergeSortCounting(a, p, q);
  mergeSortCounting(a, q+1, r);
  merge(a, p, q, r);
}

private void merge(int[] a, int p, int q, int r) {
  int i = p, j = q+1, k = 0;
  int[] tmp = new int[r-p+1];
  while (i<=q && j<=r) {
    if (a[i] <= a[j]) {
      tmp[k++] = a[i++];
    } else {
      num += (q-i+1); // 统计 p-q 之间,比 a[j] 大的元素个数
      tmp[k++] = a[j++];
    }
  }
  while (i <= q) { // 处理剩下的
    tmp[k++] = a[i++];
  }
  while (j <= r) { // 处理剩下的
    tmp[k++] = a[j++];
  }
  for (i = 0; i <= r-p; ++i) { // 从 tmp 拷贝回 a
    a[p+i] = tmp[i];
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Linux Command Line

The Linux Command Line

William E. Shotts Jr. / No Starch Press, Incorporated / 2012-1-17 / USD 39.95

You've experienced the shiny, point-and-click surface of your Linux computer-now dive below and explore its depths with the power of the command line. The Linux Command Line takes you from your very ......一起来看看 《The Linux Command Line》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具