C实现二分查找

原文链接:链接

简要描述

二分查找又称折半查找,对排好序的数组,每次取这个数和数组中间的数进行比较,复杂度是O(logn)如:设数组为a[n],查找的数x,

如果x==a[n/2],则返回n/2;

如果x < a[n/2],则在a[0]到a[n/2-1]中进行查找;

如果x > a[n/2],则在a[n/2+1]到a[n-1]中进行查找;

优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。

条件:查找的数组必须要为有序数组。

二种实现方式

1.递归

 
  1. /*

  2. 归的二分查找

  3. arrat:数组 , low:上界; high:下界; target:查找的数据; 返回target所在数组的下标

  4. */

  5. int binarySearch(int array[], int low, int high, int target) {

  6. int middle = (low + high)/2;

  7. if(low > high) {

  8. return -1;

  9. }

  10. if(target == array[middle]) {

  11. return middle;

  12. }

  13. if(target < array[middle]) {

  14. return binarySearch(array, low, middle-1, target);

  15. }

  16. if(target > array[middle]) {

  17. return binarySearch(array, middle+1, high, target);

  18. }

  19. }


 

2.非递归(循环)

 
  1. /*

  2. 非递归的二分查找

  3. arrat:数组 , n:数组的大小; target:查找的数据; 返回target所在数组的下标

  4. */

  5. int binarySearch2(int array[], int n, int target) {

  6. int low = 0, high = n, middle = 0;

  7. while(low < high) {

  8. middle = (low + high)/2;

  9. if(target == array[middle]) {

  10. return middle;

  11. } else if(target < array[middle]) {

  12. high = middle;

  13. } else if(target > array[middle]) {

  14. low = middle + 1;

  15. }

  16. }

  17. return -1;

  18. }


 

推荐使用非递归的方式,因为递归每次调用递归时有用堆栈保存函数数据和结果。能用循环的尽量不用递归。

二分查找的应用

还是对上一篇博文《C++如何跳出多层循环》中提到的抽签问题进行分析。

上一篇博文中是进行了四重循环的嵌套,基时间复杂度是O(n4),数据大时其计算量会大的惊人。为便于分析,将之前代码帖至如下:

 
  1. **

  2. 抽签问题

  3. 解决方案,复杂度n^4

  4. */

  5. void drawLots() {

  6. //从标准输入读入

  7. int numOfCard, sum;

  8. int k[MAX_N];

  9. cout<<"输入numOfCard和sum"<<endl;

  10. cin>>numOfCard>>sum;

  11. cout<<"请输入这sum张卡片的数字"<<endl;

  12. for(int i=0; i<numOfCard; i++) {

  13. cin>>k[i];

  14. }

  15. bool result = false;

  16. bool isBreakLoop = true;

  17. int _sum = 0;

  18. for(int a = 0; a < numOfCard && isBreakLoop; a ++) {

  19. for(int b = 0; b < numOfCard && isBreakLoop; b ++) {

  20. for(int c = 0; c < numOfCard && isBreakLoop; c++) {

  21. for(int d = 0; d < numOfCard && isBreakLoop; d ++) {

  22. _sum = k[a] + k[b] + k[c] + k[d];

  23. if(_sum == sum) {

  24. result = true;

  25. isBreakLoop = false;

  26. }

  27. }

  28. }

  29. }

  30. }

  31. cout << "_sum:" << _sum << " " << "sum:" << sum << endl;

  32. if(result){

  33. cout<<"Yes"<<endl;

  34. } else

  35. cout<<"No"<<endl;

  36. }

最内层循环所做事如下:

Ka + kb + kc + kd = m

移项后如下:

Kd = m - (Ka + kb + kc)

到第四层循环时,其实Ka ,kb,kc已经知道,那问题也就变成了对kd的查找,我们可用上面讲的二分查找,复杂度就降为O(n3logn).实现如下:

降低复杂度的实现

 
  1. /**

  2. 抽签问题

  3. 解决方案,复杂度n^3 * log(n)

  4. */

  5. void drawLots2() {

  6. int numOfCard, sum;

  7. int k[MAX_N];

  8. cout<<"输入numOfCard和sum"<<endl;

  9. cin>>numOfCard>>sum;

  10. cout<<"请输入这sum张卡片的数字"<<endl;

  11. for(int i=0; i<numOfCard; i++) {

  12. cin>>k[i];

  13. }

  14. //对数组进行排序

  15. sort(k, k + numOfCard);

  16. int index = -1;

  17. bool isBreakLoop = true;

  18. for(int a = 0; a < numOfCard && isBreakLoop; a ++) {

  19. for(int b = 0; b < numOfCard && isBreakLoop; b ++) {

  20. for(int c = 0; c < numOfCard && isBreakLoop; c++) {

  21. index = binarySearch2(k, numOfCard, sum - (k[a] + k[b] + k[c]));

  22. if(index >= 0) {

  23. isBreakLoop = false;

  24. }

  25. }

  26. }

  27. }

  28. if(index >= 0){

  29. cout<<"Yes"<<endl;

  30. } else

  31. cout<<"No"<<endl;

  32. }

