• 二分检索(查找)的各种变种


    其实,二分法真的不那么简单,尤其是二分法的各个变种。 最最简单的二分法,就是从一个排好序的数组之查找一个key值。 如下面的程序:

    int search(int *arr, int n, int key)
    {
        int left = 0, right = n-1;
        while(left<=right) {
            int mid = left + ((right - left) << 1);
            if (arr[mid] == key) return mid; 
            else if(arr[mid] > key) right = mid - 1;
            else left = mid + 1;
        }
        return -1;
    }

    这个程序,相信只要是一个合格的程序员应该都会写。 稍微注意一点, 每次移动left和right指针的时候,需要在mid的基础上+1或者-1, 防止出现死循环, 程序也就能够正确的运行。



    但如果条件稍微变化一下, 你还会写吗?如,数组之中的数据可能可以重复,要求返回匹配的数据的最小(或最大)的下标;更近一步, 需要找出数组中第一个大于key的元素(也就是最小的大于key的元素的)下标,等等。 这些,虽然只有一点点的变化,实现的时候确实要更加的细心。 下面列出了这些二分检索变种的实现。

    重复的例子如:1,3,3,4,5

    1. 找出第一个与key相等的元素

    int searchFirstEqual(int *arr, int n, int key)
    {
        int left = 0, right = n-1;
        while(left<=right) {
            int mid = (left+right)/2;
            if(arr[mid] >= key) right = mid - 1;
            else if(arr[mid] < key) left = mid + 1;
        }
        if( left < n && arr[left] == key) return left;
        return -1;
    }

    关键在a[mid]>=key,即使a[mid]已经和key相等了,我们也应该吧right=mid-1;为的是寻找最新的index。

    最后为什么要加判断:

    left < n 
    左边界要小于n left一直是+1.

    如果
    0 1 2 3 4
    1 3 3 4 5
    mid=2; a[2]=3>=3; right=2-1=1;
    mid=0 a[0]=1<3 left=1, right=1
    mid=1,a[mid]>=3 right=0;
    由于left>right退出while.

    为什么不需要left>=0;
    0 1 2 3 4
    1 3 3 4 5
    假设我们查找的为-1
    a[mid]=2;high=1;
    a[0]=1>-1;high=0;
    low=0; a[0]>-1; high=-1; 退出
    可以看到,如果要查找的小于第一个数的话,low始终为0.

    如果要查找的大于数组中的元素的话:
    low一直加,直到n退出了。

    如果要查找的数位2;也就是在2个数之间
    high=1;
    mid=0; a[0]<2;low=mid+1; low=1;
    a[1]=3>2; high=mid-1=0;



    2. 找出最后一个与key相等的元素
    int searchLastEqual(int *arr, int n, int key)
    {
        int left = 0, right = n-1;
        while(left<=right) {
            int mid = (left+right)/2;
            if(arr[mid] > key) right = mid - 1;
            else if(arr[mid] <= key) left = mid + 1; 
        }
        if( right>=0 && arr[right] == key) return right;
        return -1;
    }

    为什么要判断:

     right>=0  right一直减1.

    可以这么记忆,查找第一次出现的就是left,因为left一直加1,所以要判断left<n;
    查找最后一次出现的right,因为right一直减1,所以要判断right>=0;

    3. 查找第一个等于或者大于Key的元素
    和第一个查找第一个等于key前面部分是相同的,只是后面的判断不同
    int searchFirstEqualOrLarger(int *arr, int n, int key)
    {
        int left=0, right=n-1;
        while(left<=right) {
            int mid = (left+right)/2;
            if(arr[mid] >= key) right = mid-1;
            else if (arr[mid] < key) left = mid+1;
        }
        return left;
    }
    
    
    int a[5]={1,3,3,5,6};
    查找0.
    right一直减,直到right为-1.退出while,返回left为0.

    4. 查找第一个大于key的元素
    int searchFirstLarger(int *arr, int n, int key)
    {
        int left=0, right=n-1;
        while(left<=right) {
            int mid = (left+right)/2;
            if(arr[mid] > key) right = mid-1;
            else if (arr[mid] <= key) left = mid+1;
        }
        return left;
    }

    5. 查找最后一个等于或者小于key的元素

    int searchLastEqualOrSmaller(int *arr, int n, int key)
    {
        int left=0, right=n-1;
        while(left<=right) {
            int m = (left+right)/2;
            if(arr[m] > key) right = m-1;
            else if (arr[m] <= key) left = m+1;
        }
        return right;
    }

    6. 查找最后一个小于key的元素

    int searchLastSmaller(int *arr, int n, int key)
    {
        int left=0, right=n-1;
        while(left<=right) {
            int mid = (left+right)/2;
            if(arr[mid] >= key) right = mid-1;
            else if (arr[mid] < key) left = mid+1;
        }
        return right;
    }
    下面是一个测试的例子:
    int main(void) 
    {
        int arr[17] = {1, 
                       2, 2, 5, 5, 5, 
                       5, 5, 5, 5, 5, 
                       5, 5, 6, 6, 7};
        printf("First Equal           : %2d 
    ", searchFirstEqual(arr, 16, 5));
        printf("Last Equal            : %2d 
    ", searchLastEqual(arr, 16, 5));
        printf("First Equal or Larger : %2d 
    ", searchFirstEqualOrLarger(arr, 16, 5));
        printf("First Larger          : %2d 
    ", searchFirstLarger(arr, 16, 5));
        printf("Last Equal or Smaller : %2d 
    ", searchLastEqualOrSmaller(arr, 16, 5));
        printf("Last Smaller          : %2d 
    ", searchLastSmaller(arr, 16, 5));
        system("pause");
        return 0;
    }
    最后输出结果是:
    点击(此处)折叠或打开
    First Equal           :  3
    Last Equal            : 12
    First Equal or Larger :  3
    First Larger          : 13
    Last Equal or Smaller : 12
    Last Smaller          :  2

    很多的时候,应用二分检索的地方都不是直接的查找和key相等的元素,而是使用上面提到的二分检索的各个变种,熟练掌握了这些变种,当你再次使用二分检索的检索的时候就会感觉的更加的得心应手了。

    转自:http://blog.chinaunix.net/uid-1844931-id-3337784.html




  • 相关阅读:
    Ajax 的 GET 和 POST 模式
    AJax中post与get请求注意事项
    代理模式 (Proxy)
    装饰模式 (Decoratory)
    抽象工厂模式( Abstract Factory )
    单例模式(Singleton)
    原型设计模式
    Intro.js的简介和用法
    mysql 分片
    数据分片(二)如何为数据分片
  • 原文地址:https://www.cnblogs.com/youxin/p/3289441.html
Copyright © 2020-2023  润新知