三.基于随机主元选取的快速排序思想求解顺序统计量
在排序算法的学习过程中,我们知道快速排序并不总是保持O(nlgn)的时间复杂度,当主元的选取使每次划分的数组都是n-1和0的规模,那么就会产生一个时间复杂度为O(n^2)的时间复杂度。可见,主元的选取对快速排序的影响是很严重的,《算法导论》给出了一个随机选取三个主元并取中间数的方法,该方法可以保证每次划分都不会导致斜树的产生。
沿用该思想,我们递归地将该数组进行划分,假定K为求解的顺序统计量,P为主元(Pivot)在数组中的下标,如果K等于P,则直接返回该位置的元素即可,因为快速排序思想确定的Pivot在最终排序完成后下标不会发生改变,因而可以与其进行比较。但当K小于P的时候,说明K位置的元素一定在P左边,则递归求解P左边数组的主元Pivot位置;如果K大于P,则说明K位置在主元位置之后,则以主元右边的数组查找第K-P个数。此处的K-P是表示,主元右边的数组中的数据全都在主元左边数据的前边,将求解K转化为在右侧数组中求解相对坐标K-P。
算法导论部分描述如下:
算法C语言代码如下:
其中包含两个版本,一个递归版本OrderCount,一个迭代版本OrderCount_I,递归很容易造成Stack Overflow,因此将递归代码迭代化,是一个比较复杂也比较有效、有趣的过程。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//查找数组中第k个元素
//递归版本
int OrderCount(int *array,int left,int right,int i);
//迭代版本
int OrderCount_I(int *array,int size,int i);
//随机选取主元(三数取中法)
int RandomParting(int *array, int left, int right);
//交换元素 辅助函数
void swap(int *num1,int *num2);
//插入排序 辅助函数
void InsertSort(int *array, int size);
//主函数
int main(int argc, char *argv[]) {
int array[20];
srand((unsigned int)time(NULL));
for(int i=0;i<20;i++){
array[i] = rand()%171;
}
//打印数组内容
for(int i=0;i<20;i++){
printf("%d ",array[i]);
}
int i=10;
printf("\n第 %d 大的元素为 %d ",i,OrderCount(array,0,19,i));
InsertSort(array,20);
putc('\n',stdout);
for(int i=0;i<20;i++){
printf("%d ",array[i]);
}
return 0;
}
//查找array中的第k个元素
int OrderCount(int *array,int left,int right,int i){
if(left>=right){
return array[left];//只有一个元素 直接返回 说明查找的就是该元素
}
int pivot = RandomParting(array,left,right);
int k = pivot - left +1;//计算pivot含有多少个元素
if(k == i){
return array[pivot];//查找的就是该元素
}
//尾递归
//在该划分的左边
else if(k > i){
//优化尾递归为迭代 减少递归栈的空间
return OrderCount(array,left,pivot-1,i);//一直向左
}
else{
return OrderCount(array,pivot+1,right,i-k);//寻找在右边数组中的第k个元素 则就是整个数组中的第 i-k + k = i个元素
}
}
//利用随机函数划分主元
int RandomParting(int *array, int left, int right) {
//生成left到right之间左开右闭区间的随机数
//如果和边界相等又该如何
//不存在right == left 的情况
int site = rand() % (right - left) + left + 1;//如果遇到right和left相等的情况 这样取值就会error
if (site > right || site < left) {
fprintf(stderr, "This random number is wrong\n");
return left;
}
//保证最右侧数据为pivot
if (array[left] > array[right]) {
swap(&array[left], &array[right]);
}
if (array[left] > array[site]) {
swap(&array[left], &array[site]);
}
if (array[site] > array[right]) {
swap(&array[site], &array[right]);
}
swap(&array[site], &array[right]);//交换right和site的位置
int pivot = array[right];
//划分pivot左右两边的数据
int i = left - 1, j = left;//令i为left-1开始 可以避免极端情况
for (; j < right; j++) {
//如果小于左元则和i交换 令i+1处永远都是大于主元的数据
if (array[j] < pivot) {
i++;
swap(&array[j], &array[i]);
}
}
swap(&array[i + 1], &array[right]);//不能直接拿pivot交换 而要改变right的数据
return i + 1;//i+1则是pivot所在的位置
}
void swap(int *num1,int *num2){
int temp = *num1;
*num1 = *num2;
*num2 = temp;
}
int LocatePivot(int *array,int size,int pivot){
for(int i=0;i<size;i++){
if(array[i] == pivot){
return i;
}
}
return -1;//查找失败
}
//查找array中的第k个元素
int OrderCount_I(int *array,int size,int i){
int left = 0;
int right = size-1;
while(left<=right){
if(left == right){
return array[left];
}
int pivot = RandomParting(array,left,right);
int k = pivot - left + 1;
if(k == i){
return array[pivot];
}
//向右
else if(k < i){
i -= k;
left = pivot+1;
}
else{
right = pivot - 1;//向左
}
}
return -1;//不存在该元素
}
//插入排序
void InsertSort(int *array, int size) {
int temp;
for (int i = 1; i < size; i++) {
temp = array[i];
int j = i - 1;
for (; j >= 0; j--) {
if (array[j] > temp) {
array[j + 1] = array[j];
} else {
break;
}
}
array[j + 1] = temp;//空穴位置
}
}
运行结果如下:
谢谢大家指正!