1 两类经典算法
在这一章节,考虑经典的算法:查找和排序,以及具体的应用。
老生常谈,偶尔遇到阐述这两类问题相关的极好素材,它们结合示意图,言简意赅,清晰明了。故分享出来。
2 猜数字游戏
二分查找还是相对容易理解的,我们的目标是准确无误地写出其代码。为此先从原理理解透二分查找,在游戏 "20个问题"中,你的任务是猜出一个神秘的数字,它位于0~n-1共n个之内。
简化起见,我们假定n是偶数,问题的问答形式是这样:
Q: “我猜神秘数为 i”
A:“你猜的数字 i 大于或等于 真正的神秘数77”
这个过程,参考如下示意图:
此处,一个有效的策略是维护一个间隔,它包含 x ,猜的数位于这个区间中间,以下 Questions类实现了这个策略。
1public class Questions {
2
3 // invariant: answer is in [lo, hi)
4 public static int search(int lo, int hi) {
5 if ((hi - lo) == 1) return lo;
6 int mid = lo + (hi - lo) / 2;
7 StdOut.printf("Is it less than %d? ", mid);
8 if (StdIn.readBoolean())
9 return search(lo, mid);
10 else
11 return search(mid, hi);
12 }
13
14 public static void main(String[] args) {
15 int k = Integer.parseInt(args[0]);
16 int n = (int) Math.pow(2, k);
17 StdOut.printf("Think of an integer between %d and %d\n", 0, n-1);
18 int secret = search(0, n);
19 StdOut.printf("Your number is %d\n", secret);
20 }
21
22}
这里面有个细节,mid = lo + (hi-lo)/2,这样写是为了防止发生溢出。
这个猜数字的游戏就是二叉搜索的一个经典例子。
3 分析二分查找
3.1 时间复杂度
在上面这个猜谜游戏中,使用了二分查找,因为没迭代一次,求解区间减为原来一半,因此二分查找的时间复杂度为 lgn.
3.2 二分查找退化为线性搜索
如果我们不采取上面的猜数字策略,依次1,2,3,...n-1,直到命中数字,这就是 brute-force 暴力算法,此时的时间复杂度为 n.
3.3 二进制表示
某个数转化为二进制表示(代码见下)的过程与二分查找很相似,例如,神秘数为77,然后猜题的回答为:no yes yes no no yes no ,则二进制表示为 1001101,二进制表示恰好为 77.
1public class Binary {
2 public static void main(String[] args) {
3
4 // read in the command-line argument
5 int n = Integer.parseInt(args[0]);
6
7 // set power to the largest power of 2 that is <= n
8 int power = 1;
9 while (power <= n/2) {
10 power *= 2;
11 }
12
13 // check for presence of powers of 2 in n, from largest to smallest
14 while (power > 0) {
15
16 // power is not present in n
17 if (n < power) {
18 System.out.print(0);
19 }
20
21 // power is present in n, so subtract power from n
22 else {
23 System.out.print(1);
24 n -= power;
25 }
26
27 // next smallest power of 2
28 power /= 2;
29 }
30
31 System.out.println();
32
33 }
34
35}
3.4 二分查找求函数的近似解
假定函数f(x)是递增的,给定一个值y,我们的任务是发现一个值 x 使得 f(x) 近似等于 y, 我们开始于间隔 (lo,hi),它一定包含x, 应用下面的迭代策略发现x:
这个过程的示意图如下,x搜索区间缩小到 (lo,mid).
3.5 二分查找应用广泛的前提条件
在一个数组中发现某个目标值,基于二分查找的前提是这个数组得是有序数组,只有这样,我们才能缩小搜索空间,找到目标值。下面这版代码是完整的二分查找代码,注意main函数中,首先对数组进行了sort排序,然后启用search函数。
1import java.util.Arrays;
2
3public class BinarySearch {
4
5 // return the index of the key in the sorted array a[]; -1 if not found
6 public static int search(String key, String[] a) {
7 return search(key, a, 0, a.length);
8 }
9 public static int search(String key, String[] a, int lo, int hi) {
10System.out.println("lo = " + lo);
11System.out.println("hi = " + hi);
12 // possible key indices in [lo, hi)
13 if (hi <= lo) return -1;
14 int mid = lo + (hi - lo) / 2;
15 int cmp = a[mid].compareTo(key);
16 if (cmp > 0) return search(key, a, lo, mid);
17 else if (cmp < 0) return search(key, a, mid+1, hi);
18 else return mid;
19 }
20
21
22 // whitelist, exception filter
23 public static void main(String[] args) {
24 In in = new In(args[0]);
25 String s = in.readAll();
26 String[] words = s.split("\\s+");
27 System.err.println("Done reading words");
28
29 // sort the words (if needed)
30 Arrays.sort(words);
31 System.err.println("Done sorting words");
32
33 // prompt user to enter a word and check if it's there
34 while (!StdIn.isEmpty()) {
35 String key = StdIn.readString();
36 if (search(key, words) < 0) StdOut.println(key);
37 }
38 }
39}
因此二分查找的使用前提是排序算法,下面这节详细介绍归并排序,最典型的排序算法之一。
4 归并排序
4.1 排序算法
排序算法中归并排序算法应用很广泛,它是分治法的典型实例。关于 7 种常用的排序算法的纯碎源码,请参考文章:
4.2 归并排序
普林斯顿大学的算法课程要求每一个程序员都要清楚地掌握这个排序算法,可见其重要程度。
基本思想:如果数组长度为0或1,则它已经排序好了,否则把数组分为两半,独立的排序这两半,然后再融合它们。
4.3 归并排序例子
待排序的数组为: was had him and you his the but 应用归并排序的整个过程如下图所示:
框线图直观表示:
以上就是精简地介绍查找和排序问题的算法,这些都是必须要理解透的。
本文分享自 程序员郭震zhenguo 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!