一、常用排序算法性质

排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性复杂性
插入排序O(n^2)O(n^2)O(n)O(1)稳定简单
希尔排序O(nlogn)O(n^2)O(n^1.3)O(1)不稳定较复杂
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定简单
冒泡排序O(n^2)O(n^2)O(n)O(1)稳定简单
快速排序O(nlogn)O(n^2)O(nlogn)O(logn)不稳定较复杂
归并排序O(nlogn)O(nlogn)O(nlogn)O(nlogn)稳定较复杂
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定简单
计数排序O(n+k)O(n+k)O(n+k)O(n+k)稳定简单
基数排序O(d(n+k))O(d(n+k))O(d(n+k))O(n+k)稳定较复杂

其中计数排序和基数排序中的k代表count数组的大小,d代表最大位数

常见的时间复杂度函数图形
image.png

二、JAVA代码实现

1.插入排序

public static void sort(int[] arr) {
  for (int i = 1; i < arr.length; i++) {
    int tmp = arr[i];
    int j;
    for (j = i - 1; j >= 0 && tmp < arr[j]; j--) {
      arr[j + 1] = arr[j];
    }
    arr[j + 1] = tmp;
  }
}

2.希尔排序(缩小增量排序)

public static void sort(int[] arr) {
  for (int gap = arr.length / 2; gap > 0; gap = gap / 2) {
    for (int i = 0; i < gap; i++) {
      for (int j = i + gap; j < arr.length; j += gap) {
        int tmp = arr[j];
        int k;
        for (k = j - gap; k >= 0 && tmp < arr[k]; k -= gap) {
          arr[k + gap] = arr[k];
        }
        arr[k + gap] = tmp;
      }
    }
  }
}

3.选择排序

public static void sort(int[] arr) {
  for (int i = 0; i < arr.length; i++) {
    for (int j = i + 1; j < arr.length; j++) {
      if (arr[j] < arr[i]) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
      }
    }
  }
}

4.冒泡排序

public static void sort(int[] arr) {
  for (int i = 0; i < arr.length; i++) {
    for (int j = 0; j < arr.length - i - 1; j++) {
      if (arr[j + 1] < arr[j]) {
        int tmp = arr[j + 1];
        arr[j + 1] = arr[j];
        arr[j] = tmp;
      }
    }
  }
}

