二叉搜索的正确姿势

一般来说,二叉搜索是这样实现的:

int binary_search(vector<int> nums, int target) {
    int left = 0, right = nums.size() - 1;
    int mid = 0;
    while (left <= right) {
        mid = left + (right - left >> 1);
        if (nums[mid] < target) {
            left = mid + 1;
        } else if (nums[mid] > target) {
            right = mid - 1;
        } else {
            return mid;
        }
    }
    return -1;
}

然而这样是非常不合理,第一,left、right用到了左移操作,左移操作对于有符号数,最左添0还是添符号位,在c++里是未定义行为(不过在这里left、right一般大于0,所以符号位就是0),left、right、mid应该用无符号类型,这样可以保证不会溢出,如果溢出了,就变成了负数,于是段错误,程序就会挂掉。

更重要的是,这里没有考虑插入位置,如果考虑插入位置,就需要考虑重复元素的情况。按照STL里的实现,使用两个函数执行实际的二分查找:

lower_bound, upper_bound。前者返回第一个指向小于等于target的迭代器,后者则是第一个大于target的元素位置。同时,返回值处插入元素,可以保持有序序列继续是严格弱序的。对于STL的实现都只要求实现"<",所以可以理解在STL里的排序的条件就只有 a < b为真(即a<b)和a<b为假(即a>=b)两个情况。

同理,要自定义比较函数的话,必须满足a < b和b < a不同时成立,否则STL是有可能出问题的!

下面是示例版的lower_bound和upper_bound,以及返回bool的二叉搜索//返回第一个小于等于target的元素位置,在该位置执行插入,序列将保持有序

int lower_bound(vector<int> &nums, int target) {
    vector<int>::size_type left = 0, right = nums.size();   //左闭右开区间
    vector<int>::size_type mid = 0;
    while (left != right) {
        mid = left + (right - left >> 1);
        if (nums[mid] < target) {
            left = mid + 1;        //左闭右开区间,left应该指向最后一个小于target的位置之后的那个位置
        } else {                   //左闭右开区间,相当把mid所指位置以及其之后的位置除去,如果right=left,则留下left所指位置
            right = mid;
        }
    }
    return left;
}

//返回第一个大于target的元素位置,在该位置执行插入,序列将保持有序
int upper_bound(vector<int> &nums, int target) {
    vector<int>::size_type left = 0, right = nums.size(), mid = 0; //左闭右开
    while (left != right) {
        mid = left + (right - left >> 1);
        if (target < nums[mid]) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return left;
}

bool binary_search(vector<int> &nums, int target) {
int index = lower_bound(nums, target);
return index < nums.size() && nums[lower_bound(nums, target)] == target; }

 

posted on 2015-10-13 19:15  远近闻名的学渣  阅读(183)  评论(0)    收藏  举报

导航