您的位置:首页 > 其它

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

2017-02-22 07:11 357 查看
 第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();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: