常见八大排序算法

127 阅读11分钟

算法概览与性能对比

排序算法平均时间复杂度最好情况最坏情况空间复杂度稳定性
冒泡排序O(n²)O(n)O(n²)O(1)稳定
插入排序O(n²)O(n)O(n²)O(1)稳定
选择排序O(n²)O(n²)O(n²)O(1)不稳定
希尔排序O(n^1.3-1.5)O(n)O(n²)O(1)不稳定
堆排序O(n log n)O(n log n)O(n log n)O(1)不稳定
快速排序O(n log n)O(n log n)O(n²)O(log n)不稳定
归并排序O(n log n)O(n log n)O(n log n)O(n)稳定
计数排序O(n + k)O(n + k)O(n + k)O(k)稳定

一、冒泡排序 (Bubble Sort)

算法思想

通过相邻元素的比较和交换,使较大的元素逐渐"浮"到数组末端。

代码实现

java

public class BubbleSort {
    
    /**
     * 基础冒泡排序
     */
    public void bubbleSort(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {  // 改为升序
                    swap(arr, j, j + 1);
                }
            }
        }
        System.out.println("冒泡排序: " + Arrays.toString(arr));
    }
    
    /**
     * 优化版冒泡排序 - 添加标志位检测是否已有序
     */
    public void bubbleSortOptimized(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        for (int i = 0; i < n - 1; i++) {
            boolean swapped = false;  // 优化标志
            for (int j = 0; j < n - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                    swapped = true;
                }
            }
            // 如果本轮没有发生交换,说明数组已有序
            if (!swapped) break;
        }
        System.out.println("优化冒泡: " + Arrays.toString(arr));
    }
    
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

算法特点

  • 稳定排序
  • 适合小规模数据
  • 优化后对基本有序数组效果较好

二、插入排序 (Insertion Sort)

算法思想

将数组分为已排序和未排序两部分,每次将未排序部分的第一个元素插入到已排序部分的正确位置。

代码实现

java

public class InsertionSort {
    
    /**
     * 直接插入排序
     */
    public void insertionSort(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        for (int i = 1; i < n; i++) {
            int key = arr[i];  // 当前要插入的元素
            int j = i - 1;
            
            // 将比key大的元素向后移动
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;  // 插入到正确位置
        }
        System.out.println("插入排序: " + Arrays.toString(arr));
    }
    
    /**
     * 带预检查的插入排序
     */
    public void insertionSortWithCheck(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        for (int i = 1; i < n; i++) {
            // 如果当前元素已经大于等于前一个元素,不需要插入
            if (arr[i] >= arr[i - 1]) continue;
            
            int key = arr[i];
            int j = i - 1;
            
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
        System.out.println("优化插入: " + Arrays.toString(arr));
    }
}

算法特点

  • 稳定排序
  • 对基本有序数组效率很高
  • 适合小规模数据

三、选择排序 (Selection Sort)

算法思想

每次从未排序部分选择最小(或最大)元素,放到已排序部分的末尾。

代码实现

java

public class SelectionSort {
    
    /**
     * 选择排序
     */
    public void selectionSort(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        for (int i = 0; i < n - 1; i++) {
            int minIndex = i;  // 记录最小元素的索引
            
            // 在未排序部分寻找最小元素
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            
            // 将最小元素交换到当前位置
            if (minIndex != i) {
                swap(arr, i, minIndex);
            }
        }
        System.out.println("选择排序: " + Arrays.toString(arr));
    }
    
    /**
     * 双向选择排序 - 同时找到最小和最大元素
     */
    public void selectionSortBidirectional(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        int left = 0, right = n - 1;
        
        while (left < right) {
            int minIndex = left, maxIndex = right;
            
            // 确保minIndex <= maxIndex
            if (arr[minIndex] > arr[maxIndex]) {
                swap(arr, minIndex, maxIndex);
            }
            
            // 在剩余部分寻找最小和最大值
            for (int i = left + 1; i < right; i++) {
                if (arr[i] < arr[minIndex]) {
                    minIndex = i;
                } else if (arr[i] > arr[maxIndex]) {
                    maxIndex = i;
                }
            }
            
            // 将最小值放到左边,最大值放到右边
            swap(arr, left, minIndex);
            swap(arr, right, maxIndex);
            
            left++;
            right--;
        }
        System.out.println("双向选择: " + Arrays.toString(arr));
    }
    
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

算法特点

