在算法导论的第二部分主要探讨了排序和顺序统计学,第六章~第八章讨论了堆排序、快速排序以及三种线性排序算法。该部分的最后一个章节,将讨论顺序统计方面的知识。

在一个由n个元素组成的集合中,第i个顺序统计量是该集合中第i小的元素。正如我们经常遇到的中位数问题,一个中位数是它所在集合中的“中点元素”。对于一个有序元素序列,当元素个数为奇数时,中位数位于 i = (n+ 1)/ 2 位置,当元素个数为偶数时,中位数又有下中位数 i = (n+1)/2 取下限 和上中位数 i = (n+1)/2 取上限。

本章讨论的是从一个由n个不同数值构成的集合中选择第i个顺序统计量的问题。

GitHub 算法导论 第九章程序代码

以期望线性时间做选择

该选择问题定义如下:

输入:一个包含n个不同数的集合A和一个数i , 1<= i <= n

输出:元素x ,它恰大于该集合中其它i-1个元素

虽然该问题可以利用堆排序、合并排序或者快速排序得到有序序列,然后直接返回下标为i个元素,得到时间复杂度为O(nlogn)的算法,但是本节介绍的是一个更快的算法,其平均时间复杂度为O(n)

该算法的实现,与快速排序有一定的类似,同样需要对输入序列设置主元,得到分割点,然后根据分割点元素值得到其是第k小元素,对比k与i的值,得到待求元素。

具体程序实现如下:

/**
* 线性时间做选择
*/ #include <iostream>
#include <ctime>
#include <cstdlib>
#define N 10 using namespace std; //选择第i个大小的元素算法声明
int RandomizedSelect(int *data, int l, int h, int i); //求分割点
int partition(int * array, int low, int high); //以low ~ high 之间的一个随机元素作为主元 , 求分割点
int RandomPartition(int *array, int low, int high); //交换两个变量的值
void exchange(int &a, int &b); int main()
{
//声明一个待排序数组
int array[N];
//设置随机化种子,避免每次产生相同的随机数
srand(time(0));
for (int i = 0; i<N; i++)
{
array[i] = rand() % 101;//数组赋值使用随机函数产生1-100之间的随机数
}
cout << "输入原序列:" << endl;
for (int j = 0; j<N; j++)
{
cout << array[j] << " ";
} cout << endl << "求第 6 小的元素是:RandomizedSelect(array , 0 , N-1 , 6) = " ;
//调用随机线性选择算法
cout << RandomizedSelect(array, 0, N - 1 , 6); cout << endl; system("pause"); return 0;
}//main int RandomizedSelect(int *data, int l, int h, int i)
{
//如果输入序列中仅有一个元素
if (l == h)
return data[l]; //求分割点pos,该位置左边元素均小于data[pos] , 右边元素均大于data[pos]
int pos = RandomPartition(data, l, h); //求该分割点是第几小元素
int k = pos - l + 1; //如果就是当前第i小元素,则返回data[pos]
if (k == i)
return data[pos];
else if (k < i)
return RandomizedSelect(data, pos + 1, h, i - k);
else
return RandomizedSelect(data, l, pos - 1, i);
} int partition(int * array, int low, int high)
{
int i = low - 1;
//默认将划分段的最后一个元素为主元
int x = array[high]; for (int j = low; j<high; j++)
{
if (array[j] <= x)//在array[i]左边都是小于x即array[high]的数,右边均是大于它的数
{
i += 1;
exchange(array[i], array[j]);
}
}
exchange(array[i + 1], array[high]);
return i + 1;//所以循环完毕后,i+1就是该数组的分割点
} int RandomPartition(int *array, int low, int high)
{
//找到low ~ high 之间的一个随机位置
int i = rand() % (high - low + 1) + low; //交换该随机主元至尾部,
exchange(array[i], array[high]); return partition(array, low, high);
} void exchange(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}

以上即是线性时间选择算法,在平均情况下,任何顺序统计量(特别是中位数)都可以在线性时间内得到。

最坏情况下线性时间的选择

在上一节中介绍的选择算法,平均情况下为O(n)的复杂度,本节介绍一个最坏情况下运行时间为O(n)的新的选择算法SELECT。该算法同样也取自了快速排序的划分算法Partition,作了相应修改,将主元元素作为函数的一个参数。

