求解顺序统计量的7种方法

三.基于随机主元选取的快速排序思想求解顺序统计量

        在排序算法的学习过程中,我们知道快速排序并不总是保持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;//空穴位置
	}
}

运行结果如下:        

谢谢大家指正! 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值