二分搜索(基础部分)
一.知识点总结:
基础二分查找(三种代码版本)
版本一:
bool binarySearch(vector<int>values,int keyValue){
int first=0,last=values.size()-1;
while(first<=last){
int middle=(first+lase)>>1;
if(values[middle]<keyValue){
first=middle+1;
}
else if(values[middle]>keyValue){
last=middle-1;
}
else return true;
}
return false;
}
版本二:
bool binarySearch(vector<int>values,int keyValue){
int first=0,last=values.size();
while(first<last){
int middle=(first+last)>>1;
if(values[middle]<keyValue){
first=middle+1;
}
else if(values[middle]>keyValue){
last=middle;
}
else return true;
}
return false;
}
版本三:
bool binarychSearch(vector<int>values,int keyValue){
int first=0,last=values.size();
while(last-fifrst>1){
if(keyValue<values[middle]){
last=middle;
}
else{
first=middle;
}
}
if(values[i]==keyValue){
return ture;
}
else {
return false;
}
}
二.具体知识:
1.二分搜索是什么?
答:传入一个已排好序的数组和一个要查找的value值,利用“已排好序”这一条件快速缩小查找范围,最后确定value是否在数组中。
2.二分搜索相对于普通查找有何优点?
答:二分查找利用“已排好序”这一先决条件,极大节省了搜索时间。
bool commonSearch(vector<int>values,int keyValue){
for(int i=0;i<values.size();++i){
if(values[i]==keyValue){
return true;
}
}
return false;
}
这是一个顺序查找的代码,当元素的数量为n,则要作n次计算,时间复杂度为O(n)。
bool binarySearch(vector<int>values,int keyValue){
int first=0,last=values.size()-1;
while(first<=last){
int middle=(first+lase)>>1;
if(values[middle]<keyValue){
first=middle+1;
}
else if(values[middle]>keyValue){
last=middle-1;
}
else return true;
}
return false;
}
这是一个二分查找的代码,当元素的数量为n时,则做logn次计算,时间复杂度为O(logN)。
3.三个基础版本的语法细节:
(1.)版本一:
bool binarySearch(vector<int>values,int keyValue){
int first=0,last=values.size()-1;
while(first<=last){
int middle=(first+lase)>>1;
if(values[middle]<keyValue){
first=middle+1;
}
else if(values[middle]>keyValue){
last=middle-1;
}
else return true;
}
return false;
}
问题一:为什么last=values.size()-1,while的条件就要“<=”?
答:因为特殊情况有三种:1.查的是第一个元素;2.查的是最后一个元素;3.first和last重合。 其实三个特殊情况最后都会变成一个问题:first和last重合时while要不要继续。
答案是要继续。按照算法过程,导致first和last重合的上一步,first和last位置必然是相差2(first和last之间只有一个元素)而且循环一次后发现中间的这个元素不是要查找的值,于是进行first+1或者last-1,而后出现first和last重合的情况。如果这时停止循环,那就会遗漏掉重合的这一元素。所以循环条件一定是:first<=last。
问题二:为什么(first+last)>>1?
答:(first+last)>>1其实就是(first+last)/2。这是利用了二进制的性质。
有两个好处:
1.当last等于int可表示数字的最大值或者(first+last)超过int可表示范围的最大值时,>>1可以防止溢出导致的数据错误。
2.>>1比/2更快。
问题三:为什么是middle+1和middle-1?不能是middle吗?
答:这涉及循环体的一致性(我称它为一致性)。
不能是middle的原因:循环开始时first是第一个元素,last是最后一个元素,first和last的元素都是要参与比较的,比较的范围是左闭右闭区间。那么每一次循环,first和last所在的元素都一定是需要参与比较的。而上一次循环中,middle所指元素已经比较过1次,不应该再参与比较。如果再参与,那左闭右闭区间就实质上变成有一边开(左开或者右开)的区间了,那么循环在某些条件下就会出现问题。
循环的本质不仅仅是代码可以重复运行,还是代码所表达的数据结构与算法可以重复运行。
(2.)版本二:
bool binarySearch(vector<int>values,int keyValue){
int first=0,last=values.size();
while(first<last){
int middle=(first+last)>>1;
if(values[middle]<keyValue){
first=middle+1;
}
else if(values[middle]>keyValue){
last=middle;
}
else return true;
}
return false;
}
问题一:last=values.size()时,为什么循环条件要改成"<"?
答:考虑一种特殊情况:查找的是比最后一个元素还大的元素。在这种情况下,first最终不是与last重合就是达到last的下一个位置。如果最后重合,而循环条件是”<=“,那么就会越界访问。所以循环条件只能是"<"。
问题二:else if 为什么改成last=middle而不是last=middle-1?
同样是循环体一致性的问题。初始first=0,last=values.size(),实际上就是个左闭右开区间。那么下一次循环令last=middle依然与first在本质上形成左闭右开区间。因为上一次循环中middle的元素已经被比较过了。
(3.)版本三:
bool binarychSearch(vector<int>values,int keyValue){
int first=0,last=values.size();
while(last-first>1){
if(keyValue<values[middle]){
last=middle;
}
else{
first=middle;
}
}
if(values[i]==keyValue){
return true;
}
else {
return false;
}
}
问题一:为什么要有版本三?
答:为了解决版本一和版本二的平衡性问题。当用版本一和二查找最右边的元素时,while循环只需要比较一次(即if那一句)。而当查找最左边的元素时,while循环就要比较两次(比较一次if,然后再比较一次else if)。这就导致左右的不平衡。版本三就是为了优化这一问题。
问题二:为什么last-first>1作为while判断条件?
答:首先,这是左闭右开区间的写法,所以一般条件是first<last,但while的目的是通过不断缩小范围,缩小到只剩一个数时再去决定return true还是false。为了“剩下最后一个数”,所以while到last-first=1时停止。
问题三:为什么是last=middle?
答:同版本二,因为左闭右开。
问题四:为什么是first=middle,而不是first=middle+1?
因为else里面不仅包含了keyValue>values[middle],还包含了keyValue=values[middle],keyValue=values[middle]时就要first=middle,如果first=middle+1,就会错过这个“查找到”的数。
三.总结:
(1.)循环要符合代码运行的一致性和所代表的数据结构与算法的一致性。
(2.)算法细节受边界条件制约,解决边界问题,往往就容易实现正确的算法。而边界问题的处理,恰恰是算法难学的地方之一。
(3.)学习算法不能空想,唯有自行推导才能牢固。就像数学公式,唯有学完后去自行证明,才能活学活用。