最近开始复习基础找工作,二分查找算是最基本而且十分重要的算法了,现在完整的解析一下,作为后面复习只用。内容分为几个部分:

一、二分查找的基本过程

折半查找技术,又称为二分查找。它的前提条件是线性表中的记录必须是关键码有序(通常从小到大排序),线性表必须采用顺序存储。折半查找的基本思想是:在有序表中,取中间记录作为比较对象,如果给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。

二、二分查找的基本代码

问题描述为:给定一个顺序的数组arr,以及数组的长度len,要查找的目标值val,用二分查找的方法去判断val是否在数组arr中,如果存在,返回目标值val在数组arr中的下标索引index;如果不存在,那么返回-1。

下面我们将给出二分查找的基本代码,分为非递归和递归版本:

非递归版本:

[cpp] view plaincopy
  1. int bsearch(int val , int *arr , int len)
  2. {
  3. int l = 0 , r = len - 1;
  4. int m;
  5. while( l<=r )
  6. {
  7. m = (l+r)/2; <span style="font-family: 'Microsoft YaHei';">//是否正确且高效?</span>
  8. if(arr[m] == val ) break;
  9. else if(arr[m] < val)
  10. {
  11. l = m + 1 ;
  12. }
  13. else
  14. {
  15. r = m - 1 ;
  16. }
  17. }
  18. if(l<=r)  return m;
  19. else return -1;
  20. }

递归版本:

[cpp] view plaincopy
  1. int bsearch_with_recur(int val,int *arr, int l,int r)
  2. {
  3. int m ;
  4. if( l>r )   return -1;
  5. m = (l+r)/2; //是否正确且高效?
  6. if(val == arr[m]) return m;
  7. else if(val<arr[m]) return bsearch_with_recur(val,arr,l,m-1);
  8. else return bsearch_with_recur(val,arr,m+1,r);
  9. }

三、二分查找的优化代码

上述的两块代码是否正确且高效?

3.1.用指针代替寻址提高速度:值得注意的是,下面求值表达式:m = ( l + r )/2; 中的除法运算可以用移位运算代替,即:m = ( l + r )>>1;这样做的确会提高程序的运行速度。现在首先去掉一些寻址运算,在很多机器上下标运算都要比指针运算慢。我们可以把arr+m的值存储在一个局部变量中,这样就不需要每次都重复计算,从而可以稍微减小一些寻址运算。

[cpp] view plaincopy
  1. int bsearch(int val , int *arr , int len)
  2. {
  3. int l = 0 , r = len - 1;
  4. int m;
  5. while( l<=r )
  6. {
  7. m = (l+r)/2;
  8. int *p = arr+m;
  9. if(*p == val ) break;
  10. else if(*p < val)
  11. {
  12. l = m + 1 ;
  13. }
  14. else
  15. {
  16. r = m - 1 ;
  17. }
  18. }
  19. if(l<=r)  return m;
  20. else return -1;
  21. }

又假定我们系统进一步减少寻址运算,这可以通过在整个程序中用指针代替下标来做到。即把程序用凡用到下标的地方统统改成用指针的形式重写即可。

[cpp] view plaincopy
  1. int bsearch1(int val , int *arr , int len)
  2. {
  3. int *l = arr , *r = arr + len ;
  4. int *m;
  5. while( l<=r )
  6. {
  7. m = (l+r)/2;
  8. if(*m == val ) break;
  9. else if(*m < val)
  10. {
  11. l = m + 1 ;
  12. }
  13. else
  14. {
  15. r = m - 1 ;
  16. }
  17. }
  18. if(l<=r)  return m-arr;
  19. else return -1;
  20. }

实际上上面这个程序还是有点问题,m = ( l + r )/2,这个语句是非法的,因为它试图把两个指针相加。正确的做法是,首先计算出l与r之间的距离(这可以由指针减法得到,并且结果是一个整数),然后把这个距离的一半(也仍然是个整数)与l相加:m = ( r - l )/2 + l; 因为除以2就相当于向右移动一位,而移位的效率要远远高于除法,因此可以改为:m = ( r - l )>>1 + l;注意:>>的优先级低于算数运算符,上式效果实际上是:m = ( r - l )>>( l + 1 );为了避免错误要加上括号:m = ( ( r - l )>>1 ) + l。

