快速排序
快速排序是一种不稳定的排序,平均时间复杂度为O(N logN)的原地算法,最坏时间复杂度为O(N^2);
快速排序可以看成是构造一颗二叉搜索树,当大部分数据都在pivot的一边的时候,也就是都在一颗子树上时,就会导致最坏时间复杂度。
提升快速排序的性能的方法相当于使左右两棵子树尽量平衡。
随机选举pivot
如果只是选取特定数据的话,比如说auto pivot = nums[low]
,对于某些情况可能导致大部分数据都在pivot一边。
随机选举pivot的话,可以保证大部分情况左右两边数据数量差不多
代码实现如下
int randomIdx = left + rand() % (right - left + 1);
swap(nums[randomIdx], nums[left]);
int pivot = nums[left];
双路快排
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
srand((unsigned)time(NULL));
QuickSort(nums, 0, nums.size() - 1);
return nums;
}
void QuickSort(vector<int>& nums, int left, int right) {
if (left >= right) {return;}
auto pivotIdx = partition(nums, left, right);
QuickSort(nums, left, pivotIdx - 1);
QuickSort(nums, pivotIdx + 1, right);
}
int partition(vector<int>& nums, int left, int right) {
int randomIdx = left + rand() % (right - left + 1);
swap(nums[randomIdx], nums[left]);
int pivot = nums[left];
//[left, le) less than pivot
int le = left + 1;
// (ge, right] greater than pivot
int ge = right;
while (true) {
//为什么nums[le] < pivot, 是<,而不是<=
//如果是<=, 则上下任意一个whlie循环遇到一段连续相等的值的话,
//循环不会停下,导致一侧数量过多
while (le <= ge && nums[le] < pivot) {
le++;
}
while (le <= ge && nums[le] < pivot) {
ge--;
}
if (le >= ge) {
break;
}
swap(nums[le++], nums[ge--]);
}
swap(nums[ge], nums[left]);
return ge;
}
};
难点解析
- 把握循环不变量
[left, le) less than pivot
(ge, right] greater than pivot
由上述关系确定最终状态时le``ge
指针的位置
nums[le] < pivot
nums[le] < pivot
死循环中的两个循环的条件是<
,
如果是<=
,会导致连续相等的元素都在一边
从而降低效率
过程分析
初始状态如下
跳出两个指针le``ge
循环后,交换两个元素前
交换后应该分别递增两个指针
跳出while死循环后,根据循环不变量可知([left, le) less than pivot
,(ge, right] greater than pivot
)如下图关系,则需交换ge
和left
所指向的元素。
三路快排
void sortThree(vector<int>& nums, int left, int right) {
if (left >= right) { return; }
int randomIdx = left + rand() % (right - left + 1);
swap(nums[randomIdx], nums[left]);
int pivot = nums[left];
//[left...lt) less than pivot
int lt = left;
//[lt, i) equal to pivot
int i = left + 1;
//(gt, right] great than pivot
int gt = right;
while (i <= gt) {
if (nums[i] > pivot) {
swap(nums[i], nums[gt--]);
} else if (nums[i] == pivot) {
i++;
} else {
swap(nums[lt++], nums[i++]);
}
}
sortThree(nums, left, lt - 1);
sortThree(nums, gt + 1, right);
}
难点分析
循环不变量—见双路快排
过程分析
当代码刚进入while循环的表示图如下。
如果nums[i] > pivot
,则表明i所指的方块为绿色,交换gt和i所指的位置,i所指的方块就是原来gt所指的白色,所以i不用递增,gt所指方块是原来i所指的,后来判断为绿色的区域,所以递减gt;
如果nums[i] == pivot
则i所指的方块为红色,只需要递增i即可
如果nums[i] < pivot
则i所指的方块为黄色,交换i和lt所指的方块后,lt所指的方块为黄色,需要递增lt,i所指的方块为红色,需要递增i。
仔细分析代码可以知道,未进入循环前,有一个left==lt所指向的格子是红色的,也就是pivot所代表的格子。
参考
算法四