快速排序的基本实现方式及其改进方法

文章目录

    • 1.公共函数
    • 2.快速排序第一版
    • 3.快速排序的改进
      • 3.1阈值选取
      • 3.2优化不必要的交换
      • 3.3优化小数组的排序
      • 3.4.优化递归操作
    • 完整代码

快速排序的主要思想就是”分而治之“。
对于快速排序而言,”分而治之“的意思是将数组拆分为两个部分,其中一个部分的所有元素都小于另一个部分的所有元素,这样就达到了所谓的局部有序。进一步,我们对第一部分和第二部分再进行拆分,使得拆分后任然满足其中一部分的元素小于另一部分的所有元素。一直将拆分工作继续下去,直到不能进行为止。从而就行局部有序到达了整体有序。

注:本文不属于入门教程,侧重于快速排序的考点解析

1.公共函数

首先介绍三个公共的内容

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函数用于打印结构体数组。

2.快速排序第一版

接下来看快速排序第一版的代码:
我们在编程的时候,入口函数尽量要编写的简单明了,不宜太过复杂。

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 为什么是low 试想一下,low=high,拆分后的子数组就只有一个元素,就不用排序了,因此这里必须要是low 然后也可能问low能不能大于high,这个就更不行了low>high意味着局部排序的工作早已经完成了,就不要再进行递归了。
然后再看函数体,首先定义了一个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的位置。

3.快速排序的改进

考虑如下四个方面的改进

3.1阈值选取

之前都是选取数组的第一个数作为阈值的,这里可以改成三数取中的方法。
即选取数组头,数组尾还有数组中间的数这三个数去进行比较,选取这三个数的中间那个数作为阈值。
当然也可以选取”九数取中“这样类似的方法进行。

3.2优化不必要的交换

第一版的代码中,你会发现阈值所在那个数会被频繁的交换,这个是不必要的。
这样我们不再采用交换元素的方式,而是采用直接替换的方式。

3.3优化小数组的排序

这个点优化就是说当数组较小的时候采用直接插入法。

3.4.优化递归操作

由于递归操作比较消耗栈空间,这里就会对递归调用进行一些优化。

上面这四个点的优化将通过下面的代码具体解析:

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

你可能感兴趣的:(后台开发面试题)