进一步优化[O(n2logn)]

根据上一步的优化方式,我们可以进一步对内侧两层循环(也就是第三层和第四层)进行思考:

Kc+ kd = m - (Ka + kb )

我们不能直接对Kc+ kd进行查找,但是可以预先枚举出Ka + kb 的n2种数值并排序,再对Kc+ kd进行十分查找。列出枚举O(n2),排序O(n2logn), 循环O(n2logn),所以总的复杂度降为O(n2logn),实现如下:

 
  1. /**

  2. 抽签问题

  3. 解决方案,复杂度n^2 * log(n)

  4. */

  5. void drawLots3() {

  6. int numOfCard, sum;

  7. int k[MAX_N];

  8. cout<<"输入numOfCard和sum"<<endl;

  9. cin>>numOfCard>>sum;

  10. cout<<"请输入这sum张卡片的数字"<<endl;

  11. for(int i=0; i<numOfCard; i++) {

  12. cin>>k[i];

  13. }

  14. int cdNum = numOfCard*(numOfCard+1)/2;

  15. int cdSum[cdNum];

  16. int i = 0;

  17. for(int a=0; a<numOfCard; a++) {

  18. for(int b=i; b<numOfCard; b++) {

  19. cdSum[i ++] = k[a] + k[b];

  20. }

  21. }

  22. //对数组进行排序

  23. sort(cdSum, cdSum + cdNum);

  24. int index = -1;

  25. bool isBreakLoop = true;

  26. for(int a = 0; a < numOfCard && isBreakLoop; a ++) {

  27. for(int b = 0; b < numOfCard && isBreakLoop; b ++) {

  28. for(int c = 0; c < numOfCard && isBreakLoop; c++) {

  29. index = binarySearch2(cdSum, cdNum, sum - (k[a] + k[b]));

  30. if(index >= 0) {

  31. isBreakLoop = false;

  32. }

  33. }

  34. }

  35. }

  36. if(index >= 0){

  37. cout<<"Yes"<<endl;

  38. } else

  39. cout<<"No"<<endl;

  40. }

进一步思考

上面枚举Ka + kb 时其实是有重复的,因为k[i] + k[j] == k[j] + k[i],去除重复值之后,Ka + kb 值的个数是n(n+1)/2。至于n(n+1)/2怎么来,可以简单推导如下:

N     M

1     1

2      2+1

3     3+2+1

4     4+ 3+2+1

......

实现如下:

 
  1. /**

  2. 抽签问题

  3. 解决方案,复杂度n^2 * log(n)

  4. */

  5. void drawLots3_1() {

  6. int numOfCard, sum;

  7. int k[MAX_N];

  8. cout<<"输入numOfCard和sum"<<endl;

  9. cin>>numOfCard>>sum;

  10. cout<<"请输入这sum张卡片的数字"<<endl;

  11. for(int i=0; i<numOfCard; i++) {

  12. cin>>k[i];

  13. }

  14. int cdNum = numOfCard*numOfCard;

  15. int cdSum[cdNum];

  16. for(int a=0; a<numOfCard; a++) {

  17. for(int b=0; b<numOfCard; b++) {

  18. cdSum[a*numOfCard + b] = k[a] + k[b];

  19. }

  20. }

  21. //对数组进行排序

  22. sort(cdSum, cdSum + cdNum);

  23. int index = -1;

  24. bool isBreakLoop = true;

  25. for(int a = 0; a < numOfCard && isBreakLoop; a ++) {

  26. for(int b = 0; b < numOfCard && isBreakLoop; b ++) {

  27. for(int c = 0; c < numOfCard && isBreakLoop; c++) {

  28. index = binarySearch2(cdSum, cdNum, sum - (k[a] + k[b]));

  29. if(index >= 0) {

  30. isBreakLoop = false;

  31. }

  32. }

  33. }

  34. }

  35. if(index >= 0){

  36. cout<<"Yes"<<endl;

  37. } else

  38. cout<<"No"<<endl;

  39. }

猜你喜欢

转载自blog.csdn.net/weixin_42048417/article/details/81270188