3.2.l与r值过大相加溢出:当l和r表示下标而不是指针的时候,如果l或者r过大,那么m = ( l + r )/2;结果就会发生溢出,因此,我们写成:m = ( r - l )/2 + l;的形式。那么,我们可以修改最初的两段代码,作出相应优化,保证正确提高效率:

非递归:

[cpp] view plaincopy
  1. int bsearch(int val , int *arr , int len)
  2. {
  3. int l = 0 , r = len - 1;
  4. int m;
  5. while( l<=r )
  6. {
  7. <strong>m = ( ( r - l )>>1 ) + l;</strong>
  8. if(arr[m] == val ) break;
  9. else if(arr[m] < val)
  10. {
  11. l = m + 1 ;
  12. }
  13. else
  14. {
  15. r = m - 1 ;
  16. }
  17. }
  18. if(l<=r)  return m;
  19. else return -1;
  20. }

递归:

[cpp] view plaincopy
  1. int bsearch_with_recur(int val,int *arr, int l,int r)
  2. {
  3. int m ;
  4. if( l>r )   return -1;
  5. <strong>m = ( ( r - l )>>1 ) + l;</strong>
  6. if(val == arr[m]) return m;
  7. else if(val<arr[m]) return bsearch_with_recur(val,arr,l,m-1);
  8. else return bsearch_with_recur(val,arr,m+1,r);
  9. }

四、二分查找相关的STL

C语言里有bsearch:http://www.cplusplus.com/reference/cstdlib/bsearch/?kw=bsearch

STL之lower_bound : http://www.cplusplus.com/reference/algorithm/lower_bound/?kw=lower_bound

STL之upper_bound : http://www.cplusplus.com/reference/algorithm/upper_bound/?kw=upper_bound

STL之binary_search : http://www.cplusplus.com/reference/algorithm/binary_search/?kw=binary_search

STL之equal_range : http://www.cplusplus.com/reference/algorithm/equal_range/?kw=equal_range

当然学习这些还是需要应用,等做完leetcode和POJ相关问题之后再总结。

五、Cuda的简单实现

最近开始接触Cuda,一个基于GPU的并行计算架构,作为学习用cuda来实现相同的查找问题。只是用并行的方法就不存在了串行的二分查找的问题,最简单粗暴的方式就是利用GPU强大的并行计算能力,将数组arr中的每个元素一次性放到GPU核上进行并行查找,即和目标值val进行比较,那么可以简单的理解为只要比较一次,即在O(1)的时间内就能够得到比较结果(当然没有考虑到调度问题)。

Cuda程序设计的基本流程比较简单:

a.分配host(主机端)的基本变量并赋予初始值

b.在device(GPU)上分配空间,利用CudaMalloc

c.将host端的数值拷贝到device端,利用cudaMemcpy

d.调用kernal函数在device进行计算

f.将device端的计算结果拷贝回到host端,并处理结果

Talk is cheap , show me the code:

cuda_binsearch.cu:

[cpp] view plaincopy
  1. #include<iostream>
  2. #include<vector>
  3. #include<stdio.h>
  4. #include<ctime>
  5. #include "binsearch.h"
  6. using namespace std;
  7. int N;
  8. //kernal function
  9. __global__ void binsearch(int *p , int *val,int *pos, int flag)
  10. {
  11. int tid = blockIdx.x * blockDim.x + threadIdx.x;
  12. if(p[tid]==*val)
  13. {
  14. *pos = tid;
  15. }
  16. }
  17. int main(int argc, char *argv[])
  18. {
  19. if(argc<3)
  20. {
  21. perror("The argument should be : ./a.out N value");
  22. }
  23. vector<int> vec;
  24. int *hp,*dp;
  25. int hval,*dval;
  26. int hpos = -1, *dpos;
  27. int N = atoi(argv[1]);
  28. hval = atoi(argv[2]);
  29. double timing;
  30. for( int i=0;i<N;i++ )
  31. {
  32. vec.push_back(i);
  33. }
  34. //allocate space in device
  35. cudaMalloc( &dp,   N*sizeof(int) ) ;
  36. cudaMalloc( &dval, sizeof(int)  );
  37. cudaMalloc( &dpos, sizeof(int)  );
  38. hp = (int *)&vec[0];
  39. int temp = -1 ;
  40. //copy data from host to device
  41. cudaMemcpy(dp,hp,N*sizeof(int),cudaMemcpyHostToDevice) ;
  42. cudaMemcpy(dval,&hval,sizeof(int),cudaMemcpyHostToDevice);
  43. cudaMemcpy(dpos, &temp, sizeof(int),cudaMemcpyHostToDevice);
  44. timing = wtime();
  45. int block_dim = 128;
  46. int grid_dim = ( N % block_dim == 0 ? (N>>7) : (N>>7)+1 );
  47. //kernal function
  48. binsearch<<<grid_dim,block_dim>>>( dp, dval, dpos,0 );
  49. printf("Computation time is %10.10f\n",wtime()-timing);
  50. //copy data from device to host
  51. cudaMemcpy(&hpos, dpos, sizeof(int),cudaMemcpyDeviceToHost);
  52. if( hpos==-1 )
  53. {
  54. cout<<"this val "<<hval<<" can not be found "<<endl;
  55. }
  56. else
  57. {
  58. cout<<"this val "<<hval<<" can be found at position "<<hpos<<endl;
  59. }
  60. //free the space
  61. cudaFree(dp);
  62. cudaFree(dval);
  63. cudaFree(dpos);
  64. return 0;
  65. }

