注:本文不属于入门教程,侧重于快速排序的考点解析
首先介绍三个公共的内容
typedef struct
{
int a[MAXSIZE+1];
int length;
}
SqList;
void swap(SqList *L,int i,int j)
{
int temp = L->a[i];
L->a[i] = L->a[j];
L->a[j] = temp;
}
void print(SqList *L)
{
int i = 0;
for(i = 0; i < L->length; i++)
{
printf("%d,",L->a[i]);
}
printf("%d\n",L->a[i]);
}
第一个SqList是一个结构体,里面包含一个数组和数组的长度。
第二个swap函数用于交换结构体的数组里面的元素
第三个print函数用于打印结构体数组。
接下来看快速排序第一版的代码:
我们在编程的时候,入口函数尽量要编写的简单明了,不宜太过复杂。
void QuickSort(SqList *L)
{
QSort(L,1,L->length);
}
这里,我们的这个QuickSort函数就是入口函数,可以看到其内容和简单,就是去调用QSort方法。
void QSort(SqList *L,int low,int high)
{
int pivot;
if(low < high)
{
pivot = Partition(L,low,high);
QSort(L,low,pivot-1);
QSort(L,pivot+1,high);
}
}
我们的这个QSort方法是一个递归调用的函数。
这里要注意,任何递归都要有一个终止的条件,这里终止的条件就是low
然后再看函数体,首先定义了一个pivot,这个值就是一个阈值,大于他的后面要把它放到右边,小于它的要把它放到左边。这个操作就在Partition函数里面。
Partition操作完了之后呢,就对数组的左半边和右半边继续进行partition操作。
int Partition(SqList *L,int low,int high)
{
int pivotkey=L->a[low];
while(low < high)
{
while(low < high && L->a[high] >= pivotkey)
high--;
swap(L,low,high);
while(low < high && L->a[low] <= pivotkey)
low++;
swap(L,low,high);
}
return low;
}
partition操作中第一步就是要找数组的右边比阈值小的数字,
所以这里使用了
while(low < high && L->a[high] >= pivotkey)
high--;
这样就可以找到右边第一个小于阈值的数字的位置,然后进行交换。
与之类似的
找到左边第一个比阈值大的数字的位置,然后进行交换
while(low < high && L->a[low] <= pivotkey)
low++;
最后呢,我们要返回阈值所在的位置,实际上就是low的位置。
考虑如下四个方面的改进
之前都是选取数组的第一个数作为阈值的,这里可以改成三数取中的方法。
即选取数组头,数组尾还有数组中间的数这三个数去进行比较,选取这三个数的中间那个数作为阈值。
当然也可以选取”九数取中“这样类似的方法进行。
第一版的代码中,你会发现阈值所在那个数会被频繁的交换,这个是不必要的。
这样我们不再采用交换元素的方式,而是采用直接替换的方式。
这个点优化就是说当数组较小的时候采用直接插入法。
由于递归操作比较消耗栈空间,这里就会对递归调用进行一些优化。
上面这四个点的优化将通过下面的代码具体解析:
int Partition1(SqList *L,int low,int high)
{
int pivotkey;
int m = low + (high - low) / 2; /* 计算数组中间的元素的下标 */
if (L->r[low]>L->r[high])
swap(L,low,high); /* 交换左端与右端数据,保证左端较小 */
if (L->r[m]>L->r[high])
swap(L,high,m); /* 交换中间与右端数据,保证中间较小 */
if (L->r[m]>L->r[low])
swap(L,m,low); /* 交换中间与左端数据,保证左端较大 */
pivotkey=L->r[low]; /* 用子表的第一个记录作枢轴记录 */
L->r[0]=pivotkey; /* 将枢轴关键字备份到L->r[0] */
while(lowr[high]>=pivotkey)
high--;
L->r[low]=L->r[high];
while(lowr[low]<=pivotkey)
low++;
L->r[high]=L->r[low];
}
L->r[low]=L->r[0];
return low; /* 返回枢轴所在位置 */
}
void QSort1(SqList *L,int low,int high)
{
int pivot;
if((high-low)>MAX_LENGTH_INSERT_SORT)
{
while(lowr[low..high]一分为二,算出枢轴值pivot */
QSort1(L,low,pivot-1); /* 对低子表递归排序 */
/* QSort(L,pivot+1,high); /* 对高子表递归排序 */
low=pivot+1; /* 尾递归 */
}
}
else
InsertSort(L);
}
/* 对顺序表L作快速排序 */
void QuickSort1(SqList *L)
{
QSort1(L,1,L->length);
}
首先针对优化1:
int m = low + (high - low) / 2; /* 计算数组中间的元素的下标 */
if (L->r[low]>L->r[high])
swap(L,low,high); /* 交换左端与右端数据,保证左端较小 */
if (L->r[m]>L->r[high])
swap(L,high,m); /* 交换中间与右端数据,保证中间较小 */
if (L->r[m]>L->r[low])
swap(L,m,low); /* 交换中间与左端数据,保证左端较大 */
经过这三步,low存储的就是三者之间的中间值。
进而针对优化2:
while(lowr[high]>=pivotkey)
high--;
L->r[low]=L->r[high];
while(lowr[low]<=pivotkey)
low++;
L->r[high]=L->r[low];
}
L->r[low]=L->r[0];
return low; /* 返回枢轴所在位置 */
}
可以看到不再采用swap函数,而是直接替换的方式。
并把阈值被分到哨兵位置上,最后在赋值给low这个位置上
针对优化3
void QuickSort1(SqList *L)
{
if(L->length > MAX_LENGTH_INSERT_SORT)
{
printf("quicksort\n");
QSort1(L,1,L->length);
}
else
{
printf("InsertSort\n");
InsertSort(L);
}
}
我们可以看到当数组较小的时候,使用直接插入法,而当数组较大时,采用快速排序法。
针对优化4:
while(lowr[low..high]一分为二,算出枢轴值pivot */
QSort1(L,low,pivot-1); /* 对低子表递归排序 */
/* QSort(L,pivot+1,high); /* 对高子表递归排序 */
low=pivot+1; /* 尾递归 */
}
把原本的
if(low
改成了
while(low
删除了QSort(L,pivot+1,high); 改写成了low = pivot + 1
这样子就减少了一次递归调用,而改成了直接函数调用。
函数命名不带1的是原始的快速排序。
函数命名带1的是改进后的快速排序。
完整代码建议记忆背诵。
#include
#define MAXSIZE 10000
#define MAX_LENGTH_INSERT_SORT 7 /* 用于快速排序时判断是否选用插入排序阙值 */
#define N1 9
#define N2 5
typedef struct
{
int a[MAXSIZE+1];
int length;
}
SqList;
void swap(SqList *L,int i,int j);
void print(SqList *L);
void QuickSort(SqList *L);
void QSort(SqList *L,int low,int high);
int Partition(SqList *L,int low,int high);
int Partition1(SqList *L,int low,int high);
void QSort1(SqList *L,int low,int high);
void QuickSort1(SqList *L);
void InsertSort(SqList *L);
void swap(SqList *L,int i,int j)
{
int temp = L->a[i];
L->a[i] = L->a[j];
L->a[j] = temp;
}
void print(SqList *L)
{
int i = 0;
for(i = 0; i < L->length; i++)
{
printf("%d,",L->a[i]);
}
printf("%d\n",L->a[i]);
}
void QuickSort(SqList *L)
{
QSort(L,1,L->length);
}
void QSort(SqList *L,int low,int high)
{
int pivot;
if(low < high)
{
pivot = Partition(L,low,high);
QSort(L,low,pivot-1);
QSort(L,pivot+1,high);
}
}
int Partition(SqList *L,int low,int high)
{
int pivotkey=L->a[low];
while(low < high)
{
while(low < high && L->a[high] >= pivotkey)
//while(low < high && L->a[high] >= L->a[low])
high--;
swap(L,low,high);
while(low < high && L->a[low] <= pivotkey)
//while(low < high && L->a[high] >= L->a[low])
low++;
swap(L,low,high);
}
return low;
}
int Partition1(SqList *L,int low,int high)
{
int pivotkey;
int m = low + (high - low) / 2; /* 计算数组中间的元素的下标 */
if (L->a[low]>L->a[high])
swap(L,low,high); /* 交换左端与右端数据,保证左端较小 */
if (L->a[m]>L->a[high])
swap(L,high,m); /* 交换中间与右端数据,保证中间较小 */
if (L->a[m]>L->a[low])
swap(L,m,low);
pivotkey=L->a[low]; /* 用子表的第一个记录作枢轴记录 */
L->a[0]=pivotkey; /* 将枢轴关键字备份到L->r[0] */
while(lowa[high]>=pivotkey)
high--;
L->a[low]=L->a[high];
while(lowa[low]<=pivotkey)
low++;
L->a[high]=L->a[low];
}
L->a[low]=L->a[0];
return low; /* 返回枢轴所在位置 */
}
void QSort1(SqList *L,int low,int high)
{
int pivot;
while(lowr[low..high]一分为二,算出枢轴值pivot */
QSort1(L,low,pivot-1); /* 对低子表递归排序 */
/* QSort(L,pivot+1,high); /* 对高子表递归排序 */
low=pivot+1; /* 尾递归 */
}
}
/* 对顺序表L作快速排序 */
void QuickSort1(SqList *L)
{
if(L->length > MAX_LENGTH_INSERT_SORT)
{
printf("quicksort\n");
QSort1(L,1,L->length);
}
else
{
printf("InsertSort\n");
InsertSort(L);
}
}
/* 对顺序表L作直接插入排序 */
void InsertSort(SqList *L)
{
int i,j;
for(i=2;i<=L->length;i++)
{
if (L->a[i]a[i-1]) /* 需将L->r[i]插入有序子表 */
{
L->a[0]=L->a[i]; /* 设置哨兵 */
for(j=i-1;L->a[j]>L->a[0];j--)
L->a[j+1]=L->a[j]; /* 记录后移 */
L->a[j+1]=L->a[0]; /* 插入到正确位置 */
}
}
}
int main()
{
int d[N1]={50,10,90,30,70,40,80,60,20};
SqList test;
int i;
for(i=0;i
执行结构如下
$ ./test
0,50,10,90,30,70,40,80,60,20
quicksort
60,10,20,30,40,50,60,70,80,90
0,50,10,90,30,70
InsertSort
70,10,30,50,70,90