查找最小的k个元素
来源:互联网 发布:淘宝格士子湖铺 编辑:程序博客网 时间:2024/06/05 23:00
目标:给定一个整型序列,找到最小的k个元素
例如:3,2,5,6,2,1,7,8 。若k=3,则返回1,2,3
思路
1. quickSort先对序列进行排序,花费O(nlog)时间,然后取出k个元素,花费O(k)时间,所以总的时间为O(k+nlogn)
2. 首先遍历序列前k个元素,存放到一个数组中,利用选择或交换排序,找出这k个数中的最大数k_max,所花O(k)时间。然后再遍历整个序列的后n-k个数,新的元素x<?k_max 如果小于,则用x代替k_max,否则,则不做更新数组。总的时间复杂度为n*O(k)=O(nk)时间。
回顾选择排序
长度为n的无序数组,第一次遍历n个数,选出最小的数,与第一个元素交换。第二遍遍历n-1个数,就是除去了第一个元素了,在n-1个元素中找出最小的数,与第二个数交换(在n-1个元素的数组中就相当于是第一个元素了)。
时间复杂度O(n^2),空间复杂度O(1)用于存放交换用到的temp和记录最小值的index。
代码:
public static void select(String[] array){int i = 0;while(i<array.length){String min = array[i];int min_index = i;for(int j=i+1; j<array.length;j++){if(array[j].compareTo(min)<0){min = array[j];min_index = j;}}String temp = array[i];array[i] = min;array[min_index] = temp;i++;}}
3. 利用k个元素的最大堆
回顾堆数据结构
public static void minHeapUp(String[] array, int i){//插入的时候会用到上浮操作int j;String temp = array[i];j = (i - 1)/2;//父节点while(j>=0 && i!=0){//因为是最小堆,所以如果父节点比子节点小,这是正常情况,//不需要恢复堆次序,所以直接breakif(array[j].compareTo(temp)<=0)break;array[i] = array[j];i=j;j = (i-1)/2;}array[i] = temp;}对于maxHeapUp,把array[j].compareTo(temp)<=0换成>=0即可
public static void minHeapDown(String[] array, int i, int n){//删除节点会用到下移操作int j;String temp = array[i];j = 2 * i + 1;//子节点while(j<n){if(j + 1 < n && array[j+1].compareTo(array[j])<0)j = j+1;//在保证有右子节点的情况下,找左右孩子中<span style="color:#ff0000;">较小</span>的//子节点比父节点大,则不用对父节点下移if(array[j].compareTo(temp)>=0)break;array[i] = array[j];i=j;j = 2*i+1;}array[i] = temp;}对于最大堆
public static void maxHeapFixdown(String[] array, int i, int n){//删除节点会用到下移操作int j;String temp = array[i];//要下沉的节点j = 2 * i + 1;//子节点while(j<n){if(j + 1 < n && array[j+1].compareTo(array[j])>0)j = j+1;//在保证有右子节点的情况下,找左右孩子中<span style="color:#ff0000;">较大</span>的//子节点比父节点大,则不用对父节点下移if(array[j].compareTo(temp)<=0)break;array[i] = array[j];i=j;j = 2*i+1;}array[i] = temp;}
时间:O(n*log(n))
序列对应一个完全二叉树;从最后一个分支结点(n div 2-1)开始,到根(0)为止,依次对每个分支结点进行调整(下沉),
以便形成以每个分支结点为根的堆,当最后对树根结点进行调整后,整个树就变成了一个堆。
时间:O(n)
public static void makeMinHeap(String[] array, int n){for(int i = n / 2 - 1; i >= 0; i--){minHeapFixdown(array,i,n);}}建立最大堆的过程。
public static void makeMaxHeap(String[] array, int n){for(int i = n / 2 - 1; i >= 0; i--){maxHeapFixdown(array,i,n);}
这样就把一个数组创建成了一个最大堆。
首先遍历数组中的前k个元素,建立最大堆,用时O(k)。root为k个元素中的最大元素,称作k_max。然后接着在数组中遍历剩下的N-k个元素。每次取出元素x与k_max比较,如果x<k_max,则用x取代k_max,然后调整恢复堆次序,使得新一轮的k个数中的最大值浮到root,成为新的k_max。如果x>k_max,则不操作。恢复堆次序耗时O(lgk)时间,最多可进行n-k次堆恢复次序,因此需要耗时(n-k)O(lgk)时间。
加上之前的O(k),整个查找过程需要耗时O(k+(n-k)lgk)=O(nlgk)时间。
代码
public static void getKminbymaxHeap(String[] array, int n, int k){String[] k_heap = new String[k];int i = 0;//先把数组前k项存进最大堆中while(i<k){k_heap[i] = array[i];i++;}makeMaxHeap(k_heap,k_heap.length);//建立k元素最大堆//当数组的后n-k里的元素比最大堆的root小时,执行交换和下沉操作while(i<array.length){if(array[i].compareTo(k_heap[0])<0){k_heap[0] = array[i];maxHeapFixdown(k_heap,0,k);} i++;}//打印最大堆for(int j = 0; j<k_heap.length;j++){System.out.println(k_heap[j]);}}
4. 既然有最大堆保存k个元素,我们可以尝试对整个数组中的元素建立一个最小堆,然后每次取出root,执行k次。
对于n个元素的数组建堆,需要O(n)时间,每次取出root然后恢复堆次序的时间是O(lgn),执行k次,所以总的时间,加上建堆,有O(n+klgn)复杂度。
关于建立k个元素的最大堆,和建立n个元素的最小堆,也就是3,4两种方法的比较。
即O(nlgk)和O(n+klgn)。
通过两个复杂度的比值,即nlgk/(n+klgn),判断是大于1还是小于1.
当n趋向于无穷时nlgk/(n+klgn)=lgk,分子分母同时用洛必达法则得 lgk/(1+k/nln2),因为n趋向无穷,所以k/nln2->0,所以整个分式等于lgk>=1当k>=2.因此在这种情况下,建立k元素的最大堆的方案的复杂度要大于建立n元素的最小堆然后再取出k个元素的方案。但是如果考虑空间复杂度,则建立最小堆的空间复杂度为O(n),远大于k元素最大堆的空间复杂度O(k),所以,it depends.
对于n元素最小堆情况,当取出root,并且将数组尾巴的元素替换到root的位置之后,只需要对该root元素下移k次即可,不需要像堆操作的O(lgn)复杂度,只需要O(k)复杂度(每次下移),所以总的复杂度为O(n+k^2)即可。
Proof by intuition:
最小堆的数组存储方式,不是严格递增,但是总体的关系是递增的。每次下移的过程,都是把父节点与较小的子节点交换位置,所以第一次的堆次序恢复操作要执行k次,那个会被选出来的元素就上移了一位。由于每个子节点也是一个最小堆,所以在这个节点形成的子树中的其他元素肯定比这个元素要大,无需考虑它们的上浮。因此只需要移动k次即可。然后第二轮的堆次序恢复就执行k-1的,第三轮k-2次。。。
这样操作的缺点是破坏了最小堆的结构,但是遭到破坏的仅是在我们关心的元素以外,第一次保证了k层以上的最小堆结构,而下面的部分遭到破坏,第二层保证了k-1层,但是我们不care下面是否还保证最小堆结构了。
public static void getKmin(String[] array, int n, int k){int j = n-1;for(int i = k; i>0; i--, j--){String min = array[0];array[0] = array[j];minHeapFixdown(array,0,i);//这里只需要下降k次,因此n=iSystem.out.println(min);}}
5. Randomized-Select,每次随机选取数列中的一个元素作为pivot,在E[O(n)]时间内找到第k小的元素,然后花O(k)时间遍历之前的比k小的元素,总的期望时间复杂度为O(n+k)=O(k)。但是在最坏情况下的复杂度为O(n^2),怎样解决呢?
partition的代码
private static int partition(int[] a, int lo, int hi){int x = a[lo];int i = lo;int j = lo + 1;while (j<=hi){if(a[j]<=x){i++;exch(a,i,j);}j++;}exch(a,lo,i);return i;//return index of item now known to be in place}随机partition代码
private static int randomized_partition(int[] a, int lo, int hi){int i = (int) Math.round(Math.random() * (hi - lo) + lo);exch(a, a[lo],a[i]);return partition(a,lo,hi);}
随机快速选择代码
public static int randomized_select(int[] a, int lo, int hi, int k)//k is rank{if(lo==hi) return a[lo];int j = randomized_partition(a,lo,hi);int rank_pivot = j - lo + 1;if (rank_pivot==k) return a[j];if(k<rank_pivot) return randomized_select(a,lo,j-1,k); else return randomized_select(a,j+1,hi,k-rank_pivot);}
6. Idea is generating good pivot recursively, not a random pivot just like what've been done in 5.
a. Divide the n elements into floor(n/5) groups. Each group has 5 elements. Find the median of each group. It needsO(n) time
b. Recursively select the median x of the floor(n/5) groups median. NeedT(n/5) time, also linear.
c. Partition with x as pivot. Let k = rank(x).
d. if i = k, then return x.
if i<k, then recursively select i smallest element in the lower part of array.
else, recursively select (i-k)th smallest element in the high part of array. It needsT(3/4n)
T(n)<=T(n/5)+T(3/4n)+O(n)
Proof T(n)<=cn by substitution.
T(n)>cn/5+3cn/4+O(n)=19n/20+O(n) = cn-(1/20-O(n))<cn for c sufficient large.
- 查找最小的k个元素
- 查找最小的k个元素
- 查找最小的 k个元素
- 查找最小的 K 个元素
- 查找最小的k个元素
- 5.查找最小的k个元素
- 查找最小的k个元素
- 查找最小的k个元素
- 查找最小的k个元素
- 5.查找最小的k个元素
- 查找最小的k个元素(数组)
- 查找最小的K个元素
- 查找最小的K个元素
- 查找最小的k个元素
- 查找最小的k个元素[算法]
- 查找最小的k 个元素
- 查找最小的k个元素
- 查找最小的k个元素
- a+b
- Couldn’t find MySQL server
- web3.0
- android studio 编译调试 错误
- cscope
- 查找最小的k个元素
- NYOJ 219 An problem about date【数学公式】
- 求所有5位数的黑洞数
- 快速排序方法
- NYOJ 278 排队【语瑟夫环】
- Linux(RHEL)5.4/5.5/5.8/6.0/6.3 ISO镜像文件-下载地址
- 冒泡排序方法
- UVa 140 - Bandwidth(全排列+回溯剪枝)
- XML-Pull解析