【算法】排序算法总览

一、常用排序算法性质

排序方法 时间复杂度(平均) 时间复杂度(最坏) 时间复杂度(最好) 空间复杂度 稳定性 复杂性
插入排序 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代表最大位数

常见的时间复杂度函数图形
【算法】排序算法总览_第1张图片

二、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) {
  // heapSize代表待排序的堆大小
 for (int heapSize = arr.length; heapSize > 1; heapSize--) {
    buildMaxHeap(arr, heapSize);
    exchange(arr, 0, heapSize - 1);
  }
}
// 构建最大堆
private static void buildMaxHeap(int[] arr, int heapSize) {
  // 从heapSize-1开始遍历,因为之后的节点都为叶子节点,已自成一个最大堆 
  for (int i = (heapSize - 1) / 2; i >= 0; i--) {
    maxHeapify(arr, i, heapSize);
  }
}

// 最大堆化
private static void maxHeapify(int[] arr, int i, int heapSize) {
  int left = left(i);
  int right = right(i);
  int largest = i;
  if (left < heapSize && arr[left] > arr[i]) {
    largest = left;
  }
  if (right < heapSize && arr[right] > arr[largest]) {
    largest = right;
  }
  if (largest != i) {
    exchange(arr, i, largest);
    maxHeapify(arr, largest, heapSize);
  }
}

几个辅助函数,求孩子节点和交换方法:

private static int left(int i) {
  return (i << 1) + 1;
}
private static int right(int i) {
  return (i << 1) + 2;
}
private static void exchange(int[] arr, int i, int j) {
  int tmp = arr[i];
  arr[i] = arr[j];
  arr[j] = tmp;
}
堆可看做一个完全二叉树
    堆的几个概念:
    (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.基数排序

【算法】排序算法总览_第2张图片

// 最低位优先(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];
  }
}

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

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

。。。证明暂缺

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

你可能感兴趣的