让我们用一个具体的例子来详细解释 int[] nums
, int lo
, 和 int hi
在快速排序的 partition
函数中的变化,以及这个过程如何对数组进行排序。
示例数组
我们将使用下面的数组进行演示:
int[] nums = {3, 6, 8, 10, 1, 2, 1};
初始调用
首先,我们调用 sort
方法来对整个数组进行排序:
sort(nums, 0, nums.length - 1);
- 初始参数:
nums = {3, 6, 8, 10, 1, 2, 1}
lo = 0
hi = 6
(即数组的最后一个元素索引)
Partition 函数执行
-
选择基准点:
pivot = nums[hi] = nums[6] = 1
-
初始化:
i = lo - 1 = 0 - 1 = -1
-
遍历数组并分区:
- 遍历从
j = lo
到j = hi - 1
,逐个比较元素与基准点pivot
。
- 遍历从
遍历和比较过程:
Step | j | nums[j] | i | Array Status | Swap? |
---|---|---|---|---|---|
1 | 0 | 3 | -1 | {3, 6, 8, 10, 1, 2, 1} | No |
2 | 1 | 6 | -1 | {3, 6, 8, 10, 1, 2, 1} | No |
3 | 2 | 8 | -1 | {3, 6, 8, 10, 1, 2, 1} | No |
4 | 3 | 10 | -1 | {3, 6, 8, 10, 1, 2, 1} | No |
5 | 4 | 1 | -1 | {3, 6, 8, 10, 1, 2, 1} | No |
6 | 5 | 2 | -1 | {3, 6, 8, 10, 1, 2, 1} | No |
7 | 6 | 1 | 0 | {3, 6, 8, 10, 1, 2, 1} - Swap(6,6) | Yes - Swap |
-
步骤 1 到 5:
nums[j] > pivot
for all elements exceptnums[4]
(which equalspivot
). Therefore,i
remains unchanged at-1
, and no swaps occur.
-
步骤 6:
nums[j] = 2 > pivot
, so no swap occurs.i
remains-1
.
-
放置基准点:
- 最后,将基准点放置在正确位置:
swap(nums, i + 1, hi)
:swap(nums, 0, 6)
:- 这将把基准点
1
交换到索引0
处。
-
结果:
- Array status:
{1, 6, 8, 10, 3, 2, 1}
- 返回分界点索引
p = i + 1 = 0
- Array status:
结果分析
基于上述步骤,partition
函数已经将数组分成两部分:
- 左边部分包含小于或等于
pivot
的元素。 - 右边部分包含大于
pivot
的元素。
此时,数组已经部分排序。继续递归调用 sort
方法进行剩余排序。
递归调用
-
左侧子数组:
sort(nums, lo, p - 1); sort(nums, 0, -1); // 递归停止
- 因为
lo > hi
,所以递归停止。
- 因为
-
右侧子数组:
sort(nums, p + 1, hi); sort(nums, 1, 6);
第二次 Partition 调用(右侧子数组)
接下来,我们对右侧子数组 {6, 8, 10, 3, 2, 1}
继续进行 partition
操作:
-
选择基准点:
pivot = nums[6] = 1
-
初始化:
i = 1 - 1 = 0
-
遍历和比较过程:
Step | j | nums[j] | i | Array Status | Swap? |
---|---|---|---|---|---|
1 | 1 | 6 | 0 | {1, 6, 8, 10, 3, 2, 1} | No |
2 | 2 | 8 | 0 | {1, 6, 8, 10, 3, 2, 1} | No |
3 | 3 | 10 | 0 | {1, 6, 8, 10, 3, 2, 1} | No |
4 | 4 | 3 | 0 | {1, 6, 8, 10, 3, 2, 1} | No |
5 | 5 | 2 | 0 | {1, 6, 8, 10, 3, 2, 1} | No |
6 | 6 | 1 | 1 | {1, 1, 8, 10, 3, 2, 6} - Swap(6,1) | Yes - Swap |
-
放置基准点:
swap(nums, i + 1, hi)
:swap(nums, 1, 6)
:- 基准点
1
被交换到索引1
处。
-
结果:
- Array status:
{1, 1, 8, 10, 3, 2, 6}
- 返回分界点索引
p = i + 1 = 1
- Array status:
递归继续
继续对右侧子数组 {8, 10, 3, 2, 6}
进行快速排序。
完整递归过程分析
为了更好地理解整个过程,我们可以从开始到结束逐步分析递归调用和分区情况。
初始数组状态
nums = {3, 6, 8, 10, 1, 2, 1}
-
第一次
partition
:pivot = 1
- Array after
partition
:{1, 6, 8, 10, 3, 2, 1}
p = 0
-
递归调用:
- 左侧:
sort(nums, 0, -1)
- 停止 - 右侧:
sort(nums, 1, 6)
- 左侧:
-
第二次
partition
:- 子数组:
{6, 8, 10, 3, 2, 1}
pivot = 1
- Array after
partition
:{1, 1, 8, 10, 3, 2, 6}
p = 1
- 子数组:
-
递归调用:
- 左侧:
sort(nums, 1, 0)
- 停止 - 右侧:
sort(nums, 2, 6)
- 左侧:
-
第三次
partition
:- 子数组:
{8, 10, 3, 2, 6}
pivot = 6
- Array after
partition
:{1, 1, 2, 3, 6, 8, 10}
p = 4
- 子数组:
-
递归调用:
- 左侧:
sort(nums, 2, 3)
- 右侧:
sort(nums, 5, 6)
- 左侧:
-
第四次
partition
:- 子数组:
{2, 3}
pivot = 3
- Array after
partition
:{1, 1, 2, 3, 6, 8, 10}
p = 3
- 子数组:
-
递归调用:
- 左侧:
sort(nums, 2, 2)
- 停止 - 右侧:
sort(nums, 4, 3)
- 停止
- 左侧:
-
第五次
partition
:- 子数组:
{8, 10}
pivot = 10
- Array after
partition
:{1, 1, 2, 3, 6, 8, 10}
p = 6
- 子数组:
-
递归调用:
- 左侧:
sort(nums, 5, 5)
- 停止 - 右侧:
sort(nums, 7, 6)
- 停止
- 左侧:
最终排序结果
经过以上的步骤,最终排序完成的数组如下:
int[] nums = {1, 1, 2, 3, 6, 8, 10};
递归调用树
以下是快速排序过程中的递归调用树,用于更好地理解每一步的递归操作:
sort(nums, 0, 6)
├── sort(nums, 0, -1)
└── sort(nums, 1, 6)
├── sort(nums, 1, 0)
└── sort(nums, 2, 6)
├── sort(nums, 2, 3)
│ ├── sort(nums, 2, 2)
│ └── sort(nums, 4, 3)
└── sort(nums, 5, 6)
├── sort(nums, 5, 5)
└── sort(nums, 7, 6)
总结
- 分区: 通过
partition
函数对数组进行分区,确保基准点左侧元素小于基准点,右侧元素大于基准点。 - 递归: 递归调用快速排序对每个分区的子数组进行排序,直至数组完全有序。
这个示例演示了快速排序的完整过程,从选择基准点到分区,再到递归调用排序,最终达到数组排序的效果。