菜鸡笔记之——快速排序
快速排序
时间复杂度:O(n*logn)(无序情况下),O(n^2)(最坏的情况下)
稳定性:不稳定排序
快速排序有多种方法,包括:
递归快排:左右指针法、前后指针法、挖坑法;
非递归快排;
快排优化方法:三数取中法;
一、递归快排
1、左右指针法:
思路:
(1)取数据组中的一个数为基准数,一般这个数为最左端或最右端的一个数,并同时设置哨兵变量i,j,分别从数据组两头开始检测,j从右端开始,找比基准数小(大)的数,i从左端开始最左端开始,
找比基准数大的数,找到后将两数交换,反复进行,最终结果是:基准数在最左(右)端,i = j,从第二个数开始可以把数据分为两段,一段比基准数小(大),一段比基准数大(小);
(2)把基准数插入数据中间,作为分割上述连段数据的边界;
(3)分别对左边、右边的数据段做递归处理;
代码实现(左右指针法):
# include <stdio.h>
# include <malloc.h>
int main(void)
{
int N;
printf("the length of the array:"); //输入数组长度
scanf("%d",&N);
int * array = NULL;
array = (int *)malloc(sizeof(int)*N);
if (array==NULL)
{
exit(-1); //若申请内存失败,退出程序
}
for (int i = 0;i<N;i++) //输入数组
{
scanf("%d",array+i);
}
void swap(int *,int *);
void Qsort(int *,int,int);
Qsort(array,0,N-1); //排序
for (int i = 0;i<N;i++) //输出已排序数组
{
printf("%3d",*(array+i));
}
return 0;
}
void swap(int * a,int * b) //交换函数
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void Qsort(int * array,int begin,int end) //排序函数(从小到大)
{
if (begin>=end) //递归出口
{
return;
}
int key = *(array+begin); //将左边第一个数作为基准数
int i = begin,j = end; //设置哨兵变量
while (i<j)
{
while ((*(array+j)>=key)&&(i<j)) //右边哨兵变量先走,找比基准数小的数,即>=key的数留着
{
j--;
}
while ((*(array+i)<=key)&&(i<j)) //左边哨兵变量,找比基准数大的数,即<=key的数留着
{
i++;
}
swap(array+i,array+j); //交换两侧哨兵变量找到的数
}
swap(array+begin,array+j); //把基准值放到分界处
Qsort(array,begin,j-1); //递归排序左侧数据
Qsort(array,j+1,end); //递归排序右侧数据
}
第一次排序示意
代码分析:
(1)在进入Qsort后为什么要先判断begin>=end?会出现这种情况吗?
如果没有这一步,函数将会无限递归下去,例如当要排序的数组只有两个数2,1时,代入试一下就会得到begin=end=0,然后无限递归导致栈溢出。
(2)哨兵变量i,j谁先走对结果有影响吗?
如果基准数选在最左端,则j(右边的哨兵变量)开始走出第一步;如果基准数选在最右端,则i(左边哨兵变量)走出第一步,这与基准数放入分界后还能否符合一边比它小,一边比他大有关;
这是为什么?因为基准数在最左端,而排序为从小到大,这就要求在swap(array+begin,swap+j)时要保证*(swap+j)是比key小的数,j哨兵变量正是找比key小的数,让它先走能保证在i = j之前一步它是比key小的,在交换后才能满足边界左边比key小,右边比key大;同理,当key取得是最右端的数时,就要左边哨兵变量先走第一步;从大到小排序时这个规律也成立;
(3)如果选左边第一个数为基准数,i为什么不从第二个数开始检测?
如果不检测基准数会导致当第一个数为极值时这种情况的运行结果出错,用1,3,4,2尝试后会发现错误;
以上排序存在一个缺陷,当原始数据已经排好序时,它执行的步骤多出许多,时间复杂度变为O(n^2),为了避免这种情况,采用三数取中法来优化以上程序,让基准值不为极值。
三数取中法:
取数组的头、尾、中间这三个数的中间值,如果中间值与key交换;
注:当三个数都为极值时怎么办?( 未解决问题 )可能这种情况出现的概率 较低吧
# include <stdio.h>
# include <malloc.h>
int main(void)
{
int N;
printf("the length of the array:"); //输入数组长度
scanf("%d",&N);
int * array = NULL;
array = (int *)malloc(sizeof(int)*N);
if (array==NULL)
{
exit(-1); //若申请内存失败,退出程序
}
for (int i = 0;i<N;i++) //输入数组
{
scanf("%d",array+i);
}
void swap(int *,int *);
int Getmid(int *,int,int);
void Qsort(int *,int,int);
Qsort(array,0,N-1); //排序
for (int i = 0;i<N;i++) //输出已排序数组
{
printf("%3d",*(array+i));
}
return 0;
}
void swap(int * a,int * b) //交换函数
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int Getmid(int * array,int left,int right) //取中函数,返回中间值的下标
{
int mid = left+(right-left)/2;
if (left>mid)
{
if (right>left)
return left;
if (right>mid)
return right;
else
return mid;
}
else
if (right>mid)
return mid;
if (right>left)
return right;
else
return left;
}
void Qsort(int * array,int begin,int end) //排序函数(从小到大)
{
if (begin>=end) //递归出口
{
return;
}
int mid = Getmid(array,begin,end);
swap(array+begin,array+mid);
int key = *(array+begin); //将左边第一个数作为基准数
int i = begin,j = end; //设置哨兵变量
while (i<j)
{
while ((*(array+j)>=key)&&(i<j)) //右边哨兵变量先走,找比基准数小的数,即>=key的数留着
{
j--;
}
while ((*(array+i)<=key)&&(i<j)) //左边哨兵变量,找比基准数大的数,即<=key的数留着
{
i++;
}
swap(array+i,array+j); //交换两侧哨兵变量找到的数
}
swap(array+begin,array+j); //把基准值放到分界处
Qsort(array,begin,j-1); //递归排序左侧数据
Qsort(array,j+1,end); //递归排序右侧数据
}
2、前后指针法
定义两个变量,一个在前(prev)一个在后(cur),cur一开始为数组第一个元素下表0,则prev = -1,并设置基准数key为最后一个数,a[cur]从前往后扫描,以从小到大排序为例,要做的就是找比key小的数,找到后prev++,交换a[cur]和a[prev],交换后a[prev]就比基准值小了,重复此操作到a[end-1],再将key放到分界处,再对分界的两边的数进行递归排序。
代码实现(前后指针法):
# include <stdio.h>
# include <malloc.h>
int main(void)
{
int N;
printf("the length of the array:"); //输入数组长度
scanf("%d",&N);
int * array = NULL;
array = (int *)malloc(sizeof(int)*N);
if (array==NULL)
{
exit(-1); //若申请内存失败,退出程序
}
for (int i = 0;i<N;i++) //输入数组
{
scanf("%d",array+i);
}
void swap(int *,int *);
int Getmid(int *,int,int);
void Qsort(int *,int,int);
Qsort(array,0,N-1); //排序
for (int i = 0;i<N;i++) //输出已排序数组
{
printf("%3d",*(array+i));
}
return 0;
}
void swap(int * a,int * b) //交换函数
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int Getmid(int * array,int left,int right) //取中函数,返回中间值的下标
{
int mid = left+(right-left)/2;
if (left>mid)
{
if (right>left)
return left;
if (right>mid)
return right;
else
return mid;
}
else
if (right>mid)
return mid;
if (right>left)
return right;
else
return left;
}
void Qsort(int * array,int begin,int end)
{
if (begin>=end)
{
return;
}
int mid = Getmid(array,begin,end);
swap(array+end,array+mid);
int key = *(array+end); //选最右端的一个数为基准数
int prev = begin-1,cur = begin;
while (cur<end) //从*(array+begin)到*(array+end-1)逐个扫描
{
if ((*(array+cur)<key)&&(++prev!=cur)) //挑出那些比key小的家伙,跟前面的比key大的数交换
{
swap(array+cur,array+prev);
}
cur++;
} // cur自增继续扫描
prev++; //交换*(array+end)和*(array+prev)之前要把prev往后挪一位
swap(array+end,array+prev);
Qsort(array,begin,prev-1);
Qsort(array,prev+1,end);
}
第一次排序示意:
代码分析:
(1)若数组的前几个数都比key小会不会有问题出现?
如果前几个数都比key小的话,cur和prev会一起移动,直到cur遇到第一个比key大的数后,prev与cur拉开差距,以第一个数比key小为例:
(2)prev一开始为-1,会不会出现*(array-1)?
并不会出现,如果从begin到end-1中有比key小的数的话,就能保证有p++,如果没有,也不会用到prev,而是用下标为prev++的数与基准数交换。
3、填坑法
思路:先选一个基准数,并把它保存在一个变量temp中(这样就不用担心被覆盖后就找不到)以最右一个数为基准数为例从小到大排序,保存基准数后第一个数看成一个坑,然后跟左右指针法类似,哨兵变量j,i轮流检测(j先走),j遇到比基准数小的数后,把这个数挖掉并把它填到坑中,这样右边第一个比基准数小的数的那个位置看成一个新的坑,这时候轮到i检测,它遇到比基准数大的数后挖掉并填到坑里,这样左边第一个比基准数大的输的位置又看成一个新的坑,如此反复操作,直到i = j,之后把temp填到坑里,这就完成了第一轮排序,之后分别对temp左右两边的数进行递归排序。
代码实现(填坑法):
# include <stdio.h>
# include <malloc.h>
int main(void)
{
int N;
printf("the length of the array:"); //输入数组长度
scanf("%d",&N);
int * array = NULL;
array = (int *)malloc(sizeof(int)*N);
if (array==NULL)
{
exit(-1); //若申请内存失败,退出程序
}
for (int i = 0;i<N;i++) //输入数组
{
scanf("%d",array+i);
}
void swap(int *,int *);
int Getmid(int *,int,int);
void Qsort(int *,int,int);
Qsort(array,0,N-1); //排序
for (int i = 0;i<N;i++) //输出已排序数组
{
printf("%3d",*(array+i));
}
return 0;
}
void swap(int * a,int * b) //交换函数
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
int Getmid(int * array,int left,int right) //取中函数,返回中间值的下标
{
int mid = left+(right-left)/2;
if (left>mid)
{
if (right>left)
return left;
if (right>mid)
return right;
else
return mid;
}
else
if (right>mid)
return mid;
if (right>left)
return right;
else
return left;
}
void Qsort(int * array,int begin,int end)
{
if (begin>=end)
{
return;
}
int temp = *(array+begin);
int i = begin,j = end;
while (i<j)
{
while ((*(array+j)>=temp)&&(i<j))
{
j--;
}
*(array+i) = *(array+j);
while ((*(array+i)<=temp)&&(i<j))
{
i++;
}
*(array+j) = *(array+i);
}
*(array+j) = temp;
Qsort(array,begin,j-1);
Qsort(array,j+1,end);
}
二、非递归快排
(未学待补充)
/
C语言里有现成的快排函数qsort,包含在头文件<stdlib.h>中
参考文章
函数原型:
void qsort(*s, n, sizeof(s[0]), cmp);
*s :待排序的数据的首地址;
n :数据元素的个数;
sizeof(s[0]):每个元素所占字节数;
cmp:指向函数的指针,用于确定排序的顺序;
【cmp函数】:
函数原型:int (*cmp)(const void *,const void *)
需用户自定义,如:
int cmp(const void *a, const void *b)
{
return *(int*)a - *(int*)b; //由小到大排序
//return *(int *)b - *(int *)a; 由大到小排序
}
代码实现(直接调用库函数快排):
# include <stdio.h>
# include <malloc.h>
# include <stdlib.h>
int cmp(const void * a,const void * b)
{
return (*(int *)a-*(int *)b);
}
int main(void)
{
int N,i;
printf("the number of the list:");
scanf("%d",&N);
int * list = NULL;
list = (int *)malloc(sizeof(int)*N);
if (list==NULL)
{
printf("error!\n");
exit(-1);
}
for (i = 0;i<N;i++)
{
scanf("%d",list+i);
}
qsort(list,N,sizeof(int),cmp);
for (i = 0;i<N;i++)
{
printf("%3d",*(list+i));
}
return 0;
}