  • 不稳定排序
  • 交换次数较少
  • 适合数据量较小的情况

四、希尔排序 (Shell Sort)

算法思想

是插入排序的改进版,通过分组进行插入排序,逐渐减小分组间隔,最终完成排序。

代码实现

java

public class ShellSort {
    
    /**
     * 希尔排序
     */
    public void shellSort(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        // 使用Knuth序列计算间隔
        int gap = 1;
        while (gap < n / 3) {
            gap = gap * 3 + 1;
        }
        
        while (gap > 0) {
            // 对每个间隔进行插入排序
            for (int i = gap; i < n; i++) {
                int temp = arr[i];
                int j = i;
                
                // 对间隔为gap的元素进行插入排序
                while (j >= gap && arr[j - gap] > temp) {
                    arr[j] = arr[j - gap];
                    j -= gap;
                }
                arr[j] = temp;
            }
            gap /= 3;  // 减小间隔
        }
        System.out.println("希尔排序: " + Arrays.toString(arr));
    }
    
    /**
     * 使用二分法优化的希尔排序
     */
    public void shellSortOptimized(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        // 使用二分法确定间隔序列
        for (int gap = n / 2; gap > 0; gap /= 2) {
            // 对每个间隔进行插入排序
            for (int i = gap; i < n; i++) {
                int temp = arr[i];
                int j = i;
                
                // 移动元素,为temp找到正确位置
                while (j >= gap && arr[j - gap] > temp) {
                    arr[j] = arr[j - gap];
                    j -= gap;
                }
                arr[j] = temp;
            }
        }
        System.out.println("优化希尔: " + Arrays.toString(arr));
    }
}

算法特点

  • 不稳定排序
  • 插入排序的高效改进版
  • 间隔序列的选择影响性能

五、堆排序 (Heap Sort)

算法思想

利用堆这种数据结构进行排序,建立大根堆后,不断将堆顶元素与末尾元素交换并调整堆。

代码实现

java

public class HeapSort {
    
    /**
     * 堆排序
     */
    public void heapSort(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        // 构建大根堆
        buildMaxHeap(arr);
        
        // 逐个提取最大元素
        for (int i = n - 1; i > 0; i--) {
            // 将堆顶元素(最大)与当前末尾元素交换
            swap(arr, 0, i);
            // 调整剩余元素为堆
            heapify(arr, 0, i);
        }
        System.out.println("堆排序: " + Arrays.toString(arr));
    }
    
    /**
     * 构建大根堆
     */
    private void buildMaxHeap(int[] arr) {
        int n = arr.length;
        // 从最后一个非叶子节点开始向上调整
        for (int i = (n - 2) / 2; i >= 0; i--) {
            heapify(arr, i, n);
        }
    }
    
    /**
     * 堆调整(大根堆)
     */
    private void heapify(int[] arr, int parent, int heapSize) {
        int largest = parent;
        int left = 2 * parent + 1;
        int right = 2 * parent + 2;
        
        // 找出父节点、左孩子、右孩子中的最大值
        if (left < heapSize && arr[left] > arr[largest]) {
            largest = left;
        }
        if (right < heapSize && arr[right] > arr[largest]) {
            largest = right;
        }
        
        // 如果最大值不是父节点,交换并继续调整
        if (largest != parent) {
            swap(arr, parent, largest);
            heapify(arr, largest, heapSize);
        }
    }
    
    /**
     * 非递归版本的堆调整
     */
    private void heapifyIterative(int[] arr, int parent, int heapSize) {
        int temp = arr[parent];
        
        for (int child = 2 * parent + 1; child < heapSize; child = 2 * child + 1) {
            // 选择较大的子节点
            if (child + 1 < heapSize && arr[child] < arr[child + 1]) {
                child++;
            }
            
            // 如果父节点已经大于子节点,调整完成
            if (temp >= arr[child]) break;
            
            // 将子节点值赋给父节点
            arr[parent] = arr[child];
            parent = child;
        }
        arr[parent] = temp;
    }
    
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

算法特点

