有一个数组,其大小已知,现在要找出其中第K个大小的数。作为最基本的算法,我们可以先对整个数组进行排序,然后直接找出第K个最小的数即可,但这种解法必须要有一个排序的过程,且排序的结果对于所要求的结果没有直接关系。现在我们通过一个快速排序的变种算法来解决这个问题。
同样是采取分治法,我们将整个数组一分为二,将小的放到左边,将大的放到右边,并判断K和这两边的子数组长度的大小。如果K的left数组小,则K所在的数必定在left数组之中,否则则在right数组当中。这样就直接去掉接近于一半的数组。然后使用递归再在剩余的left数组中来查找。
整个过程可以描述成以下过程:
- 使用快速排序的pivot分割过程,将数组分割成left数组和right数组以及pivot三个部分
- 如果left数组长度大于K,则继续在left数组中查找,否则在right数组中查找
在上述的描述过程中,有几个特殊点是我们需要考虑的:
- 如果K=1,则表示要找的数即整个数组中最小的数,那么直接查找最小的数即可
- 如果K=数组长度,则表示要找的数即整个数组中最大的数,那么直接查找最大的数即可
- 在数组分割之后,如果K=left数组长度+1,即pivot即是我们所要找的数,直接返回即可
有了以上的描述,那么整个实现过程就不再复杂了。整个实现过程如下所示
private static int find(int[] ints, int k) { //转向子方法进行查找,将相应起始点和结束点传递之 return find(ints, k, 0, ints.length - 1); }
以上方法为起始方法,直接转向子方法
private static int find(int[] ints, int k, int start, int end) { //第1步,有效值判断 if(k < 1) throw new IllegalArgumentException("不能<1"); if(k > (end - start) + 1) throw new IllegalArgumentException("超出范围"); //第2步,找最小的数 //寻找最小 if(k == 1) { int min = ints[start]; for(int i = start + 1;i <= end;i++) { if(ints[i] < min) min = ints[i]; } return min; } //第3步,找最大的数 //寻找最大数 if(k == (end - start) + 1) { int max = ints[start]; for(int i = start + 1;i <= end;i++) { if(ints[i] > max) max = ints[i]; } return max; } //第4步,数组分割 //简单取最后一项 int i = start, j = end - 1; while(true) { while(ints[i] < ints[end]) i++; while(ints[j] > ints[end] && j > i) j--; if(i < j) swap(ints, i++, j--); else break; } swap(ints, i, end); //第5步,判断k是否为left数组长度+1上 int alreadyCount = i - start + 1; if(alreadyCount == k) return ints[i]; //第6步,转向left数组或right数组 if(alreadyCount > k) return find(ints, k, start, i - 1); return find(ints, (k - alreadyCount), i + 1, end); }
整个解决方法如上所示,简单明了,就不需要解释了。
像这类解法,其实解决的就是一个算法思想,如果数据结构和算法没学好,是怎么也想不到这种解法了。
以上的解法思想来自于《数据结构和算法 Java语言描述》,具体问题和实现与原解法有部分区别。
转载请标明出处:i flym
本文地址:https://www.iflym.com/index.php/code/201108240001.html