程序基本算法习题解析 线性时间选择:给定线性集中有n个元素,要求找出这n个元素中第k小的元素。 给定的线性集是无序的,要求在线性时间内完成。

题目:

在给定线性集中有n个元素,要求找出这n个元素中第k(1<=k<=n)小的元素。给定的线性集是无序的。要求在线性时间内完成,线性时间的要求是指在最坏情况下也要保证在O(n)时间内完成选择。输入3行,第一行为数据元素个数,第二行为需要确定的第k小的元素,第三行输入数据元素,用空格隔开。输出一行,输出第k小的元素。

思路:

若是没有要求在O(N)时间内完成选择,那么最简单的做法是先将数组进行排序(冒泡排序法,插入排序法,快速排序法),然后将第k个位置的数输出即可。用冒泡排序法和插入排序法的时间复杂度是O(n^2),快速排序法的平均复杂度是O(n),但在最坏的情况下,其复杂度也是O(n^2),因此,这几种排序法均不满足题目要求。

借鉴快速排序法的思想(不了解快速排序法的可以看我上一篇博客,快速排序算法),如果能让分割点在数组中至少为2n/3大,且不大于n/3个元素(即排序后的位置应该在中间的1/3),这样每次至少可以排除n/3个元素。T(n) <= T(2n/3) + O(n),因此T(n) = O(n)。

线性时间选择的具体算法步骤如下:

步骤1:将n个输入元素以每组m(本题选择m=5)个划分,存放到tmp二维数组中,每组分别排序,找出中位数,将每组的中位数存放到Sub数组中;

步骤2:找出这些中位数的中位数((...的中位数)直到最后的中位数不到10个数,此时直接运用冒泡排序法对这些中位数进行排序,取出这些中位数的中位数),以这个数为基准数,调用partition方法,将数组中小于基准数的数调换到基准数左边,大于基准数的数调到基准数右边;

步骤3:调用了partition方法后的基准数正是出于数组的正确位置(其余元素只是与基准元素比较后被调换到基准元素的左边或右边,并没有排序),返回基准数的下标,与k比较,若基准数下标等于k,说明第k小的数即为基准数;若基准数下标大于k,说明第k小的数在基准数左边,此时对基准数左边的子区间进行步骤1、2,;若基准数下标小于k,说明第k小的数在基准数右边,此时对基准数右边的子区间进行步骤1、2...直至第k小的元素为此时的基准数。

用不太规范的流程图示意一下:

代码如下:

// Chapter13_3.cpp : Defines the entry point for the application.
// 线性时间选择
// 在给定线性集中有n个元素,要求找出这n个元素中第k(1<=k<=n)小的元素。
// 给定的线性集是无序的。要求在线性时间内完成,线性时间的要求是指在最坏情况下
// 也要保证在O(N)时间内完成选择
// 输入3行,第一行为数据元素个数,第二行为需要确定的第k小的元素,第三行输入
// 数据元素,用空格隔开。输出一行,输出第k小的元素。

#include "stdafx.h"
#include<iostream>
using namespace std;

//申明快速排序函数,arr:数组,l:数组左边界,r:数组右边界,返回第target小的元素值
int Quick_select(int arr[],int l,int r,int target); 
//交换函数
void swap(int *a,int *b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}
//冒泡排序函数(从大到小),arr:数组,len:数组长度
void Bubble_Sort(int arr[],int len)
{
	for(int i=0;i<len;i++)
	{
		for(int j=i+1;j<len;j++)
		{
			if(arr[i] > arr[j])
				swap(&arr[i],&arr[j]);
		}
	}
}
//求基准数,arr:数组,l:数组左边界,r:数组右边界,
//返回该数组的基准数
int ChoosePivot(int arr[],int l,int r)
{
	int size = r-l+1;  //数组个数
	int sub_num = size/5; //五个数为1组,一共sub_num组
	int i,j,k=0;
	//动态分配一个二维数组,每行存5个数
	int **tmp = new int *[sub_num];
	for(i=0;i<sub_num;i++)
		tmp[i] = new int[5];
	//存放二维数组中每行数据的中位数
	int *Sub = new int[sub_num]; 
	//二维数组存数据(将arr的值重新存放进tmp数组中)
	for(i=0;i<sub_num;i++)
	{
		for(j=0;j<5;j++)
			tmp[i][j] = arr[k++]; //将l到r之间的数据重新存储
		//求每组数据的中位数,将计算结果存入Sub数组中
		Sub[i] = Quick_select(tmp[i],0,4,2);
	}
	//求中位数的中位数
	return Quick_select(Sub,0,sub_num-1,sub_num/2); 
}
//将小于M的元素放在M左边,大于M的元素放在M右边,
//arr:数组,l:数组左边界,r:数组右边界,M:基准数,
//返回基准数在整个数组中的位置
int partition(int *arr,int l,int r,int M)
{
	int i = l;
	int j = r;
	//找出基准数M在当前数组中的位置
	for(int k=l;k<=r;k++)
	{
		if(arr[k] == M)
			break;
	}
	//将基准数调换到当前数组的第一个元素
	swap(arr[l],arr[k]);
	//将小于M的元素放在M左边,大于M的元素放在M右边
	while(i < j)
	{
		while(i<j && arr[j] > M)
			j--;
		if(i<j)
			arr[i++] = arr[j];
		while(i<j && arr[i] <= M)
			i++;
		if(i<j)
			arr[j--] = arr[i];
	}
	arr[i] = M;
	//返回基准元素在整个数组中的位置
	return i;
}
//快速排序(查找)函数
int Quick_select(int arr[],int l,int r,int target)
{
	//若要求的位置在数组以外,返回-1
	if(target>r)
		return -1;
	//如果区间不只一个数
	if(l<r)
	{
		//当区间的元素小于10个时,运用冒泡排序法对数组进行排序
		if(r-l+1<10)
		{
			int *tmp = new int[r-l+1];
			for(int i=l;i<=r;i++)
				tmp[i-l] = arr[i];
			Bubble_Sort(tmp,r-l+1);
			return tmp[target-l];
		}
		//当区间的元素大于10个时,先选取基准数(中位数的中位数)
		int M = ChoosePivot(arr,l,r);
		int j;
		//返回基准数在整个数组中的位置
		int q = partition(arr,l,r,M);
		//如果该位置是所求的位置,输出其对应的数值
		if(q == target)
			return arr[q];
		//该位置大于所求的位置,说明所求的数在基准数的左边,对左边子区间进行递归
		else if(q>target)
			return Quick_select(arr,l,q-1,target);
		//该位置小于所求的位置,说明所求的数在基准数的右边,对右边子区间进行递归
		else if(q<target)
			return Quick_select(arr,q+1,r,target);
	}
	else return arr[l];
}
int main()
{
	int n,k;
	cout << "输入数据元素个数:";
	cin >> n;
	cout << "输入k:";
	cin >> k;
	int *arr = new int[n]; //动态建立数组
	for(int i=0;i<n;i++)
		cin >> arr[i];
	cout << "第" << k << "大的数为:" << Quick_select(arr,0,n-1,k-1) << endl;
	cout << endl;
	delete [n]arr;
	system("pause");
	return 0;
}

运行结果如下:

  • 11
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值