  • 不稳定排序
  • 时间复杂度稳定在O(n log n)
  • 不需要额外空间

六、快速排序 (Quick Sort)

算法思想

采用分治策略,选择一个基准元素,将数组分为两部分,递归排序。

代码实现

java

public class QuickSort {
    
    /**
     * 快速排序(递归版本)
     */
    public void quickSort(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        quickSortRecursive(arr, 0, arr.length - 1);
        System.out.println("快速排序: " + Arrays.toString(arr));
    }
    
    private void quickSortRecursive(int[] arr, int low, int high) {
        if (low < high) {
            // 分区操作,返回基准位置
            int pivot = partition(arr, low, high);
            // 递归排序左右子数组
            quickSortRecursive(arr, low, pivot - 1);
            quickSortRecursive(arr, pivot + 1, high);
        }
    }
    
    /**
     * 分区函数 - 单边扫描法
     */
    private int partition(int[] arr, int low, int high) {
        // 选择最后一个元素作为基准
        int pivot = arr[high];
        int i = low - 1;  // 小于基准的元素的边界
        
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                swap(arr, i, j);
            }
        }
        swap(arr, i + 1, high);
        return i + 1;
    }
    
    /**
     * 三数取中法优化 - 选择更好的基准
     */
    public void quickSortOptimized(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        quickSortOptimizedRecursive(arr, 0, arr.length - 1);
        System.out.println("优化快排: " + Arrays.toString(arr));
    }
    
    private void quickSortOptimizedRecursive(int[] arr, int low, int high) {
        if (low < high) {
            // 三数取中选择基准
            medianOfThree(arr, low, high);
            int pivot = partition(arr, low, high);
            quickSortOptimizedRecursive(arr, low, pivot - 1);
            quickSortOptimizedRecursive(arr, pivot + 1, high);
        }
    }
    
    /**
     * 三数取中法
     */
    private void medianOfThree(int[] arr, int low, int high) {
        int mid = low + (high - low) / 2;
        
        // 确保arr[low] <= arr[mid] <= arr[high]
        if (arr[low] > arr[mid]) swap(arr, low, mid);
        if (arr[low] > arr[high]) swap(arr, low, high);
        if (arr[mid] > arr[high]) swap(arr, mid, high);
        
        // 将中位数放到high位置作为基准
        swap(arr, mid, high);
    }
    
    /**
     * 快速排序(非递归版本)
     */
    public void quickSortIterative(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        Stack<Integer> stack = new Stack<>();
        stack.push(0);
        stack.push(n - 1);
        
        while (!stack.isEmpty()) {
            int high = stack.pop();
            int low = stack.pop();
            
            if (low < high) {
                int pivot = partition(arr, low, high);
                
                // 将左子数组边界压栈
                if (pivot - 1 > low) {
                    stack.push(low);
                    stack.push(pivot - 1);
                }
                
                // 将右子数组边界压栈
                if (pivot + 1 < high) {
                    stack.push(pivot + 1);
                    stack.push(high);
                }
            }
        }
        System.out.println("非递归快排: " + Arrays.toString(arr));
    }
    
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

算法特点

  • 不稳定排序
  • 平均性能很好
  • 对基准选择敏感

七、归并排序 (Merge Sort)

算法思想

采用分治策略,将数组递归分成两半分别排序,然后合并两个有序数组。

代码实现

java

public class MergeSort {
    
    /**
     * 归并排序
     */
    public void mergeSort(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        mergeSortRecursive(arr, 0, arr.length - 1);
        System.out.println("归并排序: " + Arrays.toString(arr));
    }
    
    private void mergeSortRecursive(int[] arr, int left, int right) {
        if (left < right) {
            int mid = left + (right - left) / 2;
            
            // 递归排序左右子数组
            mergeSortRecursive(arr, left, mid);
            mergeSortRecursive(arr, mid + 1, right);
            
            // 合并两个有序子数组
            merge(arr, left, mid, right);
        }
    }
    
