数据结构课程笔记2-认识复杂度、对数器、二分法与异或运算

一,算法评估

1,指标

(1)时间复杂度(流程决定)
时间复杂度的意义在于,当需要处理的样本很大时,真正重要的是最高阶项是什么。这就是时间复杂度的意义,它是衡量算法流程的复杂程序的一种指标,该指标只与数据量有关,与过程之外的优化无关。
(2)额外空间复杂度(流程决定)
作为输入参数的空间,不算额外空间,作为输出结果的空间,也不算额外空间,因为这些都是必要的,与实现目标有关。但除此之外,还需要开辟空间才能让流程继续下去,那这部分空间就是额外空间。
(3)常数项时间(实现细节决定)
同样算法复杂度,执行时间也很可能不相同,因为常数项花费时间不同。

2.时间复杂度

2.1 常数时间的操作:一个操作的执行时间不以具体样本量为转移,每次执行时间都是固定时间,称这样的操作为常数时间操作。

常见的常数时间操作
(1)常见的算术操作(+,-,*,/,%等)
(2)常见的位运算(>>, >>>, <<,|,&,^等)
“>>”:带符号右移动,符号位是什么,就用符号位补,正数为0,负数为1
“>>>”:不带符号右移,不管最高完成什么,都用0来补位
(3)赋值,比较,自增,自检操作等
(4)数组寻址操作
对于链表,linkedList.get(i),需要遍历,不是常数

总之,执行时间固定的操作都是常数时间的操作
反之,执行时间不固定的操作,都不是常数时间的操作

2.2 时间复杂度举例
时间复杂度为N^2的排序算法
选择排序,冒泡排序,插入排序

2.3 如何确定算法流程的总操作与样本数量之间的表达式关系
(1)想象该算法流程所处理的数据状况,要按照最差情况来。
(2)把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间的操作。
(3) 如果数据量为N,看看基本动作的数量和N是什么关系。

注意:算法过程与具体语言无关,分析算法复杂度前提,是对该流程非常熟悉。在拆分算法流程时,拆分出来的所有行为都是常数时间操作,意味着对调用的API要熟悉,否则影响对时间复杂度的估算。

2.4 算法流程的常数项比拼方式
放弃理论分析,生成随机数据直接测。

因为不同常数时间的操作,虽然都是固定时间,但还是有快慢之分。比如位运算的常数时间远小于算术运算的常数时间,而这两个的常数时间又远小于数组寻址的时间。

2.5 问题的最优解
一般情况下,认为解决一个问题的算法流程,在时间复杂度的指标上,一定要尽可能的低,先满足了时间复杂度最低这个指标之后,使用最少的空间的算法流程,叫这个问题的最优解。

不卡常数项时间,不同语言,底层运行速度也不同。

2.6 常见的时间复杂度
排名从好到差
O(1)
O(logN)
O(N)
O(NlogN)
O(N^2) 2,3,…K
O(2^N) 2,3,…K
O(N!)

二,认识对数器

1.你想要测的方法a
2.实现复杂度不好但是容易实现的方法b
3.实现一个随机样本产生器
4.把方法a和方法b跑相同的随机样本,看看得到的结果是否一样
5.如果有一个随机样本使得对比结果不一致,打印样本进行人工干预,改对方法a和方法b
6.当样本数量很多时比对测试依然正确,可以确定方法a已经正确。

Math.random();          [0,1)
Math.random()*N;        [0,N)
(int)(Math.random()*N); [0,N-1]

三,认识二分法

时间复杂度O(logN),一般使用mid = L +((R-L)>>1)来防止溢出,位运算比除运算快很多。
注意:N2可以表达为N<<1, N2+1表达为(N<<1)|1
N左移,低位补0,右移,无符号补0,有符号补符号位(正数补0,负数补1)。

1. 在一个有序数组中,找某个数是否存在

