我之前有写过关于二叉树的文章,但当时只是笼统介绍,没有详细去讲解。今天就二分查找来好好聊一聊。
这里写目录标题
二叉查找树(二分搜素树)
为什么我们要使用二分查找树
当有一个排好顺序的数组,我们需要知道我们要找的值是否在当前数组中,那么我们就需要自己实现一个查找的方法来进行实现,那么如果我们找的数在最后一个位置n处,假设这个n非常大,那么我们要是一次一次进行比较的话就要比较n次,这样就会显得非常慢,那么有没有一种相较于普通查找速度更快的方法吗,那就是二分查找。
二叉树的实际应用
二叉树的实际应用中主要是用来查找操作和维持相对顺序。
首先是查找,二叉查找树在二叉树基础上增加了几个限制条件:
1、如果左子树不为空,则左子树上所有的节点的值都小于根节点的值。
2、如果右子树不为空,则右子树上所有的节点的值都大于根节点的值。
3、左右孩子同时也满足二叉查找树。
4、没有元素相同的节点。
这里有同学就会问了,二叉查找树有这么多限制条件有什么用呢?当然是为了查找元素啦。
比如我们要查找节点4,4<8所以去左子树查找,4>3去右子树查找,四小于六去左子树找。对于节点分布均匀的二叉查找树来说,如果节点总数是n,则搜索节点的时间复杂度是O(logn)。
好了说完二叉树的查找操作,接下来继续说一下他的第二个应用就是维持相对顺序。哈哈哈其实二叉查找树的另一个名字就叫做二叉排序树。
这样的性质就可以使得插入的每个元素都能插入到适当属于他的位置。但是,仍然存在一个致命的问题。那就是特殊元素的插入。大家看下面这个图。
这样的插入导致的问题就是在查找元素的时候时间复杂度就变成了O(n)。大家看图可以发现,这个二叉树似乎退化成了类似链表的结构。
这个问题也就很好的引出了如何让二叉树变得平衡,这就是我们接下来讲的平衡二叉树(AVL树)。
除了二叉查找树以外,二叉堆也维持着相对顺序。不过他的限制条件没有二叉查找树这么苛刻。后面我们会讲。
二分搜素树的前提条件
- 要查找的序列必须是有序的。何为有序(就是按照升序或者降序的顺序排列而成)这样我们进行二分查找才有意义。
- 二分查找还有一个弊端,就是只能单值查找,不能实现多值查找。
二分查找代码
非递归(力扣704)模板
public int search(int[] nums, int target) {
int n = nums.length;
int left = 0,right = n-1;
//这里注意一下,数组的长度要比索引值多1,所以right要减一。
while (left <= right){
int mid = left+(right-left+1)/2;
int num = nums[mid];
if(num > target){
right = mid-1;
}else if(num < target){
left = mid+1;
}else {
return mid;
}
}
return -1;
}
代码注意事项
- 这里的循环终止条件 while (left <= right) 里面到底取不取等于号?这个要和你设置的右边界right来决定,其实这个就是模板,使用的时候就按自己的模板来,我们这里设置right = length-1;那么终止条件就要取等。因为right所代表的位置参与之中的计算。
- 向左边查找必须right=mid-1, 如果right=mid就会出现存在的元素可能会找不到,在赋值的时候自己可以试一试。
- 使用left=mid+1,这里不使用left=mid是因为我们都已经将mid进行比较过了,不需要再参与运算了。
递归二分查找
//递归方法
public int search2(int[] nums, int target,int left ,int right) {
int mid = left+(right-left)/2;
if(left > right){
return -1;
}
if(nums[mid] == target){
return mid;
}else if(target > nums[mid]){
return search2(nums,target,mid+1,right);
}else{
return search2(nums,target,left,mid-1);
}
}
测试代码
package 二分查找;
public class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
int left = 0,right = n-1;
//这里注意一下,数组的长度要比索引值多1,所以right要减一。
while (left <= right){
int mid = left+(right-left+1)/2;
int num = nums[mid];
if(num > target){
right = mid-1;
}else if(num < target){
left = mid+1;
}else {
return mid;
}
}
return -1;
}
//递归方法
public int search2(int[] nums, int target,int left ,int right) {
int mid = left+(right-left)/2;
if(left > right){
return -1;
}
if(nums[mid] == target){
return mid;
}else if(target > nums[mid]){
return search2(nums,target,mid+1,right);
}else{
return search2(nums,target,left,mid-1);
}
}
public static void main(String[] args) {
Solution s = new Solution();
int[] arr = {-1,2,4,5,6,7,8,9,10};
/*int m =s.search(arr,10);*/
int m = s.search2(arr,4,0,arr.length-1);
System.out.println(m);
}
}
力扣
题目1
解题思路
使用二分查找法来求平方根,就是找谁的平方是你输入的那个值,每次求中间的平方,如果结果小于输入值,就说明查找的范围小了,移动左值到中间值的后一位。
代码
package X的平方根;
/*渣渣鑫*/
public class Solution {
public static void main(String[] args) {
System.out.println(mySqrt(18));
}
public static int mySqrt(int x){
int left = 0, right = x,ans =-1;//ans来保存要返回的值
while (left < right){
int mid = left+(right-1)/2;//使用减法的方式取中间值,防止溢出
if((long)mid*mid <= x){//说明目标值比mid要大;
ans = mid;
left = mid+1;
}else {
right = mid-1;
}
}
return ans+1;
}
}
复杂度分析
题目2
解题思路
与上面的思路类似,都是使用二分查找的方法
代码
public boolean isPerfectSquare(int num) {
int left = 0, right = num;//ans来保存要返回的值
while (left <= right){
int mid = left+(right-1)/2;//使用减法的方式取中间值,防止溢出
if((long)mid*mid < num){//说明目标值比mid要大;
left = mid+1;
}else if((long)mid*mid > num){
right = mid-1;
}else{
return true;
}
}
return false;
}