【算法导论 第9章 中位数和顺序统计学】

 第9章 中位数和顺序统计学


1. 第i个顺序统计量是该集合中第i小的元素。 最小值是第1个顺序统计量(i=1)最大值是第n个顺序统计量(i=n)

2. 中位数是它所在集合的“中点元素”,n为奇数时为(n+1)/2,n为偶数时有两个。

3. 找最大最小值的算法,一般人可能以为需要2(n-1)次比较,实际上只需要最多3⌊n/2⌋次比较,使用的技巧是: 将一对元素比较,然后把较大者于max比较,较小者与min比较,因此每两个元素需要比较3次。

如何设定当前最小值和最大值取决于n是偶数还是奇数。如果n为奇数,就将最小值和最大值都设置为第一个元素,然后成对的处理余下元素。如果n是偶数,就对前两个元素做一次比较,已决定最大值和最小值的初值。然后如同n是奇数一样,成对处理余下元素。 

比较次数, n为奇数:3⌊n/2⌋       n为偶数:3(n - 2)/ 2 + 1 = 3n/2 - 2

#include <stdio.h>
#include<stdlib.h> 
#include<time.h> 

int count = 0;
int compare(int a, int b)
{
	count++;
	return a-b;
}


#define MAX(a, b) compare(a,b) > 0 ? (a) : (b)
#define MIN(a, b) compare(a,b) > 0 ? (b) : (a)

int main()
{
	int i, min, max;
	int N;
	srand((int)time(0));
	while (1)
	{
		N = rand() %10;
		if (N > 2)
		 	break;
	}
	
	printf("N:%d\n", N);
	
	int *A = (int*)malloc(N);
	for (i = 0; i < N; i++)
	{
		A[i] = rand() % 15;
		printf("%d ",A[i]); 
	}
	
	printf("\n");
	
	if (N&1)
	{
		min = max = A[0];
		i = 1;
	}
	else
	{
		i = 2;
		if (compare(A[0], A[1]) < 0)
		{
			min = A[0];
			max = A[1];
		}
		else
		{
			min = A[1];
			max = A[0];
		}
	}
	
	for (; i+1 < N; i += 2)
	{
		if (compare(A[i], A[i+1]) < 0)
		{
			min = MIN(A[i], min);
			max = MAX(A[i+1], max);
		}
		else
		{
			min = MIN(A[i+1], min);
			max = MAX(A[i], max);
		}
	}
	
	printf("count:%d, max:%d, min:%d\n", count, max, min);
}

4.以期望线性时间选择顺序统计量的方法是以快速排序为模型。如同在快速排序中一样,此算法的思想也是对输入数组进行递归划分。但和快速排序不同的是,快速排序会递归处理划分的两边,而randomized-select只处理划分的一边。并由此将期望的运行时间由O(nlgn)下降到了O(n)。 这就是顺序统计量算法能够如此高效的核心原因所在!

//
/// @file		nth_element.cpp
/// @brief		中位数和顺序统计学
/// @details	COPYRIGHT NOTICE
///			    Copyright (c) 2011
///			    All rights reserved.\n
///			    在O(n)的时间内寻找一个数组中的第i个顺序统计量\n
///				第i个顺序统计量的定义为:该集合中第i小的元素\n
///				以期望线性时间选择顺序统计量的方法是以快速排序为模型。如同在快速排序中一样,此算法的思想也是
///				对输入数组进行递归划分。但和快速排序不同的是,快速排序会递归处理划分的两边,而randomized-select
///				只处理划分的一边。并由此将期望的运行时间由O(nlgn)下降到了O(n)。
///
//

#include <time.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <iterator>
using namespace std;

namespace ita
{
	namespace
	{

		/// 寻找v数组的子集[begin_index, end_index]中的第i个元素顺序统计量,0 <= i < end_index-begin_index
		int _NthElement( vector<int> &v, int const begin_index, int const end_index, int const n )
		{
			//这个判断纯粹只是一个加速return的技巧,没有这个判断算法也是正确的!
			if ( begin_index == end_index )
			{
				return v[begin_index];
			}

			//随机取样
			int swap_index = rand() % ( end_index - begin_index + 1 ) + begin_index;
			swap( v[swap_index], v[end_index] );

			//根据最后一个主元进行分割成两部分
			int i = begin_index;
			for ( int j = begin_index; j < end_index; ++j )
			{
				if ( v[j] < v[end_index] )
				{
					swap( v[i++], v[j] );
				}
			}
			swap( v[i], v[end_index] );

			//主元是本区间的第k个元素顺序统计量,0<=k<size
			int k = i - begin_index;

			if ( n == k )
			{
				//找到了
				return v[i];
			}
			if ( n < k )
			{
				//在左区间继续找
				return _NthElement( v, begin_index, i - 1, n );
			}
			else
			{
				//在右区间继续找:由于主元是第k个元素顺序统计量(0<=k<size),所以小于等于主元的元素有k+1个(包括主元),因此寻找右区间的第n-(k+1)个顺序统计量
				return _NthElement( v, i + 1, end_index, n - k - 1 );
			}
		}

	}

	/// @brief 寻找v数组中的第i个顺序统计量,0<=i<size
	///
	/// 以快速排序为模型。如同在快速排序中一样,此算法的思想也是
	///	对输入数组进行递归划分。但和快速排序不同的是,快速排序会递归处理划分的两边,而randomized-select
	///	只处理划分的一边。并由此将期望的运行时间由O(nlgn)下降到了O(n)。
	/// @param	v	要进行查找操作的集合
	/// @param	i	查找集合中的第i个顺序统计量
	/// @return		集合中的第i个顺序统计量
	/// @see	int _NthElement(vector<int> &v, int const begin_index, int const end_index, int const n)
	int NthElement( vector<int> &v, int const i )
	{
		return _NthElement( v, 0, v.size() - 1, i );
	}

	/// 中位数和顺序统计学
	int testNthElement()
	{
		vector<int> v;
		srand((int)time(0));
		for ( int i = 0; i < 10; ++i )
		{
			v.push_back( ( rand() % 1000 ) );
		}
		copy( v.begin(), v.end(), ostream_iterator<int>( cout, "  " ) );
		cout << endl;

		for ( int i = 0; i < 10; ++i )
		{
			cout << i << "th element is:" << NthElement( v, i ) << endl;
		}

		return 0;
	}
}
using namespace ita;
int main()
{
	testNthElement();
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值