高效的、有趣的、不为人常知的排序算法

除了常见的排序算法之外,还有一些高效、有趣且不那么为人熟知的排序算法。这些算法在特定场景下表现出色,或者具有独特的特性。以下是几个这样的排序算法:

  1. Timsort
  2. Block Sort
  3. Smooth Sort
  4. Cycle Sort
  5. Intro Sort
  6. Library Sort
  7. Flashsort
  8. 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 更常用于外部排序,即数据量超过内存容量的情况。

核心概念

  1. 分割成块:将输入数组分割成多个小块,每个块的大小可以根据实际情况调整。
  2. 块内排序:使用插入排序或其他适合小数组的排序算法对每个块进行排序。
  3. 合并块:使用多路归并的方法将所有排序后的块合并成一个有序的数组。

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));
    }
}

解释

  1. splitAndSort:将输入数组分割成多个块,并对每个块进行插入排序。每个块的大小由 BLOCK_SIZE 定义。
  2. insertionSort:对每个块进行插入排序。插入排序适用于小数组,因此在这里非常高效。
  3. 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

核心概念

  1. Leonardo 数列:用于确定堆的结构。
  2. 堆的构建:使用 Leonardo 数列构建堆,确保每个节点的子节点数量符合 Leonardo 数列的要求。
  3. 堆的调整:在插入和删除操作中,通过交换节点来维持堆的性质。
  4. 插入排序:对于小数组或部分有序的数组,使用插入排序来优化性能。
特点
  • 高效:平均时间复杂度为 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));
    }
}

解释

  1. generateLeonardoNumbers:生成 Leonardo 数列,直到大于等于数组长度的最大值。
  2. buildHeap:构建初始堆。使用 Leonardo 数列确定每个节点的子节点位置,并通过交换确保堆的性质。
  3. getLargestLeonardoNumber:找到小于等于给定值的最大 Leonardo 数。
  4. siftDown:向下调整堆,确保根节点的值大于其子节点的值。
  5. swap:交换数组中的两个元素。
  6. sort:主排序函数,先构建堆,然后通过不断交换堆顶元素和最后一个元素,并重新调整堆,最终完成排序。

注意事项

  • 性能:Smooth Sort 在最坏情况下仍然是 O(n log n),但在部分有序的数组上表现更好。
  • 内存:这个实现假设所有数据都可以放入内存中。如果数据量非常大,需要考虑外部排序的方法。

4. Cycle Sort

Cycle Sort 是一种原地排序算法,特别适合在内存有限的情况下使用。它的主要特点是只需要一次遍历就能将数组排序。适用于当数组中的元素是从 0 到 n-1 的整数时。这种排序算法通过将每个元素放到其正确的位置来实现排序,而不需要额外的空间。Cycle Sort 的时间复杂度为 O(n),空间复杂度为 O(1)。

核心概念

  1. 循环置换:每个元素都应该放在与其值对应的位置上。例如,值为 0 的元素应该放在索引 0 的位置,值为 1 的元素应该放在索引 1 的位置,依此类推。
  2. 跳过已经正确放置的元素:如果一个元素已经在正确的位置上,则跳过该元素,继续处理下一个元素。
特点
  • 原地排序:不需要额外的存储空间。
  • 高效:时间复杂度为 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));
    }
}

解释

  1. sort 方法

    • 检查数组是否为空或长度小于 2,如果是则直接返回。
    • 遍历数组,从索引 0 开始。
    • 对于每个元素,找到它应该放置的正确位置。
    • 通过交换操作将元素放到正确的位置,直到当前元素已经正确放置。
  2. 循环置换

    • 使用 do-while 循环来确保每个元素都被正确放置。
    • 如果当前元素已经正确放置(即 element == array[pos]),则跳出循环,继续处理下一个元素。
    • 否则,交换当前元素和它应该放置位置的元素,继续检查新的当前元素是否需要进一步置换。

示例

假设我们有一个数组 [4, 0, 2, 1, 3],按照以下步骤进行排序:

  1. 初始状态[4, 0, 2, 1, 3]
  2. 处理索引 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]
  3. 处理索引 1
    • 1 已经在正确的位置,跳过。
  4. 处理索引 2
    • 2 已经在正确的位置,跳过。
  5. 处理索引 3
    • 3 已经在正确的位置,跳过。
  6. 处理索引 4
    • 4 已经在正确的位置,跳过。

最终数组变为 [0, 1, 2, 3, 4],排序完成。

