本文是数据结构与算法之美的学习笔记
贪心算法的概念
贪心算法是指在解决问题的时候,总是选择当前最好的,并希望通过一系列的最优选择,能够产生一个问题的全局最优解。
比如我们有一个可以容纳100kg物品的背包,我们有5种豆子吗,每种豆子的总量和总价值都不一样,如何能让背包中的物品总价值最大呢?
豆子 | 总量(kg) | 总价值(元) |
---|---|---|
黄豆 | 100 | 100 |
绿豆 | 30 | 90 |
红豆 | 60 | 120 |
黑豆 | 20 | 80 |
青豆 | 50 | 75 |
很简单:
贪心算法的思路
上面是一个有权图,从顶点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]
贪心算法不一定会给出最优解,但是它简单好理解,如果一个问题可以使用多种方法解决,那么使用贪心算法也是最好的选择之一。
分治算法
分治算法顾名思义就是分而治之,就是把一个复杂的问题分成若干个相同或者相似的子问题,在把小问题分成更小的问题,直到最后的小问题可以很简单的求解,然后把各个子问题的解合并起来形成原问题的解。
现在比较火的大数据,实际上就是使用了分治算法的思想,当数据大到一定的程度的时候,一台机器处理不了,那好就用很多台机器一起处理,把大数据分成若干个小数据分到每个机器上分别处理,然后把处理结果合并。当在处理不了的时候就加机器继续分。
还有比较常用的快速排序算法,归并排序算法都用了分治的思想。
分治算法比较适合使用递归来实现,每一层的递归一般涉及到3个操作
根据上面的分治算法的思想,我们可以想象一下可以使用分治思想解决的问题的特点
例子:求一组数据的逆序对个数
有序对就是左边小于右边,逆序对就是左边大于右边,比如一组数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,
这里分组已经排序完成,下面就是两组合并成一组
代码:
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];
}
}