二分排序与二分查找

原创 2012年03月23日 08:29:29

1、规定排序的实现

      分治法的三步法:

          划分问题:(把序列二分),

          递归求解:(分别对左右子序列递归排序)

          合并结果:(根据左右两个有序的子序列,依次取两个子序列的最小元素中的最小者加到结果中去)

      实现如下:

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. void mergeSort(int * a, int x, int y, int * t)//归并排序,t为辅助空间  
  5. {  
  6.     if(y-x > 1)//由于是区间[x,y),非空则有y-x>=1,而1个元素的子序列已经有序  
  7.     {  
  8.         int m = x + (y-x)/2;//中点  
  9.         mergeSort(a,x,m,t);  
  10.         mergeSort(a,m,y,t);  
  11.           
  12.         //合并  
  13.         int p = x, q = m, k = x;  
  14.         while(1)  
  15.         {         
  16.             if(p < m){//左非空  
  17.                 if(q < y){//右非空  
  18.                     if(a[p] < a[q]) t[k++] = a[p++];  
  19.                     else t[k++] = a[q++];  
  20.                 }  
  21.                 else t[k++] = a[p++];  
  22.             }  
  23.             else{//左空  
  24.                 if(q < y){//右非空  
  25.                     t[k++] = a[q++];  
  26.                 }//右空  
  27.                 else break;  
  28.             }  
  29.         }  
  30.         for(int i = x; i < y; i ++) a[i] = t[i];  
  31.     }  
  32. }  
  33.   
  34. int main()  
  35. {  
  36.     int n;  
  37.     int a[100], t[100];  
  38.     cin >> n;  
  39.     for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);  //输入数据  
  40.   
  41.     mergeSort(a,1,n+1,t);  
  42.   
  43.     for(int i = 1; i <= n; i ++) printf("%d ",a[i]);  
  44.     printf("\n");  
  45.       
  46.     return 0;  
  47. }  

    归并排序最主要的就是合并的时候的选择最小的元素添加到结果中的适当位置去。

 

2、归并排序的一个应用——逆序对数

      问题的提法:给出一列数A1,A2,...,An,求它的逆序对数,即有多少个逆序对(i<j,而Ai > Aj)

      主要的思想:在右边的A[j]复制到T中时,左边还没来得及复制到T中的那些数就是左边左右比A[j]大的数,因为此时A[j]前面复制到T中的数肯定比它小,而尚未复制的数肯定比它大。

      实现如下:     

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int reverseCount(int * a, int x, int y, int * t)//归并排序,t为辅助空间  
  5. {  
  6.     int res = 0;  
  7.     if(y-x > 1)//由于是区间[x,y),非空则有y-x>=1,而1个元素的子序列已经有序  
  8.     {  
  9.         int m = x + (y-x)/2;//中点  
  10.         res += reverseCount(a,x,m,t);  
  11.         res += reverseCount(a,m,y,t);  
  12.           
  13.         //合并  
  14.         int p = x, q = m, k = x;  
  15.         while(1)  
  16.         {         
  17.             if(p < m){//左非空  
  18.                 if(q < y){//右非空  
  19.                     if(a[p] < a[q]) t[k++] = a[p++];  
  20.                     else{  
  21.                         t[k++] = a[q++];  
  22.                         res += m-p;  
  23.                     }  
  24.                 }  
  25.                 else t[k++] = a[p++];  
  26.             }  
  27.             else{//左空  
  28.                 if(q < y){//右非空  
  29.                     t[k++] = a[q++];  
  30.                     res += m-p;  
  31.                 }//右空  
  32.                 else break;  
  33.             }  
  34.         }  
  35.         for(int i = x; i < y; i ++) a[i] = t[i];  
  36.     }  
  37.     return res;  
  38. }  
  39.   
  40. int main()  
  41. {  
  42.     int n;  
  43.     int a[100], t[100];  
  44.     cin >> n;  
  45.     for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);  //输入数据  
  46.   
  47.     cout << "逆序对数:" << reverseCount(a,1,n+1,t) <<endl;  
  48.   
  49.     return 0;  
  50. }  

       这个问题在O(nlogn)的时间复杂度下解决,当然了也可以通过O(n^2)的枚举直接解决,只不过这里利用了归并排序的一个特性而已。

 

