左哥算法 - 排序算法优化

86 阅读3分钟

在常见的排序算法中,有几种可以使用双指针技术来优化。让我详细介绍:

1️⃣ 快速排序(最典型的双指针应用)

public class QuickSort {
    public void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            int pivot = partition(arr, left, right);
            quickSort(arr, left, pivot - 1);
            quickSort(arr, pivot + 1, right);
        }
    }
    
    private int partition(int[] arr, int left, int right) {
        int pivot = arr[left];  // 选择第一个元素作为基准
        int i = left;           // 左指针
        int j = right;         // 右指针
        
        while (i < j) {
            // 右指针向左移,找到小于pivot的数
            while (i < j && arr[j] >= pivot) {
                j--;
            }
            // 左指针向右移,找到大于pivot的数
            while (i < j && arr[i] <= pivot) {
                i++;
            }
            // 交换两个数
            if (i < j) {
                swap(arr, i, j);
            }
        }
        // 将基准值放到最终位置
        swap(arr, left, i);
        return i;
    }
    
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2️⃣ 归并排序的合并过程(双指针)

public class MergeSort {
    private void merge(int[] arr, int left, int mid, int right) {
        int i = left;        // 左半部分的指针
        int j = mid + 1;     // 右半部分的指针
        int[] temp = new int[right - left + 1];  // 临时数组
        int t = 0;
        
        // 使用双指针合并两个有序数组
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                temp[t++] = arr[i++];
            } else {
                temp[t++] = arr[j++];
            }
        }
        
        // 处理剩余元素
        while (i <= mid) temp[t++] = arr[i++];
        while (j <= right) temp[t++] = arr[j++];
        
        // 复制回原数组
        System.arraycopy(temp, 0, arr, left, temp.length);
    }
}

3️⃣ 冒泡排序的优化版本(双指针)

public class BubbleSort {
    public void bubbleSort(int[] arr) {
        int left = 0;                    // 左边界
        int right = arr.length - 1;      // 右边界
        
        while (left < right) {
            // 从左向右冒泡,将最大值移到右边
            for (int i = left; i < right; i++) {
                if (arr[i] > arr[i + 1]) {
                    swap(arr, i, i + 1);
                }
            }
            right--;
            
            // 从右向左冒泡,将最小值移到左边
            for (int i = right; i > left; i--) {
                if (arr[i] < arr[i - 1]) {
                    swap(arr, i, i - 1);
                }
            }
            left++;
        }
    }
    
    private void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

4️⃣ 三路快排(三指针)

public class ThreeWayQuickSort {
    public void quickSort3Way(int[] arr, int left, int right) {
        if (left >= right) return;
        
        int lt = left;          // 小于区域的右边界
        int i = left + 1;       // 当前遍历的位置
        int gt = right;         // 大于区域的左边界
        int pivot = arr[left];  // 基准值
        
        while (i <= gt) {
            if (arr[i] < pivot) {
                swap(arr, lt++, i++);
            } else if (arr[i] > pivot) {
                swap(arr, i, gt--);
            } else {
                i++;
            }
        }
        
        quickSort3Way(arr, left, lt - 1);
        quickSort3Way(arr, gt + 1, right);
    }
}

📝 各种双指针实现的比较

  1. 快速排序

    • 优点:原地排序,不需要额外空间
    • 缺点:不稳定排序
  2. 归并排序

    • 优点:稳定排序
    • 缺点:需要额外空间
  3. 冒泡排序优化版

    • 优点:实现简单,稳定排序
    • 缺点:时间复杂度仍为O(n²)
  4. 三路快排

    • 优点:处理重复元素效率高
    • 缺点:实现相对复杂

🎯 使用场景建议

  1. 数据量大且内存充足:归并排序
  2. 数据量大且要求原地排序:快速排序
  3. 数据量小:冒泡排序
  4. 有大量重复元素:三路快排

⚡ 性能比较

算法        时间复杂度(平均)    空间复杂度    是否稳定
快速排序     O(nlogn)         O(1)         否
归并排序     O(nlogn)         O(n)         是
冒泡排序     O(n²)            O(1)         是
三路快排     O(nlogn)         O(1)         否

这些使用双指针的实现方式都能在某种程度上优化排序过程,具体选择哪种方法要根据实际场景考虑:

  • 内存限制
  • 数据特征
  • 稳定性要求
  • 性能要求