数据结构与算法

基础数据结构篇

初识算法

二分查找

算法描述

需求:在有序数组 A 内,查找值 target

  • 如果找到返回索引
  • 如果找不到返回 -1
算法
描述
方法一方法二
前提给定一个内含 n 个元素的有序数组 A,满足
A 0 A_0 A0 ≤ \leq A 1 A_1 A1 ≤ \leq A 2 A_2 A2 ≤ \leq . . . ≤ \leq A n − 1 A_{n-1} An1 ,一个待查值 target
给定一个内含 n 个元素的有序数组 A,满足
A 0 A_0 A0 ≤ \leq A 1 A_1 A1 ≤ \leq A 2 A_2 A2 ≤ \leq . . . ≤ \leq A n − 1 A_{n-1} An1 ,一个待查值 target
1设置 i = 0, j = n - 1 (索引 i 和 j 指向的元素都参与运算)设置 i = 0, j = n (索引 j 指向的元素不参与运算)
2如果 i > j ,结束查找,没找到如果 i >= j ,结束查找,没找到
3设置 m = floor( i + j 2 \frac{i+j}{2} 2i+j) , m为中间索引,floor 是向下取整设置 m = floor( i + j 2 \frac{i+j}{2} 2i+j) , m为中间索引,floor 是向下取整
4如果 target < \lt < A m A_m Am 设置 j = m - 1, 跳到第 2 步如果 target < \lt < A m A_m Am 设置 j = m , 跳到第 2 步
5如果 A m A_m Am < \lt < target 设置 j = m +1, 跳到第 2 步如果 A m A_m Am < \lt < target 设置 j = m +1, 跳到第 2 步
6如果 A m A_m Am = target 结束查找,找到了如果 A m A_m Am = target 结束查找,找到了

**总结:**方法一和方法二的不同主要是因为索引 j 指向的元素是否参与运算

代码实现
package Just.binary;

import java.util.Scanner;

public class BinarySearch {
    public static void main(String[] args) {
        //待查找的目标值——target,手动输入
        Scanner sc = new Scanner(System.in);
        System.out.println("输入待查找值:");
        int target = sc.nextInt();
        //待查找的升序数组
        int [] arr = {7,13,21,30,38,44,52,53};
        //定义变量,用来接收返回值
        //调用方法一
        //int num = binarySearchBasic1(arr,target);
        //调用方法二
        int num = binarySearchBasic2(arr,target);
        if(num == -1){
            System.out.println("未找到");
        }else{
            System.out.println("成功找到,索引为" + num);
        }
    }

    //二分查找
    //方法一:
    public static int binarySearchBasic1(int [] arr,int target){
        //定义数组的开头元素索引
        int i = 0;
        //定义数组的结尾元素索引
        int j = arr.length - 1;
        //while循环对数组进行遍历
        while(i <= j){

            //定义数组中索引i和索引j之间的中间索引
            int m = (i + j)/2;
            //把待查值target与中间索引对应的值相比较,确定待查值target所在区间
            if(target < arr[m]){
                //target值小于arr[m],说明如果target存在,在m索引左边,让j = m - 1,继续循环判断
                j = m - 1;
            }else if(target > arr[m]){
                //target值大于arr[m],说明如果target存在,在m索引右边,让j = m + 1,继续循环判断
                i = m + 1;
            }else{
                //target既不大于也不小于,说明target等于arr[m],返回索引
                return m;
            }
        }
        //如果在数组中不存在,返回-1
        return -1;
    }

    //方法二:
    public static int binarySearchBasic2(int [] arr,int target){
        //定义数组的开头元素索引
        int i = 0;
        //定义数组的结尾元素索引
        int j = arr.length;
        //while循环对数组进行遍历
        while(i < j){

            //定义数组中索引i和索引j之间的中间索引
            int m = (i + j) >>> 1;
            //把待查值target与中间索引对应的值相比较,确定待查值target所在区间
            if(target < arr[m]){
                //target值小于arr[m],说明如果target存在,在m索引左边,让j = m - 1,继续循环判断
                j = m;
            }else if(target > arr[m]){
                //target值大于arr[m],说明如果target存在,在m索引右边,让j = m + 1,继续循环判断
                i = m + 1;
            }else{
                //target既不大于也不小于,说明target等于arr[m],返回索引
                return m;
            }
        }
        //如果在数组中不存在,返回-1
        return -1;
    }
}
------------------------------------------------------------------------------------------
运行结果:
输入待查找值:
7
成功找到,索引为0

输入待查找值:
66
未找到

注意:

  • 求中间值索引:m = ( i + j )/2 ;可以使用 无符号右移运算符 >>> 1

m = ( i + j ) >>> 1;

衡量算法好坏

时间复杂度

计算机科学中,时间复杂度是用来衡量:一个算法的执行,随数据规模增大,而增长的时间成本

  • 不依赖于环境

