优先队列的应用:求序列第 k 个最大的元素

优先队列【堆】的应用:

    选择问题:输入时N 个元素以及一个整数k ,这N 个元素的集可以是全序的。该选择问题是要找出第 k 个最大的元素。。。

有多种解决方案:

1A算法:把这些元素读入数组,并对其进行排序,返回适当的元素。假设使用的是简单排序算法,时间复杂度是O(N^2)。

1B算法:将k 个元素读入一个数组,并对其进行排序。这些元素的最小者在第k 个位置上,一个一个的处理其余的元素。当一个元素开始被处理时,它先与第k 个元素进行比较,如果该元素大,则将第k 个元素出去,该元素放在其余k - 1 个排好序的元素中的正确位置。当算法结束时,第k 个位置上的元素就是该问题的解答。该算法的运行时间为O(N*k),如果k = N / 2(上取整),则运行时间为O(N^2)【此时的 k 为中位数】。

2A算法:将N个元素读入一个数组,然后对该数组建立一个大顶堆。最后,执行k 次删除最大值的操作。从该堆最后提取的元素就是我们的答案。该算法构造堆的最坏情况是 O(N),而每次删除操作用时O(logN),由于k 次删除,因此,得到总的运行时间是O(N + k * logN), 若k = N / 2(上取整),那么运行时间为O(NlogN) 【堆排序】。

?2A-改进:在任意时刻都维持k 个最大元素结合S, 在前 k 个元素读入以后,当再读入一个新的元素时,该元素将与第 k 个最大的元素进行比较,记这第k 大的元素为 Sk,注意 Sk 是 S 中的最小元素。如果新元素更大,则用新元素替代这个 Sk 。此时 S 中将有一个新的最小元。这里,我们使用一个堆来实现 S 。前 k 个元素通过堆的建立过程,建立一个堆,总时间为 O(k),处理其余的每个元素的时间为 O(1) (检测元素是否进入堆),在加上时间O(log k)(在必要时删除 Sk, 并插入新元素)。因此,总的时间是O(k + (N - k) * log K) = O(N log k)。若 k 为中位数,则时间为 O(NlogN)。【代码如下:】

//利用堆寻找某一序列中第k大的元素(从小到大)即第k大的元素
#include<iostream>
#define MINHEAPSIZE 1
#define MINVALUE 0.0001
using namespace std;

typedef int ElemType;
typedef struct HeapStruct
{
 ElemType *elem;
 int Size;
 int Capasity;
}Heap, *PriorityQueue;

PriorityQueue InitializeHeap(int MaxSize)    //堆的初始化
{
 if(MaxSize < MINHEAPSIZE)
  throw exception("The priority queue is too small!");
 PriorityQueue H;
 H = new HeapStruct();
 H->Size = 0;
 H->Capasity = MaxSize;
 H->elem = new ElemType[MaxSize + 1];
 H->elem[0] = MINVALUE;
 return H;
}

void InsertHeap(PriorityQueue H, ElemType key)   //上滤过程
{
 if(H->Size == H->Capasity)
  throw exception("The priority queue is full!");
 int i;
 for(i = ++H->Size; H->elem[i / 2] > key; i /= 2)
 {
  H->elem[i] = H->elem[i / 2];
 }
 H->elem[i] = key;
}

int DeleteMin(PriorityQueue H)  //下滤过程
{
 int i, child;
 ElemType MinElem, LastElem;
 if(H->Size == 0)
 {
  cout << "The priority queue is empty!" << endl;
  return H->elem[0];
 }
 MinElem = H->elem[1];   //此时根节点可以看做是一个空穴
 LastElem = H->elem[H->Size--];
 for(i = 1; i * 2 <= H->Size; i = child)   //比较空穴i的两个孩子,child指向最小的孩子
 {
  child = i * 2;
  if(child != H->Size && H->elem[child] > H->elem[child + 1])
   child++;
  if(LastElem > H->elem[child])  //最小的孩子与最后一个元素比较,若最后一个元素小于该节点的最小孩子,

                                                    //则将最后的元素填入空穴中
   H->elem[i] = H->elem[child];
  else
   break;
 }
 H->elem[i] = LastElem;  //填补空穴
 return MinElem;
}

int SearchKElem(ElemType arr[], int length, int k)
{
 if(length < k || length <MINHEAPSIZE)
  throw exception("Invalidate input!");
 PriorityQueue H;
 H = InitializeHeap(k);
 for(int i = 0; i < k; i++)
  InsertHeap(H, arr[i]);
 for(int j = k; j < length; j++)
 {
  if(arr[j] > H->elem[1])
  {
   DeleteMin(H);
   InsertHeap(H, arr[j]);
  }
 }
 ElemType kMax = H->elem[1];
 return kMax;
}

