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

在一个由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. jQuery中的.html()和.text()及.val()区别

    https://www.cnblogs.com/zhang-xun/p/6766264.html

  2. 跟我一起玩Win32开发(4):创建菜单

    也不知道发生什么事情,CSDN把我的文章弄到首页,结果有不少说我在误人子弟,是啊,我去年就说过了,如果你要成为砖家级人物,请远离我的博客,我这个人没什么特长,唯一厉害的一点就是不相信权威,鄙视砖家,所 ...

  3. C++伪函数

    #include <iostream> void say_hello() { std::cout << "hello world !" << s ...

  4. div里面放img

    div里面放img的时候 会出现包裹不住的情况,这个时候 只要将img { width:100%,height:100%  },就可以解决问题了

  5. Codeforces 669D Little Artem and Dance (胡搞 + 脑洞)

    题目链接: Codeforces 669D Little Artem and Dance 题目描述: 给一个从1到n的连续序列,有两种操作: 1:序列整体向后移动x个位置, 2:序列中相邻的奇偶位置互 ...

  6. 中国剩余定理 POJ 1006 Biorhythms

    题目传送门 题意:POJ有中文题面 分析:其实就是求一次同余方程组:(n+d)=p(%23), (n+d)=e(%28), (n+d)=i(%33),套用中国剩余定理模板 代码: /********* ...

  7. imagettftext

    ImageTTFText 写 TTF 文字到图中. 语法: array ImageTTFText(int im, int size, int angle, int x, int y, int col, ...

  8. js数组去重的三种方式的比较

    做前端的,一般实现功能是主要的,但是重中之重却是在做到功能完善的情况下提高性能. 1.遍历数组法 实现的思路:构建一个新的数组存放结果,for循环中每次从原数组中取出一个元素,用这个元素循环与结果数组 ...

  9. AJPFX浅谈关于Java程序员缺乏面向对象的基本功的问题

    为什么很多 Java 程序员会缺乏面向对象基本功?这得怪那些 Java 框架.现在 Java 的各种框架太发达.太傻瓜化了,导致很多程序员只需要按部就班.照着框架进行代码填空,基本已经丧失了 OOA ...

  10. vmware让虚拟机内外网络可互访

    以下方法可使主机可以ping通虚拟机,虚拟机也可以ping通主机 首先对虚拟机设置 然后设置虚拟机,假设主机的ip是10.0.0.9,那虚拟机的ip应如下设置: 其中ip地址任意设置一个,但要求跟主机 ...