PS: 作者是正在学习Java的小白,在这里会总结一些与Java相关知识(Java代码实现),如有不足,欢迎前来讨论指正,十分感谢 !!!
目录
- 数组各种排序算法 Java 实现总结
- 0 排序算法说明
- 0.0 概述
- 0.1 排序算法比较
- 1 库排序
- 1.1 基本类型数组排序
- 1.2 对象类型数组排序
- 1.3 Array.Sort底层实现原理
- 2 冒泡排序
- 2.1 基本思想
- 2.2 算法过程
- 2.3 代码实现
- 3 选择排序
- 3.1 基本思想
- 3.2 算法过程
- 3.3 代码实现
- 4 插入排序
- 4.1 基本思想
- 4.2 算法过程
- 4.3 代码实现
- 5 归并排序
- 5.1 基本思想
- 5.2 算法过程
- 5.3 代码实现
- 6 希尔排序
- 6.1 基本思想
- 6.2 算法过程
- 6.3 代码实现
- 7 快速排序
- 7.1 基本思想
- 7.2 算法过程
- 7.3 代码实现
- 8 堆排序
- 8.1 基本思想
- 8.2 算法过程
- 8.3 代码实现
- 9 计数排序
- 9.1 基本思想
- 9.2 算法过程
- 9.3 代码实现
- 10 桶排序
- 10.1 基本思想
- 10.2 算法过程
- 10.3 代码实现
- 11 基数排序
- 11.1 基本思想
- 11.2 算法过程
- 11.3 代码实现
数组各种排序算法 Java 实现总结
0 排序算法说明
0.0 概述
查找和排序是经常用到的基本算法。
- 查找相对而言较简单,不外乎顺序查找,二分查找,哈希表查找,二叉排序树查找。
- 排序相对而言复杂些,因为排序算法较多,而且要明确各个排序算法的时间复杂度,空间复杂度,稳定性等特点,根据情况选择不同的排序算法。
时间空间复杂度比较简单就不再赘述,感兴趣的小伙伴可专门研究一下!
稳定性:在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,S1=S2,且S1在S2之前,而在排序后的序列中,S1仍在S2之前,则称这种排序算法是稳定的;否则称为不稳定的。
稳定性场景:需要保证稳定性的场景一般是一个复杂对象具有多个属性,且初始化顺序有一定的意义(比如Student,排序前是按学号id从小到大记录,当按分数排序后,如果两个同学成绩相同,学号小的一般还要在前面,不能跑到学号大的后面),诸如此类,这种情况稳定性排序才有意义。
注意:一般做算法题或者数字排序不考虑稳定性。
0.1 排序算法比较
下面是各个排序算法比较,包括平均时间复杂度,最快时间复杂度、最慢时间复杂度、空间复杂度、是否稳定几个方面。
- n是数据规模
- k是桶的数量
序号 | 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 是否稳定 | 适用情况 |
1 | 冒泡排序 | 稳定 | n较小 | |||
2 | 选择排序 | 不稳定 | n较小 | |||
3 | 插入排序 | 稳定 | n较小 | |||
4 | 归并排序 | 稳定 | n较大 | |||
5 | 希尔排序 | 不稳定 | ||||
6 | 快速排序 | 不稳定 | n较大 | |||
7 | 堆排序 | 不稳定 | n较大 | |||
8 | 计数排序 | 稳定 | ||||
9 | 桶排序 | 稳定 | ||||
10 | 基数排序 | 稳定 |
注意: 一般使用的快速排序比较多,因为其涉及的思路(找哨兵的方法partition)可以应用到排序相关的变种问题。
1 库排序
在Java工具包里,提供了一种使用十分简便的方法对数组进行排序,即
另外不但可以对基本数据类型的数组,也可以对象数组进行排序。
下面介绍它的几种用法。
1.1 基本类型数组排序
排序有升序和降序两种。
1. 升序
基本类型数组排序可以直接调用(默认升序)
public class JavaArraySort {
/**
* @param array:待排序数据 说明:基本类型,默认升序
*/
public static void sort1(int[] array) {
System.out.println("升序前:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
Arrays.sort(array);
System.out.println("升序后:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arrayInt = {3, 1, 5, 2, 6, 7, 9, 1};
// 基本类型——整型(浮点型一样)——默认升序
sort1(arrayInt);
}
}
运行结果:
升序前:
3 1 5 2 6 7 9 1
升序后:
1 1 2 3 5 6 7 9
2. 降序
实现数组降序有三种实现方式,不过这三种方式下,有一个前提条件是待排序数组不能是基本数据类型了(如待排序数组类型为int,需要转为Integer类型),下面分别介绍这三种方式以及相关代码。
(1)使用官方提供。
(2)使用匿名内部类实现Comparator接口。
(3)创建实现Comparator接口的类。
// 方式3的工具类
public class MyIntegerComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
public class JavaArraySort {
/**
* @param array:待排序数据 说明:基本类型,降序
*/
public static void sort2(Integer[] array) {
System.out.println("降序前:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
// 1. 直接使用官方提供的
Arrays.sort(array, Collections.reverseOrder());
// 2. 使用匿名内部类实现
Arrays.sort(array, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
// 3. 同样可以创建实现Comparator接口的类即可
Arrays.sort(array, new MyIntegerComparator());
System.out.println("降序后:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
// 注意此处数组类型
Integer[] arrayInteger = {3, 1, 5, 2, 6, 7, 9, 1};
// 基本类型——整型(浮点型一样)——降序
sort2(arrayInteger);
}
}
运行结果:
降序前:
3 1 5 2 6 7 9 1
降序后:
9 7 6 5 3 2 1 1
1.2 对象类型数组排序
对象类型数组排序的实现方式有两种。下面以Student类(属性包括id、name、score三个)举例说明。
- 第一种是让该类直接实现Comparable<T>接口,并实现接口方法compareTo(),不过不推荐,因为只能实现一种排序方式(按id、name、score中的一个),如果需要其他情况,就需要更改代码,十分不方便,同时也不能做到多种排序方式。
- 第二种是创建一个实现Comparator<T>接口的类(同1.1部分的第三种方法),并实现compare()方法。推荐这种,因为便于扩展,每当有新的排序方式,直接创建新的类实现Comparator<T>接口并在compare()方法中实现具体的排序逻辑。
Student类
import org.jetbrains.annotations.NotNull;
public class Student implements Comparable<Student>{
public int id;
public String name;
public int score;
public Student(int id, String name, int score) {
this.id = id;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", score=" + score +
'}';
}
@Override
public int compareTo(@NotNull Student o) {
return Integer.compare(this.id, o.id);
}
}
排序方式类StudentSortByScore
import java.util.Comparator;
public class StudentSortByScore implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.score - o2.score;
}
}
利用Arrays.sort实现排序
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
public class JavaArraySort {
/**
* @param array 待排序数据 说明:对象类型,升序
*/
public static void sort3(Student[] array) {
System.out.println("升序前:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
System.out.println("按学号升序后:");
// 1. 对象类型排序:对象类实现Comparable<T>接口并实现其方法compareTo(),升序降序改变逻辑即可
Arrays.sort(array);
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
// 2. (推荐)对象类型排序:创建一个实现一个Comparator<T>接口并实现其方法compare(),升序降序改变逻辑即可
Arrays.sort(array, new StudentSortByScore());
System.out.println("按成绩升序后:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
Student[] arrayStudent = {
new Student(102, "Tom", 99),
new Student(105, "Lucy", 80),
new Student(100, "Jerry", 90),
new Student(103, "Tony", 98)
};
// 对象类型(Student类)——默认按学号升序
sort3(arrayStudent);
}
}
运行结果
升序前:
Student{id=102, name='Tom', score=99} Student{id=105, name='Lucy', score=80} Student{id=100, name='Jerry', score=90} Student{id=103, name='Tony', score=98}
按学号升序后:
Student{id=100, name='Jerry', score=90} Student{id=102, name='Tom', score=99} Student{id=103, name='Tony', score=98} Student{id=105, name='Lucy', score=80}
按成绩升序后:
Student{id=105, name='Lucy', score=80} Student{id=100, name='Jerry', score=90} Student{id=103, name='Tony', score=98} Student{id=102, name='Tom', score=99}
1.3 Array.Sort底层实现原理
在不同的JDK版本有不同的实现,下面分别介绍。
1. 不同版本的Array.Sort
- JDK6: 对原始类型(int[],double[],char[],byte[]),用的是快速排序,对于对象类型(Object[]),则使用归并排序
- JDK7: 快速排序升级为双基准快排(双基准快排vs三路快排);归并排序升级为归并排序的改进版TimSort
- JDK8:, 对大集合增加了Arrays.parallelSort()函数,使用fork-Join框架,充分利用多核,对大的集合进行切分然后再归并排序,而在小的连续片段里,依然使用TimSort与DualPivotQuickSort
2. TimSort
- 算法内容
为了减少对升序部分的回溯和对降序部分的性能倒退,将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的块-分区。其中每一个分区叫一个run。针对这些 run 序列,每次拿一个 run 出来按规则进行合并。每次合并会将两个 run合并成一个 run。合并的结果保存到栈中。合并直到消耗掉所有的 run,这时将栈上剩余的 run合并到只剩一个 run 为止。这时这个仅剩的 run 便是排好序的结果。 - 算法过程
- 如何数组长度小于某个值(JDK1.8=32),直接用二分插入排序算法
- 找到各个run,并入栈
- 按规则合并run
3. 总结
- 的底层实现是根据数组的大小选择不同的排序算法进行优化,使得数据不管在什么情况下都可以保持很快的排序速度。
2 冒泡排序
2.1 基本思想
冒泡排序是排序算法中最基本的一种排序方法。
重复地走访过要排序的元素列,依次比较两个相邻的元素,如果他们的顺序(如从大到小)错误就把他们交换过来
2.2 算法过程
以升序(降序只需要改变比较的大小即可)为例,需要两层循环:
(1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。
(2)对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。最后的元素是最大的数。
(3)针对所有的元素重复以上的步骤,除了最后一个。
(4)重复上面(1)(2)(3)的步骤,直到没有任何一对数字需要比较。注意可以持续每次对越来越少的元素进行重复(即随着外层循环次数的增加,内层循环次数逐渐减少,因为每次外层循环结束,数组末尾就会排好一个)
2.3 代码实现
public class BubbleSort {
public static void sort(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
for (int i = 0; i < nums.length - 1; i++) {
// 注意内层循环结束条件,因为外层循环每进行一次,数组末尾会增加一个已经排好序的元素(最大,次大,...)
for (int j = 0; j < nums.length - 1 - i; j++) {
if (nums[j] > nums[j + 1]) {
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
}
public static void main(String[] args) {
int[] nums = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] nums = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] nums = {8};
System.out.println("排序前:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
sort(nums);
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
3 选择排序
3.1 基本思想
选择排序对冒泡排序进行了优化,在每次遍历比较的过程中不进行交换,而是记录本次遍历的最小值,在遍历结束后再将最小值移到这次遍历的开始位置。然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。
这样虽然比较次数没有改变,但交换的次数大大减少,一共只需要N次交换。
3.2 算法过程
(1)首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
(2)再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
(3)重复第二步,直到所有元素均排序完毕。
3.3 代码实现
public class SelectSort {
public static void sort(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
for (int i = 0; i < nums.length - 1; i++) {
// 记录最小元素的索引位置
int minIndex = i;
for (int j = i + 1; j < nums.length; j++) {
// 与最小元素比较
if (nums[minIndex] > nums[j]) {
minIndex = j;
}
}
// 找到最小元素并进行交换
int temp = nums[minIndex];
nums[minIndex] = nums[i];
nums[i] = temp;
}
}
public static void main(String[] args) {
int[] nums = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] nums = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] nums = {8};
System.out.println("排序前:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
sort(nums);
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
4 插入排序
4.1 基本思想
插入排序是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
4.2 算法过程
(1)从第一个元素开始,该元素可以认为已经被排序
(2)取出下一个元素,在已经排序的元素序列中从后向前扫描
(3)如果该元素(已排序)大于新元素,将该元素移到下一位置
(4)重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
(5)将新元素插入到下一位置中
(6)重复步骤2~5
4.3 代码实现
public class InsertSort {
public static void sort(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
// 第一个元素默认已经排序好
for (int i = 1; i < nums.length; i++) {
// 当前待排序元素
int current = nums[i];
// 对前面排序好的元素列从末尾扫描
int j = i - 1;
while (j >= 0 && nums[j] > current) {
// 往后移动
nums[j + 1] = nums[j];
// 索引前移
j--;
}
// 将当前元素插入
nums[j + 1] = current;
}
}
public static void main(String[] args) {
int[] nums = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] nums = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] nums = {8};
System.out.println("排序前:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
sort(nums);
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
5 归并排序
5.1 基本思想
- 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
- 归并排序是将已有序的子序列合并,得到完全有序的序列;换句话说,先让每个子序列有序,再让子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
5.2 算法过程
(1)把长度为n的输入序列分成两个长度为n/2的子序列;
(2)对这两个子序列分别采用归并排序;
(3)将两个排序好的子序列合并成一个最终的排序序列。
5.3 代码实现
代码在已通过。排序链接: 排序数组
public class MergeSort {
public static void sort(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
// 数组长度小于 2 直接返回
if (nums.length < 2) {
return;
}
mergeSort(nums, 0, nums.length - 1);
}
public static void mergeSort(int[] nums, int left, int right) {
// 递归结束条件
if (left >= right) return;
// 找出中间索引,注意不要写成(right + left)/2,可能会发生溢出
int mid = left + (right - left) / 2;
// 对左边子序列进行归并排序(递归)
mergeSort(nums, left, mid);
// 对右边子序列进行归并排序(递归)
mergeSort(nums, mid + 1, right);
// 合并左右子序列
merge(nums, left, mid, right);
}
public static void merge(int[] nums, int left, int mid, int right) {
// 合并后的数组(注意数组大小设置)
int[] mergeArray = new int[right - left + 1];
// 左边子序列的第一个元素索引
int leftIndex = left;
// 右边子序列的第一个元素索引
int rightIndex = mid + 1;
// 合并后数组的索引
int index = 0;
while (leftIndex <= mid && rightIndex <= right) {
//从两个数组中取出最小的放入临时数组
if (nums[leftIndex] <= nums[rightIndex]) {
mergeArray[index++] = nums[leftIndex++];
} else {
mergeArray[index++] = nums[rightIndex++];
}
}
// 把 左边子序列或者右边子序列 剩余元素移进合并后的数组(最多只会执行一个循环)
while (leftIndex <= mid) {
mergeArray[index++] = nums[leftIndex++];
}
while (rightIndex <= right) {
mergeArray[index++] = nums[rightIndex++];
}
// 将合并后的数组元素拷贝到原数组中
for (int i = 0; i < mergeArray.length; i++) {
nums[i + left] = mergeArray[i];
}
}
public static void main(String[] args) {
int[] nums = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] nums = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] nums = {8};
System.out.println("排序前:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
sort(nums);
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
6 希尔排序
6.1 基本思想
- 希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
- 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
6.2 算法过程
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
(1)选择一个增量序列 ,其中 ;
- 如何选择增量序列:选择增量 ,缩小增量继续以
(2)按增量序列个数 k,对序列进行 k 趟排序;
(3)每趟排序,根据对应的增量 ,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
6.3 代码实现
代码在已通过。排序链接: 排序数组
public class ShellSort {
public static void sort(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
int length = nums.length;
// 增量开始
int gap = length >> 1;
while (gap > 0) {
for (int i = gap; i < length; i++) {
// 记录该分组最后一个元素作为待排序元素
int temp = nums[i];
// 寻找该分组前一个元素
int preIndex = i - gap;
// 插入排序
while (preIndex >= 0 && nums[preIndex] > temp) {
// 前一个元素大于后一个元素,则进行交换(元素后移)
nums[preIndex + gap] = nums[preIndex];
// 继续寻找该分组前一个元素
preIndex -= gap;
}
// 将待排序元素插入
nums[preIndex + gap] = temp;
}
// 缩小增量
gap >>= 1;
}
}
public static void main(String[] args) {
int[] nums = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] nums = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] nums = {8};
System.out.println("排序前:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
sort(nums);
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
7 快速排序
7.1 基本思想
在数组中随机选一个数(默认数组首个元素),数组中小于等于此数的放在左边,大于此数的放在右边,再对数组两边递归调用快速排序,重复这个过程。
7.2 算法过程
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
(1)从数列中挑出一个元素,称为 “基准”(pivot);
(2)重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
(3)递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
7.3 代码实现
代码在已通过。排序链接: 排序数组
public class QuickSort {
public static void sort(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
int n = nums.length;
quickSort(nums, 0, n - 1);
}
public static void quickSort(int[] nums, int low, int high) {
if (low < high) {
int pivot = partition(nums, low, high);
quickSort(nums, low, pivot - 1);
quickSort(nums, pivot + 1, high);
}
}
static int partition(int[] nums, int low, int high) {
int pivot = nums[low];
while (low < high) {
// 从右向左找一个比“哨兵”小的值
while (low < high && nums[high] >= pivot) --high;
nums[low] = nums[high];
// 从左向右找一个比“哨兵”大的值
while (low < high && nums[low] <= pivot) ++low;
nums[high] = nums[low];
}
// 改变“哨兵”的位置
nums[low] = pivot;
// 返回“哨兵”的位置
return low;
}
public static void main(String[] args) {
int[] nums = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] nums = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] nums = {8};
System.out.println("排序前:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
sort(nums);
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
8 堆排序
8.1 基本思想
堆排序是指利用堆数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
8.2 算法过程
(1)将初始待排序关键字序列 构建成大顶堆(父节点比子节点大),此堆为初始的无序区;
(2)将堆顶元素 与最后一个元素 交换,此时得到新的无序区 和新的有序区
(3)由于交换后新的堆顶 可能违反堆的性质,因此需要对当前无序区 调整为新的大顶堆,然后再次将 与无序区最后一个元素交换,得到新的无序区 和新的有序区
8.3 代码实现
代码在已通过。排序链接: 排序数组
public class HeapSort {
public static void sort(int[] nums) {
if (nums == null || nums.length <= 1) {
return;
}
// 新建一个最大堆
buildMaxHeap(nums);
for (int i = nums.length - 1; i >= 1; i--) {
// 堆顶元素与最后元素交换
swap(nums, 0, i);
// 调整最大堆
adjustMaxHeap(nums, 0, i - 1);
}
}
public static void buildMaxHeap(int[] nums) {
// 从子节点到根节点构建最大堆,注意父节点与子节点的索引关系(因为是之间利用数组代替,所以child=parent*2, child=parent*2+1)
for (int i = 1; i < nums.length; i++) {
int parent = (i - 1) >> 1;
int child = i;
// 当有孩子节点以及孩子节点比父节点大,则进行交换
while (child > 0 && nums[parent] < nums[child]) {
swap(nums, parent, child);
// 交换后,父节点值变化,则需要判断父节点与父节点的父节点之间的关系,为了利用循环实现这个逻辑,更新孩子与父节点的索引
// 孩子节点索引更新为父节点索引
child = parent;
// 父节点索引更新为父节点的父节点索引
parent = (parent - 1) >> 1;
}
}
}
public static void adjustMaxHeap(int[] nums, int parent, int last) {
// 根节点值变化,则需要从根节点更新大顶堆
int left = 2 * parent + 1;
int right = 2 * parent + 2;
int maxIndex = left;
// 找到最大子节点,注意索引不能超过最后的索引(即判断父节点有没有子节点)
if (right <= last && nums[right] > nums[left]) {
maxIndex = right;
}
// 和最大子节点比较
if (left <= last && nums[parent] < nums[maxIndex]) {
// 互换到最大子节点
swap(nums, parent, maxIndex);
// 自上而下递归更新大顶堆
adjustMaxHeap(nums, maxIndex, last);
}
}
public static void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
public static void main(String[] args) {
int[] nums = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] nums = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] nums = {8};
System.out.println("排序前:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
sort(nums);
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
9 计数排序
9.1 基本思想
计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对正整数进行排序。
因此:计数排序的局限性:(1)数组最大最小元素差值很大,(2)元素不是整数时,这两种情况不能使用计数排序。
但是却有其专属使用场景:如对按照年龄对公司所有员工进行排序。(员工年龄范围基本在18-65,且都是整数)
9.2 算法过程
(1)找出待排序的数组中最大和最小的元素;
(2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
(3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
(4)反向填充目标数组:将每个元素 i 放在新数组的第 项,每放一个元素就将
9.3 代码实现
代码在已通过。排序链接: 排序数组
public class CountSort {
public static void sort(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
// 找出数组最大、最小元素
int max = nums[0];
int min = nums[0];
for (int i = 1; i < nums.length; i++) {
if (nums[i] > max)
max = nums[i];
if (nums[i] < min)
min = nums[i];
}
// 统计每个元素出现次数并存入数据第 i 项
int[] countArray = new int[max - min + 1];
for (int i = 0; i < nums.length; i++) {
countArray[nums[i] - min]++;
}
// 统计数组做变形,后边的元素等于前面的元素之和
for (int i = 1; i < countArray.length; i++) {
countArray[i] += countArray[i - 1];
}
// 倒序遍历原始数组,从统计数组中找到正确的位置,输出到结果数组
int[] sortedArray = new int[nums.length];
for (int i = nums.length - 1; i >= 0; i--) {
// 给sortedArray的当前位置赋值
// countArray[nums[i] - min] - 1 代表有多少个元素小于 nums[i] 值,所以把 nums[i]放在第 countArray[nums[i] - min]-1 位
sortedArray[countArray[nums[i] - min] - 1] = nums[i];
// 给 countArray 的位置的值-1
countArray[nums[i] - min]--;
}
// 最后填充到原数组中,
for (int i = 0; i < nums.length; i++) {
nums[i] = sortedArray[i];
}
}
public static void main(String[] args) {
int[] nums = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] nums = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] nums = {8};
System.out.println("排序前:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
sort(nums);
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
10 桶排序
10.1 基本思想
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。
桶排序的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里(均匀分布导致桶的元素数量差不多),每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)
10.2 算法过程
(1)人为设置一个BucketSize,作为每个桶所能放置多少个不同数值(例如当 BucketSize==5 时,该桶可以存放{1,2,3,4,5}这几种数字,但是容量不限,即可以存放100个3,200个2等);
(2)遍历输入数据,并且把数据一个一个放到对应的桶里去;
(3)对每个不是空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
(4)从不是空的桶里把排好序的数据拼接起来。
注意: 如果递归使用桶排序为各个桶排序,则当桶数量为 1 时要手动减小bucketSize 增加下一循环桶的数量,否则会陷入死循环,导致内存溢出。
10.3 代码实现
代码(略微修改,满足题目输入是整数数组的条件,可参考下方代码main的修改)在已通过。排序链接: 排序数组
import java.util.ArrayList;
import java.util.List;
public class BucketSort {
public static List<Integer> sort(List<Integer> nums, int bucketSize) {
if (nums == null || nums.size() < 2 || bucketSize < 1) {
return nums;
}
int max = nums.get(0);
int min = nums.get(0);
// 找到最大值最小值
for (int i = 0; i < nums.size(); i++) {
if (nums.get(i) > max)
max = nums.get(i);
if (nums.get(i) < min)
min = nums.get(i);
}
// 根据桶的大小从而设置桶的数量
int bucketCount = (max - min) / bucketSize + 1;
// 新建桶集合,并将所有桶加入
List<List<Integer>> bucketArray = new ArrayList<>(bucketCount);
for (int i = 0; i < bucketCount; i++) {
bucketArray.add(new ArrayList<>());
}
// 将待排序数组元素加入到对应的桶中
for (int i = 0; i < nums.size(); i++) {
bucketArray.get((nums.get(i) - min) / bucketSize).add(nums.get(i));
}
// 新建数组保存结果
List<Integer> resultArray = new ArrayList<>();
// 对桶集合中每个桶进行排序,并将排序后的元素添加到结果数组中
for (int i = 0; i < bucketCount; i++) {
List<Integer> everyBucket = bucketArray.get(i);
if (everyBucket.size() > 0) {
if (bucketCount == 1) {
bucketSize--;
}
// 在这里可以使用其他排序方法,如插入排序
// 这里递归调用桶排序进行排序
List<Integer> temp = sort(everyBucket, bucketSize);
for (int j = 0; j < temp.size(); j++) {
// 合并数据
resultArray.add(temp.get(j));
}
}
}
return resultArray;
}
public static void main(String[] args) {
int[] numsArray = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] numsArray = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] numsArray = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] numsArray = {8};
List<Integer> nums = new ArrayList<>();
for (int i = 0; i < numsArray.length; i++) {
nums.add(numsArray[i]);
}
System.out.println("排序前:");
for (int i = 0; i < nums.size(); i++) {
System.out.print(nums.get(i) + " ");
}
System.out.println();
List<Integer> result = sort(nums, 5);
System.out.println("排序后:");
for (int i = 0; i < result.size(); i++) {
System.out.print(result.get(i) + " ");
}
System.out.println();
}
}
11 基数排序
11.1 基本思想
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
注意:这种方法只适合正整数排序,如果想要对有负数的数组进行排序,需要进行优化,下面提供一些解决方式
解决方式1:
- 将所有元素变成正值(加上最小数的相反数(注意要加以判断是否存在负数)),另外这种情况存在溢出,所以需要用long存储(导致空间变大)。
- 接着就可以按照下面方法进行处理,得到排序好的数组。
- 最后把所有元素减去最小值的绝对值(一样要注意要加以判断是否存在负数)。
解决方式2:
- 将桶的大小从10扩大为20,负数从小到大在 0-9 号桶,正数从小到大在 10 - 19 号桶。
11.2 算法过程
- 取得数组中的最大数,并取得位数 k;
- nums 为原始数组,从最低位开始取每个位组成 radix 数组;
- 对 radix 进行计数排序(利用计数排序适用于小范围数的特点);
11.3 代码实现
import java.util.ArrayList;
import java.util.List;
public class RadixSort {
public static void sort(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
// 先算出最大数
int max = nums[0];
for (int i = 1; i < nums.length; i++) {
if (nums[i] > max) {
max = nums[i];
}
}
// 再计算最大数的位数
int count = 0;
while (max > 0) {
max /= 10;
count++;
}
// 一共 0~9 10个数字,需要10个list存储
List<List<Integer>> bucketList = new ArrayList<>();
for (int i = 0; i < 10; i++)
bucketList.add(new ArrayList<>());
int mod = 10, div = 1;
// 循环数字的每一位
for (int i = 0; i < count; i++, mod *= 10, div *= 10) {
// 循环待排序数组中的每个元素, 将每个位对应的值加入到相应的list
for (int j = 0; j < nums.length; j++) {
// 得到元素位对应的值num,从而放到num对应的list
int num = (nums[j] % mod) / div;
bucketList.get(num).add(nums[j]);
}
int index = 0;
// 排序
for (int j = 0; j < bucketList.size(); j++) {
for (int k = 0; k < bucketList.get(j).size(); k++)
nums[index++] = bucketList.get(j).get(k);
// 注意清空,防止下次继续添加元素
bucketList.get(j).clear();
}
}
}
public static void main(String[] args) {
int[] nums = {3, 1, 5, 2, 6, 7, 9, 1};
// int[] nums = {1, 2, 3, 4, 5, 6, 7, 8};
// int[] nums = {8, 7, 6, 5, 4, 3, 2, 1};
// int[] nums = {8};
System.out.println("排序前:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
sort(nums);
System.out.println("排序后:");
for (int i = 0; i < nums.length; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
}
}
总结
在做算法题以及相关内容时,涉及排序的内容比较多,所以在此总结了各种排序算法的内容,方便之后回顾。
内容较多,难免有不足之处,欢迎大家前来讨论交流指正,十分感谢 !!!
参考