数据结构与算法-常见排序算法(B站左神)

本文详细介绍了数据结构中的时间复杂度概念,以及一系列排序算法,包括选择排序、冒泡排序、插入排序、二分查找、随机数生成、Master公式、归并排序、小和问题、逆序对、荷兰国旗问题、快速排序、堆排序和比较器。还讨论了不同排序算法的时间复杂度和稳定性,如快速排序的平均O(nlogn)和冒泡排序的O(n^2)。此外,文章提到了桶排序和基数排序作为非比较排序的例子。
摘要由CSDN通过智能技术生成

一.数据结构:第一天

1.时间复杂度

  • 常数时间的操作:一个操作如果和样本的数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作。
  • 先熟悉一个算法流程,然后写出这个算法流程中发生了多少常数操作,进而总结出常数操作数量的表达式
    • 只要高阶项,不要低阶项,也不要系数
  • 评价一个算法流程的好坏
    • 先看时间复杂度
    • 然后,分析不同数据样本下的实际运行时间,也就是“常数项时间”

2.常见排序问题

1.选择排序

  • 选择排序【时间复杂度O(n^2) 空间复杂度O(1)】

    for(int i = 0: i < arr.length: i++){
    	int minIndex = i;
    	for(int j = i + 1; j < arr.length; j++){
    		minIndex = arr[j] < arr[minIndex] ? j : minIndex;
    	}
    	swap(arr, i, minIndex);
    }
    

2.冒泡排序

1.冒泡排序
  • 冒泡排序【时间复杂度O(n^2) 空间复杂度O(1)】

    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);
            }
        }
    }
    
2.交换算法
  • 交换算法

    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];
    }
    
3.异或运算
  • 异或运算

    • 等同于无进位相加
    • 0 ^N = N,N^N = 0
    • 满足交换律和结合律
      • a^b = b^a
      • abc = a(bc)
    • 一堆数异或的结果跟异或的先后顺序无关
    • 注意:如果要用异或对两个数进行交换,那么一定要保证:
      • 两个数的地址不同,连个数的值可以相同
      • 如果要对数组中的值进行交换,则要保证两个数的下标不同
  • 提取一个数最右侧的1

    rightOne = eor & (~eor + 1);
    

在这里插入图片描述

  • 异或解决问题:

    • 如果有一堆数,里面有两种数是奇数个,其他都是偶数个,求这两种数

    • public static void printOddTimeNum2(int[] arr){
          int eor = 0;
          for(int i = 0; i < arr.length; i++){
              eor ^= arr[i];
          }
          int rightOne = eor & (~eor + 1)int onlyOne = 0;
           for(int cur : arr){
               if(cur & rightOne == 1){
                  onlyOne ^= cur;
                }
            }
            System.out.println(onlyOne + "" + (eor ^ onlyOne));
          }
      }
      

3.插入排序

  • 插入排序【时间复杂度O(n^2) 空间复杂度O(1)】

    public static void insertionSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        
        for(int i = 1; i < arr.length; i++){
            for(int j = i - 1; j >= 0 && arr[j] > arr[j+1]; j--){
    			swap(arr,j,j+1);
            }
        }
    }
    

4.二分查找

  • 二分查找

    • 插入点:数据状况/问题方向

    • 问题

      • 在一个有序数组中,查找某个数是否存在

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

      • 局部最小值问题

        拉格朗日中值定理
        if(最左侧和最右侧是否是局部最小值){
        	return 最左侧/最右侧;
        }
        else{
            必定存在一点的局部最小值
            二分法选择中点
            判断是否是局部最小值
            if(yes) return 此点;
            else	继续向小的一侧二分查找,直至找到位置;
        }
        
        

5.随机数

  • 随机数

    Math.random()		//[0,1)所有小数,等概率返回
    Math.random() * N 	//[0,N)所有小数,等概率返回
    (int)(Math.random() * N)		//[0,N-1]所有整数等概率返回
    
  • 两数取中点(防止溢出)

    mid = (l + r) / 2;		//一般形式,可能会溢出
    mid = l + (r - l) / 2;	//不会溢出的写法
    mid = l + (r - l) >> 1;	//更简化
    