3、快速排序的实现

       还是分治法的三个步骤,(这里的partion函数还有问题,还没实现)

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int partion(int * a, int x, int y)  
  5. {  
  6.     int p = x;//以x为基准元素  
  7.     int i = x+1, j = y-1;  
  8.     while(i < j)  
  9.     {  
  10.         while(i < j && a[i] <= a[p]) i ++;  
  11.         while(j > i && a[j] >= a[p]) j --;  
  12.         if(i < j){int t = a[i]; a[j] = a[j]; a[j] = t;}  
  13.     }  
  14.     int t = a[p]; a[p] = a[i]; a[i] = t;  
  15.     return p;  
  16. }  
  17.   
  18. void quickSort(int * a, int x, int y)//快速排序,区间为[x,y)  
  19. {  
  20.     if(y-x > 1)//如果只有一个元素,不用排序  
  21.     {  
  22.         //划分成左右两个部分(尽量等长),m返回的是划分基准元素所在的位置  
  23.         int m = partion(a,x,y);  
  24.         //递归解决左右两边的排序  
  25.         quickSort(a,x,m);  
  26.         quickSort(a,m+1,y);  
  27.         //不用合并,此时已经有序  
  28.     }  
  29. }  
  30.   
  31. int main()  
  32. {  
  33.     int n;  
  34.     int a[100];  
  35.     cin >> n;  
  36.     for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);  //输入数据  
  37.   
  38.     quickSort(a,1,n+1);  
  39.   
  40.     for(int i = 1; i <= n; i ++) printf("%d ",a[i]);  
  41.     printf("\n");  
  42.   
  43.     return 0;  
  44. }  


 

4、快速排序的一个应用——第k小的数

5、二分查找的实现

      二分查找是在元素有序的前提下的,否则结果是错的。

      问题划分:比较待查找元素v和中间位置的元素a[m]的大小,如果v小,则在左边查找,如果相等,则返回下表m,否者在右边查找。

      递归求解:在判断了v和a[m]大小的情况下,选择左边或者右边进行递归求解。

      合并结果:由于v要么在左边,要么在右边,直接返回就可以了。

       另外,此处的实现是非递归的:

        非递归实现如下:       

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int binarySearch(int * a, int x, int y, int v)//半开区间[x,y)  
  5. {  
  6.     int m;  
  7.     while(x < y)  
  8.     {  
  9.         m = x + (y-x)/2;  
  10.         if(v == a[m]) return  m;//找到了  
  11.         else if(v < a[m]) y = m;//在左边  
  12.         else x = m+1;//在右边  
  13.     }  
  14.     return -1;  
  15. }  
  16.   
  17. int main()  
  18. {  
  19.     int n;  
  20.     int a[100];  
  21.     cin >> n;  
  22.     for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);  //输入数据  
  23.   
  24.     cout << binarySearch(a,1,n+1,3) <<endl;  
  25.   
  26.     return 0;  
  27. }  

递归实现如下:

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int binarySearch(int * a, int x, int y, int v)//半开区间[x,y)  
  5. {  
  6.     if(y-x == 1){//递归边界处理  
  7.         if(v == a[x]) return x;  
  8.         else return -1;//所找元素不存在  
  9.     }  
  10.     else//尚未达到边界,继续划分  
  11.     {  
  12.         int m = x + (y-x)/2;  
  13.         if(v == a[m]) return m;  
  14.         else if(v < a[m]) //在左边查找  
  15.             return binarySearch(a,x,m,v);  
  16.         else //在右边查找  
  17.             return binarySearch(a,m+1,y,v);  
  18.     }  
  19. }  
  20.   
  21. int main()  
  22. {  
  23.     int n;  
  24.     int a[100];  
  25.     cin >> n;  
  26.     for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);  //输入数据  
  27.   
  28.     cout << binarySearch(a,1,n+1,3) <<endl;  
  29.   
  30.     return 0;  
  31. }  

二分查找在每次子问题划分的时候,只选择子问题的一个分支进行递归查找,从而T(n) = log(n),当然了,前提条件是元素有序。

