复杂度和简单排序算法
认识时间复杂度
常数时间的操作#
- 一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。
- 例如
int num = arr[i];
中不管arr数组中有多少数据,每次赋值都是根据索引一次查询,都是固定时间内完成,是常数操作 - 而假如有链表list
int num = list.get[i];
中获取链表中的元素要逐个遍历,若获取第10万个数据需要10万次操作,所以不是常数操作
- 例如
时间复杂度#
- 拿选择排序来举例,选择排序是每一轮找到最小值,再与最前面的值交换。数组长度为N,总共下来会有多少次常数操作:
- 拿到数组中的元素是一次常数操作 如arr[i] ,一轮排序中每个元素都会拿到,所以是N次
- 比较两个元素也是一次常数操作 ,一轮中每个元素都会比较一次,也是N次
- 找到最小的元素后,要与最前面的元素交换,一轮会交换 1次
- 总的下来:
- 获取元素:N + N-1 + N-2 + ...
- 进行比较:N + N-1 + N-2 + ...
- 进行交换:1 + 1 + 1 + ... = N 次
- 总共加起来 可以写成 aN² + bN + c 次
- 其中 去掉低次项,去掉最高次项的系数也就是 N²
- 时间复杂度为 O(N²)
简单排序算法
选择排序 #
- 前面已经说了选择排序的具体过程,直接看算法实现
public static void selectSort(int[] arr){ if(arr == null || arr.length < 2){ return; } for (int i = 0; i < arr.length - 1; i++) { int minIndex = i; for (int j = i + 1; j < arr.length; j++) { // i~N-1 找最小值下标 minIndex = arr[j] < arr[minIndex] ? j : minIndex; } int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } }
冒泡排序#
- 挨个比较,后者比前者大,则交换位置,最终一轮过后,最大的会被放到最后面,如同冒泡般冒出来
- 和选择排序一样,算法时间复杂度也为 O(N²)
- 代码实现:
/** * 小数往前冒 * @param arr */ public static void bubbleSort_0(int[] arr){ for (int i = 0; i < arr.length; i++) { for (int j = arr.length - 1; j > i; j--) { if(arr[j] < arr[j - 1]){ int temp = arr[j]; arr[j] = arr[j-1]; arr[j-1] = temp; } } } } /** * 大数往后冒 * @param arr */ public static void bubbleSort_1(int[] arr){ for (int i = arr.length - 1; i > 0; i--) { for(int j = 0; j < i; j++){ if(arr[j] > arr[j + 1]){ int temp = arr[j]; arr[j] = arr[j + 1]; arr[j+1] = temp; } } } }
异或运算#
- 按位运算,相同为0,相异为1,也可以理解为无进位相加
- 异或性质:
- 1) 0 ^ N = N N ^ N = 0
- 2)满足交换律和结合律
- 推导出以下代码可以交换两数的值
-
// 令 a = 甲; b = 乙; a = a ^ b; // a = 甲 ^ 乙 b = a ^ b; // b = 甲 ^ 乙 ^ 乙 = 甲 ^(乙 ^ 乙)= 甲 a = a ^ b; // a = 甲 ^ 乙 ^ 甲 = (甲 ^ 甲)^ 乙 = 乙 // 最终 a = 乙,b = 甲;完成了两数的互换 - 注意:该异或运算是对内存地址进行交换(数值相同也可以完成互换),若内存地址相同运算结果会为0;
- 在数组的交换中慎用!(交换i和j位置时,i不能等于j)
例题:#
- 一个数组中,只有一种数出现了奇数次,其他数都是偶数次,求这个奇数次的数。
- 利用异或的两个性质:交换律结合律以及 n ^ n = 0, 0^ n = n;
- 也就是说如果整个数组全部异或起来,出现偶数次的数最终都会变成0(通过结合律与交换律),最终留下的就只有奇数次的数
int eor = 0; for(int i = 0; i < arr.length; i ++){ eor ^= arr[i]; } System.out.println(eor);
- 一个数组中,有两种数出现了奇数次,其他的数都是偶数次,求这两个数。
- 假设两个数是a和b,同上一题一样,全部异或一遍之后 会得到 eor = a ^ b;
- 又因为 a 不等于 b,所以 eor 的值一定不等于 0 ,eor的二进制32位中,一定有某位是为1的(不可能全0)假设为第n位
- 说明第 n 位上,要么 a=0,b=1,要么a=1,b=0;
- 数组中第 n 位为 1 的数就有 a、b二者之一,以及偶数个第 n 位为 1 的其他数
- 再定义一个 eor2 = 0;仅遍历异或数组中数据的第n位为 1 数
- 就能够得到 a 或者 b 既 eor2 = a 或者 eor2 = b;
- eor ^ eor2 即为剩下的另一个数
int eor = 0; for(int i = 0; i < arr.length; i ++){ eor ^= arr[i]; } // eor = a ^ b // eor != 0 eor 必然有一个位置上是1 (二进制) int rightOne = eor & (~eor + 1); // 位运算中提取出最右边的一:取反加一再相与 int eor2 = 0; for(int i = 0; i < arr.length; i++){ if(arr[i] & rightOne == 0){ eor2 ^= arr[i]; } } System.out.println(eor2 + " " + eor ^ eor2);
插入排序#
- 插入排序要求每轮保证 0 ~ i 范围内有序,先0~1范围内有序,0~2范围内有序......直到整个都有序
- 具体做法在 0~i-1 已经有序的情况下,拿第 i 个数从后往前依次比较,如果 i 较小则交换,i 较大说明当前位置为正确的位置,即可保证 0 ~ i 范围内有序
- 时间复杂度也为O(N²),但是在最好的情况下可以达到O(N)
- 而选择排序和冒泡排序是严格的O(N²)
- 在基本有序的情况下,插入排序效率是最高的
- 代码实现:
/** * 插入排序 * @param arr */ public static void insertSort(int[] arr){ if(arr == null || arr.length < 2){ return; } // 0~0 肯定有序 // 直接从1开始 for (int i = 1; i < arr.length; i++) { for (int j = i; j > 0; j--) { if(arr[j] < arr[j-1]){ int temp = arr[j]; arr[j] = arr[j - 1]; arr[j - 1] = temp; }else{ break; } } } }
二分法#
- 时间复杂度:O(log2N)
例:在一个有序数组中找某个数是否存在#
- 前提是有序,先找出中间数,如果该数比中间数大,那么该数一定不在左边,然后再找右边的中间数,依次下去
例:在一个有序数组中,找>=某个数最左侧的位置#
- 如例1,同样先二分,如果中间数满足>=要找的数,用一个变量t存储这时的下标,取左边 继续二分,如果不满足,则取右边二分。
- 继续二分之后得到的中间数如果也满足,且下标比t更小,则替换t的值。
- 最终一定能找到最靠左侧的值的位置
- 与题1不同的是,本题一定要二分到底,不会提前结束
例:无序数组,没有相等的数,局部最小值问题#
- 最左侧是数如果小于其右边一个的数,则它是局部最小值,最右侧的数如果小于其左边的一个数,则它是局部最小值,中间的数要比两边的两个数都小,才是局部最小值,现在要寻找一个局部最小值。
- 无序也有二分法可以利用的时候
- 先判断两端的数,如果满足局部最小直接返回
- 两端不满足的情况下,一定是两端的大于内侧的数,两端数据的趋势都是向内递减
- 从一端到另一端要满足这种趋势,中间则一定存在局部最小值
- 二分法取中间值,判断其与两边的大小情况,若小于两边,则返回该值
- 若大于左边,则和数组右端的趋势一样,则左半数据一定存在局部最小值
- 若大于右边,则和数组左端的趋势一样,则右半数据一定存在局部最小值
- 依次下去,一定能找到一个局部最小值
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)