基础班第一课 算法复杂度&&简单的排序算法 (左神算法听课笔记)

1. 算法复杂度&&简单的排序算法

本章知识点:

  • 时间复杂度
  • 选择排序,冒泡排序O(n^2),插入排序
  • 了解位运算——异或运算(抖机灵实现swap)和取最低位不为0的方法
  • 二分法的基本应用
  • 使用对数器,摆脱对OJ输入集的依赖
  • Master公式:递归算法的时间复杂度计算公式

(1)认识时间复杂度

常数时间的操作

🤔❓什么是常数时间操作?
一个操作如果和输入样本的数据量没有关系,每次都是按照固定的时间内完成,就是常数操作。

在算法学习中我们关注什么?

  • 在算法学习中,我们只会关注最坏的情况,就是使用O(就是big O,读作“大o”)来表示时间复杂度。
    但是在工程实践中,我们可能还会关注平均的时间复杂度。
  • 注意一些运算规则:如果出现多项式,只保留高阶的n,并且删掉系数。
  • 如果两个算法伯仲难分,用理论上的时间复杂度难以比较他们的优劣(例如快排和归并都是O(n*logn ) 的算法),可以根据实际运行的时间来比较时间效率。这时候比较的就是所谓的“常数项时间”。剧透一下,快排更快
    Wiki

(2)三个简单的O(N^2)排序算法

选择排序

🤔❓选择排序做什么?

  1. 循环遍历数组,每次都通过一个个比较找到最小值。
  2. 找到最小值之后将最小值与数组未排序好的的第一个数swap交换。
public class Code02_BubbleSort {  
    public static void bubbleSort(int[] arr){  
        if(arr == null ||arr.length < 2){  
            return;  
        }  
        // "e" means "end" 
        for(int e = arr.length - 1;e > 0; e--){  
            for(int i = 0; i < e; i++){  
                if(arr[i]>arr[i+1]) {  
                    swap(arr,i,i+1);  
                }  
            }  
        }  
    }  
    public static void swap(int[] arr, int i, int j) {  
        arr[i] = arr[i] ^ arr[j];  
        arr[j] = arr[i] ^ arr[j];  
        arr[i] = arr[i] ^ arr[j];  
    }
 }

冒泡排序

🤔❓冒泡排序做什么?

  1. 循环遍历数组,将相邻两位数字比较
    ->如果后数字>前数字:swap交换,继续比较后面的相邻数字
    ->如果后数字<前数字:继续比较后面的相邻数字
public class Code02_BubbleSort {  
    public static void bubbleSort(int[] arr){  
        if(arr == null ||arr.length < 2){  
            return;  
        }  
        //e means end  
        for(int e = arr.length - 1;e > 0; e--){  
            for(int i = 0; i < e; i++){  
                if(arr[i]>arr[i+1]) {  
                    swap(arr,i,i+1);  
                }  
            }  
        }  
    }  
    public static void swap(int[] arr, int i, int j) {  
        arr[i] = arr[i] ^ arr[j];  
        arr[j] = arr[i] ^ arr[j];  
        arr[i] = arr[i] ^ arr[j];  
    }
}

插入排序

🤔❓插入排序做什么?
理解成有一个和原数组等大的空数组,根据原数组中顺序不断向空数组中插入,每次插入都不会破坏“有序”!
一个个读入,保证有序的部分不断增加。
但是实际上操作不需要这个help辅助数组,直接在原数组上swap就好了,其实也可以理解成按顺序读入交换,保证0~0上有序,0~1上有序,0~2上有序,0~4上有序…0~n-1上有序,排序结束就好了。

public class Code03_InsertionSort {  
    public  static void insertionSort(int[] arr){  
       //只有一个或者没有元素,不需要排序  
        if(arr == null || arr.length < 2){  
            return;  
        }  
        //插入排序 每次插入的是arr[i]  
        for(int i = 1;i < arr.length; i++){  
            //arr[0]不用插入,因为只有一个是有序的  
            for(int j = i - 1; j>= 0 && arr[j] >arr[j+1];j--){  
                swap(arr,j,j+1);  
            }  
        }  
    }  
  
    public  static  void swap(int[] arr, int i,int j){  
        //这种交换方法有缺点:如果交换的两个东西存在同一块内存,就会背洗成0 
        //利用位运算:N^N=0, N^0=N这个性质  
        arr[i] = arr[i] ^ arr[j];  
        arr[j] = arr[i] ^ arr[j];  
        arr[i] = arr[i] ^ arr[j];  
    }
}

(3)二分法

在有序数组中查找一个数是否存在?(经典二分法)

  1. 初始化:使用L和R来表示搜索的左右边界,L表示下边界,R表示上边界。
  2. 只要 L<R 就一直循环每次都检查mid = L+R/2的中点位置

