快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序n个元素要O(nlogn)次比较。在最坏状况下则需要O(n^2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他O(nlogn)算法更快,因为它的内部循环可以在大部分的架构上很有效率地被实现出来。
每经过一趟快排,轴点元素都必然就位,也就是说,一趟下来至少有1个元素在其最终位置
快速排序使用分治策略(Divide and Conquer)来把一个序列分为两个子序列。步骤为:
- 从序列中挑出一个元素,作为"基准"(pivot).
- 把所有比基准值小的元素放在基准前面,所有比基准值大的元素放在基准的后面(相同的数可以到任一边),这个称为分区(partition)操作。
- 对每个分区递归地进行步骤1~2,递归的结束条件是序列的大小是0或1,这时整体已经被排好序了。
void quickSort(int a[],int left,int right)
{
if(left>right) return;
int i,j,t,temp;
i=left;j=right;
temp=a[left]; //temp为基准数,暂定将最左边的第一个元素设置为基准
while(i!=j)
{
while(a[j]>=temp&&i<j) //顺序很重要!!!必须先从右边开始找
j--;
while(a[i]<=temp&&i<j) //再从左边开始找
i++;
if(i<j) //交换两个数
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
a[left]=a[i]; //最终将基准数归位,将基准数调到该数组的大致中间位置
a[i]=temp;
quickSort(a,left,i-1); //继续递归处理左边的
quickSort(a,i+1,right);//继续递归处理右边的
}
//快排思想,分区处理
int Partition(int a[],int left,int right)
{
if(left>right) return 0;
int i,j,t,temp;
i=left;j=right;
temp=a[left]; //temp为基准数,暂定将最左边的第一个元素设置为基准
while(i!=j)
{
while(a[j]>=temp&&i<j) //顺序很重要!!!必须先从右边开始找
j--;
while(a[i]<=temp&&i<j) //再从左边开始找
i++;
if(i<j) //交换两个数
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
a[left]=a[i]; //最终将基准数归位,将基准数调到该数组的大致中间位置
a[i]=temp;
return i; //返回第i的大的索引
}
//求top k数字
int FindKMax(int a[],int start,int end,int k)
{
int q;
int index=-1;
if(start < end)
{
q = Partition(a , start , end);
int len = q - start + 1; //表示第几个位置
if(len == k)
index=q; //返回第k个位置
else if(len < k)
index= FindKMax(a , q + 1 , end , k-len); //左边数字小于K个,则去右边寻找第k-len个
else
index=FindKMax(a , start , q - 1, k); //左边数字大于K个,则继续去左边寻找第k个
}
return index;
}
//快速排序
void QuickSort(int a[],int start,int end)
{
if(start==end)
return;
int index=Partition(a,start,end);
if(index>start)
QuickSort(a,start,index-1);
if(index<end)
QuickSort(a,index+1,end);
}
int main(int argc, char *argv[])
{
//返回top k数字
int a[]={20,100,4,2,87,9,8,5,46,26};
int Len=sizeof(a)/sizeof(int);
int K=4;
FindKMax(a , 0 , Len- 1 , K) ;
for(int i = 0 ; i < K ; i++)
cout<<a[i]<<" ";
return 0;
}
http://blog.youkuaiyun.com/vayne_xiao/article/details/53508973
快排的优化
选择基准的方式:
对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。也就是说,基准的选择 是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。
最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列
固定选取基准
基本的快速排序选取第一个或最后一个元素作为基准(固定选取如前面代码所示)。但是,这是一直很不好的处理方法。
测试数据分析:如果输入序列是随机的,处理时间可以接受的。如果数组已经有序时,此时的分割就是一个非常不好的分割。因为每次划分只能使待排序序列减一(不是理想中的等长子序列),此时为最坏情况,快速排序沦为起泡排序,时间复杂度为Θ(n^2)。而且,输入的数据是有序或部分有序的情况是相当常见的。因此,使用第一个元素作为枢纽元是非常糟糕的,为了避免这个情况,就引入了下面获取基准的方法。
随机选取基准
引入的原因:在待排序列是部分有序时,固定选取枢轴使快排效率底下,要缓解这种情况,就引入了随机选取枢轴
思想:取待排序列中任意一个元素作为基准
随机化算法
/*随机选择枢轴的位置,区间在low和high之间*/
int SelectPivotRandom(int arr[],int low,int high)
{
//产生枢轴的位置
srand((unsigned)time(NULL));
int pivotPos = rand()%(high - low) + low;
//把枢轴位置的元素和low位置元素互换,此时可以和普通的快排一样调用划分函数
swap(arr[pivotPos],arr[low]);
return arr[low];
}
测试数据分析::这是一种相对安全的策略。由于枢轴的位置是随机的,那么产生的分割也不会总是会出现劣质的分割。在整个数组数字全相等时,仍然是最坏情况,时间复杂度是O(n^2)。实际上,随机化快速排序得到理论最坏情况的可能性仅为1/(2^n)。所以随机化快速排序可以对于绝大多数输入数据达到O(nlogn)的期望时间复杂度。一位前辈做出了一个精辟的总结:“随机化快速排序可以满足一个人一辈子的人品需求。”
转载链接:最全的优化方案:https://blog.youkuaiyun.com/hacker00011000/article/details/52176100