编程之美——寻找最大的K个数

2024-09-06 02:32

本文主要是介绍编程之美——寻找最大的K个数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

编程之美——寻找最大的K个数

解法一:

我们先假设元素的数量不大,例如在几千个左右,在这种情况下,那我们就排序一下吧。在这里,快速排序或堆排序都是不错的选择,他们的平均时间复杂度都是 O(N * log2N)。然后取出前 K 个,O(K)。
总时间复杂度 O(N * log2N)+ O(K) = O(N * log2N)。

你一定注意到了,当 K=1 时,上面的算法也是 O(N * log2N)的复杂度,
而显然我们可以通过 N-1 次的比较和交换得到结果。上面的算法把整个数组都
进行了排序,而原题目只要求最大的 K 个数,并不需要前 K 个数有序,也不需
要后 N-K 个数有序。

怎么能够避免做后 N-K 个数的排序呢?我们需要部分排序的算法,选择排
序和交换排序都是不错的选择。把 N 个数中的前 K 大个数排序出来,复杂度是O(N * K)。

那一个更好呢?O(N * log2N)还是 O(N * K)?
这取决于 K 的大小,这是你需要在面试者那里弄清楚的问题。在 K(K < = log2N)较小的情况下,可以选择部分排序。

在下一个解法中,会通过避免对前 K 个数排序来得到更好的性能。

解法二:

回忆一下快速排序,快排中的每一步,都是将待排数据分做两组,其中一组
的数据的任何一个数都比另一组中的任何一个大,然后再对两组分别做类似的操
作,然后继续下去……

在本问题中,假设 N 个数存储在数组 S 中,我们从数组 S 中随机找出一个
元素 X,把数组分为两部分 Sa 和 Sb。Sa 中的元素大于等于 X,Sb 中元素小于 X。

这时,有两种可能性:

1. Sa中元素的个数小于K,Sa中所有的数和Sb中最大的K-|Sa|个元素(|Sa|指Sa

中元素的个数)就是数组S中最大的K个数。

2. Sa中元素的个数大于或等于K,则需要返回Sa中最大的K个元素。

这样递归下去,不断把问题分解成更小的问题,平均时间复杂度 O(N *
log2K)。

测试代码如下:
#include<iostream>
using namespace std;
const int N=8;
const int K=4;
swap(int &a,int &b)
{
  int temp;
  temp=a;
  a=b;
  b=temp;
}
int partition(int data[],int start,int end)
{
  int index=start;
  swap(data[index],data[end]);
  int small=start-1;
  for(index=start;index<end;++index)
  {
    if(data[index]>data[end])
{
 ++small;
 if(small!=index)
 swap(data[index],data[small]);
}
  }
  ++small;
  swap(data[small],data[end]);
  return small;
}
int findk(int a[],int low,int high,int k)
{
  if(low<high)
  {
    int q=partition(a,low,high);
int len=q-low+1;
if(len==k)
{
 return q;
}
else if(len<k)
return findk(a,low,q-1,k);
else
return findk(a,q+1,high,k-len);
  }
}

int main()
{
int data[N]={5,2,66,23,11,1,4,55};
findk(data,0,N-1,K);
for(int i=0;i<K;i++)
cout<<data[i]<<endl;
system("pause");
return 0;
}

解法三:
寻找N个数中最大的K个数,本质上就是寻找最大的K个数中最小的那个,也就是第K大的数。
#include<iostream>
using namespace std;
const int N=8;
const int K=4;

int find(int *a,int x)
{
  int sum=0;
  for(int i=0;i<N;i++)
  {
    if(a[i]>=x)
sum++;
  }
  return sum;
}

int getK(int *a,int max,int min)
{
  while(max-min>1)
  {
    int mid=(max+min)/2;
int num=find(a,mid);
if(num>=K)
min=mid;
else
max=mid;
  }
  cout<<"end"<<endl;
  return min;
}

int main()
{
int a[N]={54,2,5,11,554,65,33,2};
int x=getK(a,554,2);
for(int i=0;i<N;i++)
{
 if(a[i]>=x)
 cout<<a[i]<<" ";
}
getchar();
return 0;
}


解法四:
就是当N这个数字非常大的时候,那么就不能将所有的数据全部装入内存,所以要尽可能少地遍历所有数据。那么可以采用堆方法。堆方法是需要把所有数据装入内存,如果内存空间不足,系统颠簸,性能必然下降。如果取最大的K个数,可以先用前K个数建立一个最小堆,然后每次读入一个之后的数据与堆顶元素比较,如果比堆顶元素大则替换,并且调整堆维护堆的性质。
#include<iostream>
using namespace std;
const int N=8;
const int K=4;

class CTopK
{
public:
CTopK();
~CTopK();
int* m_Data;
int GetTopK(int Data[],int nTop);
private:
void Clear();
void HeapAdjust(int nStart,int nLen);
void BuildHeap(int nLen);
};

CTopK::CTopK()
{
  m_Data=NULL;
}
CTopK::~CTopK()
{
  Clear();
}

void CTopK::Clear()
{
  if(NULL!=m_Data)
  {
    delete[] m_Data;
m_Data=NULL;
  }
}