5. Intro Sort

Intro Sort 是一种混合排序算法,结合了快速排序、堆排序和插入排序的优点。旨在提供快速排序的速度,同时避免快速排序在最坏情况下的 O(n^2) 时间复杂度。Intro Sort 在递归深度达到某个阈值时切换到堆排序,从而保证了 O(n log n) 的最坏时间复杂度。

核心概念

  1. 快速排序:作为主要的排序算法,快速排序在大多数情况下表现非常好。
  2. 堆排序:当递归深度超过某个阈值时,切换到堆排序,以避免快速排序的最坏情况。
  3. 插入排序:对于小数组,使用插入排序来优化性能。
特点
  • 高效:平均时间复杂度为 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));
    }
}

解释

  1. sort 方法

    • 检查数组是否为空或长度小于 2,如果是则直接返回。
    • 调用 introSort 方法开始排序。
  2. introSort 方法

    • 如果左边界大于或等于右边界,直接返回。
    • 如果递归深度达到阈值 MAX_DEPTH,切换到堆排序。
    • 否则,使用快速排序的分区方法 partition 来找到枢轴点,并递归地对左右两部分进行排序。
  3. partition 方法

    • 使用最后一个元素作为枢轴,将数组分为两部分,左边部分小于或等于枢轴,右边部分大于枢轴。
    • 返回枢轴点的索引。
  4. heapSort 方法

    • 构建最大堆。
    • 反复将堆顶元素与最后一个元素交换,并重新调整堆,直到整个数组有序。
  5. buildMaxHeap 方法

    • 从最后一个非叶子节点开始,逐层向上调整,构建最大堆。
  6. maxHeapify 方法

    • 确保以给定索引为根的子树满足最大堆的性质。
    • 如果根节点不是最大值,交换根节点和最大子节点,并递归地调整子树。
  7. parent 方法

    • 计算给定索引的父节点索引。
  8. swap 方法

    • 交换数组中的两个元素。

6. Library Sort

Library Sort 是一种基于插入排序的排序算法,它通过在数组中预留一些空位来加速插入操作,从而提高排序的效率。这种算法特别适用于动态数据集,因为它可以在插入新元素时保持已排序部分的顺序。

核心概念

  1. 预留空位:在数组中预留一些空位,使得插入新元素时不需要频繁移动大量元素。
  2. 插入排序:使用插入排序的思想,将每个元素插入到已排序部分的正确位置。
  3. 动态调整:当预留的空位不足时,可以动态调整数组大小,增加更多的空位。
特点
  • 高效:平均时间复杂度为 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));
    }
}

解释

  1. sort 方法

    • 检查数组是否为空或长度小于 2,如果是则直接返回。
    • 计算所需的容量,确保有足够的空位。
    • 创建一个新的数组 sortedArray,用于存放排序后的结果。
    • 遍历原始数组,将每个元素插入到 sortedArray 中。
    • 将排序后的结果复制回原数组。
  2. insert 方法

    • 找到插入位置。
    • 将插入位置及其右侧的元素向右移动一位。
    • 插入新元素。
  3. findInsertPosition 方法

    • 使用二分查找找到新元素的插入位置。
  4. shiftRight 方法

    • 将插入位置及其右侧的元素向右移动一位,为新元素腾出空间。

注意事项

  • 负载因子:负载因子控制预留空位的比例。较小的负载因子会增加预留的空位,从而减少插入操作的移动次数,但会增加空间开销。
  • 动态调整:如果需要处理动态数据集,可以在预留的空位不足时动态调整数组大小,增加更多的空位。

7. Flashsort

Flashsort 是一种基于桶排序的算法,通过预处理步骤将数据分成多个桶,然后对每个桶进行排序。它特别适用于具有均匀分布的数据集。Flashsort 的主要思想是通过统计每个元素在最终排序数组中的大致位置,然后进行一次大规模的移动操作,最后使用插入排序来完成最终的排序。

特点
  • 高效:平均时间复杂度为 O(n)。
  • 不常用:因为它的适用场景比较特殊,不如其他算法广泛使用。
  • 数据分布均匀:特别适合数据分布均匀的情况。
应用场景
  • 数据分布均匀:适用于数据分布均匀的场景。
  • 大规模数据:适合大规模数据的排序。

核心概念

  1. 计数数组:用于记录每个元素的大致位置。
  2. 大规模移动:根据计数数组的信息,将元素移动到接近最终位置的地方。
  3. 插入排序:对移动后的数组进行局部调整,以完成最终的排序。

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));
    }
}