    /**
     * 合并两个有序子数组
     */
    private void merge(int[] arr, int left, int mid, int right) {
        // 创建临时数组
        int[] temp = new int[right - left + 1];
        int i = left;      // 左子数组起始索引
        int j = mid + 1;   // 右子数组起始索引
        int k = 0;         // 临时数组索引
        
        // 合并两个有序数组
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }
        
        // 复制左子数组剩余元素
        while (i <= mid) {
            temp[k++] = arr[i++];
        }
        
        // 复制右子数组剩余元素
        while (j <= right) {
            temp[k++] = arr[j++];
        }
        
        // 将临时数组复制回原数组
        System.arraycopy(temp, 0, arr, left, temp.length);
    }
    
    /**
     * 归并排序(非递归版本)
     */
    public void mergeSortIterative(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        
        // 从大小为1的子数组开始合并
        for (int currSize = 1; currSize < n; currSize *= 2) {
            // 合并所有成对的子数组
            for (int left = 0; left < n - 1; left += 2 * currSize) {
                int mid = Math.min(left + currSize - 1, n - 1);
                int right = Math.min(left + 2 * currSize - 1, n - 1);
                
                merge(arr, left, mid, right);
            }
        }
        System.out.println("非递归归并: " + Arrays.toString(arr));
    }
    
    /**
     * 优化版归并排序 - 避免小数组时的递归
     */
    public void mergeSortOptimized(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        int n = arr.length;
        int[] temp = new int[n];  // 一次性分配临时数组
        
        mergeSortOptimizedRecursive(arr, 0, n - 1, temp);
        System.out.println("优化归并: " + Arrays.toString(arr));
    }
    
    private void mergeSortOptimizedRecursive(int[] arr, int left, int right, int[] temp) {
        // 小数组使用插入排序
        if (right - left < 15) {
            insertionSort(arr, left, right);
            return;
        }
        
        if (left < right) {
            int mid = left + (right - left) / 2;
            mergeSortOptimizedRecursive(arr, left, mid, temp);
            mergeSortOptimizedRecursive(arr, mid + 1, right, temp);
            
            // 如果已经有序,不需要合并
            if (arr[mid] <= arr[mid + 1]) return;
            
            mergeWithTemp(arr, left, mid, right, temp);
        }
    }
    
    private void mergeWithTemp(int[] arr, int left, int mid, int right, int[] temp) {
        System.arraycopy(arr, left, temp, left, right - left + 1);
        
        int i = left, j = mid + 1, k = left;
        while (i <= mid && j <= right) {
            if (temp[i] <= temp[j]) {
                arr[k++] = temp[i++];
            } else {
                arr[k++] = temp[j++];
            }
        }
        
        while (i <= mid) arr[k++] = temp[i++];
        while (j <= right) arr[k++] = temp[j++];
    }
    
