目录
序列文章
非递归实现的快速排序:http://t.csdnimg.cn/UEcL6
快速排序的挖坑法与双指针法:http://t.csdnimg.cn/I1L7Q
快速排序的hoare法:http://t.csdnimg.cn/SV0nA
前言
学习完hoare版本的快速排序方法后,今天我们来学习挖坑法和伪双指针法实现快速排序,它们的实现思想相比于hoare版本的快速排序更简单一些,需要注意的细节问题也没有hoare版本的快速排序多,相对而言还是比较好理解的,只要你足够细心~~~🥰
快速排序(挖坑法)
注意事项:key一经确定则不能再更改,直到一轮排序结束后才会将该元素放入队列中
实现步骤
1、将数组首元素放在临时变量key中,key = 6,形成一个坑位
2、令right向左走查找比6小的元素,left在right未找到之前一直停留在坑位
3、当right找到比key小的元素5,将5存放在坑位中,并将当前位置变为坑位
4、此时left开始移动,向右找比6大的元素,right在left未找到之前一直停留在坑位
5、当left找到比6大的元素7,将7存放在坑位中,并将当前位置变为坑位
6、接着right开始移动,向左走找比6小的元素,left在right未找到之前一直停留在坑位
7、当right找到比6小的元素4,将4存放在坑位中,并将当前位置变为空位
8、然后left开始移动,向右走找比6大的元素,right在left未找到之前一直停留在坑位
9、当left找到比6大的元素9,将9存放在坑位中,并将当前位置变为坑位
10、接着right开始移动,向左找比6小的元素,left在right未找到之前一直停留在坑位
11、当right找到比6大的元素3,将3存放在坑位中,并将当前位置变为坑位
12、然后left开始移动,向右找比6大的元素,right在left未找到之前一直停留在坑位
13、 left继续向前移动就会与right相遇,此时将key放入坑位
代码实现
错误写法
//三数取中
int GetMidi(int* a, int begin, int end)
{
int midi = (begin + end) / 2;
// begin midi end 三个数选中位数
if (a[begin] < a[midi])
{
if (a[midi] < a[end])
return midi; //返回a[midi] < a[midi] < a[end]
else if (a[begin] > a[end])
return begin; //返回a[end] < a[begin] < a[midi]
else
return end; //返回a[begin] < a[end] < a[midi]
}
else // a[begin] > a[midi]
{
if (a[midi] > a[end])
return midi; //返回a[end] < a[mid] < a[begin]
else if (a[begin] < a[end])
return begin; //返回a[midi] < a[begin] < a[end]
else
return end; //返回a[midi] < a[end] < a[begin]
}
}
//方法二:挖坑法
void QuickHoleSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int midi = GetMidi(a, begin, end);
Swap(&a[midi], &a[begin]);
int key = a[begin];
int hole = begin;
while (begin < end)
{
//right向左移动找小于key的元素,找到了就将该数字放入坑(左边的)
while (begin < end && a[end] >= key)
{
--end;
}
//填坑
a[hole] = a[end];
hole = end;
//left向右移动找大于key的元素,找到了就将该数字放入坑(右边的)
while (begin < end && a[begin] <= key)
{
++begin;
}
//填坑
a[hole] = a[begin];
hole = begin;
}
a[hole] = key;
QuickSort(a, begin, hole - 1);
QuickSort(a, hole + 1, end);
}
原因分析:在hoare版本的快排中,begin和end的位置在每一轮比较和交换的过程中是没有改变的,改变的是left和right,而在这里当我们第一次递归开始时的begin的值是改变后的值
正确写法
//三数取中
int GetMidi(int* a, int begin, int end)
{
int midi = (begin + end) / 2;
// begin midi end 三个数选中位数
if (a[begin] < a[midi])
{
if (a[midi] < a[end])
return midi; //返回a[midi] < a[midi] < a[end]
else if (a[begin] > a[end])
return begin; //返回a[end] < a[begin] < a[midi]
else
return end; //返回a[begin] < a[end] < a[midi]
}
else // a[begin] > a[midi]
{
if (a[midi] > a[end])
return midi; //返回a[end] < a[mid] < a[begin]
else if (a[begin] < a[end])
return begin; //返回a[midi] < a[begin] < a[end]
else
return end; //返回a[midi] < a[end] < a[begin]
}
}
//方法二:挖坑法
int PartSort2(int* a, int begin, int end)
{
int midi = GetMidi(a, begin, end);
Swap(&a[midi], &a[begin]);
int key = a[begin];
int hole = begin;
while (begin < end)
{
//right向左移动找小于key的元素,找到了就将该数字放入坑(左边的)
while (begin < end && a[end] >= key)
{
--end;
}
//填坑
a[hole] = a[end];
hole = end;
//left向右移动找大于key的元素,找到了就将该数字放入坑(右边的)
while (begin < end && a[begin] <= key)
{
++begin;
}
//填坑
a[hole] = a[begin];
hole = begin;
}
a[hole] = key;
return hole;
}
//快速排序
void QuickSort(int* a, int begin, int end)
{
if (begin >= end)
return;
int keyi = PartSort2(a, begin, end);
QuickSort(a, begin, keyi - 1);
QuickSort(a, keyi+1, end);
}
时空复杂度
最坏时间复杂度:O(N^2)(当数组已经降序有序或升序有序时,此时基准元素一直位于首元素或尾元素,n个元素要进行n次快速排序才能将当前的顺序改变,n*n,三数取中的办法的确可以优化这种情况,但还是可能会出现O(N^2)的情况)
最好时间复杂度:O(N*logN)(每次划分都能将数组均匀地分成两个接近子数组,N个元素要进行logN次的排序,N*logN)
空间复杂度:O(logN)或O(N)(在递归过程中需要使用栈来保存函数调用信息,所以快速排序的空间复杂度取决于递归调用的层数。在最坏情况下,递归调用栈可能达到O(n)的空间复杂度,最好的空间复杂度为O(logn))
快速排序(伪双指针法)
基本思想
1、cur遇到比key大的值,++cur
2、cur遇到比key小的值,++prev,交换prev和cur位置的值,++cur
3、当cur走到头时一轮排序结束
注意事项:
1、++prev != cur时才可以交换prev和cur位置的值
2、if语句中的++prev也会导致每次prev的++
3、注意判断语句中的&&的短路效应,当a[cur] < a[keyi]为假时,&&后面的++prev != cur的判断不会执行,prev也就不会同时跟着向前移动
实现步骤
1、初始化时,选取数组首元素6作为key,将prev指向数组首元素,cur指向首元素的下一个元素
2、cur负责找到比key小的,2小于key,此时++prev,并交换a[prev]与a[cur]的值,但是如果就像图中所示的情况,++prev == cur 二者重合了,如果此时还要求二者交换位置,这是不合理的,所以我们除了基本思想外还需要额外的加上一条注意事项:当++prev != cur时才能交换二者位置
3、 无论是交换还是不交换,cur都需要向前走,此时prev也往前走,在cur未找到比key大的值之前,二者的间隔总是相差1,当cur走到7时,由于&&的短路,prev不会向前移动,只有cur移动,此时二者之间的距离会不断拉大,直到cur找到了比key小的数字,即a[cur] < a[keyi],且此时由于二者之间已经有了间隔++prev != cur 的判断结果为真,故交换a[cur]与a[prev]位置上的值
(可以将整个过程抽象的理解为推箱子游戏的过程)
4、剩余的步骤如下图所示:
代码实现
//方法三:伪双指针法
int PartSort3(int* a, int begin, int end)
{
int midi = GetMidi(a, begin, end);
Swap(&a[midi], &a[begin]);
int keyi = begin;
int prev = begin;
int cur = prev + 1;
while (cur <= end)
{
if (a[cur] < a[keyi] && ++prev != cur)
Swap(&a[prev], &a[cur]);;
++cur;
}
Swap(&a[prev], &a[keyi]);
keyi = prev;
return prev;
}
关于各种排序性能的测试代码以及全部排序的代码内容会放在最后一篇排序的内容中,这两天更新......
时空复杂度
与挖坑法的复杂度一致~
~over~