排序算法 - 图1

稳定性

能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。

冒泡排序(Bubble Sort)

冒牌排序是一种简单的排序算法,核心就是每次比较两个相邻的元素,如顺序错误则交换两个位置。如果按照升序排列,重复遍历一次数组之后,数组最后一个一定是最大的数!重复遍历比较,最终则会得到一个升序数组。

排序算法 - 图2

  1. public static int[] bubbleSort(int[] array) {
  2. if (array.length == 0)
  3. return array;
  4. for (int i = 0; i < array.length; i++)
  5. for (int j = 0; j < array.length - 1 - i; j++)
  6. if (array[j + 1] < array[j]) {
  7. int temp = array[j + 1];
  8. array[j + 1] = array[j];
  9. array[j] = temp;
  10. }
  11. return array;
  12. }

选择排序(Selection Sort)

表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。

工作原理

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
排序算法 - 图3

  1. public static int[] selectionSort(int[] array) {
  2. if (array.length == 0)
  3. return array;
  4. for (int i = 0; i < array.length; i++) {
  5. int minIndex = i;
  6. for (int j = i; j < array.length; j++) {
  7. if (array[j] < array[minIndex]) //找到最小的数
  8. minIndex = j; //将最小数的索引保存
  9. }
  10. int temp = array[minIndex];
  11. array[minIndex] = array[i];
  12. array[i] = temp;
  13. }
  14. return array;
  15. }

插入排序(Insertion Sort)

它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

工作原理

  1. 从第一个元素开始,该元素可以认为已经被排序;
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
  5. 将新元素插入到该位置后;
  6. 重复步骤2~5。

排序算法 - 图4

  1. public static int[] insertionSort(int[] array) {
  2. if (array.length == 0)
  3. return array;
  4. int current;
  5. for (int i = 0; i < array.length - 1; i++) {
  6. current = array[i + 1];
  7. int preIndex = i;
  8. while (preIndex >= 0 && current < array[preIndex]) {
  9. array[preIndex + 1] = array[preIndex];
  10. preIndex--;
  11. }
  12. array[preIndex + 1] = current;
  13. }
  14. return array;
  15. }

希尔排序(Shell Sort)

快速排序(Quick Sort)

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

工作原理

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

排序算法 - 图5
快速排序在最坏情况下的时间复杂度和冒泡排序一样,是 O(n),实际上每次比较都需要交换,但是这种情况并不常见。我们可以思考一下如果每次比较都需要交换,那么数列的平均时间复杂度是 O(nlogn),事实上在大多数时候,排序的速度要快于这个平均时间复杂度。这种算法实际上是一种分治法思想,也就是分而治之,把问题分为一个个的小部分来分别解决,再把结果组合起来。

快速排序只是使用数组原本的空间进行排序,所以所占用的空间应该是常量级的,但是由于每次划分之后是递归调用,所以递归调用在运行的过程中会消耗一定的空间,在一般情况下的空间复杂度为 O(logn),在最差的情况下,若每次只完成了一个元素,那么空间复杂度为 O(n)。所以我们一般认为快速排序的空间复杂度为 O(logn)。

  1. public static void quickSort(int[] nums, int begin, int end) {
  2. if (begin >= end)
  3. return;
  4. int i = begin;
  5. int j = end;
  6. int key = nums[begin];
  7. while (i < j) {//必须先从j开始遍历,因为key=nums[begin];先从i开始,会覆盖掉nums[end]
  8. while (i < j && nums[j] > key) {
  9. j--;
  10. }
  11. if (i < j) {
  12. nums[i] = nums[j];
  13. i++;
  14. }
  15. while (i < j && nums[i] < key) {
  16. i++;
  17. }
  18. if (i < j) {
  19. nums[j] = nums[i];
  20. j--;
  21. }
  22. }
  23. nums[i] = key;
  24. quickSort(nums, begin, i - 1);
  25. quickSort(nums, i + 1, end);
  26. }

参考