排序算法相关知识

Posted by CHuiL on July 3, 2020

[TOC]

排序算法

简单选择排序(了解)

  • 基本思路:不断的从未排序的区域中选择出最小(大)的元素放入到排序的数组区域中;
  • 时间复杂度:最好=平均=最差:O(n^2);
  • 空间复杂度:O(1)
  • 不稳定
public int[] selectSort(int[] nums){
    int len = nums.length;
    for(int i = 0 ; i < len ; i++){
        int min = i;
        for(int j = i ; j < len ; j++){
            if (nums[j] < nums[min]){
                min = j;
            }
        }
        swap(nums,min,i);
    }
    return nums;
}

public void swap(int[] nums,int i,int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

插入排序(熟悉)

  • 基本思路:从第一个元素开始,依次从后选择一个元素,然后跟前面排好序的区域进行比较,插入到合适的位置中;
  • 时间复杂度:最好:O(n);平均=最差:O(n^2);
  • 空间复杂度:O(1)
    • 特点:可以提前终止内存循环,在数组几乎有序的前提下,插入排序的时间复杂度可以达到O(N);
  • 稳定
      public int[] insertSort(int[] nums){
          int len = nums.length;
          for(int i = 1 ; i < len ; i++){
              int temp = nums[i];
              int j = i;
              while(j > 0 && nums[j-1] > temp){
                  nums[j] = nums[j-1];
                  j--;
              }
              nums[j] = temp;
          }
          return nums;
      }
    

归并排序

  • 基本思路:先两两拆分递归至一个元素,然后依次往上让相邻的子数组进行排序。分而治之加上对两个已经排好序的数组合并为一个新的排序数组;
  • 时间复杂度:平均=最差=最好:O(nlogn)
  • 空间复杂度:O(n)
  • 稳定 || 不稳定:可以选择稳定也可以不稳定,区别在于将前后两个数组进行排序放入临时数组时,nums[l]与nums[r]的比较是< 还是 <= ;<=就会保持原来的位置。
    class Solution {
      public int[] sortArray(int[] nums) {
          mergeSrot(nums,0,nums.length-1,new int[nums.length]);
          return nums;
      }
        
      public void mergeSrot(int[] nums,int left,int right,int[] temp){
          if (left >= right){
              return;
          }
    
          int mid = left+(right-left)/2;
          mergeSrot(nums,left,mid,temp);
          mergeSrot(nums,mid+1,right,temp);
            
          //本身就有序了,不需要在进行操作。
          if (nums[mid] < nums[mid+1]){
              return;
          }
          int l = left;
          int r = mid+1;
          int index = l;
          while(l <= mid && r <= right){
              if (nums[l] <= nums[r]){
                  temp[index++] = nums[l++];
              }else{
                  temp[index++] = nums[r++];
              }
          }
          while(index <= right) temp[index++] = l==(mid+1)?nums[r++]:nums[l++];
          for(int i = left;i<=right;i++){
              nums[i] = temp[i];
          }
      }
    }
    

快速排序

  • 基本思路:分治;每次选择数组中的一个元素(一般是第一个),然后从尾部开始比较,当尾部指针指向的元素小于选择的元素,与头指针进行交换,然后再从头指针开始寻找比选择元素大的元素,与尾部指针交换,再从尾部开始,直到首位相邻;因为是分治,所以数组会被不断的折半;
  • 时间复杂度:最好=平均=O(nlogn);最差O(n^2)
  • 空间复杂度:O(logn)
  • 不稳定
class Solution {
    private static final Random RANDOM = new Random();

    public int[] sortArray(int[] nums){
        quickSort(nums,0,nums.length-1);
        return nums;
    }

    public void quickSort(int[] nums,int left,int right){
        if (left >= right){
            return;
        }

        int pivot = partition(nums,left,right);
        quickSort(nums,left,pivot-1);
        quickSort(nums,pivot+1,right);
    }

    public int partition(int[] nums,int left,int right){
        int randomIndex = RANDOM.nextInt(right - left + 1) + left;
        swap(nums, left, randomIndex);
        int lt = left;
        int pivot = nums[lt];

        for(int i = left+1;i <= right ; i++){
            if (nums[i] < pivot){
                lt++;
                swap(nums,lt,i);
            }
        }
        swap(nums,left,lt);
        return lt;
    }    
    public void swap(int[] nums,int i,int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

}

这段代码优化的点: 1.不是选择数组中的第一个元素,而是随机选取一个,这样才能使算法的复杂度平均,不然很容易就变为插入排序了。 2.在根据pivot数组左右分割时,将pivot放在第一个,然后(left,lt]表示小于pivot的元素,依次遍历当前数组,若遍历到元素小于lt,则lt后移一位,然后将小于pivot的元素交换过来,相当于将小于pivot的元素都移到前面。遍历完成之后,此时小于pivot的元素值就都在(left,lt],再讲left与lt交换,此时pivot就在它最终的位置上,小于pivot的值都在他前面了

堆排序

  • 基本思路:以大根堆为例,将一维的数组看成一个完成二叉树,保证每个分支节点都符合堆;
     :大根堆就是左右子节点都小于父节点,小根堆就是左右子节点都大于父节点
    调整堆:将首位元素交换后,需要重新将当前最大的元素调整到根节点,从根节点开始依次找出子节点较大的那个节点,然后交换上去,直到没有比他大的或者到底的; 初建堆:就是将原来树调整为堆。
  • 时间复杂度:平均=最差=最好:O(nlong)
  • 空间复杂度:O(1);
  • 不稳定
  • 堆排序的过程,可以看这个视频: 数据结构排序算法之堆排序演示
    public int[] heapSort(int[] nums) {
        int len = nums.length;
        // 将数组整理成堆
        buildHeap(nums);

        // 循环不变量:区间 [0, i] 堆有序
        for (int i = len - 1; i >= 1; ) {
            // 把堆顶元素(当前最大)交换到数组末尾
            swap(nums, 0, i);
            // 逐步减少堆有序的部分
            i--;
            // 下标 0 位置下沉操作,使得区间 [0, i] 堆有序
            siftDown(nums, 0, i);
        }
        return nums;
    }

    /**
     * 将数组整理成堆(堆有序);
     * 因为数据本身是用数组表示一棵完全二叉树,所以最后一个有子节点的根节点就为(len-1)/2
     * 所以我们只需要对0~(len-1)/2个节点进行调整
     *
     * @param nums
     */
    private void buildHeap(int[] nums) {
        int len = nums.length;
        // 只需要从 i = (len - 1) / 2 这个位置开始逐层下移
        for (int i = (len - 1) / 2; i >= 0; i--) {
            siftDown(nums, i, len - 1);
        }
    }

    /**
     * 调整的思路:
     * 当前节点k,他的左节点为 2*k + 1 右节点为 2*k + 2;
     * 如果他的左右节点都存在(不超过end),则先获取他们中的最大值下标j
     * 然后将该值与根节点(k)比较,如果根节点小,则将j与k交换(根节点下移)
     * 交换完成之后,k=j,来到原根节点被交换下来的位置,继续判断他是否需要被继续交换下去。
     * @param nums
     * @param k    当前下沉元素的下标
     * @param end  [0, end] 是 nums 的有效部分
     */
    private void siftDown(int[] nums, int k, int end) {
        while (2 * k + 1 <= end) {
            int j = 2 * k + 1;
            if (j + 1 <= end && nums[j + 1] > nums[j]) {
                j++;
            }
            if (nums[j] > nums[k]) {
                swap(nums, j, k);
            } else {
                break;
            }
            k = j;
        }
    }
    
    public void swap(int[] nums,int i,int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

排序常用的api

Arrays.sort()

指针对数组类型的排序

int[] a = {9, 8, 7, 2, 3, 4, 1, 0, 5};
Arrays.sort(a);
for(int i = 0; i < a.length; i ++) {
    System.out.print(a[i] + " ");
}

如果要自定义排序规则,不能用基本类型的数组,因为要用到泛型。

Integer[] a = {9, 8, 7, 2, 3, 4, 1, 0, 5};
Arrays.sort(a,(o1,o2)->{
    return o2-o1;
});
for(int i = 0; i < a.length; i ++) {
    System.out.print(a[i] + " ");
}

Collections.sort()

针对List等Collection容器的排序api

    List<Character> charList = new ArrayList<>(map.keySet());
    Collections.sort(charList,(c1,c2)->{
        return map.get(c2)-map.get(c1);
    });

优先队列PriorityQueue

    PriorityQueue<Map.Entry<Character,Integer>> items = new PriorityQueue<>((o1,o2)->o2.getValue()-o1,getValue());