【算法学习】二分搜索与二叉排序树:你确定会了吗?要不进来看看?

二分搜索与二叉排序树

二分搜索基础知识

适用于有序数组

二分查找(递归)

bool binary_search(vector<int> &sort_array, int begin, int end, int target){
    if (begin > end) return false;
    int mid = begin + (end - begin)/2;
    if (target == sort_array[mid]) return true;
    else if (target < sort_array[mid])
        return binary_search(sort_array, begin, mid - 1, target)
    else if (target > sort_array[mid])
        return binary_search(sort_array, mid + 1, end, target)
}

二分查找(循环)

bool binary_search(vector<int> &sort_array, int target){
    int begin = 0;
    int end = sort_array.size() - 1;
    while (begin <= end){
        int mid = begin + (end - begin)/2;
        if (target == sort_array[mid]) return true;
        else if (target < sort_array[mid])
            end = mid - 1;
        else if (target > sort_array[mid])
            begin = mid + 1;
    }
    return false;
}

//或者
bool binary_search(vector<int> &sort_array, int target){
    int begin = 0;
    int end = sort_array.size();
    while (begin < end){
        int mid = begin + (end - begin)/2;
        if (target == sort_array[mid]) return true;
        else if (target < sort_array[mid])
            end = mid;
        else if (target > sort_array[mid])
            begin = mid + 1;
    }
    return false;
}

代码细究:

二分循环一般有两种写法,左闭右闭区间 和 左闭右开区间,如上;很多同学在写二分时一下就陷入了死循环,想解决这个问题,我们需要细究以下问题:

问题1:int mid = begin + (end - begin)/2;

int mid = begin + (end - begin)/2 :这么写主要是为了防止溢出,如果你确保不会溢出的话,可以用直接写为(begin+end)/2的方法。

当这么写时,如果begin与end间数量为偶数时,mid更偏向begin,比如begin=1,end=2 => mid=1

问题2:左闭右闭时 begin <= end;

之所以要用=,是因为begin=end时仍然需要判断(有点像废话),同样来看,begin=1,end=2 => mid=1,这里我们一般会判断mid=1时条件是否成立,如果不成立:1)end=mid-1,退出;2)begin=mid+1,这时我们发现如果此时退出的话,mid=2 这个条件就不会被判断了,但是事实上,mid=2这个条件是没有被判断过的。这样我们就发现了=的意义。

问题3:begin=mid+1、end=mid-1;

其实这个就比较简单了,因为我们进循环的第一步就是判断mid时是否成立,因为mid点已经判断过,所以就没有必要再继续加入探索序列。

左闭右开与之相似,这里留给读者自己。

二叉排序树基础知识

具有二叉树性质,且具有以下性质:

若左子树非空,则左子树上所有节点的值均小于或等于根节点值
若右子树非空,则右子树上所有节点的值均大于根节点值(等号也可置于右子树,但只能在一侧)

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode (int x) : val(x), left(NULL), right(NULL) {}
};

二叉排序树中序排序结果满足从小到大

二叉排序树插入节点
void BST_insert (TreeNode* node, TreeNode* insert_node){
    if (insert_node->val <= node->val){
        if (node->left){
            BST_insert(node->left, insert_node);
        }
        else{
            node->left = insert_node;
        }
    }
    else{
        if (node->right){
            BST_insert(node->right, insert_node);
        }
        else{
            node->right = insert_node;
        }
    }
}
二叉排序树查找数值
bool BST_search (TreeNode* node, int value){
    if (node->val == value) return true;
    if (node->val > value) {
        if (node->left) return BST_search(node->left, value);
        else return false;
    }
    else{
        if (node->right) return BST_search(node->right, value);
        else return false;
    }
}

1.插入位置(easy)

题目说明:

给定一个无重复元素有序数组,判断target是否在nums中,是则返回元素下标;否则返回应该插入的位置

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int begin = 0;
        int end = nums.size() - 1;
        while (begin <= end){
            int mid = begin + (end - begin)/2;
            if (target == nums[mid]) return mid;
            else if (target < nums[mid]){
                end = mid - 1;
            }
            else if (target > nums[mid]){
                begin = mid + 1;
            }
        }
        if (begin < nums.size() && nums[begin] < target)
            return begin + 1;
        else
            return begin;
    }
};

2.区间查找(medium)

题目说明:

给定一个有重复数字排序数组,判断target是否在nums中,是则返回左右端点下标;否则返回[-1,-1]

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        return vector<int>{left_bound(nums, target), right_bound(nums, target)};
    }
    int left_bound(vector<int>& nums, int target){
        int begin = 0;
        int end = nums.size() - 1;
        while (begin <= end){
            int mid = begin + (end - begin)/2;
            if (target == nums[mid]){
                if (mid == 0 || nums[mid-1] < target)
                    return mid;
                end = mid - 1;
            }
            else if (target < nums[mid]){
                end = mid - 1;
            }
            else if (target > nums[mid]){
                begin = mid + 1;
            }
        }
        return -1;
    }
    int right_bound(vector<int>& nums, int target){
        int begin = 0;
        int end = nums.size() - 1;
        while (begin <= end){
            int mid = begin + (end - begin)/2;
            if (target == nums[mid]){
                if (mid == nums.size()-1 || nums[mid+1] > target)
                    return mid;
                begin = mid + 1;
            }
            else if (target < nums[mid]){
                end = mid - 1;
            }
            else if (target > nums[mid]){
                begin = mid + 1;
            }
        }
        return -1;
    }
};