int CTopK::GetTopK(int Data[],int nTop)
{
  int fData;
  int i=0;
  Clear();
  cout<<"please wait..."<<endl;
  for(i=0;i<nTop;i++)
  {
 m_Data[i]=Data[i];
  }
  if(i<nTop)
  {
    nTop=i;
  }
  else
  {
    BuildHeap(nTop);
for(;i<N;i++)
{
      if(fData>m_Data[0])
 {
   m_Data[0]=fData;
HeapAdjust(0,nTop);
 }
}
  }
  for(i=0;i<nTop;i++)
  {
 cout<<m_Data[i]<<" ";
  }
  return 0;
}

void CTopK::HeapAdjust(int nStart,int nLen)
{
  int nMinChild=0;
  int fTemp;
  while((2*nStart+1)<nLen)
  {
    nMinChild=2*nStart+1;
if((2*nStart+2)<nLen)
{
 if(m_Data[2*nStart+2]<m_Data[2*nStart+1])
 {
   nMinChild=2*nStart+2;
 }
}
if(m_Data[nStart]>m_Data[nMinChild])
{
 fTemp=m_Data[nStart];
 m_Data[nStart]=m_Data[nMinChild];
 m_Data[nMinChild]=fTemp;
 nStart=nMinChild;
}
else
{
 break;
}
  }
}

void CTopK::BuildHeap(int nLen)
{
  int i=0;
  for(i=nLen/2-1;i>=0;i--)
  {
    HeapAdjust(i,nLen);
  }
}

int main(int argc,char *argv[])
{
int Data[N]={54,2,5,11,554,65,33,2};
CTopK topk;
topk.GetTopK(Data,K);
return 0;
}


解法五:
如果N个数都是正数,取值范围不太大,可以考虑用空间换时间。申请一个包括N中最大值的MAXN大小的数组count[MAXN],count[i]表示整数i在所有整数中的个数。这样只要扫描一遍数组,就可以得到低K大的元素。
代码为:
for(sumCount = 0, v = MAXN -1; v >=0; v--)  
{  
   sumCount += count[v];  
   if(sumCount >= k)  
       break;  
}  
return v;  

原文链接:http://blog.csdn.net/chdhust/article/details/8266238

这篇关于编程之美——寻找最大的K个数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1140746

相关文章

spoj705( 求不相同的子串个数)

题意:求串s的不同子串的个数 解题思路:任何子串都是某个后缀的前缀,对n个后缀排序,求某个后缀的前缀的个数,减去height[i](第i个后缀与第i-1 个后缀有相同的height[i]个前缀)。 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#include<math.h>#include<cstrin

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

XTU 1233 n个硬币连续m个正面个数(dp)

题面: Coins Problem Description: Duoxida buys a bottle of MaiDong from a vending machine and the machine give her n coins back. She places them in a line randomly showing head face or tail face o

poj 3723 kruscal,反边取最大生成树。

题意: 需要征募女兵N人,男兵M人。 每征募一个人需要花费10000美元,但是如果已经招募的人中有一些关系亲密的人,那么可以少花一些钱。 给出若干的男女之间的1~9999之间的亲密关系度,征募某个人的费用是10000 - (已经征募的人中和自己的亲密度的最大值)。 要求通过适当的招募顺序使得征募所有人的费用最小。 解析: 先设想无向图,在征募某个人a时,如果使用了a和b之间的关系

poj 3258 二分最小值最大

题意: 有一些石头排成一条线,第一个和最后一个不能去掉。 其余的共可以去掉m块,要使去掉后石头间距的最小值最大。 解析: 二分石头,最小值最大。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <c

poj 2175 最小费用最大流TLE

题意: 一条街上有n个大楼,坐标为xi,yi,bi个人在里面工作。 然后防空洞的坐标为pj,qj,可以容纳cj个人。 从大楼i中的人到防空洞j去避难所需的时间为 abs(xi - pi) + (yi - qi) + 1。 现在设计了一个避难计划,指定从大楼i到防空洞j避难的人数 eij。 判断如果按照原计划进行,所有人避难所用的时间总和是不是最小的。 若是,输出“OPETIMAL",若

poj 2135 有流量限制的最小费用最大流

题意: 农场里有n块地,其中约翰的家在1号地,二n号地有个很大的仓库。 农场有M条道路(双向),道路i连接着ai号地和bi号地,长度为ci。 约翰希望按照从家里出发,经过若干块地后到达仓库,然后再返回家中的顺序带朋友参观。 如果要求往返不能经过同一条路两次,求参观路线总长度的最小值。 解析: 如果只考虑去或者回的情况,问题只不过是无向图中两点之间的最短路问题。 但是现在要去要回

poj 2594 二分图最大独立集

题意: 求一张图的最大独立集,这题不同的地方在于,间接相邻的点也可以有一条边,所以用floyd来把间接相邻的边也连起来。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <sta

poj 3422 有流量限制的最小费用流 反用求最大 + 拆点

题意: 给一个n*n(50 * 50) 的数字迷宫,从左上点开始走,走到右下点。 每次只能往右移一格,或者往下移一格。 每个格子,第一次到达时可以获得格子对应的数字作为奖励,再次到达则没有奖励。 问走k次这个迷宫,最大能获得多少奖励。 解析: 拆点,拿样例来说明: 3 2 1 2 3 0 2 1 1 4 2 3*3的数字迷宫,走两次最大能获得多少奖励。 将每个点拆成两个