【算法基础】三指针排序算法 - JAVA
一、基础概念
1.1 什么是三指针排序
三指针排序是一种特殊的分区排序算法,通过使用三个指针同时操作数组,将元素按照特定规则进行分类和排序。这种算法在处理包含有限种类值的数组时表现出色,最经典的应用是荷兰国旗问题(Dutch National Flag Problem)。
1.2 三指针排序的基本思想
三指针排序的核心思想是:
- 使用三个指针将数组划分为若干个区域
- 通过元素交换,确保每个区域内的元素都满足特定条件
- 一次遍历完成所有元素的分类排序
1.3 时间复杂度与空间复杂度
- 时间复杂度:O(n),仅需一次遍历
- 空间复杂度:O(1),只使用常数级别的额外空间(几个指针变量)
二、三指针排序的分类
2.1 按值分类的三指针排序
这是最常见的三指针排序应用,将数组中的元素按照特定值分为三类:
- 小于基准值的元素
- 等于基准值的元素
- 大于基准值的元素
2.2 多值三指针排序
处理有三种不同元素的数组,如荷兰国旗问题中的红、白、蓝三色:
- 第一类元素(如0或红色)
- 第二类元素(如1或白色)
- 第三类元素(如2或蓝色)
三、三指针排序实现(荷兰国旗问题)
3.1 数据结构设计
三指针排序主要操作简单数组,不需要特殊的数据结构设计,只需要三个指针变量:
int low = 0; // 指向第一类元素(0)的右边界
int mid = 0; // 遍历指针,指向当前处理的元素
int high = n - 1; // 指向第三类元素(2)的左边界
3.2 排序算法实现
public class ThreePointerSort {/*** 三指针排序算法(荷兰国旗问题)* 将数组中的0、1、2三种元素排序*/public void sortColors(int[] nums) {int low = 0; // 0的右边界int mid = 0; // 当前处理元素int high = nums.length - 1; // 2的左边界while (mid <= high) {if (nums[mid] == 0) {// 当前元素为0,将其交换到左侧区域swap(nums, low, mid);low++;mid++;} else if (nums[mid] == 1) {// 当前元素为1,保持位置不变mid++;} else { // nums[mid] == 2// 当前元素为2,将其交换到右侧区域swap(nums, mid, high);high--;// 注意:这里mid不递增,因为交换后的元素需要重新检查}}}/*** 交换数组中两个元素的位置*/private void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
}
3.3 实现细节
三个区域划分:
[0, low-1]
:所有等于0的元素[low, mid-1]
:所有等于1的元素[mid, high]
:待处理的元素[high+1, n-1]
:所有等于2的元素
处理逻辑:
- 当前元素为0:将其交换到左侧区域,
low
和mid
都向右移动 - 当前元素为1:保持位置不变,只移动
mid
- 当前元素为2:将其交换到右侧区域,
high
向左移动,mid
不动(需要重新检查交换后的元素)
终止条件:
mid > high
时,所有元素都已处理完毕
四、执行示例
4.1 示例数组
以数组 [2, 0, 1, 2, 1, 0]
为例:
4.2 执行过程
- 初始状态:
low=0, mid=0, high=5
- 数组:
[2, 0, 1, 2, 1, 0]
- 数组:
- 第一步:
nums[mid]=2
- 交换
nums[mid]
和nums[high]
:[0, 0, 1, 2, 1, 2]
high=4
,mid=0
(不变)
- 交换
- 第二步:
nums[mid]=0
- 交换
nums[low]
和nums[mid]
:[0, 0, 1, 2, 1, 2]
(实际未变) low=1
,mid=1
- 交换
- 第三步:
nums[mid]=0
- 交换
nums[low]
和nums[mid]
:[0, 0, 1, 2, 1, 2]
(实际未变) low=2
,mid=2
- 交换
- 第四步:
nums[mid]=1
mid=3
- 第五步:
nums[mid]=2
- 交换
nums[mid]
和nums[high]
:[0, 0, 1, 1, 2, 2]
high=3
,mid=3
- 交换
- 第六步:
nums[mid]=1
mid=4
- 第七步:
mid=4 > high=3
,算法终止- 最终数组:
[0, 0, 1, 1, 2, 2]
- 最终数组:
五、三指针扩展应用
5.1 快速排序的三路划分
三路快排是三指针思想的另一个重要应用:
- 将数组划分为小于、等于、大于基准值的三个部分
- 特别适合处理有大量重复元素的数组
- 可显著提高快排效率
public void quickSort3Way(int[] arr, int low, int high) {if (low >= high) return;// 选择基准值int pivot = arr[low];int lt = low; // 小于区域右边界int gt = high; // 大于区域左边界int i = low + 1; // 当前处理元素while (i <= gt) {if (arr[i] < pivot) {swap(arr, lt++, i++);} else if (arr[i] > pivot) {swap(arr, i, gt--);} else {i++;}}// 递归排序小于和大于部分quickSort3Way(arr, low, lt - 1);quickSort3Way(arr, gt + 1, high);
}
5.2 处理特定数据集的分组
三指针排序可用于将数组按特定规则分为三组,如:
- 负数、零和正数
- 奇数、能被3整除的数和其他数
- 特定范围内的元素、低于和高于范围的元素
5.3 优化特定范围查找
当需要找出数组中属于特定值范围的所有元素时,可使用三指针方法快速划分:
public int[] findElementsInRange(int[] nums, int low, int high) {// 使用三指针划分数组int[] result = new int[nums.length];int count = partitionByRange(nums, low, high);// 复制中间区域元素并返回// ...
}private int partitionByRange(int[] nums, int lowVal, int highVal) {int left = 0;int curr = 0;int right = nums.length - 1;while (curr <= right) {if (nums[curr] < lowVal) {swap(nums, left++, curr++);} else if (nums[curr] > highVal) {swap(nums, curr, right--);} else {curr++;}}return right - left + 1; // 返回范围内元素数量
}
六、完整示例程序
6.1 处理三种颜色的完整实现
public class ThreeColorSort {public static void main(String[] args) {int[] colors = {2, 0, 1, 1, 0, 2, 1, 2, 0, 0, 1, 2};System.out.println("排序前: " + arrayToString(colors));sortColors(colors);System.out.println("排序后: " + arrayToString(colors));}public static void sortColors(int[] nums) {int low = 0; // 0的右边界int mid = 0; // 当前处理元素int high = nums.length - 1; // 2的左边界while (mid <= high) {if (nums[mid] == 0) {swap(nums, low++, mid++);} else if (nums[mid] == 1) {mid++;} else { // nums[mid] == 2swap(nums, mid, high--);}}}private static void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}private static String arrayToString(int[] arr) {StringBuilder sb = new StringBuilder("[");for (int i = 0; i < arr.length; i++) {sb.append(arr[i]);if (i < arr.length - 1) {sb.append(", ");}}sb.append("]");return sb.toString();}
}
6.2 基于值范围的三路划分
public class ThreeWayPartition {public static void main(String[] args) {int[] arr = {5, 2, 8, 12, 3, 6, 9, 4, 10, 7};int lowVal = 4;int highVal = 8;System.out.println("原数组: " + arrayToString(arr));System.out.println("划分范围: [" + lowVal + ", " + highVal + "]");partitionByRange(arr, lowVal, highVal);System.out.println("划分后: " + arrayToString(arr));}public static void partitionByRange(int[] nums, int lowVal, int highVal) {int left = 0;int curr = 0;int right = nums.length - 1;while (curr <= right) {if (nums[curr] < lowVal) {swap(nums, left++, curr++);} else if (nums[curr] > highVal) {swap(nums, curr, right--);} else {curr++;}}}private static void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}private static String arrayToString(int[] arr) {StringBuilder sb = new StringBuilder("[");for (int i = 0; i < arr.length; i++) {sb.append(arr[i]);if (i < arr.length - 1) {sb.append(", ");}}sb.append("]");return sb.toString();}
}
七、总结
三指针排序算法是一种高效、简洁的算法,特别适合处理有限种类值的排序问题。
核心要点:
- 一次遍历完成排序
- 原地操作,不需要额外空间
- 线性时间复杂度 O(n)
- 特别适合处理三种不同元素的排序问题
优点:
- 简单易实现
- 高效(一次遍历)
- 空间利用率高(原地操作)
- 稳定性可控
缺点:
- 仅适用于处理有限种类元素的排序
- 需要明确定义元素的优先级或类别
应用场景:
- 荷兰国旗问题(三色排序)
- 快速排序的三路划分优化
- 特定元素分组(如正数、零、负数)
- 数据预处理和清洗
- 基于特征值的数据分类
三指针排序是分治思想和指针技术的巧妙结合,体现了算法设计中"简单而高效"的理念。掌握这一技术不仅有助于解决特定问题,更能启发我们思考更广泛的算法设计方法。