算法与数据结构----快速排序


前面说过,希尔排序是插入排序的升级版,堆排序是选择排序的升级版,快速排序则是最平凡的冒泡排序的升级版(都是使用交换的方式排序)
注:快速排序被公认为是最快的排序方法,但是如果有某些细节处理不到位,就会导致这个排序不是快速排序了,还会相当的慢比如主元的选取,左右集合的划分方法

算法描述

主要采用分而治之、递归算法
1.在一堆数据中任意选择一个数作为主元(pivot)
2.将剩下的数据以主元为中枢,分为大于主元和小于主元的两个集合,这就是分的过程
3.接下来对左右两个集合递归进行治,递归进行相同的操作

伪码实现:

void QuickSort(ElementType A[],int N)
{
	if(n<2)  当只有一个数时结束递归 
	pivot=从A[]中选一个主元
	将S = {A[]\pivot } 分成2个独立子集:
	A1= { a 属于 S| a<= pivot }和
	A2= { a 属于 S| a>= pivot };
	A[] =Quicksort(A1, N1)|| {pivot} || Quicksort(A2,N2);
}

虽然算法并不复杂,但是其中有很多细节需要注意

快速排序的最好情况:
每次正好中分,T(N)=O(NlogN) 只要是每次中分的分治法时间复杂度都是 nlogn

快速排序的最坏情况:
选主元直接取第0个元素
假设一开始数据集为:1 2 3 4 5 6 7 8 …n-1 n
1 2 3 4 5 6 7 8 …n-1 n
2 3 4 5 6 7 8…n-1 n
T(N) = O(N) + T(N-1) =O(N**2) 是最慢的排序方式了

选主元

每次选取数组第一个元素为主元
效率较低
取头、中、尾的中位数
如 8 、12 、3 的中位数就是8
实现代码:

ElementType Median3(ElementType A[],int Left,int Right)
{
	int Center = (Left+Right)/2;
	if(A[Left]>A[center])
	{
		Swap(&A[Left],&A[center]);
	}
	if(A[Left]>A[Right])
	{
		Swap(&A[Left],&A[Right]);
	}   //这两步后保证了最左边的元素一定是最小的
	if(A[center]>A[Right])
	{
		Swap(&A[center] , &A[Right]);
	} 
	//交换后 三者的大小关系为: A[Left]<=A[center]<=A[Right]
	Swap( &A[ center ],&A[ Right-1 ])  //将picot藏到右边,即倒数第二个,因为A[Right]肯定比pivot大
									//A[Left]肯定比pivot小,则相当于这两个位置的元素已经排好序了
									//下一次只需要将A[Left+1]到A[Right-2] 进行排序即可
	return A[Right-1];  //返回pivot 
} 

子集划分及快速排序具体实现

快速排序实现:

#include<bits/stdc++.h>
using namespace std;
typedef int ElementType;
void Swap(ElementType &a,ElementType &b)
{
	ElementType t=a;
	a = b;
	b = t; 
} 
ElementType Median3(ElementType A[],int Left,int Right)
{
	int center = (Left+Right)/2;
	if(A[Left]>A[center])
	{
		Swap(A[Left],A[center]);
	}
	if(A[Left]>A[Right])
	{
		Swap(A[Left],A[Right]);
	}   
	if(A[center]>A[Right])
	{
		Swap(A[center] , A[Right]);
	} 
	Swap( A[ center ],A[ Right-1 ]);  
	return A[Right-1];  //返回pivot 
} 

int Partition(ElementType *a,int Left,int Right)
{
	int pivot=Median3(a,Left,Right);   //获取数组头、中、尾三个元素的中位数作为主元 
	int i=Left; 
	int j=Right-1;
	while(1)      
	{
		while(a[++i]<pivot);       
		while(a[--j]>pivot);           
		if(i<j) Swap(a[i],a[j]);
		else break;			
	}
	if(i<Right-1) Swap(a[i],a[Right-1]);
	return i;
}

void Quicksort(ElementType *a,int Left,int Right)
{
		if(Left<Right)
		{
			int mid=Partition(a,Left,Right);
			Quicksort(a,Left,mid-1); 
			Quicksort(a,mid+1,Right);
		}         
}
int main()
{
	int n;cin>>n;
	int data[n];
	for(int i=0;i<n;i++)
	{
		cin>>data[i];
	} 
	Quicksort(data,0,n-1);
	cout<<data[0];
	for(int i=1;i<n;i++)
	cout<<" "<<data[i];
	return 0;
} 

快速排序优化

(1)快速排序既可以使用以下的设置阈值的方法优化,也可以利用三值取中的方法优化,还有一种优化方法就是,快速排序主要使用递归实现,递归过程函数参数的传递都会耗费一定的栈空间,函数的参数越多,每次递归耗费的空间也越多,因此如果能减少递归,将会大大提高性能
因此可以对Quicksort实施尾递归优化,来看代码:

void Quicksort(ElementType *a,int Left,int Right)
{
	while(Left<Right)
	{
		int mid=Partition(a,Left,Right);
		Quicksort(a,Left,mid-1); 
		Left=mid+1;
	}         
}

