算法导论9.1-1找第二小的元素
文章目录
- 算法导论9.1-1
- 1、证明:在最坏情况下,找到n个元素中第二小的元素需要 n+ceil(lgn)-2 次比较。(提示:可以同时找到最小元素)
- 1.证明 n-1
- 2.证明 ceil(lg) -1
- 2.算法实现:
- 3、总结
算法导论9.1-1
1、证明:在最坏情况下,找到n个元素中第二小的元素需要 n+ceil(lgn)-2 次比较。(提示:可以同时找到最小元素)
做以下断言:无论采用何种比较算法,在寻找最小元素的过程中,第二小的元素一定与最小元素做过比较。
显然,因为第二小的元素直到遇到最小元素前,一定会胜出进入下一轮。如果最小元素没有与第二小元素相比较,则无法得出该元素是最小的关系。
因此,要想是我们找到最小元素的比较次数最少,就需要在寻找最小元素的过程中与最小元素发生比较的次数尽量的少。
由于每次我们只能取两个元素进行比较,从而考虑每一轮将我们比较元素两两比较,较小的元素进入下一轮(如比较元素有奇数个,落单的直接进入下一轮),这样比较所得的最小元素比较过的元素是最少的(每轮只有一个)。
我们将比较过程转化为二叉树(以数组 [7,8,1,3,4,5,9]为例):
1.证明 n-1
其中,除了叶节点外,每个结点都是一次比较的结果。因此该算法中,比较次数为树中度为2的结点数。根据二叉树的性子可以得出比较次数为 n−1n-1n−1 (二叉树:度为2的结点数=叶子叶子结点数-1)。可以看出树的每一层只有一个结点与最小元素比较,达到了下界,因此是最优的。
这里的 n−1n-1n−1 次比较下界还有另外的思路。可以把每个元素看成一个集合。集合中用最小元素代表这个集合。初始状态下 nnn个点都是单独的集合,比较后相当于把集合做合并。合并后用最小值代表这个集合。那么初始状态下的 nnn 个集合,至少经过 n−1n-1n−1 次合并才能得到1个集合。
2.证明 ceil(lg) -1
可以看出,与最小元素比较的次数为树高h-1(根节点的高度为1)。有因为叶节点数个数为n。从而有: n≤2h−1n \leq 2^{h-1}n≤2h−1,故有 h−1=⌈lgn⌉h-1 = \lceil lg n \rceilh−1=⌈lgn⌉,即与最小元素比较的次数至少为 ⌈lgn⌉\lceil lg n \rceil⌈lgn⌉。
从这 ⌈lgn⌉\lceil lg n \rceil⌈lgn⌉个元素中找到的最小元素即是第二小的元素。最坏情况下第二小元素第一轮遇到最小元素被淘汰,从而在寻找最小元素的过程中,我们无法得到其他元素与第二小元素比较大小关系。这种情况下,要在 ⌈lgn⌉\lceil lg n \rceil⌈lgn⌉个元素中找到最小元素需要 ⌈lgn⌉−1\lceil lg n \rceil - 1⌈lgn⌉−1次比较。
综上:最坏情况下,要找到第二小的元素所需的比较次数为 n+⌈lgn⌉−2n + \lceil lg n\rceil -2n+⌈lgn⌉−2 次比较。
2.算法实现:
根据上面的解释。我们其实只要找到最小元素,然后在最小元素比较过程中比较过的元素记录下来,然后我们在这堆元素(上面证明共⌈lgn⌉\lceil lg n\rceil⌈lgn⌉个)中找最小元素即可。
具体实现:对于每一个元素,采用数组或链表的结构,每次比较后胜出的元素记录与它比较的元素在原数组的下标,同时自身进入下一轮的比较,根据上面的图可知,每经过一轮比较,元素被筛选掉一半,直至最终仅剩一个元素时,它就是最小元素,返回与它比较元素的元素集合。最终在该集合中寻找第二小元素。
#include<iostream>
#include<vector>
#include<ctime>
#include<cmath>
#include<algorithm>
using namespace std;
const int INF=0x3f3f3f3f;
class Solution
{public:static int cnt;//记录比较次数static void init(vector<int> &vec){srand(time(nullptr));for_each(vec.begin(),vec.end(),[](int &v){v=rand();});}static vector<int> findSmallestAndCollection(const vector<int>& vec,vector<int>* paths){//初始状太全是胜者进入下一轮vector<int> winnerIndex(vec.size());for(int i=0;i<winnerIndex.size();++i)winnerIndex[i]=i;//直到胜者唯一while(winnerIndex.size()!=1){winnerIndex=compareAndStore(vec,winnerIndex,paths);}return paths[winnerIndex[0]];//最终剩一个返回对于的path}static int findSecondSmallest(const vector<int>& vec,vector<int> *paths){int smallest2=INF;vector<int> path=Solution::findSmallestAndCollection(vec,paths);for(auto i:path)//最 lgn个元素的最小值即是第二小元素{if(vec[i]<smallest2){Solution::cnt++;//比较次数+1smallest2=vec[i];}}return smallest2;}static vector<int> compareAndStore(const vector<int> &arr,const vector<int> &winnerIndex,vector<int>* paths){vector<int> ret;int sz=winnerIndex.size();if(sz&1){ret.push_back(winnerIndex[sz-1]);--sz;//这里为了方便,奇数个元素时最后一个直接加入,同时数组大小-1}for(int i=1;i<sz;i+=2){//小的加入ret进入下一轮,同时记录它的比较元素下标if(arr[winnerIndex[i]]<=arr[winnerIndex[i-1]]){ret.push_back(winnerIndex[i]);paths[winnerIndex[i]].push_back(winnerIndex[i-1]);}else{ret.push_back(winnerIndex[i-1]);paths[winnerIndex[i-1]].push_back(winnerIndex[i]);}Solution::cnt++;//比较次数+1}return ret;}};
int Solution::cnt=0;
void showTopTen(vector<int> &vec)
{sort(vec.begin(),vec.end());for(int i=0;i<vec.size()&&i<10;++i)cout << vec[i] << " ";cout << endl;
}
int main()
{int len;cout << "输入元素个数"<<endl;cin >> len;vector<int> vec(len);Solution::init(vec);//产生len个随机数vector<int> *comparePath=new vector<int>[vec.size()];//建立每个元素比较的路径int smallest2=Solution::findSecondSmallest(vec,comparePath);cout << "Second smallest element :"<<smallest2 << endl;cout << "Compare time O(n+lgn-2) \n Std:" << (len+ceil(log(len)/log(2))-2);cout << "\nExtra:" << Solution::cnt << endl;showTopTen(vec);//打印vec最小的10个元素delete[] comparePath;
}
3、总结
该算法主要注重比较次数,但是空间复杂度不乐观(保存比较路径),时间复杂度为 O(n)O(n)O(n)。适合于那种一次比较需要耗费大量时间的值。下面有两篇我解决该题参考的博客。
参考博客
只涉及证明:
博客1:https://blog.csdn.net/qq_33382034/article/details/53495036
包括证明和算法实现(python)
博客2:http://windmissing.github.io/算法导论/2015-12/9.1-1-second_smallest_element.html
算法导论9.1-1找第二小的元素相关推荐
- 求无序数组的第二小的元素
1. 比较是成对比较的,先拿前2个数比较,大的那个记作当前最大值,小的那个是第二大值.然后每次比较一队元素,大的那个去和最大值比,如果比最大值大,就把它记为最大的,原来的最大和较小的那个比,大的记为第 ...
- 【算法导论】学习笔记——第9章 中位数和顺序统计量
在一个由n个元素组成的集合中,第i个顺序统计量(order statistic)是该集合中第i小的元素.用非形式化的描述来说,一个中位数(median)使它所属集合的"中点元素". ...
- 找出第二小元素(算法导论第三版9.1-1题)
找出第二小元素(算法导论第三版9.1-1题) 时间复杂度Θ(n) 比较次数n+⌈lgn⌉−2次 思路:将元素每次分成2部分,第一部分和第二部分元素成对比较.最终获得最小的元素,记录那些和最小元素比较后 ...
- 找出第i个小元素(算法导论第三版9.2-4题)
找出第i个小元素(算法导论第三版9.2-4题) 期望时间复杂度:Θ(n) 最坏情况的时间复杂度Θ(n^2) int randomized_select_based_loop(int *array,in ...
- 算法导论:快速找出无序数组中第k小的数
题目描述: 给定一个无序整数数组,返回这个数组中第k小的数. 解析: 最平常的思路是将数组排序,最快的排序是快排,然后返回已排序数组的第k个数,算法时间复杂度为O(nlogn),空间复杂度为O(1). ...
- 算法导论第一,第二部分总结
最近正在看一部大著<算法导论>,这本书无论从严密还是从思维上面都达到了一个难以企及的高度.里面的算法分析与设计基本上都在理论性上,如果想实用的话自己还需要看点其它的东西.接下来,就自己学了 ...
- 我眼中的算法导论 | 第一章——算法在计算中的作用、第二章——算法基础
一个小白的算法学习之路.读<算法导论>第一天.本文仅作为学习的心得记录. 算法(Algorithm) 对于一个程序员来说,无论资历深浅,对算法一词的含义一定会或多或少有自己的体会,在< ...
- 算法导论第三版第二章思考题答案
算法导论第三版第二章思考题答案 第二章思考题 算法导论第三版第二章思考题答案 2.1 2.2 2.3 2.4 汇总传送门 2.1 #include<iostream> using name ...
- 算法导论 第二部分——排序和顺序统计量
一.堆排序 : 原址排序 复杂度: nlg n 最大堆: A[parent(i)] > = A[i] 最小堆: A[parent(i)] < = A[i] 除了最底层外,其它层都是满状态. ...
最新文章
- 判断题:oracle自带的sql语言环境是pl/sql,Oracle之PL/SQL学习笔记之数据类型(三)
- 【C 语言】数组 ( 验证二维数组内存是线性的 | 打印二维数组 | 以一维数组方式打印二维数组 | 打印二维数组值和地址 )
- 编码与乱码(05)---GBK与UTF-8之间的转换--转载
- 【手写数据结构】双链表最详细图解
- 大量词云出现的时候IO的负担很大
- IO Streams:缓冲流
- windows下配置opencv
- 细学PHP 09 MySql
- windows nginx c++读取请求数据_轻松应对百万并发的Nginx,搞懂LinuxC/C++这些技术栈升职加薪...
- 平面设计中的网格系统pdf_深入浅出,带你认识网格系统与版式设计
- h5页面预览pdf文件_H5移动端在线浏览pdf文件,推荐插件TouchPDF
- 应用程序如何链接静态QT Plugin库
- 057 Java中kafka的Producer程序实现
- sql语句有没有怎么优化的空间,这条语句在我这里执行是死机
- Android游戏源码的分析、疑问与交流
- M语言简单示例--网页数据抓取
- Tomcat 的安装与配置
- AUTOSAR工程师,年薪50W?
- 实车路试注意事项(路试类)
- 【无标题】deployment does not have minimum availability