⚠️ L+R/2 是存在问题的,数组可能开的特别特别大,会导致L+R超过了计算机浮点可以表示的范围,导致溢出,变成负数,这里的解决办法是写成:
mid = L + ((R-L) >> 1) //二进制右移一位就是除以2

  1. 如果arr[mid] > find 更新上界R = mid-1
    如果arr[mid] < find 更新下界R = mid+1
    如果相等返回mid
package class01;  
public class Code04_BSExist {  
    public static boolean exist(int[] Sortedarr, int num){  
        if(Sortedarr == null || Sortedarr.length == 0 ){  
            return false;  
        }  
        int L = 0;  
        int R = Sortedarr.length -1;  
        int mid = 0;  
        while (L < R){  
            mid = L + ((L+R) >> 1);  
            //原来这样子写也可以,就是要做三次条件判断不太好,其实可以写成if else的形式  
//            if(Sortedarr[mid] > num){  
//                R = mid - 1;  
//            }  
//            if(Sortedarr[mid] < num){  
//                L = mid + 1;  
//            }  
            if(Sortedarr[mid] == num){  
                return true;  
            }else if(Sortedarr[mid] > num){  
                R = mid - 1;  
            }else{  
                L = mid + 1;  
            }  
        }  
       // return false; 自己这样子写有问题  
        // 如果L=R的时候正好就有Sortarr[L] == num呢???所以不能退出循环就return false啊  
        return Sortedarr[L] == num;  
    }  
}

在有序数组中,找到>=某个数最左侧的位置。

局部最小问题

⚠️ 不一定有序才能二分,这道题就是很典型的例子

(4)异或运算的性质拓展

异或运算的一些性质:
(1)0^N=N N^N=0
(2) 异或运算满足交换律和结合律
(3)可以实现不使用额外变量tmp交换两个数
走神啦:D

一个数组中有一个数字出现了奇数次,其他数出现了偶数次,请找到这个数。

使用了一会运算的性质(1) 的拓展,如果N异或自己异或了偶数次会得到0,奇数次会得到自己。
所以只要将数组中的数字全部都异或一遍,剩下的就是那个出现了奇数次的数字。

public static void  printOddTimesNum1(int arr[]){  
    int eO = 0;  
    //不需要控制边界条件,只需要用值的时候可以这样写  
	    for(int cur : arr){  
        eO^=cur;  
    }  
    System.out.println(eO);  
}

一个数组中有两个数字出现了奇数次,其他数出现了偶数次,请找到这两个数。

这里的情况有点复杂,假设这两个出现了奇数次的数字n和m,如果把数组中的数字都异或一遍得到的结果是n^m,此路不通❕

所以考虑别的做法:
题目显然告诉我们n != m这件事情。
异或运算是位运算,因为这里是整形,所以二进制是32位的0或者1。
不相等的话就意味着着32位里面肯定有至少一位n和m是不相同的!
所以如果能做到将这一位数字都为0的数字自己异或一遍,就会得到n和m中该位为0的那一个数字
然后在用这个数字异或n^m就可以得到另外一个数字了。

这里涉及到的难点就是:
(1)怎么找到这一位不为零的数字?
(2)然后把这类数字全部提取出来呢?

第一个问题其实不难,因为异或运算已经帮我们解决了。只要n^m中不为零的位,就代表着两个数在这一位上面是不相同的。
第二个问题稍微有点难,这涉及到一个位运算的常规操作:把二进制数中最右侧的数提取出来。
这里直接举例子来看怎么做,设待提取字符串位ero:

ero:        1 0 1 0 1 1 1 1 1 1 0 0
(1)取反加一 
~ero + 1 : 0 1 0 1 0 0 0 0 0 1 0 0
(2)上面两个数进行&运算
结果:       0 0 0 0 0 0 0 0 0 1 0 0
最后是1的那位就被提取出来了!确实是第一位为1的位。

代码如下:

public static void printOddTimesNum2(int[] arr){  
    //这个代码听课的时候就听不太懂555  
    //前面和num1是一样的  
    int e0 = 0, eOhasOne = 0;  
    for(int curNum : arr){  
        e0 ^= curNum;  
    }  
    //取反加一!再与得到那位不相同的地方  
    int rightOne = e0 & (~e0 + 1);  
    for(int cur : arr){  
        //再那个不同位为1的数字全部进行与运算。  
        if((cur & rightOne) != 0){  
        //因为rightOne会是一个只有一位为1,其余位为0的32位二进制数字,所以只要在它为1的位上不为1的数字只要和他&运算了,就会得到0,相同的就不会为0。
            eOhasOne ^= cur;  
        }  
    }  
    System.out.println(eOhasOne + " " + (e0 ^ eOhasOne));  
}

(4)递归行为的时间复杂度估算:master公式

T(N) = a*T(N/b) + O(N^d)
这里记一下结论吧(主打一个谁大谁做主):
-> 1) `log(b,a) > d : 复杂度为O(N^log(b,a))
-> 2)`log(b,a) = d` : 复杂度为O(N^d*logN)
-> 3)`log(b,a) < d` : 复杂度为O(N^d)

  • 51
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值