算法SELECT的执行步骤确定一个n>1个元素的输入数组中第i小的元素。

具体步骤见下程序实现:

/**
* 最坏情况线性时间的选择
*/ #include <iostream>
#include <cstdlib>
#include <ctime> using namespace std; //快排的求分割点算法
int Partition(int * array, int low, int high); //SELECT中修改的分割算法,以大小为val的元素作为主元
int _Partition(int *data, int l, int h, int val); //线性选择算法,从下标l~h的data序列中找出第i小的元素
int Select(int *data, int l, int h, int i); //求中位数的中位数,对输入序列分为n/5组,每组5个元素
int Find(int *data, int low, int high); //交换两个变量的值
void exchange(int &a, int &b); const int N = 10; const int MAX = 101; int main()
{
//声明一个待排序数组
int array[N];
//设置随机化种子,避免每次产生相同的随机数
srand(time(0));
for (int i = 0; i<N; i++)
{
array[i] = rand() % MAX;//数组赋值使用随机函数产生1-100之间的随机数
}
cout << "输入原序列:" << endl;
for (int j = 0; j<N; j++)
{
cout << array[j] << " ";
} cout << endl << "求第 6 小的元素是:RandomizedSelect(array , 0 , N-1 , 6) = ";
//调用随机线性选择算法
cout << Select(array, 0, N - 1, 6); system("pause"); return 0;
} int Partition(int * array, int low, int high)
{
int i = low - 1;
//默认将划分段的最后一个元素为主元
int x = array[high]; for (int j = low; j<high; j++)
{
if (array[j] <= x)//在array[i]左边都是小于x即array[high]的数,右边均是大于它的数
{
i += 1;
exchange(array[i], array[j]);
}
}
exchange(array[i + 1], array[high]);
return i + 1;//所以循环完毕后,i+1就是该数组的分割点
} void exchange(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
} //SELECT中修改的分割算法,以大小为val的元素作为主元
int _Partition(int *data, int l, int h, int val)
{
for (int i = l; i <= h; i++)
{
if (data[i] == val)
{
exchange(data[i], data[h]);
break;
}
} return Partition(data, l, h);
} //线性选择算法,从下标l~h的data序列中找出第i小的元素
int Select(int *data, int l, int h, int i)
{
//如果数组中只有一个元素,直接返回该元素
if (l == h)
return data[l];
//步骤1、2、3 将数组n个元素划分为n/5组;选出每组中位数;然后从5个中位数中选出中位数
int value = Find(data , l , h); //步骤4 以此中位数为主元,划分输入序列
int pos = _Partition(data, l, h, value); //步骤5 判断该分割点元素是否为第i个元素
int k = pos - l + 1;
if (k == i)
return data[pos];
else if (k < i)
return Select(data, pos + 1, h, i - k);
else
return Select(data, l, pos - 1, i);
} int Find(int *data, int low, int high)
{
int mid[N] = { 0 }, k = 0; for (int m = low , n = low+4 ; m < high; m+=5)
{
if (n >= high || m + 4 >= high)
n = high;
else
n = m + 4;
//对每组元素按照插入排序,求每组的中位数,加入数组mid
for (int j = m+1; j <= n; j++)
{
int key = data[j];
int i = j - 1;
while (i >= m && data[i] > key)
{
data[i + 1] = data[i];
i = i - 1;
}
data[i+1] = key;
}
cout << endl;
mid[k++] = data[(n + m) / 2]; }//for return Select(mid, 0, k-1, (k+1) / 2);
}

总结

本章中选择算法之所以具有线性运行时间,是因为这些算法没有进行排序;线性时间的行为并不是类似第八章中的排序算法因为对输入做假设所得到的结果。

