目录
01 希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。但希尔排序是非稳定排序算法。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
- 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;
- 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;
希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
1.1 希尔排序举例和图解
1.2 算法步骤
选择一个增量序列 t1,t2,……,tk,其中 ti > tj, tk = 1;
按增量序列个数 k,对序列进行 k 趟排序;
每趟排序,根据对应的增量 ti,将待排序列分割成若干长度为 m 的子序列,分别对各子表进行直接插入排序。仅增量因子为 1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
1.3 代码实现
1.3.1 交换法实现
这种方法其实是插入排序的升级版,只是在插入排序的基础上增加了步长, 取一个新的数,与步长之外的数进行比较, 遇到合适的地方插入
public class ShellSort {
public static void main(String[] args){
int[] arr = new int[]{8,9,1,7,2,3,5,4,6,0};
shellSort(arr);
System.out.println(Arrays.toString(arr));
}
// 交换法
public static void shellSort(int[] arr){
// 取增量序列, 第一次取分组为length / 2, 之后逐步减小
for(int gap = arr.length /2 ; gap > 0; gap /= 2){
// 令数组间隔有序
for(int i = gap; i < arr.length; i++){
// 遍历元素
for(int j = i - gap; j >= 0; j -= gap){
// 如果当前元素 大于加上步长的元素, 交换
if(arr[j + gap] < arr[j]){
swap(arr, j, j+gap);
}
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
1.3.2 移动法实现
// 移位法
public static void shellSort2(int[] arr){
// 拿到增量, 并逐步减小
for(int gap = arr.length /2 ; gap > 0; gap /= 2){
// 从gap开始, 对其所在的组进行直接插入排序
for(int i = gap; i < arr.length; i++){
int j = i;
int temp = arr[j];
for(j = i; j - gap >= 0 && arr[j - gap] > temp ; j -= gap ){
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
02 基数排序(桶排序)
基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。
2.1 基数排序原理图解
采用次位优先排序:
pass 1: 取出数组中各数的个位数,分别放到不同的桶里, 保存在一个数组里
pass 2: 按pass1 数组的顺序取出数组中各数的十位数, 再分别放在不同的桶里
pass 3: 按 pass2 中数组顺序取出各个数的百位数, 放到数组里, 此时排序完成
有几个pass 取决于最大数是几位数
2.2 基数排序代码实现
import java.util.Arrays;
public class RadixSort {
public static void main(String[] args){
int arr[] = {53,3,542,748,14,256};
radixSort(arr);
}
public static void radixSort(int[] arr) {
// 先定义一个二维数组, 表示10个桶, 每个桶就是一个一维数组
int[][] bucket = new int[10][arr.length];
// 为了记录每个桶实际存放了多少个数据, 定义一个一维数组记录各个桶每次放入数据的个数
int[] bucketElementCounts = new int[10];
// bucketElementCounts[0] 记录 bucket[0] 中有效数据个数
// 得到最大位数
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
// 得到最大数是几位数
int maxLength = (max + "").length();
// 使用循环
for (int m = 0, n = 1;m < maxLength; m++, n *= 10) {
// 第一次是个位, 第二次是十位, 第三次是百位
// 遍历开始放数
for (int i = 0; i < arr.length; i++) {
// 取出每个元素的对应位数
int digitOfElements = arr[i] / n % 10;
// 放到对应的桶
bucket[digitOfElements][bucketElementCounts[digitOfElements]] = arr[i];
bucketElementCounts[digitOfElements]++;
}
// 按照这个桶的顺序, 依次放到原来的数组中
int index = 0;
// 遍历每个桶
for (int k = 0; k < bucketElementCounts.length; k++) {
// 如果桶中有数据, 才放到原数组中
if (bucketElementCounts[k] != 0) {
// 遍历第k个一维数组,放到原数组中
for (int l = 0; l < bucketElementCounts[k]; l++) { // 这里注意放入几个数值
arr[index] = bucket[k][l];
index++;
}
}
// 每轮结束, 需要将 bucketElementCounts[k] == 0
bucketElementCounts[k] = 0;
}
System.out.println("第" + m + "轮排序结果: " + Arrays.toString(arr));
}
}
}
基数排序是对传统桶排序的扩展, 速度很快
基数排序是典型的空间换时间的方式, 占用内存很大,当对海量数据排序时,容易造成OutOfMemoryError
基数排序是稳定的
有负数的数组, 不建议使用基数排序进行排序