如何表示时间复杂度呢?

  • 假设算法要处理的数据规模是n,代码总的执行行数 f(n) 来表示,例如:
    • 线性查找算法的函数 f(n) = 3*n + 3
    • 二分查找算法的函数 f(n) = ( floor( l o g 2 log_2 log2(n) ) + 1) * 5 + 4
  • 为了对 f(n) 进行化简,应当抓住主要矛盾,找到一个变化趋势与之相近的表示方法

大 O 表示法

其中

  • c c c , c 1 c_1 c1 , c 2 c_2 c2 都是一个常数
  • f ( n ) f(n) f(n) 是实际执行代码行数与 n n n 的函数
  • g ( n ) g(n) g(n) 是经过简化,变化趋势与 f ( n ) f(n) f(n) 一致的 n n n 的函数

asymptotic upper bound

**渐进上界:**从某个常数 n 0 n_0 n0 开始, c ∗ g ( n ) c*g(n) cg(n) 总是位于 f ( n ) f(n) f(n) 上方,那么记作 O ( g ( n ) ) O(g(n)) O(g(n))

  • 举例: f ( n ) = n 2 + 100 f(n)=n^2+100 f(n)=n2+100 ,从 n 0 = 10 n_0=10 n0=10 时, g ( n ) = 2 ∗ n 2 g(n)=2*n^2 g(n)=2n2 是它渐进上界,记作 O ( n 2 ) O(n^2) O(n2)

例1:

  • f ( n ) = 3 ∗ n + 3 f(n)=3*n+3 f(n)=3n+3
  • g ( n ) = n g(n)=n g(n)=n
  • c = 4 c=4 c=4 , 在 n 0 = 3 n_0=3 n0=3 之后, g ( n ) g(n) g(n) 可以作为 f ( n ) f(n) f(n) 的渐进上界,因此表示法写作 O ( n ) O(n) O(n)

例2:

  • f ( n ) = 5 ∗ f l o o r ( l o g 2 ( n ) ) + 9 f(n)=5*floor(log_2(n))+9 f(n)=5floor(log2(n))+9
  • g ( n ) = l o g 2 ( n ) g(n)=log_2(n) g(n)=log2(n)
  • O ( l o g 2 ( n ) ) O(log_2(n)) O(log2(n))

已知 f ( n ) f(n) f(n)来说,求 g ( n ) g(n) g(n)

  • 表达式中相乘的常量,可以省略,如

    • f ( n ) = 100 ∗ n 2 f(n)=100*n^2 f(n)=100n2中的 n n n
  • 多项式中数量规模较小(低次项)的表达式可以省略,如

    • f ( n ) = n 2 + n f(n)=n^2+n f(n)=n2+n中的 n n n
    • f ( n ) = n 3 + n 2 f(n)=n^3+n^2 f(n)=n3+n2中的 n n n
  • 不同底数的对数,渐进上界可以用一个对数函数 l o g n log_n logn表示

    • 例如: l o g 2 ( n ) log_2(n) log2(n)可以替换为 l o g 1 0 ( n ) log_10(n) log10(n)
  • 类似的,对数的常数次幂可以省略

    • 如: l o g ( n c ) = c ∗ l o g ( n ) log(n^c)=c*log(n) log(nc)=clog(n)

常见大 O O O表示法

按时间复杂度从低到高

  • O ( 1 ) O(1) O(1) ,常量时间,意味着算法时间并不随数据规模而变化
  • O ( l o g ( n ) ) O(log(n)) O(log(n)) ,对数时间
  • O ( n ) O(n) O(n) ,线性时间,算法时间与数据规模成正比
  • O ( n ∗ l o g ( n ) ) O(n*log(n)) O(nlog(n)),拟线性时间
  • O ( n 2 ) O(n^2) O(n2) ,平方时间
  • O ( 2 n ) O(2^n) O(2n) ,指数时间
  • O ( n ! ) O(n!) O(n!)
空间复杂度

与时间复杂度类似,一般也使用大 O O O表示法来衡量:一个算法执行随数据规模增大,而增长的额外空间成本

举例:

public static int binarySearchBasic1(int [] arr,int target){
        int i = 0,int j = arr.length - 1;      //设置指针和初值
        while(i <= j){                         //i~j 范围内有东西
            int m = (i + j)/2;
            if(target < arr[m]){               // 目标在左边
                j = m - 1;
            }else if(target > arr[m]){         // 目标在右边
                i = m + 1;
            }else{                             // 找到了
                return m;
            }
        }       
        return -1;
    }

空间复杂度——额外空间成本

​ 在本例中,额外的空间成本是 i、j 和 m 占用的空间,由于其三个都是整形,所以占12个字节,空间复杂度: O ( 1 ) O(1) O(1)

二分查找性能

下面分析二分查找算法的性能

时间复杂度

  • 最坏情况: O ( l o g 2 ( n ) ) O(log_2(n)) O(log2(n))
  • 最好情况:如果待查找元素恰好在数组中央,只需要循环一次 O ( 1 ) O(1) O(1)

空间复杂度

  • 需要常数个指针 i ,j,m,因此额外暂用的空间是 O ( 1 ) O(1) O(1)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值