int main()
{
 int arr[10] = {5, 2, 8, 1, 3, 9, 7, 4, 6,10};
 int k;
 cout << "请输入k值:" ;
 cin >> k;
 int result = SearchKElem(arr, 10, k);
 cout <<"倒数第k大的元素为:" << result << endl;

 system("pause");
 return 0;
}

时间: 2024-10-10 02:39:12

优先队列的应用:求序列第 k 个最大的元素的相关文章

POJ 2104 求序列里第K大 主席树裸体题

给定一个n的序列,有m个询问 每次询问求l-r 里面第k大的数字是什么 只有询问,没有修改 可以用归并树和划分树(我都没学过..囧) 我是专门冲着弄主席树来的 对主席树的建树方式有点了解了,不过这题为什么是在主席树里面这么操作的 还是有点不懂,今天照着模板敲了一遍就打多校了 再研究吧 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using name

[csu/coj 1080]划分树求区间前k大数和

题意:从某个区间内最多选择k个数,使得和最大 思路:首先题目给定的数有负数,如果区间前k大出现负数,那么负数不选和更大,于是对于所有最优选择,负数不会出现,所以用0取代负数,问题便转化为区间的前k大数和. 划分树: [1  6  3  8  5  4  7  2] [6  8  5  7][1  3  4  2] [8  7][6  5][3  4][1  2] [8][7][6][5][4][3][2][1] 把快排的结果从上至下依次放入线段树,就构成了划分树,划分的意思就是选定一个数,把原序

hdu 2665 可持久化线段树求区间第K大值(函数式线段树||主席树)

http://acm.hdu.edu.cn/showproblem.php?pid=2665 Problem Description Give you a sequence and ask you the kth big number of a inteval. Input The first line is the number of the test cases. For each test case, the first line contain two integer n and m (

poj2104 求区间第k大 可持久化线段树

poj2104 求区间第k大  可持久化线段树 #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef

POJ2761---Feed the dogs (Treap求区间第k大)

题意 就是求区间第k大,区间 不互相包含. 尝试用treap解决一下 第k大的问题. 1 #include <set> 2 #include <map> 3 #include <cmath> 4 #include <ctime> 5 #include <queue> 6 #include <stack> 7 #include <cstdio> 8 #include <string> 9 #include <

poj 2104主席树求区间第k小

POJ - 2104 题意:求区间第k小 思路:无修改主席树 AC代码: #include "iostream" #include "iomanip" #include "string.h" #include "stack" #include "queue" #include "string" #include "vector" #include "set&

求序列完美度(trie+贪心)

题目链接: 求序列完美度 题目描述 给出由n个数组成的序列s,规定第i个数s[i]到第j个数s[j]组成的子序列的完美度为该子序列中所有数的和与任意一个不在该子序列中的数进行异或运算得到的值中的最大值,即perfect(s[i]~s[j])=max(sum(s[i]~s[j])^x),x为非子序列的数.现求完美度最大的子序列与它的完美度是多少 输入 第一行输入一个数T表示共有T组数据 输入一个数n表示序列有n个数 接下来有n个数,角标为1-n,表示序列的组成 1<=T<=100 3<=n

给定n,a求最大的k,使n!可以被a^k整除但不能被a^(k+1)整除。

题目描述: 给定n,a求最大的k,使n!可以被a^k整除但不能被a^(k+1)整除. 输入: 两个整数n(2<=n<=1000),a(2<=a<=1000) 输出: 一个整数. 样例输入: 6 10 样例输出: 1 这个题首先如果数字小的话是可以考虑轮流试的,但是1000的数字范围无论是对阶乘还是幂都太大了.于是我们想一下,既然要求整除,说明每个素因子都是可以抵消的,这样我们就可以求解了.但是还要考虑到,因为后面是求哪个k,所以说我们不是对n!和a的幂分别求出对应的素数因子数组.我

循环-10. 求序列前N项和(15)

本题要求编写程序,计算序列 2/1+3/2+5/3+8/5+... 的前N项之和.注意该序列从第2项起,每一项的分子是前一项分子与分母的和,分母是前一项的分子. 输入格式: 输入在一行中给出一个正整数N. 输出格式: 在一行中输出部分和的值,精确到小数点后2位.题目保证计算结果不超过双精度范围. 输入样例: 20 输出样例: 32.66 #include <iostream> #include <stdio.h> using namespace std; int main(){ i