十依据一个有用的算法来找到最小(最大)的k的数量-线性搜索算法
例如:进入1。2。3,4,5,6。7。8此8数字,最小的4图的1,2,3,4。
思路1:最easy想到的方法:先对这个序列从小到大排序。然后输出前面的最小的k个数就可以。假设选择高速排序法来进行排序,则时间复杂度:O(n*logn)
注:针对不同问题我们应该给出不同的思路。假设在应用中这个问题的规模不大。或者求解前k个元素的频率非常高,或者k是不固定的。
那么我们花费较多的时间对问题排序。在以后是使用中能够线性时间找到问题的解,整体来说,那么思路一的解法是最优的。
思路2:在思路1的基础上更进一步想想,题目并没有要求要查找的k个数。甚至后n-k个数是有序的。既然如此。咱们又何必对全部的n个数都进行排序列?如此。我们能想打的一个方法是:遍历n个数,先把最先遍历到得k个数存入大小为k的数组之中,对这k个数。利用选择或交换排序,找到k个数中的最大数kmax(kmax设为k个元素的数组中最大元素),用时O(k)(你应该知道,插入或选择排序查找操作须要O(k)的时间),后再继续遍历后n-k个数,x与kmax比較:假设x<kmax,则x取代kmax,并再次又一次找出k个元素的数组中最大元素kmax‘;假设x>kmax,则不更新数组。这样。每次更新或不更新数组的所用的时间为O(k)或O(0),整趟下来,总的时间复杂度平均下来为:n*O(k)=O(n*k)
思路3:与思路2方法类似,仅仅是用容量为k的最大堆代替思路2中数组的作用(从数组中找最大数须要O(k)次查找,而从更新一个堆使之成为最大堆仅仅须要O(logk)次操作)。详细做法例如以下:用容量为k的最大堆存储最先遍历到的k个数,并如果它们即是最小的k个数。建堆费时O(k)后。有k1<k2<…<kmax(kmax设为大顶堆中最大元素)。继续遍历数列,每次遍历一个元素x。与堆顶元素比較,x<kmax,更新堆(用时logk),否则不更新堆。
这样下来,总费时O(k+(n-k)*logk)=O(n*logk)。
思路4:按编程之美中给出的描写叙述,类似高速排序的划分方法,N个数存储在数组S中,再从数组中随机选取一个数X(随机选取枢纽元。可做到线性期望时间O(N)的复杂度)。把数组划分为Sa和Sb俩部分,Sa<=X<=Sb,假设要查找的k个元素小于Sa的元素个数,则返回Sa中较小的k个元素,否则返回Sa中全部元素+Sb中小的k-|Sa|个元素。像上述过程一样,这个运用类似高速排序的partition的高速选择SELECT算法寻找最小的k个元素,在最坏情况下亦能做到O(N)的复杂度。
oh。太酷了,有没有!
思路5:仍然用到数据结构:堆。详细做法为:针对整个数组序列建最小堆。建堆所用时间为O(n),然后取堆中的前k个数。总的时间复杂度即为:O(n+k*logn)。
思路6:与上述思路5类似,不同的是在对元素数组原地建最小堆O(n)后,然后提取K次。可是每次提取时。换到顶部的元素仅仅须要下移顶多k次就足够了,下移次数逐次降低(而上述思路5每次提取都须要logn,所以提取k次,思路5须要k*logn。而本思路仅仅须要K^2)。此种方法的复杂度为O(n+k^2)。
此方法可能不太直观,一个更直观的理解是:每次取出堆顶元素后,最小堆的性质被破坏了,我们须要调整最小堆使之满足最小堆的性质。
因为我们仅仅须要求取前k个数,我们无需将整个堆都完整的调整好。仅仅需保证堆的最上面k个数是最小的就能够,即第一趟调整保持第0层到第k层是最小堆,第二趟调整保持第0层到第k-1层是最小堆…,依次类推。
几个简单的提取前k个元素,居然能够有这么多的思路来实现,复杂度逐渐减少。感觉是多么酷的一件事情啊。
以下给出思路三和思路四的參考代码(这些代码都凝结了高速排序和堆排序的思想,所以说之前的算法有用,主要是思想):
思路三:
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#define PARENT(i) (i)/2
#define LEFT(i) 2*(i)+1
#define RIGHT(i) 2*(i+1) void swap(int *a,int *b)
{
*a=*a^*b;
*b=*a^*b;
*a=*a^*b;
}
void max_heapify(int *arr,int index,int len)//建立大顶堆的过程。求前k个最小,要健最大堆
{
int l=LEFT(index);//全部操作相似于堆排序
int r=RIGHT(index);
int largest;
if(l<len && arr[l]>arr[index])
largest=l;
else
largest=index;
if(r<len && arr[r]>arr[largest])
largest=r;
if(largest != index){//将最大元素提升。并递归
swap(&arr[largest],&arr[index]);
max_heapify(arr,largest,len);//递归
}
} void build_maxheap(int *arr,int len)//開始建立大顶堆是必须的
{
int i;
if(arr==NULL || len<=1)
return;
for(i=len/2+1;i>=0;--i)
max_heapify(arr,i,len);
} void k_min(int *arr,int len,int k)
{
int i;
build_maxheap(arr,k);
for (i = k;i < len;i++)
{
if (arr[i] < arr[0])//就是这一个地方跟堆排序不一样,这里仅仅是交换比堆顶大的元素。
{
arr[0] = arr[i];
max_heapify(arr,0,k);
}
}
}
/*
void heap_sort(int *arr,int len)//这是堆排序的主函数
{
int i;
if(arr==NULL || len<=1)
return;
build_maxheap(arr,len); for(i=len-1;i>=1;--i){
swap(&arr[0],&arr[i]);
max_heapify(arr,0,--len);
}
}
*/ int main()
{
int arr[10]={91,8,6,82,15,18,7,46,29,12};
int i;
k_min(arr,10,4);
for(i=0;i<10;++i)//仅仅须要输出前k个元素就可以。
printf("%d ",arr[i]);
system("pause");
}
思路四的实现:
Kbig(S, k):
if(k <= 0):
return [] // 返回空数组
if(length S <= k):
return S
(Sa, Sb) = Partition(S)
return Kbig(Sa, k).Append(Kbig(Sb, k – length Sa) Partition(S):
Sa = [] // 初始化为空数组
Sb = [] // 初始化为空数组
Swap(s[1], S[Random()%length S]) // 随机选择一个数作为分组标准,以
// 避免特殊数据下的算法退化,也可
// 以通过对整个数据进行洗牌预处理
// 实现这个目的
p = S[1]
for i in [2: length S]:
S[i] > p ? Sa.Append(S[i]) : Sb.Append(S[i])
// 将p增加较小的组。能够避免分组失败。也使分组
// 更均匀,提高效率
length Sa < length Sb ? Sa.Append(p) : Sb.Append(p)
return (Sa, Sb)
以下是代码实现:
#include <stdio.h>
#include <stdlib.h>
void swap(int *a,int *b)
{ *a=*a^*b;
*b=*a^*b;
*a=*a^*b;
}
/*
为了简单起见,这里仅仅是单纯的选取第一个元素作为枢纽元素。这样选取枢纽,就难避免使得算法easy退化。 */
int k_big(int arr[],int low,int high,int k)
{
int pivot = arr[low];//pivot的选择能够更优
int high_tmp = high;
int low_tmp = low;
while(low < high){
//从右向左查找,直到找到第一个小于枢纽元素为止
while (low < high && arr[high] >= pivot)
{
--high;
}
arr[low] = arr[high];
//从左向右查找,直到找到第一个大于枢纽元素为止
while (low < high && arr[low] <= pivot)
{
++low;
}
arr[high] = arr[low];
}
arr[low] = pivot;//这里low == high if (low == k - 1)//正好取到了第k个值
{
return arr[low];
}else if(low > k - 1)//前k个值在low前面的子数组中
{
return k_big(arr,low_tmp,low-1,k);
}else//前low个数值已经是前k个数值,后k-low个在后半部分中
{
return k_big(arr,low+1,high_tmp,k);
}
}
int main()
{
int arr[10]={-91,0,6,82,15,18,7,46,-29,12};
int i;
k_big(arr,0,9,4);
for(i=0;i<10;++i)
printf("%d ",arr[i]);
system("pause");
}
以下是《算法设计与分析》书中给出的一种思路,源代码例如以下:
//QuickSelect 将第k小的元素放在 a[k-1]
void QuickSelect( int a[], int k, int left, int right )
{
int i, j;
int pivot; if( left + cutoff <= right )
{
pivot = median3( a, left, right );
//取三数中值作为枢纽元。能够非常大程度上避免最坏情况
i = left; j = right - 1;
for( ; ; )
{
while( a[ ++i ] < pivot ){ }
while( a[ --j ] > pivot ){ }
if( i < j )
swap( &a[ i ], &a[ j ] );
else
break;
}
//重置枢纽元
swap( &a[ i ], &a[ right - 1 ] ); if( k <= i )
QuickSelect( a, k, left, i - 1 );
else if( k > i + 1 )
QuickSelect( a, k, i + 1, right );
}
else
InsertSort( a + left, right - left + 1 );
}
这个高速选择SELECT算法。类似高速排序的划分方法。N个数存储在数组S中,再从数组中选取“中位数的中位数”作为枢纽元X,把数组划分为Sa和Sb俩部分,Sa<=X<=Sb,假设要查找的k个元素小于Sa的元素个数,则返回Sa中较小的k个元素。否则返回Sa中全部元素+Sb中小的k-|Sa|个元素。这样的解法在平均情况下能做到 O(N)
的复杂度。
以下是算法导论中给出的一种方法:
《算法导论》介绍了一个随机选取主元的选择算法RANDOMIZED-SELECT。它每次都是随机选取数列中的一个元素作为主元,在 O(n)
的时间内找到第k小的元素,然后遍历输出前面的k个小的元素。平均时间复杂度: O(n+k)=O(n)
(当k比較小时)。
我们知道,高速排序是以固定的第一个或最后一个元素作为主元,每次递归划分都是不均等的。最后的平均时间复杂度为: O(n*logn)
。而RANDOMIZED-SELECT与普通的高速排序不同,它每次递归都是随机选择序列,从第一个到最后一个元素中任一一个作为主元。
以下是RANDOMIZED-SELECT(A, p, r)完整伪码:
PARTITION(A, p, r) //partition过程 p为第一个数,r为最后一个数
x ← A[r] //以最后一个元素作为主元
i ← p - 1
for j ← p to r - 1
do if A[j] ≤ x
then i ← i + 1
exchange A[i] <-> A[j]
exchange A[i + 1] <-> A[r]
return i + 1 RANDOMIZED-PARTITION(A, p, r) //随机快排的partition过程
i ← RANDOM(p, r) //i 随机取p到r中个一个值
exchange A[r] <-> A[i] //以随机的 i作为主元
return PARTITION(A, p, r) //调用上述原来的partition过程 RANDOMIZED-SELECT(A, p, r, i) //以线性时间做选择。目的是返回数组A[p..r]中的第i 小的元素
if p = r //p=r。序列中仅仅有一个元素
then return A[p]
q ← RANDOMIZED-PARTITION(A, p, r) //随机选取的元素q作为主元
k ← q - p + 1 //k表示子数组 A[p…q]内的元素个数,处于划分低区的元素个数加上一个主元元素
if i == k //检查要查找的i 等于子数组中A[p....q]中的元素个数k
then return A[q] //则直接返回A[q]
else if i < k
then return RANDOMIZED-SELECT(A, p, q - 1, i)
//得到的k 大于要查找的i 的大小,则递归到低区间A[p。q-1]中去查找
else return RANDOMIZED-SELECT(A, q + 1, r, i - k)
//得到的k 小于要查找的i 的大小。则递归到高区间A[q+1,r]中去查找。
也就是线性查找算法:
算法步骤:
1. 将n个元素每5个一组,分成n/5(上界)组。
2. 取出每一组的中位数,随意排序方法,比方插入排序。
3. 递归的调用selection算法查找上一步中全部中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。
4. 用x来切割数组。设小于等于x的个数为k。大于x的个数即为n-k。
5. 若i==k,返回x。若i<k,在小于x的元素中递归查找第i小的元素。若i>k,在大于x的元素中递归查找第i-k小的元素。
终止条件:n=1时,返回的即是i小元素。
版权声明:本文博客原创文章,博客,未经同意,不得转载。
十依据一个有用的算法来找到最小(最大)的k的数量-线性搜索算法的更多相关文章
- WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]
原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇] 在<上篇>中,我通过使用Delegate的方式解决了服务调用过程中的异常处理以及对服务代理的关闭.对于<WCF技术 ...
- WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]
原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇] 在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道:当遇到某些异常,我们需要强行中止(Abor ...
- 每天一个小算法(5)----找到链表倒数第K个结点
估计这个问题在面试中被问烂了. 思路是先找到正数的第K个结点的指针pT,然后和指向头结点的指针pN一起向后移动,直到第K个指针指向NULL,此时pN指向的结点即倒数第K个结点. 如图: #includ ...
- Python之路【第二十四篇】Python算法排序一
什么是算法 1.什么是算法 算法(algorithm):就是定义良好的计算过程,他取一个或一组的值为输入,并产生出一个或一组值作为输出.简单来说算法就是一系列的计算步骤,用来将输入数据转化成输出结果. ...
- 近十年one-to-one最短路算法研究整理【转】
前言:针对单源最短路算法,目前最经典的思路即标号算法,以Dijkstra算法和Bellman-Ford算法为根本演进了各种优化技术和算法.针对复杂网络,传统的优化思路是在数据结构和双向搜索上做文章,或 ...
- 近十年one-to-one最短路算法研究整理
前言:针对单源最短路算法,目前最经典的思路即标号算法,以Dijkstra算法和Bellman-Ford算法为根本演进了各种优化技术和算法.针对复杂网络,传统的优化思路是在数据结构和双向搜索上做文章,或 ...
- Alink漫谈(十二) :在线学习算法FTRL 之 整体设计
Alink漫谈(十二) :在线学习算法FTRL 之 整体设计 目录 Alink漫谈(十二) :在线学习算法FTRL 之 整体设计 0x00 摘要 0x01概念 1.1 逻辑回归 1.1.1 推导过程 ...
- XMind十大最有用的功能
XMind十大最有用的功能 XMind是一款顶级商业品质的思维导图软件和头脑风暴软件,在企业和教育领域都有很广泛的应用,XMind功能全面,易上手,在此小编给大家整理出了XMind十大最有用的功能以供 ...
- 算法:输入一个链表,输出该链表中倒数第k个结点。
算法:输入一个链表,输出该链表中倒数第k个结点.<剑指offer> 思路加到注释里面了: 1:两个if判断是否返回值为空,首个为空,没有第k个值: 2:for循环找到倒数第k个值,返回为a ...
随机推荐
- 大爱jQuery,10美女模特有用jQuery/CSS3插入(集成点免费下载)
整合下载地址:http://download.csdn.net/detail/yangwei19680827/7343001 jQuery真的是一款非常犀利的Javascript框架,利用jQuery ...
- swift笔记 (三) —— 字符和字符串
字符串和字符 苹果要是不提供了unicode的字符串和字符,那就是他们公司全部人的脑袋都被门夹过 他自己家都要发非常多国家的版本号的软件,怎么可能不用unicode呢 此处略去30字... 这里能够拿 ...
- Cocos2d-x学习笔记(六) 定时器Schedule的简单应用
Cocos2d-x中的定时器使用非常easy,共同拥有3种:schedule.scheduleUpdate和scheduleOnce.简介一下三种的差别: schedule,每隔指定时间运行某个 ...
- ZOJ 3826 Hierarchical Notation 模拟
模拟: 语法的分析 hash一切Key建设规划,对于记录在几个地点的每个节点原始的字符串开始输出. . .. 对每一个询问沿图走就能够了. .. . Hierarchical Notation Tim ...
- Vbox创建COM对象失败
近期在使用vbox时出现下面错误:创建COM对象失败,应用程序将被中断 在CMD里面输入下面命令: C:\Users\Administrator>d: D:\>cd D:\Program ...
- 使用Simple DNS plus 构建自己的DNS
1.下载并安装Simple DNS plus 2.界面例如以下: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2tfYm9zcw==/font/5a6L ...
- Atitit.软件GUIbutton和仪表板(01)--警报系统--
Atitit.软件GUIbutton和仪表板(01)--警报系统-- 1. 温度报警防区(鲁大师,360taskman) 1 2. os-区-----cpu_mem_io资源占用监測 1 3. Vm区 ...
- UVA 12206 - Stammering Aliens(后缀数组)
UVA 12206 - Stammering Aliens 题目链接 题意:给定一个序列,求出出现次数大于m,长度最长的子串的最大下标 思路:后缀数组.搞出height数组后,利用二分去查找就可以 这 ...
- UVALive 6469 Deranged Exams (排列:力绝对是无辜的高中知识啊)
标题手段 : 给你个n([1,17])表达n无论从数据结构.然后n个对这些术语的定义,让你对这些术语和定义对号入座(相当于进行连线,A术语连A术语的定义).然后一个 k([0,n]).问你至少前k个术 ...
- 合理设置MTU,提升下载速度
可能很少有雷友注意过“本机.网络”的“MTU”值对自己网络性能产生的影响.对于追求更快的下载速度来说,MTU值设置不当,就仿佛穿着高跟鞋跑步一般. MTU是什么? “MTU=最大传输单元 单位:字节” ...