zoukankan      html  css  js  c++  java
  • 【Cuda并行编程之一】二分查找的探究以及Cuda的简单实现&&相关面试题介绍


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

    一、二分查找的基本过程

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


    二、二分查找的基本代码

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

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

    非递归版本:

    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.   
    9.         if(arr[m] == val ) break;  
    10.         else if(arr[m] < val)   
    11.         {  
    12.             l = m + 1 ;  
    13.         }  
    14.         else  
    15.         {  
    16.             r = m - 1 ;   
    17.         }  
    18.     }  
    19.   
    20.     if(l<=r)  return m;  
    21.     else return -1;  
    22. }  
    递归版本:

    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的值存储在一个局部变量中,这样就不需要每次都重复计算,从而可以稍微减小一些寻址运算。

    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.   
    20.     if(l<=r)  return m;  
    21.     else return -1;  
    22. }  
    又假定我们系统进一步减少寻址运算,这可以通过在整个程序中用指针代替下标来做到。即把程序用凡用到下标的地方统统改成用指针的形式重写即可。

    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.   
    19.     if(l<=r)  return m-arr;  
    20.     else return -1;  
    21. }  
    实际上上面这个程序还是有点问题,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;的形式。那么,我们可以修改最初的两段代码,作出相应优化,保证正确提高效率:

    非递归:

    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.   
    19.     if(l<=r)  return m;  
    20.     else return -1;  
    21. }  
    递归:

    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:

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

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

    1. #ifndef _BINSEARCH_H_  
    2. #define _BINSEARCH_H_  
    3.   
    4. double wtime(void);  
    5.   
    6. #endif  

    运行结果:

    从1~1000中查找666:


    从1~1000中查找6666:



    六、相关面试题

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

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

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

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


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


  • 相关阅读:
    MFC调用C动态库函数-----待补充
    硬盘知识总结:
    Android 四:区分刷机与root
    总结:Linux系统启动流程
    Android 三:手机adb 命令解锁
    UVa11136 Hoax or what
    UVa11988 Broken Keyboard (a.k.a. Beiju Text)
    UVa11280 Flying to Fredericton
    UVa10269 Adventure of Super Mario
    UVa12589 Learning Vector
  • 原文地址:https://www.cnblogs.com/walccott/p/4957572.html
Copyright © 2011-2022 走看看