6.master公式

  • master公式:T(N) = a* + O(N^d)
    • 应用条件:子问题的规模一致
    • 解释
      • a:子问题调用的次数
      • T(N/b):子问题的规模
      • O(N^d):额外的花费
    • 判断
      • log(b,a) > d ——复杂度:O(N的log(b,a)次方) O(N^log(b, a))
      • log(b,a) = d ——复杂度:O(logN * (N的d次方)) 0(N^d *logN)
      • log(b,a) < d ——复杂度:O(N的d次方) 0(N^d)

7.归并排序

  • 归并排序【时间复杂度O(N*logN) 空间复杂度O(N)】

    public static void mergeSort(int[] arr){
        if(arr == null || ar.length < 2){
            return;
        }
        process(arr, 0, arr.length - 1);
    }
    
    public static void process(int[] arr, int L, int R){
        if(L == R){
            return;
        }
        int mid = L + ((R - L) >> 1);
        process(arr, L, mid);
        process(arr, mid + 1, R);
        merge(arr, L, mid, R);
    }
    
    public static void merge(int[] arr, int L, int M, int R){
        int[] help = new int[R - L + 1];
        int i = 0;
        int p1 = L;
        int p2 = M + 1;
        while(p1 <= M && p2 <= R){
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 <= M){
            help[i++] = arr[p1++];
        }
        while(p2 <= R){
            help[i++] = arr[p2++];
        }
        for(i = 0; i < help.length; i++){
            ar[L + i] = help[i];
        }
    }
    

