除了常见的排序算法之外,还有一些高效、有趣且不那么为人熟知的排序算法。这些算法在特定场景下表现出色,或者具有独特的特性。以下是几个这样的排序算法:
- Timsort
- Block Sort
- Smooth Sort
- Cycle Sort
- Intro Sort
- Library Sort
- Flashsort
- Bead Sort
1. Timsort
Timsort 是一种混合排序算法,结合了归并排序和插入排序的优点。它是由 Tim Peters 在 2002 年为 Python 标准库设计的。Timsort 在大多数情况下都表现得非常好,特别是在数据部分有序的情况下。
特点
- 高效:平均时间复杂度为 O(n log n),最坏情况下也是 O(n log n)。
- 稳定:是一种稳定的排序算法。
- 适应性强:特别适合部分有序的数据,。
应用场景
- Python 标准库:Python 的
sorted()
函数和列表的sort()
方法都使用 Timsort。 - Java 标准库:Java 7 及以后版本的
Arrays.sort()
方法也使用 Timsort,特别是针对对象数组的排序。
Java 中的简化实现
这里提供一个非常简化的版本,旨在展示 Timsort 的核心思想。请注意,实际的 Java 实现要复杂得多,并且包含了多种优化以提高性能。
import java.util.Arrays;
public class Timsort {
private static final int MIN_MERGE = 32;
public static void sort(int[] array) {
int n = array.length;
if (n < 2) return;
// 计算 minrun
int minRun = calculateMinRun(n);
// 对每个 minrun 进行插入排序
for (int start = 0; start < n; start += minRun) {
insertionSort(array, start, Math.min((start + minRun - 1), n - 1));
}
// 合并排序过的 run
for (int size = minRun; size < n; size = 2 * size) {
for (int left = 0; left < n; left += 2 * size) {
int mid = left + size - 1;
int right = Math.min((left + 2 * size - 1), (n - 1));
if (mid < right) {
merge(array, left, mid, right);
}
}
}
}
private static int calculateMinRun(int n) {
int r = 0; // 成为 1, 1, 0, 0, ...
while (n >= MIN_MERGE) {
r |= (n & 1);
n >>= 1;
}
return n + r;
}
private static void insertionSort(int[] array, int left, int right) {
for (int i = left + 1; i <= right; i++) {
int temp = array[i];
int j = i - 1;
while (j >= left && array[j] > temp) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = temp;
}
}
private static void merge(int[] array, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (array[i] <= array[j]) {
temp[k++] = array[i++];
} else {
temp[k++] = array[j++];
}
}
while (i <= mid) {
temp[k++] = array[i++];
}
while (j <= right) {
temp[k++] = array[j++];
}
System.arraycopy(temp, 0, array, left, temp.length);
}
public static void main(String[] args) {
int[] array = {5, 2, 9, 1, 5, 6};
sort(array);
System.out.println(Arrays.toString(array));
}
}
这段代码展示了如何使用插入排序对小数组进行排序,然后通过归并的方式将它们组合起来。请注意,这里的实现是为了教学目的而简化了实际的 Timsort 算法。真正的 Timsort 实现包括更多的细节和优化,例如处理不同长度的运行序列、避免不必要的复制等。
2. Block Sort
Block Sort 是一种原地排序算法,由 Michael Herf 发明。它结合了插入排序和归并排序的思想,特别适合内存有限的环境。是一种高效的排序算法,特别适合于大数据集的外部排序。它的主要思想是将数据分成多个块(或“堆”),每个块内部使用插入排序进行排序,然后将这些块合并成一个有序的输出。这种方法可以有效地利用内存,减少磁盘 I/O 操作,从而提高排序效率。
特点
- 原地排序:不需要额外的存储空间。
- 高效:平均时间复杂度为 O(n log n)。
- 简单:实现相对简单,适合嵌入式系统。
应用场景
- 嵌入式系统:内存有限的设备。
- 实时系统:需要高效且低内存占用的排序。
下面是 Block Sort 的一个简化 Java 实现,用于理解其基本原理。这个实现假设所有数据都可以放入内存中,但在实际应用中,Block Sort 更常用于外部排序,即数据量超过内存容量的情况。
核心概念
- 分割成块:将输入数组分割成多个小块,每个块的大小可以根据实际情况调整。
- 块内排序:使用插入排序或其他适合小数组的排序算法对每个块进行排序。
- 合并块:使用多路归并的方法将所有排序后的块合并成一个有序的数组。
Java 实现
import java.util.ArrayList;
import java.util.List;
public class BlockSort {
private static final int BLOCK_SIZE = 10; // 块的大小
public static void sort(int[] array) {
int n = array.length;
if (n < 2) return;
// 分割成块并排序
List<int[]> blocks = splitAndSort(array);
// 合并块
mergeBlocks(blocks, array);
}
private static List<int[]> splitAndSort(int[] array) {
List<int[]> blocks = new ArrayList<>();
int n = array.length;
for (int i = 0; i < n; i += BLOCK_SIZE) {
int end = Math.min(i + BLOCK_SIZE, n);
int[] block = Arrays.copyOfRange(array, i, end);
insertionSort(block);
blocks.add(block);
}
return blocks;
}
private static void insertionSort(int[] block) {
for (int i = 1; i < block.length; i++) {
int key = block[i];
int j = i - 1;
while (j >= 0 && block[j] > key) {
block[j + 1] = block[j];
j--;
}
block[j + 1] = key;
}
}
private static void mergeBlocks(List<int[]> blocks, int[] result) {
int[] indices = new int[blocks.size()];
int resultIndex = 0;
while (resultIndex < result.length) {
int minIndex = -1;
int minValue = Integer.MAX_VALUE;
for (int i = 0; i < blocks.size(); i++) {
if (indices[i] < blocks.get(i).length && blocks.get(i)[indices[i]] < minValue) {
minValue = blocks.get(i)[indices[i]];
minIndex = i;
}
}
result[resultIndex++] = minValue;
indices[minIndex]++;
}
}
public static void main(String[] args) {
int[] array = {5, 2, 9, 1, 5, 6, 3, 8, 7};
sort(array);
System.out.println(Arrays.toString(array));
}
}
解释
- splitAndSort:将输入数组分割成多个块,并对每个块进行插入排序。每个块的大小由
BLOCK_SIZE
定义。 - insertionSort:对每个块进行插入排序。插入排序适用于小数组,因此在这里非常高效。
- mergeBlocks:使用多路归并的方法将所有排序后的块合并成一个有序的数组。每次从所有块中选择当前最小的元素,将其添加到结果数组中。
注意事项
- 内存使用:这个实现假设所有数据都可以放入内存中。如果数据量非常大,需要考虑外部排序的方法,即将数据分块存储在磁盘上,只在必要时读取到内存中进行处理。
- 性能优化:实际应用中,可以通过增加块的大小、优化合并过程等方式进一步提升性能。
3. Smooth Sort
Smooth Sort 是一种改进的堆排序算法,由 Dijkstra 发明。它使用了一种特殊的堆结构,称为 Leonardo 堆,可以在 O(n log n) 时间内完成排序。
它结合了堆排序和插入排序的优点,能够在最坏情况下保持 O(n log n) 的时间复杂度,同时在某些特定情况下(如部分有序的数组)表现更好。
Smooth Sort 的核心思想是使用 Leonardo 数列来构建堆,这样可以在插入和删除操作中更高效地维护堆的性质。Leonardo 数列定义如下:
- L(0)=1L(0)=1
- L(1)=1L(1)=1
- L(n)=L(n−1)+L(n−2)+1L(n)=L(n−1)+L(n−2)+1
核心概念
- Leonardo 数列:用于确定堆的结构。
- 堆的构建:使用 Leonardo 数列构建堆,确保每个节点的子节点数量符合 Leonardo 数列的要求。
- 堆的调整:在插入和删除操作中,通过交换节点来维持堆的性质。
- 插入排序:对于小数组或部分有序的数组,使用插入排序来优化性能。
特点
- 高效:平均时间复杂度为 O(n log n)。
- 原地排序:不需要额外的存储空间。
- 稳定:不是稳定的排序算法。
应用场景
- 内存受限的环境:需要高效且低内存占用的排序。
- 数据量较大:适合大规模数据的排序。
Java 实现
下面是一个简化的 Smooth Sort 实现,用于理解其基本原理:
import java.util.Arrays;
public class SmoothSort {
public static void sort(int[] array) {
if (array == null || array.length < 2) {
return;
}
int n = array.length;
int[] leonardo = generateLeonardoNumbers(n);
int root = buildHeap(array, leonardo);
for (int i = n - 1; i > 0; i--) {
swap(array, 0, i);
root = siftDown(array, 0, i, root, leonardo);
}
}
private static int[] generateLeonardoNumbers(int n) {
List<Integer> leonardo = new ArrayList<>();
leonardo.add(1);
leonardo.add(1);
int next = 3;
while (next <= n) {
leonardo.add(next);
next = leonardo.get(leonardo.size() - 1) + leonardo.get(leonardo.size() - 2) + 1;
}
return leonardo.stream().mapToInt(i -> i).toArray();
}
private static int buildHeap(int[] array, int[] leonardo) {
int n = array.length;
int root = 0;
for (int i = 1; i < n; ) {
int size = getLargestLeonardoNumber(leonardo, n - i);
int left = i;
int right = i + size - 1;
i += size;
if (i < n) {
int size2 = getLargestLeonardoNumber(leonardo, n - i);
int left2 = i;
int right2 = i + size2 - 1;
i += size2;
if (array[left2] > array[left]) {
swap(array, left, left2);
}
root = left2;
}
root = left;
}
return root;
}
private static int getLargestLeonardoNumber(int[] leonardo, int n) {
for (int i = leonardo.length - 1; i >= 0; i--) {
if (leonardo[i] <= n) {
return leonardo[i];
}
}
return 1;
}
private static int siftDown(int[] array, int root, int size, int currentRoot, int[] leonardo) {
int left = root + 1;
int right = root + 2;
if (right < size && array[root] < array[right]) {
if (array[left] < array[right]) {
swap(array, root, right);
currentRoot = siftDown(array, right, size, currentRoot, leonardo);
} else {
swap(array, root, left);
currentRoot = siftDown(array, left, size, currentRoot, leonardo);
}
} else if (left < size && array[root] < array[left]) {
swap(array, root, left);
currentRoot = siftDown(array, left, size, currentRoot, leonardo);
}
return currentRoot;
}
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
int[] array = {5, 2, 9, 1, 5, 6, 3, 8, 7};
sort(array);
System.out.println(Arrays.toString(array));
}
}
解释
- generateLeonardoNumbers:生成 Leonardo 数列,直到大于等于数组长度的最大值。
- buildHeap:构建初始堆。使用 Leonardo 数列确定每个节点的子节点位置,并通过交换确保堆的性质。
- getLargestLeonardoNumber:找到小于等于给定值的最大 Leonardo 数。
- siftDown:向下调整堆,确保根节点的值大于其子节点的值。
- swap:交换数组中的两个元素。
- sort:主排序函数,先构建堆,然后通过不断交换堆顶元素和最后一个元素,并重新调整堆,最终完成排序。
注意事项
- 性能:Smooth Sort 在最坏情况下仍然是 O(n log n),但在部分有序的数组上表现更好。
- 内存:这个实现假设所有数据都可以放入内存中。如果数据量非常大,需要考虑外部排序的方法。
4. Cycle Sort
Cycle Sort 是一种原地排序算法,特别适合在内存有限的情况下使用。它的主要特点是只需要一次遍历就能将数组排序。适用于当数组中的元素是从 0 到 n-1 的整数时。这种排序算法通过将每个元素放到其正确的位置来实现排序,而不需要额外的空间。Cycle Sort 的时间复杂度为 O(n),空间复杂度为 O(1)。
核心概念
- 循环置换:每个元素都应该放在与其值对应的位置上。例如,值为
0
的元素应该放在索引0
的位置,值为1
的元素应该放在索引1
的位置,依此类推。 - 跳过已经正确放置的元素:如果一个元素已经在正确的位置上,则跳过该元素,继续处理下一个元素。
特点
- 原地排序:不需要额外的存储空间。
- 高效:时间复杂度为 O(n)。
- 不常用:因为它的适用场景比较特殊,不如其他算法广泛使用。
应用场景
- 内存受限的环境:需要高效且低内存占用的排序。
- 特定数据结构:适用于某些特定的数据结构,如循环数组。
Java 实现
下面是一个 Cycle Sort 的 Java 实现:
public class CycleSort {
public static void sort(int[] array) {
if (array == null || array.length < 2) {
return;
}
int n = array.length;
for (int start = 0; start < n - 1; start++) {
int element = array[start];
int pos = start;
// 找到 element 应该放置的位置
do {
pos = element;
if (element != array[pos]) {
int temp = array[pos];
array[pos] = element;
element = temp;
}
} while (pos != start);
}
}
public static void main(String[] args) {
int[] array = {4, 0, 2, 1, 3};
sort(array);
System.out.println(Arrays.toString(array));
}
}
解释
-
sort 方法:
- 检查数组是否为空或长度小于 2,如果是则直接返回。
- 遍历数组,从索引
0
开始。 - 对于每个元素,找到它应该放置的正确位置。
- 通过交换操作将元素放到正确的位置,直到当前元素已经正确放置。
-
循环置换:
- 使用
do-while
循环来确保每个元素都被正确放置。 - 如果当前元素已经正确放置(即
element == array[pos]
),则跳出循环,继续处理下一个元素。 - 否则,交换当前元素和它应该放置位置的元素,继续检查新的当前元素是否需要进一步置换。
- 使用
示例
假设我们有一个数组 [4, 0, 2, 1, 3]
,按照以下步骤进行排序:
- 初始状态:
[4, 0, 2, 1, 3]
- 处理索引 0:
4
应该放在索引4
的位置,但索引4
已经是3
,所以交换4
和3
。- 数组变为
[3, 0, 2, 1, 4]
3
应该放在索引3
的位置,但索引3
已经是1
,所以交换3
和1
。- 数组变为
[1, 0, 2, 3, 4]
1
应该放在索引1
的位置,但索引1
已经是0
,所以交换1
和0
。- 数组变为
[0, 1, 2, 3, 4]
- 处理索引 1:
1
已经在正确的位置,跳过。
- 处理索引 2:
2
已经在正确的位置,跳过。
- 处理索引 3:
3
已经在正确的位置,跳过。
- 处理索引 4:
4
已经在正确的位置,跳过。
最终数组变为 [0, 1, 2, 3, 4]
,排序完成。
5. Intro Sort
Intro Sort 是一种混合排序算法,结合了快速排序、堆排序和插入排序的优点。旨在提供快速排序的速度,同时避免快速排序在最坏情况下的 O(n^2) 时间复杂度。Intro Sort 在递归深度达到某个阈值时切换到堆排序,从而保证了 O(n log n) 的最坏时间复杂度。
核心概念
- 快速排序:作为主要的排序算法,快速排序在大多数情况下表现非常好。
- 堆排序:当递归深度超过某个阈值时,切换到堆排序,以避免快速排序的最坏情况。
- 插入排序:对于小数组,使用插入排序来优化性能。
特点
- 高效:平均时间复杂度为 O(n log n),最坏情况下也是 O(n log n)。
- 自适应:能够自动调整排序策略。
- 稳定:不是稳定的排序算法。
应用场景
- 通用排序:适用于各种场景,特别是需要避免快速排序最坏情况的场景。
- C++ 标准库:C++ STL 中的
std::sort
使用 Intro Sort。
Java 实现
下面是一个 Intro Sort 的 Java 实现:
import java.util.Arrays;
public class IntroSort {
private static final int MAX_DEPTH = 32;
public static void sort(int[] array) {
if (array == null || array.length < 2) {
return;
}
introSort(array, 0, array.length - 1, MAX_DEPTH);
}
private static void introSort(int[] array, int left, int right, int depthLimit) {
if (left >= right) {
return;
}
if (depthLimit == 0) {
heapSort(array, left, right);
return;
}
int pivotIndex = partition(array, left, right);
introSort(array, left, pivotIndex - 1, depthLimit - 1);
introSort(array, pivotIndex + 1, right, depthLimit - 1);
}
private static int partition(int[] array, int left, int right) {
int pivot = array[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (array[j] <= pivot) {
i++;
swap(array, i, j);
}
}
swap(array, i + 1, right);
return i + 1;
}
private static void heapSort(int[] array, int left, int right) {
buildMaxHeap(array, left, right);
for (int i = right; i > left; i--) {
swap(array, left, i);
maxHeapify(array, left, i - 1, left);
}
}
private static void buildMaxHeap(int[] array, int left, int right) {
for (int i = parent(right - left); i >= 0; i--) {
maxHeapify(array, left, right, left + i);
}
}
private static void maxHeapify(int[] array, int left, int right, int index) {
int largest = index;
int leftChild = left + (index - left) * 2 + 1;
int rightChild = left + (index - left) * 2 + 2;
if (leftChild <= right && array[leftChild] > array[largest]) {
largest = leftChild;
}
if (rightChild <= right && array[rightChild] > array[largest]) {
largest = rightChild;
}
if (largest != index) {
swap(array, left + index, left + largest);
maxHeapify(array, left, right, largest);
}
}
private static int parent(int i) {
return (i - 1) / 2;
}
private static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
int[] array = {5, 2, 9, 1, 5, 6, 3, 8, 7};
sort(array);
System.out.println(Arrays.toString(array));
}
}
解释
-
sort 方法:
- 检查数组是否为空或长度小于 2,如果是则直接返回。
- 调用
introSort
方法开始排序。
-
introSort 方法:
- 如果左边界大于或等于右边界,直接返回。
- 如果递归深度达到阈值
MAX_DEPTH
,切换到堆排序。 - 否则,使用快速排序的分区方法
partition
来找到枢轴点,并递归地对左右两部分进行排序。
-
partition 方法:
- 使用最后一个元素作为枢轴,将数组分为两部分,左边部分小于或等于枢轴,右边部分大于枢轴。
- 返回枢轴点的索引。
-
heapSort 方法:
- 构建最大堆。
- 反复将堆顶元素与最后一个元素交换,并重新调整堆,直到整个数组有序。
-
buildMaxHeap 方法:
- 从最后一个非叶子节点开始,逐层向上调整,构建最大堆。
-
maxHeapify 方法:
- 确保以给定索引为根的子树满足最大堆的性质。
- 如果根节点不是最大值,交换根节点和最大子节点,并递归地调整子树。
-
parent 方法:
- 计算给定索引的父节点索引。
-
swap 方法:
- 交换数组中的两个元素。
6. Library Sort
Library Sort 是一种基于插入排序的排序算法,它通过在数组中预留一些空位来加速插入操作,从而提高排序的效率。这种算法特别适用于动态数据集,因为它可以在插入新元素时保持已排序部分的顺序。
核心概念
- 预留空位:在数组中预留一些空位,使得插入新元素时不需要频繁移动大量元素。
- 插入排序:使用插入排序的思想,将每个元素插入到已排序部分的正确位置。
- 动态调整:当预留的空位不足时,可以动态调整数组大小,增加更多的空位。
特点
- 高效:平均时间复杂度为 O(n log n)。
- 原地排序:需要额外的空间,但空间复杂度较低。
- 简单:实现相对简单。
应用场景
- 数据部分有序:特别适合部分有序的数据。
- 内存有限的环境:需要高效且低内存占用的排序。
Java 实现
下面是一个 Library Sort 的 Java 实现:
import java.util.Arrays;
public class LibrarySort {
private static final double LOAD_FACTOR = 0.5; // 负载因子,控制预留空位的比例
public static void sort(int[] array) {
if (array == null || array.length < 2) {
return;
}
int n = array.length;
int capacity = (int) Math.ceil(n / LOAD_FACTOR); // 计算所需的容量
int[] sortedArray = new int[capacity];
int count = 0; // 当前已排序的元素数量
for (int i = 0; i < n; i++) {
insert(sortedArray, array[i], count);
count++;
}
// 将排序后的结果复制回原数组
for (int i = 0; i < n; i++) {
array[i] = sortedArray[i];
}
}
private static void insert(int[] sortedArray, int value, int count) {
int index = findInsertPosition(sortedArray, value, count);
shiftRight(sortedArray, index, count);
sortedArray[index] = value;
}
private static int findInsertPosition(int[] sortedArray, int value, int count) {
int low = 0;
int high = count - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
if (sortedArray[mid] < value) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return low;
}
private static void shiftRight(int[] sortedArray, int index, int count) {
for (int i = count; i > index; i--) {
sortedArray[i] = sortedArray[i - 1];
}
}
public static void main(String[] args) {
int[] array = {5, 2, 9, 1, 5, 6, 3, 8, 7};
sort(array);
System.out.println(Arrays.toString(array));
}
}
解释
-
sort 方法:
- 检查数组是否为空或长度小于 2,如果是则直接返回。
- 计算所需的容量,确保有足够的空位。
- 创建一个新的数组
sortedArray
,用于存放排序后的结果。 - 遍历原始数组,将每个元素插入到
sortedArray
中。 - 将排序后的结果复制回原数组。
-
insert 方法:
- 找到插入位置。
- 将插入位置及其右侧的元素向右移动一位。
- 插入新元素。
-
findInsertPosition 方法:
- 使用二分查找找到新元素的插入位置。
-
shiftRight 方法:
- 将插入位置及其右侧的元素向右移动一位,为新元素腾出空间。
注意事项
- 负载因子:负载因子控制预留空位的比例。较小的负载因子会增加预留的空位,从而减少插入操作的移动次数,但会增加空间开销。
- 动态调整:如果需要处理动态数据集,可以在预留的空位不足时动态调整数组大小,增加更多的空位。
7. Flashsort
Flashsort 是一种基于桶排序的算法,通过预处理步骤将数据分成多个桶,然后对每个桶进行排序。它特别适用于具有均匀分布的数据集。Flashsort 的主要思想是通过统计每个元素在最终排序数组中的大致位置,然后进行一次大规模的移动操作,最后使用插入排序来完成最终的排序。
特点
- 高效:平均时间复杂度为 O(n)。
- 不常用:因为它的适用场景比较特殊,不如其他算法广泛使用。
- 数据分布均匀:特别适合数据分布均匀的情况。
应用场景
- 数据分布均匀:适用于数据分布均匀的场景。
- 大规模数据:适合大规模数据的排序。
核心概念
- 计数数组:用于记录每个元素的大致位置。
- 大规模移动:根据计数数组的信息,将元素移动到接近最终位置的地方。
- 插入排序:对移动后的数组进行局部调整,以完成最终的排序。
Java 实现
下面是一个 Flashsort 的 Java 实现:
import java.util.Arrays;
public class FlashSort {
public static void sort(int[] array) {
if (array == null || array.length < 2) {
return;
}
int n = array.length;
int max = array[0];
int min = array[0];
// 找到最大值和最小值
for (int i = 1; i < n; i++) {
if (array[i] > max) {
max = array[i];
}
if (array[i] < min) {
min = array[i];
}
}
// 计算桶的数量
int bucketCount = (int) Math.sqrt(n);
int range = max - min + 1;
int[] counts = new int[bucketCount];
// 统计每个桶的元素数量
for (int i = 0; i < n; i++) {
int index = (array[i] - min) * (bucketCount - 1) / (range - 1);
counts[index]++;
}
// 计算每个桶的累积计数
for (int i = 1; i < bucketCount; i++) {
counts[i] += counts[i - 1];
}
// 大规模移动
int[] temp = new int[n];
for (int i = n - 1; i >= 0; i--) {
int index = (array[i] - min) * (bucketCount - 1) / (range - 1);
temp[--counts[index]] = array[i];
}
// 将临时数组复制回原数组
System.arraycopy(temp, 0, array, 0, n);
// 使用插入排序进行局部调整
for (int i = 0; i < bucketCount; i++) {
int start = (i == 0) ? 0 : counts[i - 1];
int end = counts[i] - 1;
insertionSort(array, start, end);
}
}
private static void insertionSort(int[] array, int start, int end) {
for (int i = start + 1; i <= end; i++) {
int key = array[i];
int j = i - 1;
while (j >= start && array[j] > key) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = key;
}
}
public static void main(String[] args) {
int[] array = {5, 2, 9, 1, 5, 6, 3, 8, 7};
sort(array);
System.out.println(Arrays.toString(array));
}
}
解释
-
找最大值和最小值:
- 遍历数组,找到数组中的最大值
max
和最小值min
。
- 遍历数组,找到数组中的最大值
-
计算桶的数量:
- 使用
sqrt(n)
作为桶的数量,其中n
是数组的长度。 - 计算每个桶的范围
range
,即max - min + 1
。
- 使用
-
统计每个桶的元素数量:
- 使用公式
(array[i] - min) * (bucketCount - 1) / (range - 1)
计算每个元素所属的桶的索引。 - 统计每个桶的元素数量。
- 使用公式
-
计算每个桶的累积计数:
- 计算每个桶的累积计数,以便知道每个桶的元素在最终数组中的起始位置。
-
大规模移动:
- 使用累积计数信息,将每个元素移动到接近最终位置的地方。
- 使用临时数组
temp
来存储移动后的结果。
-
局部调整:
- 将临时数组复制回原数组。
- 使用插入排序对每个桶内的元素进行局部调整,以完成最终的排序。
注意事项
- 均匀分布:Flashsort 特别适用于具有均匀分布的数据集。如果数据分布不均匀,效果可能会打折扣。
- 空间复杂度:Flashsort 需要额外的空间来存储临时数组,但总体空间复杂度仍然是 O(n)。
8. Bead Sort
Bead Sort(珠排序)是一种物理模拟排序算法,通过模拟珠子在柱子上的移动来排序数据。它只适用于非负整数排序。Bead Sort 的时间复杂度为 O(n^2),但它在某些情况下可以非常直观和高效。
核心概念
- 珠子模型:将每个数字表示为一列珠子,每一行代表一个数字的值。
- 下落过程:让所有的珠子自由下落,最终形成一个稳定的排列,每一列的高度即为排序后的数字。
- 读取结果:从下往上读取每一列的高度,得到排序后的数组。
特点
- 直观:算法过程非常直观,容易理解。
- 不常用:因为它的适用场景非常有限,不如其他算法广泛使用。
- 非负整数:只适用于非负整数排序。
应用场景
- 教育目的:用于教学和演示目的。
- 特定数据类型:适用于非负整数排序。
Java 实现
下面是一个 Bead Sort 的 Java 实现:
public class BeadSort {
public static int[] sort(int[] array) {
if (array == null || array.length < 2) {
return array;
}
int n = array.length;
int max = Arrays.stream(array).max().orElse(0);
// 创建珠子矩阵
boolean[][] beads = new boolean[max][n];
// 放置珠子
for (int i = 0; i < n; i++) {
for (int j = 0; j < array[i]; j++) {
beads[j][i] = true;
}
}
// 让珠子下落
for (int i = 0; i < max; i++) {
int count = 0;
for (int j = 0; j < n; j++) {
if (beads[i][j]) {
beads[i][j] = false;
count++;
}
}
for (int j = n - count; j < n; j++) {
beads[i][j] = true;
}
}
// 读取结果
int[] sortedArray = new int[n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < max; j++) {
if (beads[j][i]) {
sortedArray[i]++;
}
}
}
return sortedArray;
}
public static void main(String[] args) {
int[] array = {5, 2, 9, 1, 5, 6, 3, 8, 7};
int[] sortedArray = sort(array);
System.out.println(Arrays.toString(sortedArray));
}
}
解释
-
初始化:
- 检查数组是否为空或长度小于 2,如果是则直接返回。
- 找到数组中的最大值
max
,用于确定珠子矩阵的高度。
-
创建珠子矩阵:
- 创建一个布尔类型的二维数组
beads
,大小为max x n
,其中n
是数组的长度。
- 创建一个布尔类型的二维数组
-
放置珠子:
- 遍历数组,将每个数字表示为一列珠子,高度为该数字的值。
-
让珠子下落:
- 遍历每一行,统计该行中的珠子数量。
- 将这些珠子移到该行的右侧。
-
读取结果:
- 遍历珠子矩阵,从下往上读取每一列的高度,得到排序后的数组。
示例
假设我们有一个数组 [5, 2, 9, 1, 5, 6, 3, 8, 7]
,按照以下步骤进行排序:
-
初始化:
- 最大值
max
是 9。 - 创建一个 9x9 的布尔矩阵
beads
。
- 最大值
-
放置珠子:
- 第一列放置 5 个珠子,第二列放置 2 个珠子,第三列放置 9 个珠子,依此类推。
-
让珠子下落:
- 对每一行进行处理,将珠子移到右侧。
-
读取结果:
- 从下往上读取每一列的高度,得到排序后的数组
[1, 2, 3, 5, 5, 6, 7, 8, 9]
。
- 从下往上读取每一列的高度,得到排序后的数组
总结
这些高效的、有趣的、不为人常知的排序算法在特定场景下表现优异,具有独特的特性。选择合适的排序算法可以显著提高程序的性能和效率。以下是这些算法的简要总结:
- Timsort:适用于部分有序的数据,高效且稳定。
- Block Sort:适用于内存有限的环境,原地排序且高效。
- Smooth Sort:适用于内存受限的环境,高效且原地排序。
- Cycle Sort:适用于内存有限的环境,一次遍历即可完成排序。
- Intro Sort:自适应的混合排序算法,适用于各种场景。
- Library Sort:适用于部分有序的数据,高效且原地排序。
- Flashsort:适用于数据分布均匀的场景,高效且适用于大规模数据。
- Bead Sort:适用于非负整数排序,直观且易于理解。
通过了解这些算法的特点和适用场景,可以在特定问题中选择最合适的排序算法。