3.旋转数组查找(medium)

题目说明:

给定一个无重复元素排序数组nums,且nums可能以某个未知下标旋转,给定target,判断在nums中,是则返回下标;否则返回-1

mid将数组分为两部分,必然有一部分是有序的,判断target是否在有序数组内

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        if(n < 2)
            return (target == nums[0]) - 1;
        int first = 0, end = n - 1;
        while(first <= end){
            int mid = (first + end) / 2;
            if(nums[mid] == target){
                return mid;
            }
            if(nums[mid] >= nums[0]){ //左边是有序的
                if(target >= nums[first] && target < nums[mid])
                    end = mid - 1;
                else
                    first = mid + 1;
            }
            else{
                if(target <= nums[end] && target > nums[mid])
                    first = mid + 1;
                else
                    end = mid - 1;
            }
        }
        return -1;
    }
};

4.二叉排序树编码与解码(medium)

题目说明:

给定一个二叉排序树,实现对其编码与解码。编码即将该树转化为字符串,解码即将字符串转化为二叉排序树。

代码解析:

编码:我们使用前序遍历将二叉树中的元素依序加入到字符串中,并以‘#’为分隔符,为了方便后续解码。这里需要将数字转化为字符串,我们使用自己编写的函数。

解码:解码第一步我们要先将字符串中的数字先提取出来,然后再插入到二叉树中(这里为了能够还原,我们前边就必须使用前序遍历,这样根节点是固定的)

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode (int x) : val(x), left(NULL), right(NULL) {}
};

class Codec {
public:
    string serialize(TreeNode* root){
        string data;
        BST_preorder(root, data);
        return data;
    }
    TreeNode* deserialize(string data){
        if (data.size() == 0) return NULL;
        vector<TreeNode*> node_vec;
        int val = 0;
        for (int i = 0; i < data.length(); i++){
            if (data[i] == '#'){
                node_vec.push_back(new TreeNode(val));
                val = 0;
            }
            else{
                val = val*10 + data[i] - '0';
            }
        }
        for (int i = 1; i < node_vec.size(); i++){
            BST_insert(node_vec[0], node_vec[i]);
        }
        return node_vec[0];
    }
private:
    void change_int_to_string(int val, string& str_val){
        while (val){
            char temp = val % 10 + '0';
            str_val = temp + str_val;
            val = val / 10;
        }
    }
    void BST_preorder(TreeNode* node, string& data){
        if (!node) return;
        string str_val;
        change_int_to_string(node->val, str_val);
        data += str_val + '#';
        BST_preorder(node->left, data);
        BST_preorder(node->right, data);
    }
    void BST_insert (TreeNode* node, TreeNode* insert_node){
        if (insert_node->val <= node->val){
            if (node->left){
                BST_insert(node->left, insert_node);
            }
            else{
                node->left = insert_node;
            }
        }
        else{
            if (node->right){
                BST_insert(node->right, insert_node);
            }
            else{
                node->right = insert_node;
            }
        }
    }
};

5.逆序数(hard)

题目说明:

已知数组nums,求新数组count,count[i]代表了在nums[i]右侧且比nums[i]小的元素个数。

(这道题之前用分治算法做过)

思考:

将元素按照原数组逆置后的顺序插入到二叉排序树中,如何在元素插入时计算已有多少个元素比当前插入元素小?

struct BSTNode {
    int val;
    int count;
    BSTNode* left;
    BSTNode* right;
    BSTNode (int x) : val(x), count(0), left(NULL), right(NULL) {};
};

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums){
        vector<int> result;
        vector<BSTNode* > node_vec;
        vector<int> count;
        for (int i = nums.size()-1; i >= 0; i--){
            node_vec.push_back(new BSTNode(nums[i]));
        }
        count.push_back(0);
        for (int i = 1; i < node_vec.size(); i++){
            int count_small = 0;
            BST_insert(node_vec[0], node_vec[i], count_small);
            count.push_back(count_small);
        }
        for (int i = node_vec.size() - 1; i >= 0; i--){
            delete node_vec[i];
            result.push_back(count[i]);
        }
        return result;
    }
    void BST_insert (BSTNode* node, BSTNode* insert_node, int& count_small){
        if (insert_node->val <= node->val){
            node->count ++;
            if (node->left){
                BST_insert(node->left, insert_node, count_small);
            }
            else{
                node->left = insert_node;
            }
        }
        else{
            count_small += node->count + 1;
            if (node->right){
                BST_insert(node->right, insert_node, count_small);
            }
            else{
                node->right = insert_node;
            }
        }
    }
};
  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值