智能摘要文章源自JAVA秀-https://www.javaxiu.com/33192.html
这是最容易想到的方法,先把无序数组从大到小进行排序,排序后的第k个元素,自然就是数组中的第k大元素。如果大于数组A的最小元素,则插入到数组A中,并把曾经的最小元素"挤出去"。遍历到元素20,由于20>3,20取代堆顶位置,并调整堆。遍历到元素24,由于24>5,24取代堆顶位置,并调整堆。我们在寻找第k大元素的时候,也可以利用这个思路,以某个元素A为基准,把大于于A的元素都交换到数组左边,小于A的元素都交换到数组右边。文章源自JAVA秀-https://www.javaxiu.com/33192.html
原文约 1921 字 | 图片 40 张 | 建议阅读 4 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/33192.html
【文末送书】漫画:寻找无序数组的第k大元素
小灰 小哈学Java 文章源自JAVA秀-https://www.javaxiu.com/33192.html
文末送书【漫画算法2】哟,有兴趣的小伙伴可以参加一波~文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
————— 第二天 —————文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
题目是什么意思呢?比如给定的无序数组如下:文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
如果 k=6,也就是要寻找第6大的元素,这个元素是哪一个呢?文章源自JAVA秀-https://www.javaxiu.com/33192.html
显然,数组中第一大的元素是24,第二大的元素是20,第三大的元素是17 ...... 第6大的元素是9。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
方法一:排序法文章源自JAVA秀-https://www.javaxiu.com/33192.html
这是最容易想到的方法,先把无序数组从大到小进行排序,排序后的第k个元素,自然就是数组中的第k大元素。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
方法二:插入法文章源自JAVA秀-https://www.javaxiu.com/33192.html
维护一个长度为k的数组A的有序数组,用于存储已知的k个较大的元素。文章源自JAVA秀-https://www.javaxiu.com/33192.html
接下来遍历原数组,每遍历到一个元素,和数组A中最小的元素相比较,如果小于等于数组A的最小元素,继续遍历;如果大于数组A的最小元素,则插入到数组A中,并把曾经的最小元素“挤出去”。文章源自JAVA秀-https://www.javaxiu.com/33192.html
比如k=3,先把最左侧的7,5,15三个数有序放入数组A当中,代表当前最大的三个数。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
这时候,遍历到3, 由于3<5,继续遍历。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
接下来遍历到17,由于17>5,插入到数组A的合适位置,类似于插入排序,并把原先最小的元素5“挤出去”。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
继续遍历原数组,一直遍历到数组的最后一个元素......文章源自JAVA秀-https://www.javaxiu.com/33192.html
最终,数组A中存储的元素是24,20,17,代表着整个数组中最大的3个元素。此时数组A中的最小的元素17就是我们要寻找的第k大元素。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
————————————文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
什么是二叉堆?不太了解的小伙伴可以先看看这一篇:漫画:什么是二叉堆?(修正版)文章源自JAVA秀-https://www.javaxiu.com/33192.html
简而言之,二叉堆是一种特殊的完全二叉树,它包含大顶堆和小顶堆两种形式。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
其中小顶堆的特点,是每一个父节点都大于等于自己的子节点。要解决这个算法题,我们可以利用小顶堆的特性。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
方法三:小顶堆法文章源自JAVA秀-https://www.javaxiu.com/33192.html
维护一个容量为k的小顶堆,堆中的k个节点代表着当前最大的k个元素,而堆顶显然是这k个元素中的最小值。文章源自JAVA秀-https://www.javaxiu.com/33192.html
遍历原数组,每遍历一个元素,就和堆顶比较,如果当前元素小于等于堆顶,则继续遍历;如果元素大于堆顶,则把当前元素放在堆顶位置,并调整二叉堆(下沉操作)。文章源自JAVA秀-https://www.javaxiu.com/33192.html
遍历结束后,堆顶就是数组的最大k个元素中的最小值,也就是第k大元素。文章源自JAVA秀-https://www.javaxiu.com/33192.html
假设k=5,具体的执行步骤如下:文章源自JAVA秀-https://www.javaxiu.com/33192.html
1.把数组的前k个元素构建成堆。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
2.继续遍历数组,和堆顶比较,如果小于等于堆顶,则继续遍历;如果大于堆顶,则取代堆顶元素并调整堆。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
遍历到元素2,由于 2<3,所以继续遍历。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
遍历到元素20,由于 20>3,20取代堆顶位置,并调整堆。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
遍历到元素24,由于 24>5,24取代堆顶位置,并调整堆。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
以此类推,我们一个一个遍历元素,当遍历到最后一个元素8的时候,小顶堆的情况如下:文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
3.此时的堆顶,就是堆中的最小值,也就是数组中的第k大元素。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
这个方法的时间复杂度是多少呢?文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
1.构建堆的时间复杂度是 O(k)文章源自JAVA秀-https://www.javaxiu.com/33192.html
2.遍历剩余数组的时间复杂度是O(n-k)文章源自JAVA秀-https://www.javaxiu.com/33192.html
3.每次调整堆的时间复杂度是 O(logk)文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
其中2和3是嵌套关系,1和2,3是并列关系,所以总的最坏时间复杂度是O((n-k)logk + k)。当k远小于n的情况下,也可以近似地认为是O(nlogk)。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
这个方法的空间复杂度是多少呢?文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
刚才我们在详细步骤中把二叉堆单独拿出来演示,是为了便于理解。但如果允许改变原数组的话,我们可以把数组的前k个元素“原地交换”来构建成二叉堆,这样就免去了开辟额外的存储空间。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
因此,方法的空间复杂度是O(1)。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
/**
* 寻找第k大的元素
* @param array 待调整的堆
* @param k 第几大
*/
publicstaticint findNumberK(int[] array,int k){
//1.用前k个元素构建小顶堆
buildHeap(array, k);
//2.继续遍历数组,和堆顶比较
for(int i=k; i<array.length;i++){
if(array[i]> array[0]){
array[0]= array[i];
downAdjust(array,0, k);
}
}
//3.返回堆顶元素
return array[0];
}
/**
* 构建堆
* @param array 待调整的堆
* @param length 堆的有效大小
*/
privatestaticvoid buildHeap(int[] array,int length){
// 从最后一个非叶子节点开始,依次下沉调整
for(int i =(length-2)/2; i >=0; i--){
downAdjust(array, i, length);
}
}
/**
* 下沉调整
* @param array 待调整的堆
* @param index 要下沉的节点
* @param length 堆的有效大小
*/
privatestaticvoid downAdjust(int[] array,int index,int length){
// temp保存父节点值,用于最后的赋值
int temp = array[index];
int childIndex =2* index +1;
while(childIndex < length){
// 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
if(childIndex +1< length && array[childIndex +1]< array[childIndex]){
childIndex++;
}
// 如果父节点小于任何一个孩子的值,直接跳出
if(temp <= array[childIndex])
break;
//无需真正交换,单向赋值即可
array[index]= array[childIndex];
index = childIndex;
childIndex =2* childIndex +1;
}
array[index]= temp;
}
publicstaticvoid main(String[] args){
int[] array =newint[]{7,5,15,3,17,2,20,24,1,9,12,8};
System.out.println(findNumberK(array,5));
}
文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
方法四:分治法文章源自JAVA秀-https://www.javaxiu.com/33192.html
大家都了解快速排序,快速排序利用分治法,每一次把数组分成较大和较小的两部分。文章源自JAVA秀-https://www.javaxiu.com/33192.html
我们在寻找第k大元素的时候,也可以利用这个思路,以某个元素A为基准,把大于于A的元素都交换到数组左边,小于A的元素都交换到数组右边。文章源自JAVA秀-https://www.javaxiu.com/33192.html
比如我们选择以元素7作为基准,把数组分成了左侧较大,右侧较小的两个区域,交换结果如下:文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
包括元素7在内的较大元素有8个,但我们的k=5,显然较大元素的数目过多了。于是我们在较大元素的区域继续分治,这次以元素12位基准:文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
这样一来,包括元素12在内的较大元素有5个,正好和k相等。所以,基准元素12就是我们所求的。文章源自JAVA秀-https://www.javaxiu.com/33192.html
这就是分治法的大体思想,这种方法的时间复杂度甚至优于小顶堆法,可以达到O(n)。有兴趣的小伙伴可以尝试用代码实现一下。文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html
感谢小伙伴们长期以来对【小哈学Java】的支持,本次抽奖送书小灰的新书《漫画算法2》,共计5本,开奖时间为6.18日晚八点,有兴趣的小伙伴请关注下面的公众号,后台回复关键词【抽奖】,即可参与~文章源自JAVA秀-https://www.javaxiu.com/33192.html
文章源自JAVA秀-https://www.javaxiu.com/33192.html

评论