6、二分查找的lower_bound和upper_bound

      lower_bound和二分查找类似,不过在查找到了元素v == a[m]之后,由于可能在m左边还可能存在元素v,因此,此时不应该返回m,而是继续向左边查找(即使查找不到,最后返回的也是左边的右边界后一个位置,即m),而upper_bound也类似,在找到了v == a[m]之后,由于右边还可能出现v,因此不应该返回m,而是继续向右边查找(即使查找不到,返回的是m+1,即v在数组a中最后一次出现位置的下一个位置)

      程序实现如下:

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. int lower_bound(int * a, int x, int y, int v)//半开区间[x,y)  
  5. {  
  6.     int m;  
  7.     while(x < y)  
  8.     {  
  9.         m = x + (y-x)/2;  
  10.         if(v == a[m]){//找到了,但是可能在左边还存在v  
  11.             //return  m;  
  12.             y = m;  
  13.         }  
  14.         else if(v < a[m]) y = m;//在左边  
  15.         else x = m+1;//在右边  
  16.     }  
  17.     return x;  
  18. }  
  19.   
  20. int upper_bound(int * a, int x, int y, int v)//开区间[x,y)  
  21. {   //返回的是v在数组a中最后一次出现的下一个位置  
  22.     int m;  
  23.     while(x < y)  
  24.     {  
  25.         m = x + (y-x)/2;  
  26.         if(v == a[m]){//找到了,但是可能在右边还存在v,应该继续向右边搜,如果右边没有,则返回的事m+1,即v在数组a中最后一次出现位置的下一个位置。  
  27.             //return  m;  
  28.             x = m+1;  
  29.         }  
  30.         else if(v < a[m]) y = m;//在左边  
  31.         else x = m+1;//在右边  
  32.     }  
  33.     return x;  
  34. }  
  35. int main()  
  36. {  
  37.     int n;  
  38.     int a[100];  
  39.     cin >> n;  
  40.     for(int i = 1; i <= n; i ++) scanf("%d",&a[i]);  //输入数据  
  41.   
  42.     int low = lower_bound(a,1,n+1,2);  
  43.     int up = upper_bound(a,1,n+1,2);  
  44.     cout << "2的下界:" << low <<endl;  
  45.     cout << "2的上界:" << up <<endl;  
  46.     cout << "2出现的个数为:" << up-low << endl;  
  47.   
  48.     return 0;  
  49. }  


7、总结

     上面的一些问题,主要是利用了分治法(分解问题、递归求解、合并结果)的思想,具体问题有具体问题的实际技巧。

二分排序法(折半插入法)

二分排序法貌似是第一次听说,在wangsifu2009的博客上看到。 以前只听说过堆排序、快排序、希尔排序、选择、插入、冒泡排序,于是在网上搜索了一下,其代码实现是这样的: #include ...

二分排序与二分查找

1、规定排序的实现       分治法的三步法:           划分问题:(把序列二分),           递归求解:(分别对左右子序列递归排序)           合并结果:(根...

二分排序(java)

基本思想就是:将待排序元素分成大小大致相同 的两个子集合,分别 对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合。package com.suanfa; /** * 二分排序 ...

二分查找、快速排序对比和详解

**二分查找和快速排序(binarySearch)&&(quickSort)** 这两个都是用到分治的思想很容易搞混。而且即使binarySearch是用到分治到不一定意味着递归可以通过循环实现。而且...

排序之二分插入排序

作为一个稳定的排序算法, 插入排序很重要,大多数程序员都可以很轻松的写出插入排序! 先看一下插入排序的代码: void sort_insert(int a[],int n) { int i,j; ...

排序算法二:二分(折半)插入排序

在我的博文《“主宰世界”的10种算法短评》中给出的首个算法就是高效的排序算法。本文将对排序算法做一个全面的梳理,从最简单的“冒泡”到高效的堆排序等。上一篇博文《排序算法一:直接插入排序》讲述了直接插入...

java 二分排序

package com.guxia; public class Test { public static void main(String[] args) { int []a={4,2,1,6...

排序(选择、希尔、二分插入)

选择排序法                                                                                    第1趟,在待排序...
  • zzy7075
  • zzy7075
  • 2014年07月27日 22:35
  • 455

快速排序和二分查找

  • 2015年06月10日 17:16
  • 48KB
  • 下载

二分查找算法和冒泡排序算法

  • 2015年04月22日 11:27
  • 942KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:二分排序与二分查找
举报原因:
原因补充:

(最多只允许输入30个字)