public static boolean binary(int[] arr, int tmp) {
        boolean result = false;
        if (arr != null || arr.length >= 2) {
            int left = 0, right = arr.length - 1, mid;
            while (left <= right) {
                mid = left + ((right - left) >> 1);
                if (tmp == arr[mid]) {
                    result = true;
                    break;
                } else if (tmp < arr[mid]) {
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
        }
        return result;
    }

2. 在一个有序数组中,找>=某个数最左侧的位置

满足>=某个数的条件之后,需要再看看左边是否还有满足条件的数。

public static int binary(int[] arr, int tmp) {
        int result = -1;
        if (arr != null || arr.length >= 2) {
            int left = 0, right = arr.length - 1, mid;
            while (left <= right) {
                mid = left + ((right - left) >> 1);
                if (arr[mid] >= tmp) {
                    result = mid;
                    right = mid - 1;
                } else {
                    left = mid + 1;
                }
            }
        }
        return result;
    }

3. 在一个有序数组中,找<=某个数最右侧的位置

当满足<=某个数之后,需要再向右看,是否有新的满足条件的数。

public static int binary(int[] arr, int tmp) {
        int result = -1;
        if (arr != null || arr.length >= 2) {
            int left = 0, right = arr.length - 1, mid;
            while (left <= right) {
                mid = left + ((right - left) >> 1);
                if (arr[mid] <= tmp) {
                    result = mid;
                    left = mid + 1;
                } else {
                    right = mid - 1;
                }
            }
        }
        return result;
    }

4. 局部最小值问题

什么是局部最小
(1) 0位置比1位置小,0位置就是局部最小
(2)N位置比N-1位置小,N位置就是局部最小
(3)中间位置i,既比i-1小,也比i+1小,就是局部最小。
给定一个无序数组,相邻数不等,返回一个局部最小的数
局部最小

public static int smallest(int[] arr) {
        if(arr==null||arr.length==0){
            return -1;
        }
        if(arr.length==1 || arr[0]<arr[1]){
            return 0;
        }
        if(arr[arr.length-1]<arr[arr.length-2]){
            return arr.length-1;
        }
        int left = 1,right = arr.length-2,mid;
        while (left<right){
            mid=left+((right-left)>>1);
            if(arr[mid]>arr[mid-1]){
                right=mid-1;
            }else if(arr[mid]>arr[mid+1]){
                left=mid+1;
            }else{
                return mid;
            }
        }
        return left;
    }

四,异或操作

异或运算:相同为0,不同为1
同或操作:相同为1,不同为0

异或运算为无进位相加!

1,异或性质

(1)0^N == N, N^N==0
(2)异或运算满足交换律和结合律
异或性质

2.用法

(1)不用额外变量,就交换两个数的值(必须保证两数不是同一个内存)
交换两数值

public static void swapTwoNumber(int a,int b){
        a = a^b;
        b = a^b;
        a = a^b;
}

(2)数组中只有一个数出现了奇数次,其他数都出现了偶数次,求这个数

public static int findOddNumber(int[] nums) {
        int eor = nums[0];
        for (int i = 1; i < nums.length; i++) {
            eor ^= nums[i];
        }
        return eor;
    }

(3)怎么把一个int类型的数,提取出最右侧的1来
N&((~N)+1)
示例
(4)一个数组中,有两种数出现了奇数次,其余数出现了偶数次,求这两种数
设这两种数分别为a,b。先遍历数组,将数组里的数异或一遍,得出eor,eor=a^b。然后找出eor最右侧的1,此时a或者b在最右侧1的位置,必定一个是1,一个是0,否则该位置值为0,提取出最右侧的1为value值。再次遍历数组,将满足最右侧1位置为1的数异或,此时就能得到eor’,此时eor’为a或者b。再将eor’与eor异或,就能得到另一个数。
在这里插入图片描述

public static void findOddNumber(int[] nums){
        if(nums!=null && nums.length>1){
            int eor = 0;
            for(int i = 0;i< nums.length;i++){
                eor = eor ^ nums[i];
            }
            int num = eor&((~eor)+1);
            int eor1=0;
            for(int i =0;i<nums.length;i++){
                if(((num&nums[i])!=0)){
                    eor1 = eor1^nums[i];
                }
            }
            System.out.println("First: "+eor1+", Second:"+(eor^eor1));
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值