5.快速排序

  • 挖坑填数法

    public static void quickSort(int[] arr, int l, int r) {
    if (l < r) {
      int tmp = arr[l], i = l, j = r;
      while (l < r) {
        while (arr[r] >= tmp && l < r) {
          r--;
        }
        if (l < r) {
          arr[l++] = arr[r];
        }
        while (arr[l] < tmp && l < r) {
          l++;
        }
        if (l < r) {
          arr[r--] = arr[l];
        }
      }
      arr[l] = tmp;
      quickSort(arr, i, l - 1);
      quickSort(arr, l + 1, j);
    }

    挖坑填数:从右往走找比基数小的数填左边的坑,从左向右找比基数大的数填右边的坑,当左右指针相交,将基数填入最后这个坑。
    递归终止条件:基数左边或右边<=1个数字

  • 算法导论的方法
private static void quickSort(int[] arr, int p, int r) {
  if (p < r) {
    int q = partition(arr, p, r);
    quickSort(arr, p, q - 1);
    quickSort(arr, q + 1, r);
  }
}
// 基准值为最右边的元素
private static int partition(int[] arr, int p, int r) {
  int i = p - 1;
  for (int j = p; j < r; j++) {
    if (arr[j] < arr[r]) {
      i++;
      swap(arr, i, j);
    }
  }
  swap(arr, ++i, r);
  return i;
}
private static void swap(int[] arr, int i, int j) {
  int tmp = arr[j];
  arr[j] = arr[i];
  arr[i] = tmp;
}
  • 随机化的快速排序
    在排序前使用随机算法获得数组的一个元素与固定的基准元素做交换即可

    6.归并排序(分治策略典型)

    private static void sort(int[] arr, int l, int r) {
    // 递归需要有终止策略:如果子问题的规模足够小,则停止递归,直接求解。
     if (l == r) {
      return;
    }
    int mid = l + (r - l) / 2;
    sort(arr, l, mid);
    sort(arr, mid + 1, r);
    merge(arr, l, mid, r);
    }
    private static void merge(int[] arr, int l, int mid, int r) {
    // 存储合并后的子数组,空间复杂度 r-l+1 
    int[] tmpArr = new int[r - l + 1];
    int p = 0;
    int l0 = l;
    int r0 = mid + 1;
    // 归并操作中最精髓的部分,仔细吸收强化
    while (l0 <= mid && r0 <= r) {
      tmpArr[p++] = arr[l0] <= arr[r0] ? arr[l0++] : arr[r0++];
    }
    while (l0 <= mid) {
      tmpArr[p++] = arr[l0++];
    }
    while (r0 <= r) {
      tmpArr[p++] = arr[r0++];
    }
    // 赋值原数组
     for (p = 0; p < tmpArr.length; p++) {
      arr[l + p] = tmpArr[p];
    }
    }

    分而治之,切分到只有一个元素即为递归终止条件

    7.堆排序

    // 使用最大堆做排序,根节点最大
    private static void heapSort(int arr[]) {
      // (arr.length-2)/2 为第一个最后一个非叶子结点
      for (int i = (arr.length - 2) / 2; i >= 0; i--) {
        heapAdjust(arr, i, arr.length - 1);
      }
    
      for (int i = arr.length - 1; i > 0; ) {
        // 交换堆顶元素与末尾元素
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        // 调整堆顶元素,因为交换元素后只有顶点元素不符合堆规则
        heapAdjust(arr, 0, --i);
      }
    }
    
    // 调整堆
    private static void heapAdjust(int arr[], int parentIndex, int endIndex) {
      int temp = arr[parentIndex];
      int childIndex = (parentIndex * 2) + 1;
      while (childIndex <= endIndex) {
        if ((childIndex + 1 <= endIndex) && arr[childIndex] < arr[childIndex + 1]) {
          childIndex++;
        }
        if (arr[childIndex] <= temp) {
          break;
        }
        arr[parentIndex] = arr[childIndex];
        parentIndex = childIndex;
        childIndex = childIndex * 2 + 1;
      }
      arr[parentIndex] = temp;
    }
    
    public static void main(String[] args) {
      int arr[] = new int[]{1, 2, 9, 4, 1, 3};
      heapSort(arr);
      System.out.println(args);
      Stream.of(arr).forEach(item -> System.out.print(item + "\t"));
    }

    堆可看做一个完全二叉树

      堆的几个概念:
      (1)最大堆:arr[parent(i)]>=arr[i],意味着根节点最大,常用于堆排序
      (2)最小堆: arr[parent(i)]<=arr[i],意味着根节点最小,常用于构建优先队列   

    8.计数排序

    private static int[] countSort(int[] arr) {
     int max = getMax(arr);
     int[] c = new int[max + 1]; // 计数数组
     int[] b = new int[arr.length]; // 输出数组
     // 初始化计数数组
     for (int i = 0; i < arr.length; i++) {
      c[arr[i]]++;
    }
    // 计数数组计算小于等于当前值的元素有多少个
     for (int i = 1; i < c.length; i++) {
      c[i] = c[i] + c[i - 1];
    }
    // 填充输出数组
     for (int i = 0; i < arr.length; i++) {
      b[c[arr[i]] - 1] = arr[i];
      c[arr[i]]--;
    }
    return b;
    }
    private static int getMax(int[] arr) {
    int max = arr[0];
    for (int i = 1; i < arr.length; i++) {
      if (arr[i] > max) {
        max = arr[i];
      }
    }
    return max;
    }
    

    在实际工作中,当k=O(n)时,我们一般会采用计数排序,这时的运行时间为O(n)

    9.基数排序

    image.png

    // 最低位优先(Least Significant Digit first)LSD法
    private static void radixSort(int[] arr) {
    int max = getMax(arr);
    for (int num = 1; max / num > 0; num *= 10) {// num代表位数,依次从低位往高位执行稳定排序
     countSort(arr, num);
    }
    }
    private static void countSort(int[] arr, int num) {
    int[] c = new int[10];// 保存0~9
     int[] b = new int[arr.length];
    for (int i = 0; i < arr.length; i++) {
      c[(arr[i] / num) % 10]++;
    }
    for (int i = 1; i < c.length; i++) {
      c[i] = c[i] + c[i - 1];
    }
    for (int i = 0; i < arr.length; i++) {
      b[--c[(arr[i] / num) % 10]] = arr[i];
    }
    for (int i = 0; i < b.length; i++) {
      arr[i] = b[i];
    }
    }

    获取最大值的函数省略了,例子中基数排序内部使用的稳定排序就是前面写的计数排序

三、算法正确性、时间复杂度、空间复杂度等证明

。。。证明暂缺

备注:。。。待完善,最终会把所有常用排序都放进来


花溪的小石头
180 声望8 粉丝

境由心造,一切只在当下的转念之间。