8.小和问题

  • 小和问题

    • 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和,求一个数组的小和
    public static int smallSum(int[] arr){
        if(arr == null || arr.length < 2){
            return 0;
        }
        return process(arr, 0, arr.length - 1);
    }
    
    public static int process(int[] arr, int l, int r){
        if(l == r){
            return 0;
        }
        int mid = l + ((r - 1) >> 1);
        return process(arr, 1, mid) + process(arr, mid + 1, r) + merge(arr, 1, mid, r);
    }
    
    public static int merge(int[] arr, int L, int m, int r){
        int[] help = new int[r - L + 1];
        int i = 0;
        int p1 = L;
        int p2 = m + 1;
        int res = 0;
        while(p1 <= m && p2 <= r){
            res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while(p1 < = m){
            help[i++] = arr[p1++];
        }
        while(p2 <= r){
            help[i++] = arr[p2++];
        }
        for(i = 0; i < help.length; i++){
            arr[L + i] = help[i];
        }
        return res;
    }
    

9.逆序对

  • 逆序对
    • 在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,找出逆序对的数量
    • 解法同上,小和问题

10.荷兰国旗

  • 荷兰国旗问题

    • 给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求时间复杂度O(N),额外空间复杂度O(1)。

    • 一个数组
      设置两个指针
      一个左指针:指针以左小于num(一开始指向最左边,数组之外)
      一个右指针:指针以右大于num(一开始指向最右边,数组之外)
      两指针之间:等于num
      然后从数组下标0开始遍历整个数组
      会遇到3种情况
          1.[i] < num,[i] 和左指针的下一个交换,左指针加一,i++
          2.[i] = num,i++
          3.[i] > num,[i] 和右指针的上一个交换,右指针减一,i++
      

11.快速排序

  • 快速排序【时间复杂度O(N * logN) 空间复杂度O(logN)】

    public static void qucikSort(int[] arr,int L,int R){
        if(l == r){
            return;
        }
        if(l > r - 60){
            //在arr[l..r]插入排序
            //O(N^2)小样本时,跑得快
            //return;
        }
        if(L < R){
            swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
            int[] p = partition(arr, L, R);
            quickSort(arr, L, p[0] - 1);
            quickSort(arr, p[1] + 1, R);
        }
    }
    
    public static int[] partition(int[] arr, int L, int R){
        int less = L - 1;
        int more = R;
        while(L < more){
            if(arr[L] < arr[R]){
                swap(arr, ++less, L++);
            } else if(arr[L] > arr[R]){
                swap(ar, --more, L);
            } else {
                L++;
            }
        }
        swap(arr, more, R);
        return new int[] { less + 1, more};
    }
    

12.堆排序

  • 堆排序【时间复杂度O(N * logN) 空间复杂度O(1)】

    • 总算法
    public static void heapSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        for(int i = 0; i < arr.length; i++){
            headpInsert(arr, i);
        }
        int heapSize = arr.length;
        swap(arr, 0, --heapSize);
        while(heapSize > 0){
            heapify(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
        }
    }
    
    public static void heapInsert(int[] arr, int index){
        while(arr[index] > ar[(index - 1) / 2]){
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }
    
    public static void heapify(int[] arr, int index, int heapSize){
        int left = index * 2 + 1;
        while(left < heapSize){
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            largest = arr[largest] > arr[index] ? largest : index;
            if(largest == index){
                break;
            }
            swap(arr, largest, index);
            index = largest;
            left = index * 2 + 1}
    }
    
    • 如果已生成完全二叉树,然后将其改为大根堆【时间复杂度O(N)】

      在这里插入图片描述

    在这里插入图片描述

    • 系统默认的函数

      public void sortedArrDistanceLessK(int[] arr, int k){
          PriorityQueue<Integer> heap = new PriorityQueue<>();
          int index = 0;
          for(; index <= Math.min(arr.length, k); index++){
              heap.add(arr[index]);
          }
          int i = 0;
          for(; index < arr.length; i++,index++){
              heap.add(arr[index]);
              arr[i] = heap.poll();
          }
          while(!heap.isEmpty()){
              arr[i++] = heap.poll();
          }
      }
      

13.比较器

  • 比较器
    • 实质就是重载比较运算符
    • 可以很好的应用在特殊标准的排序上
    • 可以很好的应用在根据特殊标准排序的结构上

14.桶排序(计数排序和基数排序)

  • 桶排序

    • 分析

      • 不基于比较的排序
      • 时间复杂度O(N),空间复杂度O(M)
      • 应用范围有限,需要样本的数据情况满足桶的划分
    • 计数排序

      • 统计有多少个数,每个数的有多少个
      • 然后输出
    • 基数排序

      • 先查找最大的数,然后将其他数位数不足的用0补齐

      • 然后,从个位开始比较排序

      • 然后,比较十位

      • 然后,依次比较直至最高位

      • 出桶入桶

      • 在这里插入图片描述

      • 代码

      public static void radixSort(int[] arr){
          if(arr == null || arr.length < 2){
              return;
          }
          radixSort(arr, 0, arr.length - 1,maxbits(arr));
      }
      
      public static int maxbits(int[] arr){
          int max = Integer.MIN_VALUE;
          for(int i = 0; i < arr.length; i++){
              max = Math.max(max, arr[i]);
          }
          int res = 0;
          while(max != 0){
              res++;
              max /= 10;
          }
          return res;
      }
      
      public static void radixSort(int[] arr, int L, int R, int digit){
          final int radix = 10;
          int i = 0, j = 0;
          int[] bucket = new int[R - L + 1];
          for(int d = 1; d <= digit; d++){
              in[] count = new int[radix];
              for(i = L; i <= R; i++){
                  j = getDigit(arr[i], d);
                  count[j]++;
              }
              for(i = 1; i < radix; i++){
                  count[i] = count[i] + count[i - 1];
              }
              for(i = R; i >= L; i--){
                  j = getDigit(arr[i], d);
                  bucket[count[j] - 1] = ar[i];
                  count[j]--;
              }
              for(i = L, j = 0; i <= R; i++, j++){
                  arr[i] = bucket[j];
              }
          }
      }
      

15.常见排序算法总结

  • 常见排序算法总结
    • 目前没有找到时间复杂度为O(nlog2n),额外空间复杂度O(1),又稳定的算法。
排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性复杂性
直接插入排序O(n2)O(n2)O(n)O(1)稳定简单
希尔排序O(nlogn)O(n2)O(n1.3)O(1)不稳定较复杂
直接选择排序O(n2)O(n2)O(n2)O(1)不稳定简单
堆排序(空间小)O(nlogn)O(nlogn)O(nlogn)O(1)不稳定较复杂
冒泡排序O(n2)O(n2)O(n)O(1)稳定简单
快速排序(常数项少,推荐)O(nlogn)O(n2)O(nlogn)O(nlogn)不稳定较复杂
归并排序(空间复杂度高,稳定性好)O(nlogn)O(nlogn)O(nlogn)O(n)稳定较复杂
基数排序O(d (n+r))O(d(n+r))O(d(n+r))O(n+r)稳定较复杂
  • 对于排序的改进
    • 充分利用O(nlogn)和O(n2)排序各自的优势
    • 稳定性考虑
  • 额外了解
    • 01 stable sort
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值