将if改为while,并将第一次递归后的low变量利用起来,用迭代而不是递归的方法缩减栈深度,从而提高整体性能

(2)将交换元素改为移动元素,减少函数的调用,提高效率
先将基元暂存到temp,然后右指针往前找,再左指针往后找,顺序不能调换

int Partition(int left,int right)
{
	int temp=a[left];
	while(left<right)
	{
		while(left<right&&a[right]>temp) right--;
		a[left]=a[right];
		while(left<right&&a[left]<temp)  left++;
		a[right]=a[left];
	}
	a[left]=temp;
	return left;
}

快速排序的问题

1.用递归算法,会占用额外的系统堆栈空间,会不断的入栈、出栈,所以整个递归过程是很慢的
2.对于小规模的数据,(例如n不到100)可能还不如插入排序快
3.解决方案
当递归的数据规模充分小,则停止递归,直接调用简单排序(例如插入排序)
在程序中定义一个Cutoff的阈值,实践一下,比较不同的Cutoff对效率的影响阈值一般设置为16,也有人说7或者50更合理

加上Cutoff阈值改进后的程序(快速排序与插入排序相结合)

ElementType Median3( ElementType A[], int Left, int Right )
{ 
    int Center = (Left+Right) / 2;
    if ( A[Left] > A[Center] )
        Swap( &A[Left], &A[Center] );
    if ( A[Left] > A[Right] )
        Swap( &A[Left], &A[Right] );
    if ( A[Center] > A[Right] )
        Swap( &A[Center], &A[Right] );
    /* 此时A[Left] <= A[Center] <= A[Right] */
    Swap( &A[Center], &A[Right-1] ); /* 将基准Pivot藏到右边*/
    /* 只需要考虑A[Left+1] … A[Right-2] */
    return  A[Right-1];  
}

void Qsort( ElementType A[], int Left, int Right )
{ /* 核心递归函数 */ 
     int Pivot, Cutoff, Low, High;
      
     if ( Cutoff <= Right-Left ) { /* 如果序列元素充分多,进入快排 */
          Pivot = Median3( A, Left, Right ); /* 选基准 */ 
          Low = Left; High = Right-1;
          while (1) { /*将序列中比基准小的移到基准左边,大的移到右边*/
               while ( A[++Low] < Pivot ) ;
               while ( A[--High] > Pivot ) ;
               if ( Low < High ) Swap( &A[Low], &A[High] );
               else break;
          }
          Swap( &A[Low], &A[Right-1] );   /* 将基准换到正确的位置 */ 
          Qsort( A, Left, Low-1 );    /* 递归解决左边 */ 
          Qsort( A, Low+1, Right );   /* 递归解决右边 */  
     }
     else InsertionSort( A+Left, Right-Left+1 ); /* 元素太少,用简单排序 */ 
}

void QuickSort( ElementType A[], int N )
{ /* 统一接口 */
     Qsort( A, 0, N-1 );
}

快排错误写法:
快速排序的指针移动的时候,最好是使用do-while结构,先移动指针,再判断指针所指向的位置元素是否交换,如果像下面一样先判断再移动会导致死循环
例如:此时l和r会一直保持不动,进入死循环在这里插入图片描述

void Quicksort(int l,int r)
{
	if(l>=r) return;
	int mid=Prepare(l,r);
	int left=l+1;    //错误写法
	int right=r-2;
	while(left<right)
	{
		while(num[left]<mid) left++; //会陷入死循环
		while(num[right]>mid) right--;
		if(left<right) swap(num[left],num[right]);
	}
	if(left<r-1) swap(num[left],num[r-1]);
	Quicksort(l,left-1);
	Quicksort(left+1,r);
}

c语言快速排序库函数qsort

qsort详细用法

/* 快速排序 - 直接调用库函数 */

#include <stdlib.h>

/*---------------简单整数排序--------------------*/
int compare(const void *a, const void *b)
{ /* 比较两整数。非降序排列 */
    return (*(int*)a - *(int*)b);
}
/* 调用接口 */ 
qsort(A, N, sizeof(int), compare);
/*---------------简单整数排序--------------------*/


/*--------------- 一般情况下,对结构体Node中的某键值key排序 ---------------*/
struct Node {
    int key1, key2;
} A[MAXN];
 
int compare2keys(const void *a, const void *b)
{ /* 比较两种键值:按key1非升序排列;如果key1相等,则按key2非降序排列 */
    int k;
    if ( ((const struct Node*)a)->key1 < ((const struct Node*)b)->key1 )
        k = 1;
    else if ( ((const struct Node*)a)->key1 > ((const struct Node*)b)->key1 )
        k = -1;
    else { /* 如果key1相等 */
        if ( ((const struct Node*)a)->key2 < ((const struct Node*)b)->key2 )
            k = -1;
        else
            k = 1;
    }
    return k;
}
/* 调用接口 */ 
qsort(A, N, sizeof(struct Node), compare2keys);
/*--------------- 一般情况下,对结构体Node中的某键值key排序 ---------------*/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值