一、二分查找的介绍
二分查找:就是在有序的序列当中进行快速查找你想要的数据,通过不断从中间分割查找,达到查找目的
算法思想:假如我现在有一个有序的序列,我需要查找数字 x ,但是如果我从第一个慢慢比较的话,那就太慢了一点,万一我需要的数据再很后面,那就会很影响效率了,那么不如咱们从中间开始找如何?如果序列中间的数恰好是我们想要的数字,那么就查找成功,如果中间的数字比我们所想的数字大,那么就从左边的数组继续二分查找,反之右边也一样。
所以咱们现在有如下规定:
low 代表数组的最左边下标,hight 代表数组的最右边下标,mid 代表数组的中间下标
接着咱们有如下步骤:
1、如果 arr[mid] == target ,那么就直接返回,查找成功啦!
2、如果 arr[mid] > target,那么就令 hight = mid - 1
3、如果 arr[mid] < target,那么就令 low = mid
4、每次改变 low ,hight 都要修改 mid = (low + hight)/ 2
5、直到最后 low >= hight , 如果此时还没有找到咱们的数据,那就只能告辞了,这个数组没有咱们想要的
二、相关题目
看完了二分查找的介绍,是时候来个题目练练手了!
题目很简单,就是问小Q第一天最多能吃多少个,又能保证在他爸妈回来之前不会被饿死,我在这在做个简单的题目解析:小Q第一天需要吃 x 块巧克力(这里的巧克力不能被分割即要么就吃一块,要么就别吃,老子就是贪心,才不吃 0.5,0.6块呢),往后的每一天都至少是前一天的 x/2 块,所以如果想要第一天吃最多巧乐力的话,那么往后的每一天都直接吃满足条件又不多吃就行了(就是只吃 x/2 块),所以就会有如果 x/2 是小数的话这个数就要向上取整!
1、此时,为何需要二分查找呢?
因为咱们第一天根本不知道小Q要吃多少才能维持到最后,所以咱们要一个个去尝试呀,比如输入例子中的 7 块,但是我第一天不知道要吃多少,可能是 1 块,也能是 2 块,或者是 3 块,所以咱们就只能在 1 — 7 中寻找符合咱们期望的答案。
2、那怎么才算符合预期?
只要从第一天起吃的巧克力数一直到最后的巧克力数全部相加和总巧克力数相等就符合预期了
3、别说了,咱们还是代码说话吧
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace 贪吃的小Q 8 { 9 class 贪吃的小Q 10 { 11 //小Q的父母要出差N天,走之前给小Q留下了M块巧克力。小Q决定每天吃的巧克力数量不少于前一天吃的一半, 12 //但是他又不想在父母回来之前的某一天没有巧克力吃,请问他第一天最多能吃多少块巧克力 13 14 static int n = 0; 15 static int m = 0; 16 static void Main(string[] args) 17 { 18 var nm = Console.ReadLine().Split(' '); 19 int temp = 0; 20 while(true) 21 { 22 if(nm.Length!=2 || !Int32.TryParse(nm[0],out temp) || !Int32.TryParse(nm[1],out temp) || Convert.ToInt32(nm[0]) > 50000 || Convert.ToInt32(nm[1])< Convert.ToInt32(nm[0]) || Convert.ToInt32(nm[1]) > 100000) 23 { 24 nm = Console.ReadLine().Split(' '); 25 } 26 else 27 { 28 n = Convert.ToInt32(nm[0]); 29 m = Convert.ToInt32(nm[1]); 30 break; 31 } 32 } 33 // Sum(144); 34 Eat(); 35 Console.ReadKey(); 36 } 37 38 // 计算假设第一天吃s块,那么n天过后需要多少块 39 public static int Sum(int s) // @1 40 { 41 int sum = 0; 42 for(int i=0;i<n;++i) 43 { 44 sum += s; 45 s = (int )Math.Ceiling((double)s / 2); 46 // Console.WriteLine("sum:" + sum + " s:" + s + " n:" + i); 47 } 48 return sum; 49 } 50 public static void Eat() 51 { 52 if (n == 1) // @2 53 { 54 Console.WriteLine(m); 55 return; 56 } 57 int low = 1, hight = m; 58 int mid = 0; 59 while(low<hight) 60 { 61 mid = (int)Math.Ceiling((double)(low + hight) / 2); // @3 62 if (Sum(mid) == m) 63 { 64 Console.WriteLine(mid); 65 return; 66 } 67 else if (Sum(mid) >m) 68 { 69 70 hight = mid -1; 71 } 72 else if(Sum(mid) < m) 73 { 74 low = mid; 75 } 76 } 77 Console.WriteLine(low);// @4 78 } 79 } 80 }
三、最后的代码解读
@1:Sum()方法是计算从第一天起,到爸妈回来之前总共需要吃的数量
@2:如果爸妈出差一天就回来,那么就直接吃完吧,别留着了
@3:在找到符合期望的数量之前,都要一直折中查找;如果Sum(mid) == m 就意味着第一天吃 mid 块就可以解决温饱,如果 > m 就意味着你第一天吃太多了贪吃的猪,如果 < m 就意味着吃的还不够解不了嘴馋,碰到这些情况就一直找下去直到所有的数据都找完了。
@4:(重点!重点!重点!)为什么,我还要在这里多输出一句话呢?如果我一直找的话,不是迟早都会找到符合 Sum(mid) == m 的情况吗?非也!这个方法并不能满足所有的数据达到我们想要的期望值,也就是说不是所有的数据找到最后会满足这个条件 Sum(mid) == m ,比如说 100,382(我当时就是被这个数据卡到了),如果出现这种情况的话,就意味着如果小Q按照给定的条件吃下去,最后会剩下一块巧克力多出来,所以 Sum(mid) 就不会和 m 相等,但是其实,这个期望值已经是我们想要的了,那就是 low 或者 hight ,因为此时,我们已经完成了二分查找,所以当二分查找退出时,low 或者 hight 就是我们需要的值。
四、吐槽
该吐槽与题目无关,单纯想吐槽这个C#的输入,是真的坑死爹不偿命,因为题目的输入,通常都是要2个数同时输入,如果同时输入就要分割,而且C#输入的时候都是字符串,还要转变成整型,在转变之前还要先判断他是不是数字,如果不是还要重新输入,真的是被这个设定搞得烦死了,c,c++哪有这么麻烦的,直接输入就完事了(好吧!其实是我不会写c++,太久没用了,只能看懂写不动了)