【算法基础】三指针排序算法 - JAVA

一、基础概念

1.1 什么是三指针排序

三指针排序 是一种特殊的分区排序算法,通过使用三个指针同时操作数组,将元素按照特定规则进行分类和排序。这种算法在处理包含有限种类值的数组时表现出色,最经典的应用是荷兰国旗问题(Dutch National Flag Problem)。

1.2 三指针排序的基本思想

三指针排序的核心思想是:

  • 使用三个指针将数组划分为若干个区域
  • 通过元素交换,确保每个区域内的元素都满足特定条件
  • 一次遍历完成所有元素的分类排序

1.3 时间复杂度与空间复杂度

  • 时间复杂度:O(n),仅需一次遍历
  • 空间复杂度:O(1),只使用常数级别的额外空间(几个指针变量)

二、三指针排序的分类

2.1 按值分类的三指针排序

这是最常见的三指针排序应用,将数组中的元素按照特定值分为三类:

  • 小于基准值的元素
  • 等于基准值的元素
  • 大于基准值的元素

2.2 多值三指针排序

处理有三种不同元素的数组,如荷兰国旗问题中的红、白、蓝三色:

  • 第一类元素(如0或红色)
  • 第二类元素(如1或白色)
  • 第三类元素(如2或蓝色)

三、三指针排序实现(荷兰国旗问题)

3.1 数据结构设计

三指针排序主要操作简单数组,不需要特殊的数据结构设计,只需要三个指针变量:

java 复制代码
int low = 0;        // 指向第一类元素(0)的右边界
int mid = 0;        // 遍历指针,指向当前处理的元素
int high = n - 1;   // 指向第三类元素(2)的左边界

3.2 排序算法实现

java 复制代码
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 :将其交换到左侧区域,lowmid都向右移动
  • 当前元素为1 :保持位置不变,只移动mid
  • 当前元素为2 :将其交换到右侧区域,high向左移动,mid不动(需要重新检查交换后的元素)

终止条件

  • mid > high时,所有元素都已处理完毕

四、执行示例

4.1 示例数组

以数组 [2, 0, 1, 2, 1, 0] 为例:

4.2 执行过程

  1. 初始状态low=0, mid=0, high=5
    • 数组:[2, 0, 1, 2, 1, 0]
  2. 第一步nums[mid]=2
    • 交换 nums[mid]nums[high][0, 0, 1, 2, 1, 2]
    • high=4, mid=0 (不变)
  3. 第二步nums[mid]=0
    • 交换 nums[low]nums[mid][0, 0, 1, 2, 1, 2] (实际未变)
    • low=1, mid=1
  4. 第三步nums[mid]=0
    • 交换 nums[low]nums[mid][0, 0, 1, 2, 1, 2] (实际未变)
    • low=2, mid=2
  5. 第四步nums[mid]=1
    • mid=3
  6. 第五步nums[mid]=2
    • 交换 nums[mid]nums[high][0, 0, 1, 1, 2, 2]
    • high=3, mid=3
  7. 第六步nums[mid]=1
    • mid=4
  8. 第七步mid=4 > high=3算法终止
    • 最终数组:[0, 0, 1, 1, 2, 2]

五、三指针扩展应用

5.1 快速排序的三路划分

三路快排是三指针思想的另一个重要应用:

  • 将数组划分为小于、等于、大于基准值的三个部分
  • 特别适合处理有大量重复元素的数组
  • 可显著提高快排效率
java 复制代码
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 优化特定范围查找

当需要找出数组中属于特定值范围的所有元素时,可使用三指针方法快速划分:

java 复制代码
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 处理三种颜色的完整实现

java 复制代码
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] == 2
                swap(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 基于值范围的三路划分

java 复制代码
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)
  • 特别适合处理三种不同元素的排序问题

优点

  • 简单易实现
  • 高效(一次遍历)
  • 空间利用率高(原地操作)
  • 稳定性可控

缺点

  • 仅适用于处理有限种类元素的排序
  • 需要明确定义元素的优先级或类别

应用场景

  • 荷兰国旗问题(三色排序)
  • 快速排序的三路划分优化
  • 特定元素分组(如正数、零、负数)
  • 数据预处理和清洗
  • 基于特征值的数据分类

三指针排序是分治思想和指针技术的巧妙结合,体现了算法设计中"简单而高效"的理念。掌握这一技术不仅有助于解决特定问题,更能启发我们思考更广泛的算法设计方法。

相关推荐
寂空_1 分钟前
【算法笔记】动态规划基础(二):背包dp
笔记·算法·动态规划
程序员曼布19 分钟前
ThreadLocal源码深度剖析:内存管理与哈希机制
java·开发语言·哈希算法
hac132225 分钟前
IDEA快速上手Maven项目:模板选择 + 多模块拆分
java·maven·intellij-idea
搏博32 分钟前
神经网络在专家系统中的应用:从符号逻辑到连接主义的融合创新
人工智能·深度学习·神经网络·算法·机器学习
钢铁男儿39 分钟前
C# 方法(局部函数和参数)
java·数据库·c#
Eric.Lee202140 分钟前
数据集-目标检测系列- 印度人脸 检测数据集 indian face >> DataBall
人工智能·算法·目标检测·计算机视觉·yolo检测·印度人脸检测
遇见你的雩风1 小时前
Java---Object和内部类
java·开发语言
逊嘘1 小时前
【纪念我的365天】我的创作纪念日
java
E___V___E1 小时前
线性DP(动态规划)
算法·动态规划
vibag1 小时前
启发式算法-禁忌搜索算法
java·算法·启发式算法·禁忌搜索