解释

  1. 找最大值和最小值

    • 遍历数组,找到数组中的最大值 max 和最小值 min
  2. 计算桶的数量

    • 使用 sqrt(n) 作为桶的数量,其中 n 是数组的长度。
    • 计算每个桶的范围 range,即 max - min + 1
  3. 统计每个桶的元素数量

    • 使用公式 (array[i] - min) * (bucketCount - 1) / (range - 1) 计算每个元素所属的桶的索引。
    • 统计每个桶的元素数量。
  4. 计算每个桶的累积计数

    • 计算每个桶的累积计数,以便知道每个桶的元素在最终数组中的起始位置。
  5. 大规模移动

    • 使用累积计数信息,将每个元素移动到接近最终位置的地方。
    • 使用临时数组 temp 来存储移动后的结果。
  6. 局部调整

    • 将临时数组复制回原数组。
    • 使用插入排序对每个桶内的元素进行局部调整,以完成最终的排序。

注意事项

  • 均匀分布:Flashsort 特别适用于具有均匀分布的数据集。如果数据分布不均匀,效果可能会打折扣。
  • 空间复杂度:Flashsort 需要额外的空间来存储临时数组,但总体空间复杂度仍然是 O(n)。

8. Bead Sort

Bead Sort(珠排序)是一种物理模拟排序算法,通过模拟珠子在柱子上的移动来排序数据。它只适用于非负整数排序。Bead Sort 的时间复杂度为 O(n^2),但它在某些情况下可以非常直观和高效。

核心概念

  1. 珠子模型:将每个数字表示为一列珠子,每一行代表一个数字的值。
  2. 下落过程:让所有的珠子自由下落,最终形成一个稳定的排列,每一列的高度即为排序后的数字。
  3. 读取结果:从下往上读取每一列的高度,得到排序后的数组。
特点
  • 直观:算法过程非常直观,容易理解。
  • 不常用:因为它的适用场景非常有限,不如其他算法广泛使用。
  • 非负整数:只适用于非负整数排序。
应用场景
  • 教育目的:用于教学和演示目的。
  • 特定数据类型:适用于非负整数排序。

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));
    }
}

解释

  1. 初始化

    • 检查数组是否为空或长度小于 2,如果是则直接返回。
    • 找到数组中的最大值 max,用于确定珠子矩阵的高度。
  2. 创建珠子矩阵

    • 创建一个布尔类型的二维数组 beads,大小为 max x n,其中 n 是数组的长度。
  3. 放置珠子

    • 遍历数组,将每个数字表示为一列珠子,高度为该数字的值。
  4. 让珠子下落

    • 遍历每一行,统计该行中的珠子数量。
    • 将这些珠子移到该行的右侧。
  5. 读取结果

    • 遍历珠子矩阵,从下往上读取每一列的高度,得到排序后的数组。

示例

假设我们有一个数组 [5, 2, 9, 1, 5, 6, 3, 8, 7],按照以下步骤进行排序:

  1. 初始化

    • 最大值 max 是 9。
    • 创建一个 9x9 的布尔矩阵 beads
  2. 放置珠子

    • 第一列放置 5 个珠子,第二列放置 2 个珠子,第三列放置 9 个珠子,依此类推。
  3. 让珠子下落

    • 对每一行进行处理,将珠子移到右侧。
  4. 读取结果

    • 从下往上读取每一列的高度,得到排序后的数组 [1, 2, 3, 5, 5, 6, 7, 8, 9]

总结

这些高效的、有趣的、不为人常知的排序算法在特定场景下表现优异,具有独特的特性。选择合适的排序算法可以显著提高程序的性能和效率。以下是这些算法的简要总结:

  1. Timsort:适用于部分有序的数据,高效且稳定。
  2. Block Sort:适用于内存有限的环境,原地排序且高效。
  3. Smooth Sort:适用于内存受限的环境,高效且原地排序。
  4. Cycle Sort:适用于内存有限的环境,一次遍历即可完成排序。
  5. Intro Sort:自适应的混合排序算法,适用于各种场景。
  6. Library Sort:适用于部分有序的数据,高效且原地排序。
  7. Flashsort:适用于数据分布均匀的场景,高效且适用于大规模数据。
  8. Bead Sort:适用于非负整数排序,直观且易于理解。

通过了解这些算法的特点和适用场景,可以在特定问题中选择最合适的排序算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值