快速排序算法是排序算法集合中相对比较优秀的一个,运行速度快、效率高,而且它是“原地排序”节省内存,也是分治法思想的体现。
算法思想:快速排序是在给定的集合中选取一个元素作为元数据,然后对集合进行分区操作,使小于该元数据的集合元素位于分区的左侧,大于该元数据的集合元素位于分区的右侧,经过一次循环后该元数据就放在了集合中的正确位置,然后在分区左侧和右侧再分别选取一个元数据进行排序,如此递归进行下来,集合就可以按正确顺序排序了。快速排序的核心是分区的划分,即如何调整元数据的位置以及返回基准的最终位置以便分治递归。
实例分析: 6 10 13 5 8 3 2 11 // 6 选取为元数据
i j
6 10 13 5 8 3 2 11 // j向后循环,直到它指向的元素比选定的元数据小,与 i+1 位置处的元素进行交换,然后 j 继续加一向后走。
i j
6 5 13 10 8 3 2 11 // 交换完成后的集合数据
i j
6 5 3 10 8 13 2 11 // 同样 j 走到3的时候会和 i+1 处的值(13)进行一次交换
i j
6 5 3 2 8 13 10 11 // 同样 j 走到2的时候会和 i+1 处的值(10)进行一次交换
i j
2 5 3 6 8 13 10 11 // 最后元数据和当前 i 处的元素进行一次交换 ,现在集合已经以6为基准进行了划分,左侧<=6,右侧 >=6
i j
然后接下来的任务就是对左右分区再分别递归执行类似以上的操作。
划分伪代码:
Partition(A,p,q)
key <- A[p]
i <- p
for j <- p+1 to q
do if A[p]<key
then i <- i+1
A[i] <-> A[p]
A[p] <-> A[i]
return i
快排伪代码:
QuickSort(A,p,q)
if p < q
do r = Partition(A,p,q)
Partition(A,p,r-1)
Partition(A,r+1,q)
性能分析:
1> 最差情况分析:最差情况就是每次选取的主元都是当前无需集合中最大或最小的元素,即输入已经排好序(正序或逆序)的情况下,划分完之后主元的一侧会没有元素。
此时: T(n) = T(0) + T(n-1) + θ(n)
= θ(1) + T(n-1) + θ(n)
= T(n-1) + θ(n)
= θ(n^2)
2> 最优情况分析:即每次选取的主元都差不多在集合的“中央”,此时:
T(n) = 2T(n/2) + θ(n)
= θ(nlgn)
根据递归树表明:当分化发生在1/10:9/10的位置,总体性能还是很不错的;还有一种情况就是如果我们划分的时候有时是性能较好的情况,有时划分是性能很差的情况,
这种交替出现的划分现象总的性能还是趋于较好的性能。
因此:为了达到较好的性能,我们就不能让它有序,怎么办呢?那就是随机选择主元,由此产生了随机快速排序,它的性能不受输入集合的排序情况而影响,比较稳定,这种情
况下性能的好坏只受随机数产生器影响。
自己实现的快速排序代码:(没有使用随机,默认是用集合中第一个元素作为主元,仅供测试)
/* 分治法求快速排序 */
#include <iostream>
#include <iomanip>
using namespace std ;
int QuickSort(int *a , int low , int high)
{
int key = *(a+low) ;
int i = low ;
int transfer ;
for(int j = low+1 ; j<=high ; j++)
{
if(*(a+j)<key)
{
i++ ;
transfer = *(a+i) ;
*(a+i) = *(a+j);
*(a+j) = transfer ;
}
}
transfer = *(a+low) ;
*(a+low) = *(a+i) ;
*(a+i) = transfer ;
return i ;
}
void QuickStart(int *a , int p ,int q )
{
int r ;
if(p<q)
{
r = QuickSort(a,p,q);
QuickStart(a,p,r-1);
QuickStart(a,r+1,q);
}
}
int main()
{
int a[8] = {6,10,13,5,8,3,2,11};
QuickStart(a,0,7);
for(int i=0 ; i<= 7 ;i++)
{
cout << setw(5)<< a[i];
}
getchar();
return 0 ;
}
网上比较不错的采用随机选取主元的算法:
#include <iostream>
using namespace std;
class QuickSort
{
private:
int *arr;//待排序数组
int length;//数组长度
public:
//构造函数
QuickSort(int length)
{
arr=new int[length+1];
this->length=length;
}
//交换函数
void swap(int i,int j)
{
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
//输入数据
void input()
{
int i=1;
while(i<=length)
{
cin>>arr[i];
++i;
}
}
//输出函数
void display()
{
int i=1;
while(i<=length)
{
cout<<arr[i]<<" ";
++i;
}
cout<<endl;
}
//随机化划分函数
int randomizedPartition(int start,int end)
{
int i=start+rand()%(end-start+1);//产生随机数start~end
swap(i,start);//交换元素
return partition(start,end);//返回划分位置
}
//划分函数
int partition(int start,int end)
{
int key=arr[start];//arr[start]作为划分关键字
int i=start;
int j=end;
//交换元素
while(true)
{
while(arr[i]<key)
{
++i;
}
while(arr[j]>key)
{
--j;
}
//寻找两个可以交换的元素
//如果j<=i,说明arr[j]在左半域,arr[i]在右半域,划分完成,在j,j+1之间划分
if(i<j)
{
swap(i,j);
++i;
--j;
}
else
{
return j;
}
}
}
//快速排序
void quickSort(int start,int end)
{
if(start<end)//起码有2个元素
{
int middle;
middle=randomizedPartition(start,end);
quickSort(start,middle);
quickSort(middle+1,end);
}
}
//线性时间选择第k小元素
int randomizedSelect(int start,int end,int k)
{
if(start==end)//如果有两个元素的段被随机化划分之后,则应该有此等式成立,即找到了第K小元素
{
return arr[start];
}
else
{
int middle=randomizedPartition(start,end);
int left=middle-start+1;
if(k<=left)
{
return randomizedSelect(start,middle,k);//在划分后的左侧寻找第k小元素
}
else
{
return randomizedSelect(middle+1,end,k-left);//在划分后的右侧寻找第k-左段长度小的元素
}
}
}
};
void main()
{
QuickSort test(10);
test.input();
test.display();
cout<<test.randomizedSelect(1,10,3)<<endl;//寻找第3小元素
test.quickSort(1,10);//随机化快速排序
test.display();//打印结果
}
坚持学习!!