排序算法 9
P1:排序算法的分类
排序算法可以分为内部排序和外部排序,在内存中进行的排序称为内部排序,当要排序的数据量很大时无法全部拷贝到内存,需要使用外存进行排序,这种排序称为外部排序。
内部排序包括比较排序和非比较排序,比较排序包括插入排序、选择排序、交换排序和归并排序,非比较排序包括计数排序、基数排序和桶排序。其中插入排序又包括直接插入排序和希尔排序,选择排序包括直接选择排序和堆排序,交换排序包括冒泡排序和快速排序。
P2:直接插入排序
直接插入排序属于插入排序,是一种稳定的排序,平均时间复杂度和最差时间复杂度均为 O(n²),当元素基本有序时的最好时间复杂度为O(n),空间复杂度为 O(1)。
基本原理是每一趟将一个待排序的记录,按其关键字的大小插入到已经排好序的一组记录的适当位置上,直到所有待排序记录全部插入为止。适用于待排序记录较少或基本有序的情况。
`public` `void` `insertionSort(``int``[] nums) {`
`for` `(``int` `i =` `1``; i < nums.length; i++) {`
`int` `insertNum = nums[i];`
`int` `insertIndex;`
`for` `(insertIndex = i -` `1``; insertIndex >=` `0` `&& nums[insertIndex] > insertNum; insertIndex--) {`
`nums[insertIndex +` `1``] = nums[insertIndex];`
`}`
`nums[insertIndex +` `1``] = insertNum;`
`}`
`}`
**优化:**直接插入并没有利用到要插入的序列已有序的特点,插入第 i 个元素时可以通过二分查找找到要插入的位置,再把第 i 个元素前 1位与插入位置之间的所有元素后移,把第 i 个元素放在目标位置上。
`public` `void` `binaryInsertionSort(``int``[] nums) {`
`for` `(``int` `index =` `1``; index < nums.length; index++) {`
`int` `insertNum = nums[index];`
`int` `insertIndex = -``1``;`
`int` `start =` `0``;`
`int` `end = index -` `1``;`
`while` `(start <= end) {`
`int` `mid = start + (end - start) /` `2``;`
`if` `(insertNum > nums[mid])`
`start = mid +` `1``;`
`else` `if` `(insertNum < nums[mid])`
`end = mid -` `1``;`
`else` `{`
`insertIndex = mid +` `1``;`
`break``;`
`}`
`}`
`if` `(insertIndex == -``1``)`
`insertIndex = start;`
`if` `(index - insertIndex >=` `0``)`
`System.arraycopy(nums, insertIndex, nums, insertIndex +` `1``, index - insertIndex);`
`nums[insertIndex] = insertNum;`
`}`
`}`
P3:希尔排序
希尔排序属于插入排序,又称缩小增量排序,是对直接插入排序的一种改进,并且是一种不稳定的排序,平均时间复杂度为O(n1.3),最差时间复杂度为 O(n²),最好时间复杂度为 O(n),空间复杂度为 O(1)。
基本原理是把记录按下标的一定增量分组,对每组进行直接插入排序,每次排序后减小增量,当增量减至 1 时,排序完毕。适用于中等规模的数据量,对规模非常大的数据量不是最佳选择。
`public` `void` `shellSort(``int``[] nums) {`
`for` `(``int` `d = nums.length /` `2``; d >` `0` `; d /=` `2``) {`
`for` `(``int` `i = d; i < nums.length; i++) {`
`int` `insertNum = nums[i];`
`int` `insertIndex;`
`for` `(insertIndex = i - d; insertIndex >=` `0` `&& nums[insertIndex] > insertNum; insertIndex -= d) {`
`nums[insertIndex + d] = nums[insertIndex];`
`}`
`nums[insertIndex + d] = insertNum;`
`}`
`}`
`}`
P4:直接选择排序
直接选择排序属于选择排序,是一种不稳定的排序,任何情况下时间复杂度都是 O(n²),空间复杂度为 O(1)。基本原理是每次在未排序序列中找到最小元素,和未排序序列的第一个元素交换位置,再在剩余的未排序序列重复该操作直到所有元素排序完毕。适用于数据量较小的情况,比直接插入排序稍快。
`public` `void` `selectSort(``int``[] nums) {`
`int` `minIndex;`
`for` `(``int` `index =` `0``; index < nums.length -` `1``; index++){`
`minIndex = index;`
`for` `(``int` `i = index +` `1``;i < nums.length; i++){`
`if``(nums[i] < nums[minIndex])`
`minIndex = i;`
`}`
`if` `(index != minIndex){`
`swap(nums, index, minIndex);`
`}`
`}`
`}`
P5:堆排序
堆排序属于选择排序,是对直接选择排序的改进,并且是一种不稳定的排序,任何情况时间复杂度都为 O(nlogn),空间复杂度为 O(1)。
基本原理是将待排序记录看作完全二叉树,可以建立大根堆或小根堆,大根堆中每个节点的值都不小于它的子节点值,小根堆中每个节点的值都不大于它的子节点值。适用于数据量较大的情况。
以大根堆为例,在建堆时首先将最后一个节点作为当前节点,如果当前结点存在父节点且值大于父节点,就将当前节点和父节点交换。在移除时首先暂存根节点的值,然后用最后一个节点代替根节点并作为当前节点,如果当前节点存在子节点且值小于子节点,就将其与值较大的子节点进行交换,调整完堆后返回暂存的值。
`public` `void` `add(``int``[] nums,` `int` `i,` `int` `num){`
`nums[i] = num;`
`int` `curIndex = i;`
`while` `(curIndex >` `0``) {`
`int` `parentIndex = (curIndex -` `1``) /` `2``;`
`if` `(nums[parentIndex] < nums[curIndex])`
`swap(nums, parentIndex, curIndex);`
`else` `break``;`
`curIndex =parentIndex;`
`}`
`}`
`public` `int` `remove(``int``[] nums,` `int` `size){`
`int` `result = nums[``0``];`
`nums[``0``] = nums[size -` `1``];`
`int` `curIndex =` `0``;`
`while` `(``true``) {`
`int` `leftIndex = curIndex *` `2` `+` `1``;`
`int` `rightIndex = curIndex *` `2` `+` `2``;`
`if` `(leftIndex >= size)` `break``;`
`int` `maxIndex = leftIndex;`
`if` `(rightIndex < size && nums[maxIndex] < nums[rightIndex])`
`maxIndex = rightIndex;`
`if` `(nums[curIndex] < nums[maxIndex])`
`swap(nums, curIndex, maxIndex);`
`else` `break``;`
`curIndex = maxIndex;`
`}`
`return` `result;`
`}`
P6:冒泡排序
冒泡排序属于交换排序,是一种稳定的排序,平均时间复杂度和最坏时间复杂度均为 O(n²),当元素基本有序时的最好时间复杂度为O(n),空间复杂度为 O(1)。
基本原理是比较相邻的元素,如果第一个比第二个大就进行交换,对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对,每一轮排序后末尾元素都是有序的,针对 n 个元素重复以上步骤 n -1 次排序完毕。
`public` `void` `bubbleSort(``int``[] nums) {`
`for` `(``int` `i =` `0``; i < nums.length -` `1``; i++) {`
`for` `(``int` `index =` `0``; index < nums.length -` `1` `- i; index++) {`
`if` `(nums[index] > nums[index +` `1``])`
`swap(nums, index, index +` `1``)`
`}`
`}`
`}`
**优化:**当序列已经有序时仍会进行不必要的比较,可以设置一个标志位记录是否有元素交换,如果没有直接结束比较。
`public` `void` `betterBubbleSort(``int``[] nums) {`
`boolean` `swap;`
`for` `(``int` `i =` `0``; i < nums.length -` `1``; i++) {`
`swap =` `true``;`
`for` `(``int` `index =` `0``; index < nums.length -` `1` `- i; index++) {`
`if` `(nums[index] > nums[index +` `1``]) {`
`swap(nums, index ,index +` `1``);`
`swap =` `false``;`
`}`
`}`
`if` `(swap)` `break``;`
`}`
`}`
P7:快速排序
快速排序属于交换排序,是对冒泡排序的一种改进,并且是一种不稳定的排序,平均时间复杂度和最好时间复杂度均为 O(nlogn),当元素基本有序时的最坏时间复杂度为O(n²),空间复杂度为 O(logn)。
基本原理是首先选择一个基准元素,然后通过一趟排序将要排序的数据分割成独立的两部分,一部分全部小于等于基准元素,一部分全部大于等于基准元素,然后再按此方法递归对这两部分数据分别进行快速排序。适用于数据量较大且元素基本无序的情况。
快速排序的一次划分从两头交替搜索,直到 low 和 high 指针重合,因此时间复杂度是