《算法导论》— Chapter 9 中位数和顺序统计学的更多相关文章

  1. 算法导论 第九章 中位数和顺序统计量(python)

    第i个顺序统计量:该集合中第i小的元素(建集合排序后第i位 当然算法可以不排序) 中位数:集合中的中点元素 下中位数 上中位数 9.1最大值和最小值 单独的max或min每个都要扫一遍 n-1次比较 ...

  2. (搬运)《算法导论》习题解答 Chapter 22.1-1(入度和出度)

    (搬运)<算法导论>习题解答 Chapter 22.1-1(入度和出度) 思路:遍历邻接列表即可; 伪代码: for u 属于 Vertex for v属于 Adj[u] outdegre ...

  3. 《算法导论》 — Chapter 7 高速排序

    序 高速排序(QuickSort)也是一种排序算法,对包括n个数组的输入数组.最坏情况执行时间为O(n^2). 尽管这个最坏情况执行时间比較差.可是高速排序一般是用于排序的最佳有用选择.这是由于其平均 ...

  4. 《算法导论》 — Chapter 8 线性时间排序

    序 到目前为止,关于排序的问题,前面已经介绍了很多,从插入排序.合并排序.堆排序以及快速排序,每一种都有其适用的情况,在时间和空间复杂度上各有优势.它们都有一个相同的特点,以上所有排序的结果序列,各个 ...

  5. 《算法导论》— Chapter 15 动态规划

    序 算法导论一书的第四部分-高级设计和分析技术从本章开始讨论,主要分析高效算法的三种重要技术:动态规划.贪心算法以及平摊分析三种. 首先,本章讨论动态规划,它是通过组合子问题的解而解决整个问题的,通常 ...

  6. 《算法导论》 — Chapter 7 快速排序

    序 快速排序(QuickSort)也是一种排序算法,对包含n个数组的输入数组,最坏情况运行时间为O(n^2).虽然这个最坏情况运行时间比较差,但是快速排序通常是用于排序的最佳实用选择,这是因为其平均性 ...

  7. 《算法导论》— Chapter 11 散列表

    1 序 在很多应用中,都要用到一种动态集合结构,它仅支持INSERT.SEARCH以及DELETE三种字典操作.例如计算机程序设计语言的编译程序需要维护一个符号表,其中元素的关键字为任意字符串,与语言 ...

  8. B树——算法导论(25)

    B树 1. 简介 在之前我们学习了红黑树,今天再学习一种树--B树.它与红黑树有许多类似的地方,比如都是平衡搜索树,但它们在功能和结构上却有较大的差别. 从功能上看,B树是为磁盘或其他存储设备设计的, ...

  9. 基本数据结构(2)——算法导论(12)

    1. 引言     这一篇博文主要介绍链表(linked list),指针和对象的实现,以及有根树的表示. 2. 链表(linked list) (1) 链表介绍      我们在上一篇中提过,栈与队 ...

随机推荐

  1. 哈夫曼费用计算C++

    #include<stdio.h> #include<string.h> #include<math.h> #include<iostream> #in ...

  2. 模拟 HDOJ 5099 Comparison of Android versions

    题目传送门 /* 题意:比较型号的大小 模拟:坑点在长度可能为5,此时设为'A' */ #include <cstdio> #include <algorithm> #incl ...

  3. 题解报告:poj 2631 Roads in the North(最长链)

    Description Building and maintaining roads among communities in the far North is an expensive busine ...

  4. windows session 管理

    Killing an Oracle process from inside Oracle I had a following situation few days ago – I was runnin ...

  5. CentOS6.5下中文输入法的相关问题

    问题.点击Input Method Preferences没反应. 首先执行 yum install "@Chinese Support" yum install -yibus-t ...

  6. RHEL7.2安装及配置实验环境

    截图太多了,就不一一上传了,请查看这个分享网址 http://pan.baidu.com/s/1kVeYANH 什么时候博客更新下能直接把图一下复制进来多好!省事.

  7. HtmlUnit爬取Ajax动态生成的页面内容

    HtmlUnit说白了就是一个浏览器,这个浏览器是用Java写的无界面的浏览器,正因为其没有界面,因此执行的速度还是可以滴. HtmlUnit提供了一系列的API,这些API可以干的功能比较多,如表单 ...

  8. 十个非常棒的学习angularjs的英文网站

    AngularJS 是非常棒的JS框架,能够创建功能强大,动态功能的Web app.AngularJS自2009发布以来,已经广泛应用于Web 开发中.但是对想要学习Angular JS 的人而言,只 ...

  9. android java 知识点

    ublic,protected,friendly,private的访问权限如下: 关键字        当前类       同一package        子孙类       其他package p ...

  10. 7z解压参数

    7z.exe x D:/test/dwpath/xxx.zip -oD:/test/dwpath/ -aoa