快速排序的流程比较经典:
选定枢轴,先右后左,找到不等关系后交换,直到左右相等。
int partition_up(List& list, int low, int high)
{
list.elems[0] = list.elems[low];//记录
while (low < high) //三个判断条件都是low<high,所以一定终止在low==high
{
//找不等关系,先右后左
while (low < high && list.elems[high].key >= list.elems[0].key)
{
high--;
}
list.elems[low] = list.elems[high];
while (low < high && list.elems[low].key <= list.elems[0].key)
{
low++;
}
list.elems[high] = list.elems[low];
}
list.elems[low] = list.elems[0];//将枢轴插入
return low;//返回枢轴位置
}
void primitive_quick_sort_up(List& list, int low, int high)
{
if (low >= high)//终止条件,low和high相等
{
return;
}
int pivot_pos = partition_up(list, low, high);
primitive_quick_sort_up(list, low, pivot_pos - 1);
primitive_quick_sort_up(list, pivot_pos + 1, high);
}
排序思路很简单,但是需要注意两个边界的问题:
1 最后一定是low==high吗?
是的,因为主循环条件是low<high,内部两个和游标移动的前提条件也是low<high,因为游标是逐个移动的,所以最后一定是因为low==high而导致三个循环相继退出。
2 随机选择的枢轴和最后的 low==high位置交换,为什么low==high位置的元素一定会换在枢轴合理的一侧呢?
首先我们要分析一下最后low==high是个什么东西。最终条件有两种情况,而且这两种是分先后的。
首先是由右到左的时候相等,这个时候相当于找比pivot更小的没找到,于是找到了low元素这里,low是个什么东西?low已经是上次排剩下的,已经确定比pivot小的元素了,好,现在的low==high元素是小于pivot的元素。
如果high提前卡住,也就是找到了比pivot小的元素,就可能触发第二种情况,在low找比pivot大的没有找到,最后导致low==high。在寻找low位置的时候,我们实际上是有比较high位置是否比pivot小的,因为在比较之前就已经被low==high卡住退出了(这点需要理解一下),那么会不会有这个位置实际上就比pivot大但是最后我们把大的换到左边了呢?
不会,因为在进行从右到左寻找的过程中已经确定high位置是小于pivot位置的了。也就是说,这种情况下,选定位置仍然是小于pivot的。
综上,只要你是先右后左,找不等关系,那最后low==high的时候,元素必定是小于pivot的。
那么,只要pivot放在low==high的左边,交换后必然是合理位置。
如何保证pivot一定在low==high左边?
这就是很多程序的写法,先随机找到一个位置当做枢轴量,但是人家会先把第一个元素和pivot交换,这就确保pivot同时具有随机性(防止单边情况)和最左边的条件。
很多算法都偷懒,选择第一个,但是又不告诉你选其他位置会怎样。
以上,我们从理论上证明了市面上快排的正确,总结一下套路:
随机选择一个元素作为pivot
将第一个元素和pivot交换,然后记录pivot位置,如果没有这步可能会被最后一步交换打乱顺序。
进行经典的快速排序:
先右后左,大前提是low<high,寻找不等关系,完成一趟快排
完成分治写法
如果是从大到小排序,那就是pivot放到最后,然后先左后右了,其实就是彻底反过来。