算法六 二分查找与二叉排序树(Java实现)

547 阅读3分钟

刷题时间:2021.8.25-2021.8.30

二分查找

二分查找又称折半查找。首先,假设表中元素是按升序排列,将表中间位置的关键字与查找关键字比较:

1.如果两者相等,则查找成功;

2.否则利用中间位置将表分成前、后两个子表:1)如果中间位置的关键字大于查找关键字,则进一步查找前一子表;2)否则进一步查找后一子表。

重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

二叉查找(排序)树

二叉查找树(Binary Search Tree),它是-颗具有下列性质的二叉树:

1.若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值。

2.若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值。

3.左、右子树也分别为二叉排序树。

4.等于的情况只能出现在左子树或右子树中的某一侧。

由于二叉查找树的中序遍历是从小到大的,故又名二叉排序树(Binary Sort Tree)。

二叉查找树插入节点

将某节点(insert node), 插入至以node为根二叉查找树中:

如果insert _node节点值小于当前node节点值:如果node有左子树,则递归的将该节点插入至左子树为根二叉排序树中;否则,将node->left赋值为该节点地址。

否则(大于等于情况): 如果node有右子树,则递归的将该节点插入至右子树为根二叉排序树中;否则,将node->right赋值为该节点地址。

二叉查找树查找数值

查找数值value是否在二叉查找树中出现:

如果value等于当前查看node的节点值:返回真。

如果value节点值小于当前node节点值:如果当前节点有左子树,继续在左子树中查找该值;否则,返回假。

否则(value节点值大于当前node节点值):如果当前节点有右子树,继续在右子树中查找该值;否则,返回假。

1、LeetCode35搜索插入位置

class Solution {
    public int searchInsert(int[] nums, int target) {
        int index = -1;//最终返回下标
        int begin = 0;//搜索区间左端点
        int end = nums.length-1;//搜索区间右端点
        while(index == -1){
            int mid = (begin+end)/2;
            if(target == nums[mid]){
                index = mid;
            }
            else if(target < nums[mid]){
                if(mid == 0||target > nums[mid-1]){
                    index = mid;
                }
                end = mid-1;//如果target小于中点,更新区间右端点
            }
            else if(target > nums[mid]){
                if(mid == nums.length-1||target < nums[mid+1]){
                    index = mid+1;
                }
                begin = mid+1;//如果target大于中点,更新区间左端点
            }
        }
        return index;
    }
}

2、LeetCode34在排序数组中查找元素的第一个和最后一个位置

思路:查找区间左端点时,增加如下限制条件:当target == nums[mid]时,若此时mid == 0或nums[mid-1] < target,则说明mid即为区间左端点,返回;否则设置区间右端点为mid-1。查找区间右端点时,增加如下限制条件:当target == nums[mid]时,若此时mid == nums.size()- 1或nums[mid + 1]>target,则说明mid即为区间右端点;否则设置区间左端点为mid + 1。

class Solution {
    public int[] searchRange(int[] nums, int target) {
        int leftIdx = left_bound(nums, target);
        int rightIdx = right_bound(nums, target);
        if (leftIdx <= rightIdx && rightIdx < nums.length) {
            return new int[]{leftIdx, rightIdx};
        } 
        return new int[]{-1, -1};
    }

    public int left_bound(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (target == nums[mid]){
                if (mid == 0||nums[mid-1] < target){
                    return mid;
                }
                right = mid - 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] < target){
                left = mid + 1;
            }
        }
        return -1;
    }

    public int right_bound(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (target == nums[mid]){
                if (mid == nums.length - 1||nums[mid+1] > target){
                    return mid;
                }
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] < target){
                left = mid + 1;
            }
        }
        return -1;
    }
}

3、LeetCode33搜索旋转排序数组

思路:nums[begin]>nums[end]

image.png

image.png

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

leetcode官方题解

class Solution {
    public int search(int[] nums, int target) {
        int n = nums.length;
        if (n == 0) {
            return -1;
        }
        if (n == 1) {
            return nums[0] == target ? 0 : -1;
        }
        int l = 0, r = n - 1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (nums[mid] == target) {
                return mid;
            }
            if (nums[0] <= nums[mid]) {
                if (nums[0] <= target && target < nums[mid]) {
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            } else {
                if (nums[mid] < target && target <= nums[n - 1]) {
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
        }
        return -1;
    }
}

4、LeetCode449序列化和反序列化二叉搜索树

思路:二叉查找树编码为字符串:将二叉查找树前序遍历,遍历时将整型的数据转为字符串,并将这些字符串数据进行连接,连接时使用特殊符号分隔。

将字符串解码为二叉查找树:将字符串按照编码时的分隔符”#”,将各个数字逐个拆分出来,将第一个数字构建为二叉查找树的根节点,后面各个数字构建出的节点按解析时的顺序插入根节点中,返回根节点,即完成了解码工作。

LeetCode评论代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Codec {
    // Encodes a tree to a single string.
    public String serialize(TreeNode root) {
        if (root == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        BST_preorder(root, sb);//将root指向的树转为字符串sb
        return sb.substring(0, sb.length() -1);
    }

    // 前序遍历二叉排序树,将root.val转换为字符串存储至sb
    private void BST_preorder(TreeNode root, StringBuilder sb) {
        if (root == null) {
            return;
        }
        sb.append(root.val).append("#");
        BST_preorder(root.left, sb);
        BST_preorder(root.right, sb);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
         if (data.isEmpty()) {
            return null;
        }
        String[] split = data.split("#");
        return buildTreeNode(split, 0, split.length -1);
    }

    private static TreeNode buildTreeNode(String[] split, int left, int right) {
        if (left > right) {
            return null;
        }
        TreeNode root = new TreeNode(Integer.parseInt(split[left]));
        int index = right + 1;
        for (int i = left + 1; i <= right; i++) {
            if (Integer.parseInt(split[i]) > root.val) {
                index = i;
                break;
            }
        }
        root.left = buildTreeNode(split, left + 1, index -1);
        root.right = buildTreeNode(split, index, right);
        return root;
    }
}

// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// String tree = ser.serialize(root);
// TreeNode ans = deser.deserialize(tree);
// return ans;