cuda_wtime.cu:

[cpp] view plaincopy
  1. #include <stdio.h>
  2. #include <sys/time.h>
  3. #include <iostream>
  4. #include <cstdlib>
  5. double wtime(void)
  6. {
  7. double now_time;
  8. struct timeval etstart;
  9. struct timezone tzp;
  10. if(gettimeofday(&etstart,&tzp)==-1)
  11. {
  12. perror("Error:calling gettimeofday() not successfully.\n");
  13. }
  14. now_time = ( (double)etstart.tv_sec ) + ((double)etstart.tv_usec) / 1000000.0;
  15. return now_time;
  16. }
  17. #if 0
  18. int main()
  19. {
  20. double time;
  21. time = wtime();
  22. printf("time of day = %10.4f\n",time);
  23. return 0;
  24. }
  25. #endif

binsearch.h:

[cpp] view plaincopy
  1. #ifndef _BINSEARCH_H_
  2. #define _BINSEARCH_H_
  3. double wtime(void);
  4. #endif

运行结果:

从1~1000中查找666:

从1~1000中查找6666:

六、相关面试题

也在CSDN上看到了一篇不错的二分查找的总结,贴在这里以供学习:http://blog.csdn.net/luckyxiaoqiang/article/details/8937978。在此添加下遇到的校招题目。

2015美团合肥站一道题:现在给你一个数组,左边是升序的,右边是降序的,现在让你找到最大的那个值。要求尽可能小的时间复杂度和空间复杂度。

分析:在不考虑边界的情况下(即最大值一定出现在数组的中间位置,而不是最左边和最右边),那么我通过递归的方式不断的去搜索左右两边的数组序列,那么一定会在几次查找之后找到那个值。当然也能用O(n)的时间复杂度搞定。

[cpp] view plaincopy
  1. #include<iostream>
  2. using namespace std;
  3. int Max(int a[] , int low , int high)
  4. {
  5. if(low > high)     return -1;
  6. int m = low + ( (high-low)>>1 );
  7. if( a[m]>a[m-1] && a[m]>a[m+1] ) return a[m];
  8. else if( a[m]<a[m+1] && a[m]>a[m-1] )
  9. return Max(a,m+1,high);
  10. else return Max(a,low,m-1);
  11. }
  12. int main()
  13. {
  14. int a[] = {-10,0,1,3,5,6,7,9,8,4,2,-1};
  15. cout<<Max(a,0,sizeof(a)/sizeof(a[0])-1)<<endl;
  16. system("pause");
  17. return 0;
  18. }

转载请注明:http://blog.csdn.net/lavorange/article/details/21961045

