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

在一个由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. bzoj2301 [HAOI2011]Problem b【莫比乌斯反演 分块】

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2301 很好的一道题.首先把每个询问转化为4个子询问,最后的结果就是这四个子询问的记过加加减减 ...

  2. UvaLive6442(思维、结论)

    结论是:按位置排序好以后,对于真正的答案,走法应该是:依次走向第0个等分点,第1个等分点……这样对于这种等分情况,是最优的调度. /* 先假设一个终点位置然后按位站好 这个位置不一定是最优所以要调 调 ...

  3. linux中用户组和用户

    linux中用户组和用户 1.介绍 在我们的linux系统,有很多用户组,也可以手动创建用户组,不同的用户组下面有很多的用户. 2.创建用户组及有关的命令 groupadd phpzu:创建一个php ...

  4. fscanf

    fscanf (PHP 4 >= 4.0.1, PHP 5, PHP 7) fscanf — 从文件中格式化输入 说明 mixed fscanf ( resource $handle , str ...

  5. RHEL 6.5----Varnish缓存服务器

    主机名 IP  服务  master  192.168.30.130   varnish   slave  192.168.30.131  httpd WebServer   192.168.30.1 ...

  6. spark序列化及MapOutputTracker解析

    本文主要打算对spark内部的序列化机制以及在shuffle map中起衔接作用的MapOutputTracker做一下剖析.主要涉及具体实现原理以及宏观设计的一些思路. 1,spark序列化 任何一 ...

  7. 动手实现 React-redux(四):mapDispatchToProps

    在重构 ThemeSwitch 的时候我们发现,ThemeSwitch 除了需要 store 里面的数据以外,还需要 store 来 dispatch: ... // dispatch action ...

  8. nginx配置参考

    server { listen 443 ssl; server_name apps.qimeng.fm; #charset koi8-r; #证书 ssl_certificate /usr/local ...

  9. JVM内存区域参数配置

    转自:https://www.jianshu.com/p/5946c0a414b5 需要提前了解的知识点: JVM内存模型 JVM垃圾回收算法 下图是JVM内存区域划分的逻辑图   JVM内存区域逻辑 ...

  10. Java堆分配参数总结

    与Java应用程序堆内存相关的JVM参数有: -Xms:设置Java应用程序启动时的初始堆大小 -Xmx:设置Java应用程序能获得的最大堆大小 -Xss:设置线程栈的大小 -XX:MinHeapFr ...