目录
一、快速排序实现思想
是每次制定一个基准值,再一次排序后将除基准值之外的数据分为两部分,一边全部小于等于,另一边均大于等于。
然后再对左右各部分重新寻找基准值排序。
二、快速排序的执行步骤
- 选取基准值
- 左右分别遍历,找出位置有误的元素后交换
- 重复1.2步骤,直至遍历完
三、实现方法
- 基础实现:对递归进来数组,随机选一个基准值,我用的是第一个数据作为。再左右遍历,遇到不符合的数就退出交换,最后返回基准数的新坐标
- 双指针法:对递归进来数组,随机选基准值,左右指针分别向中间遍历,左右分别遇到不符要求的数后在循环末尾将两数交换。
- 三数取中法(也叫霍尔法):对递归进来数组,和上面的方法主要区别在于基准值得选取,在数组的头,尾和中间,三数中取中间值作为基准值。
- 挖坑法(去掉不必要交换法):对递归进来数组,将基准值用一个临时变量存储起来,相当于在原来的基准值地方挖了一个坑,后面不符的值直接填进去,不用再交换。最后将这个临时变量(基准值)填会最后一个坑里。这样省去了不必要的交换!
- 非递归实现:借助队列或者栈,对基准值左右排序,思想类似于树的层序遍历和前序遍历。将头结点和尾节点入队列,然后再对内部使用前面的算法进行调整,之后再将返回下标分成的左右数组分别入队列首尾节点,再将原来的首尾节点出队列,这样不断循环,上层首尾出队前,会将调整后的数组一分为二,首尾入队。直至首尾间隔为一个元素。
四、代码实现
基础实现:
int PointerWay(int* src, int start, int end)
{
int point = src[start];
while (start<end)
{
while (start< end && src[end] >= point)
{
end--;
}
SwapArgs(&src[start], &src[end]);
while ( start<end && src[start] <= point)
{
start++;
}
SwapArgs(&src[start], &src[end]);
}
return start;
}
图解:
双指针法:
int doublePointerWay(int *src, int start, int end)
{
int point = src[start];
int* p = src+start;//指向操作数组最左处
int* q = src + end;//指向操作数组最右处,即最后一个操作数据
while (p < q)
{
while (p<q && *q >= point)
{
q--;
}
while (p<q && *p <= point)
{
p++;
}
SwapArgs(p, q);//p<q&&p指向左边大于point的数,q指向右边小于point的数,两者交换
}
//q指针只能停在*q<=point的数据上,除非q走到了p的指向,这时q,p的指向均指向p的指向,而*p<=point,
//若从q的while循环跳出,则说明,p遇到了q,而q的while()执行在前,除非遇到*q<=point,否则q一直向前,直到遇到p,
//因此无论这两种情况的哪一种,p,q的最终指向都小于等于point,故最后指向的数据只能放在左边,刚好和src[start]交换
SwapArgs(&src[start], p);//将point换到p和q的交叉位置,左边
return p - src;
}
图解:
三数取中法:
int HoareWay(int* src, int start, int end)
{
int mid = start + (end - start) / 2;
if (src[mid] > src[end])
{
SwapArgs(&src[mid], &src[end]);
}
if (src[start] > src[end])
{
SwapArgs(&src[start], &src[end]);
}
if (src[mid] > src[start])
{
SwapArgs(&src[start], &src[mid]);
}//使得三个数中start下标为三个数中间大小的那个数
int point = src[start];
while (start<end)
{
while (start < end && src[end] >= point)
{
end--;
}
SwapArgs(&src[start], &src[end]);
while (start < end && src[start] <= point)
{
start++;
}
SwapArgs(&src[start], &src[end]);
}
return start;
}
图解:
去掉不必要交换法:
int DigWay(int* src, int start, int end)//挖坑法减少不必要交换,结合霍尔法
{
int mid = start + (end - start) / 2;
if (src[mid] > src[end])
{
SwapArgs(&src[mid], &src[end]);
}
if (src[start] > src[end])
{
SwapArgs(&src[start], &src[end]);
}
if (src[mid] > src[start])
{
SwapArgs(&src[start], &src[mid]);
}//使得三个数中start下标为三个数中间大小的那个数
int tmp = src[start];//保存基准值
while (start < end)
{
while (start < end &&src[end] >= tmp)
{
end--;
}
src[start] = src[end];
while (start < end && src[start] <= tmp)
{
start++;
}
src[end] = src[start];
}
src[start] = tmp;
return start;
}
画了第一层递归的草图理解一下,后续递归道理相同,来感受一下:
非递归实现:
//非递归实现
void QuickingNonR(int*src, int n)
{
Queue qu;//定义一个队列
int mid = n / 2;
QueueInit(&qu);//初始化队列
QueuePush(&qu, 0);//首下标入队
QueuePush(&qu, n-1);//尾下标入队
while (!QueueIsEmpty(&qu))
{
int start = QueueTop(&qu);//取队头元素(首元素下标),
QueuePop(&qu);//出队,
int end = QueuTop(&qu);//取队头元素,(尾元素下标)
QueuePop(&qu);//出队,
mid = DigWay(src, start, end);//调用函数,将mid的左右调整并mid接收下标值
if (start < mid-1)//将mid分开的数组左部分,首尾入队
{
QueuePush(&qu, start);
QueuePush(&qu, mid);
}
if (mid+1 < end)//将mid右半部分首尾下标入队
{
QueuePush(&qu, mid+1);
QueuePush(&qu, end);
}
}
QueueDestory(&qu);//队列销毁
}
非递归就是借助队列的特点,先将原数组的首尾下标入队,再从队头中拿出要操作数组的首尾下标,传入DigWay函数,其返回的基准值下标将一个数组分为左右两部分,然后再将左右两部分的数组首尾下标入队。循环直至整个数组有序。
五、优化
当数组基本趋于有序时使用插入排序较为有效,所以在快速排序的基础上,当待排序数组小于一定规模的时候使用插入排序效果更优。
在这里我定义为当数据规模小于8个的时候去调用直接排序调整。
void Quicking(int*src, int start, int end)
{
int mid;//作为基准点
if (start+8 < end)//满足条件,快速排序递归调用
{
mid = DigWay(src, start, end);
Quicking(src, start, mid-1);
Quicking(src, mid + 1, end);
}
else//数据规模小,使用插入排序
{
InsertSort(src + start, end - start + 1);
}
}
void QuickSort(int* src, int n)//快速排序
{
Quicking(src, 0, n - 1);
}
想看完整代码的可以点击我的gitHub链接:Sort完整代码