【Cuda并行编程之一】二分查找的探究以及Cuda的简单实现相关面试题介绍相关推荐

  1. CUDA并行编程较有用的总结

    Cuda并行编程学习时候需注意的一些基本概念 1.Cuda的编程风格:spmp(单程序多数据)的并行编程风格. 2.在多GPU下,cudaMemcpy()不能用于GPU之间的数据复制 3.cudaMe ...

  2. CUDA并行编程概述

    前往我的主页以获取更好的阅读体验 CUDA并行编程概述 - DearXuan的主页https://blog.dearxuan.com/2021/11/15/CUDA%E5%B9%B6%E8%A1%8C ...

  3. java 二分搜索获得大于目标数的第一位_程序员数据结构算法编程,二分查找搜索算法的原理与应用介绍!...

    本文来讲一种搜索算法,即二分搜索算法,通常在面试时也会被问到. 我们先来看一个例子,在图书馆通常是根据查到的编号去找书,可以在书架上按顺序一本本地查找,也可以找到一本书不符合预期时,再跳过一大部分书再 ...

  4. cuda C 编程权威指南 Grossman 第2章 CUDA编程模型

    2.1 CUDA编程模型概述 CUDA编程模型提供了一个计算机架构抽象作为应用程序和其可用硬件之间的桥梁. 通信抽象是程序与编程模型实现之间的分界线,它通过专业的硬件原语和操作系统的编译器或库来实现. ...

  5. 【CUDA并行编程之八】Cuda实现Kmeans算法

    本文主要介绍如何使用CUDA并行计算框架编程实现机器学习中的Kmeans算法,Kmeans算法的详细介绍在这里,本文重点在并行实现的过程. 当然还是简单的回顾一下kmeans算法的串行过程: 伪代码: ...

  6. cuda并行编程之求解ConjugateGradient(共轭梯度迭代)丢失dll解决方式

    在进行图像处理过程中,我们常常会用到梯度迭代求解大型线性方程组.今天在用cuda对神秘矩阵进行求解的时候.出现了缺少dll的情况: 报错例如以下图: 缺少cusparse32_60.dll 缺失cub ...

  7. MFC编程入门之十三(对话框:属性页对话框及相关类的介绍)

    前面讲了模态对话框和非模态对话框,本节来将一种特殊的对话框--属性页对话框. 属性页对话框的分类 属性页对话框想必大家并不陌生,XP系统中桌面右键点属性,弹出的就是属性页对话框,它通过标签切换各个页面 ...

  8. CUDA C++编程手册(总论)

    CUDA C++编程手册(总论) CUDA C++ Programming Guide The programming guide to the CUDA model and interface. C ...

  9. 多核时代,并行编程为何“臭名昭著”?

    作者 | Yan Gu 来源 | 转载自知乎用户Yan Gu [导读]随着计算机技术的发展,毫无疑问现代计算机的处理速度和计算能力也越来越强.然而细心的同学们可能早已注意到,从2005年起,单核的 C ...

最新文章

  1. 李沐:五年工作反思!
  2. sega+model+3+android,世嘉MODEL2经典老游戏移植登场 追加联网对战
  3. php弹出消息翻页,一个很不错的PHP翻页类
  4. spring boot异常——java.net.BindException: Address already in use: bind
  5. vscode php输出,js程序如何在vscode控制台输出
  6. 【架构】典型的 K8s 架构图-核心概念(简化)
  7. PrepareStatement 和Statement 的区别?
  8. 自学嵌入式能找到工作吗_如何找到理想的嵌入式软件工作
  9. php fitnesse,Fitnesse+RestFixture:Web 服务回归测试利器
  10. 下半年值得关注的新机和科技趋势
  11. TP5 Validate 验证
  12. 【图论】拉普拉斯矩阵(Laplacian matrix)
  13. [poj2449]Remmarguts' Date(spfa+A*)
  14. 如何进行windows数据恢复呢
  15. FOI 冬令营 Day6
  16. devc++工程提示“源文件未编译”的可能问题
  17. linux中获取日志5分钟以内的内容
  18. 【总结思考】如何提高项目的稳定性和开发效率
  19. 全世界明星都在穿白T?永不发黄,显瘦十斤,两件才99元!
  20. 京东商城手机爬虫和数据分析项目

热门文章

  1. 剑指offer面试题(31-40)——java实现
  2. 线上教育核心竞争力是什么?声网发布在线素质、职业教育解决方案
  3. 读书笔记 Tom Expert 00章-配置环境
  4. latch的概念与机制
  5. 翻译 | Qt for Python的技术愿景前瞻
  6. VSCode设置中文语言显示
  7. 【OpenGL C++】画一个空心汉字和一个圆,并填充汉字(中点画线法,中点画圆法,种子填充法)
  8. 教师资格证面试试讲时可以戴手表吗
  9. 数据库中用户登录注册用户信息表怎么设计如何设计
  10. 英语字母c的语言教案,幼儿园小班英语单词教案五篇