复杂度和简单排序算法

发布时间 2023-11-03 02:01:41作者: 风陵南

认识时间复杂度

常数时间的操作

  • 一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作
    • 例如 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);

 

 

插入排序