    private void insertionSort(int[] arr, int left, int right) {
        for (int i = left + 1; i <= right; i++) {
            int key = arr[i];
            int j = i - 1;
            while (j >= left && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    }
}

算法特点

  • 稳定排序
  • 时间复杂度稳定
  • 需要额外空间

八、计数排序 (Counting Sort)

算法思想

非比较排序,通过统计每个元素出现的次数,然后计算每个元素在输出数组中的位置。

代码实现

java

public class CountingSort {
    
    /**
     * 计数排序 - 基础版本
     */
    public void countingSort(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        
        if (arr.length == 0) return;
        
        // 找出数组中的最大值和最小值
        int max = arr[0], min = arr[0];
        for (int num : arr) {
            if (num > max) max = num;
            if (num < min) min = num;
        }
        
        // 创建计数数组
        int range = max - min + 1;
        int[] count = new int[range];
        
        // 统计每个元素出现的次数
        for (int num : arr) {
            count[num - min]++;
        }
        
        // 重构原数组
        int index = 0;
        for (int i = 0; i < range; i++) {
            while (count[i] > 0) {
                arr[index++] = i + min;
                count[i]--;
            }
        }
        System.out.println("计数排序: " + Arrays.toString(arr));
    }
    
    /**
     * 计数排序 - 稳定版本
     */
    public void countingSortStable(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        
        if (arr.length == 0) return;
        
        // 找出数组中的最大值和最小值
        int max = arr[0], min = arr[0];
        for (int num : arr) {
            if (num > max) max = num;
            if (num < min) min = num;
        }
        
        int range = max - min + 1;
        int[] count = new int[range];
        int[] output = new int[arr.length];
        
        // 统计每个元素出现的次数
        for (int num : arr) {
            count[num - min]++;
        }
        
        // 计算累积频次
        for (int i = 1; i < range; i++) {
            count[i] += count[i - 1];
        }
        
        // 构建输出数组(从后往前保证稳定性)
        for (int i = arr.length - 1; i >= 0; i--) {
            output[count[arr[i] - min] - 1] = arr[i];
            count[arr[i] - min]--;
        }
        
        System.arraycopy(output, 0, arr, 0, arr.length);
        System.out.println("稳定计数: " + Arrays.toString(arr));
    }
    
    /**
     * 适用于大范围数据的计数排序
     */
    public void countingSortForLargeRange(int[] array) {
        int[] arr = Arrays.copyOf(array, array.length);
        
        if (arr.length == 0) return;
        
        // 使用哈希思想处理大范围数据
        Map<Integer, Integer> countMap = new TreeMap<>();
        
        // 统计频率
        for (int num : arr) {
            countMap.put(num, countMap.getOrDefault(num, 0) + 1);
        }
        
        // 重构数组
        int index = 0;
        for (Map.Entry<Integer, Integer> entry : countMap.entrySet()) {
            int num = entry.getKey();
            int frequency = entry.getValue();
            
            for (int i = 0; i < frequency; i++) {
                arr[index++] = num;
            }
        }
        System.out.println("大范围计数: " + Arrays.toString(arr));
    }
}

算法特点

  • 稳定排序(稳定版本)
  • 非比较排序
  • 适合数据范围不大的情况

综合测试类

java

public class SortingTest {
    public static void main(String[] args) {
        int[] testArray = {64, 34, 25, 12, 22, 11, 90, 5, 77, 30};
        System.out.println("原始数组: " + Arrays.toString(testArray));
        System.out.println();
        
        // 测试各种排序算法
        BubbleSort bubble = new BubbleSort();
        bubble.bubbleSort(testArray);
        bubble.bubbleSortOptimized(testArray);
        
        InsertionSort insertion = new InsertionSort();
        insertion.insertionSort(testArray);
        insertion.insertionSortWithCheck(testArray);
        
        SelectionSort selection = new SelectionSort();
        selection.selectionSort(testArray);
        selection.selectionSortBidirectional(testArray);
        
        ShellSort shell = new ShellSort();
        shell.shellSort(testArray);
        shell.shellSortOptimized(testArray);
        
        HeapSort heap = new HeapSort();
        heap.heapSort(testArray);
        
        QuickSort quick = new QuickSort();
        quick.quickSort(testArray);
        quick.quickSortOptimized(testArray);
        quick.quickSortIterative(testArray);
        
        MergeSort merge = new MergeSort();
        merge.mergeSort(testArray);
        merge.mergeSortIterative(testArray);
        merge.mergeSortOptimized(testArray);
        
        CountingSort counting = new CountingSort();
        counting.countingSort(testArray);
        counting.countingSortStable(testArray);
        
        // 测试大范围数据
        int[] largeRangeArray = {1000, 5, 200, 50, 1000, 5, 200};
        counting.countingSortForLargeRange(largeRangeArray);
    }
}

算法选择指南

  1. 小规模数据 (n < 50) : 插入排序、选择排序
  2. 基本有序数据: 插入排序、冒泡排序
  3. 大规模数据: 快速排序、归并排序、堆排序
  4. 数据范围有限: 计数排序
  5. 需要稳定性: 归并排序、插入排序
  6. 内存有限: 堆排序、快速排序

每种排序算法都有其适用场景,理解它们的原理和特性有助于在实际问题中选择最合适的算法。