复杂度和简单排序算法

风陵南·2023-11-03 01:59·5 次阅读

复杂度和简单排序算法

认识时间复杂度

常数时间的操作#

  • 一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作
    • 例如 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不同的是,本题一定要二分到底,不会提前结束

例:无序数组,没有相等的数,局部最小值问题#

  • 最左侧是数如果小于其右边一个的数,则它是局部最小值,最右侧的数如果小于其左边的一个数,则它是局部最小值,中间的数要比两边的两个数都小,才是局部最小值,现在要寻找一个局部最小值。
  • 无序也有二分法可以利用的时候
  • 先判断两端的数,如果满足局部最小直接返回
  • 两端不满足的情况下,一定是两端的大于内侧的数,两端数据的趋势都是向内递减
  • 从一端到另一端要满足这种趋势,中间则一定存在局部最小值
  • 二分法取中间值,判断其与两边的大小情况,若小于两边,则返回该值
  • 若大于左边,则和数组右端的趋势一样,则左半数据一定存在局部最小值
  • 若大于右边,则和数组左端的趋势一样,则右半数据一定存在局部最小值
  • 依次下去,一定能找到一个局部最小值

 

posted @   风陵南  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
目录
点击右上角即可分享
微信分享提示