package com.demo;
/**
* 寻找最大的K个数
* @author ying
*
*/
public class FindTheBigestK {
/**
* 如果当N很大很大的时候,例如有100亿,这个时候数据不能全部装进内存中,所
* 以要求尽可能少地遍历所有数据。
*
* 不防假设N>K,前K个数中的最大K个数是一个退化的情况,所有K个数就是最大的
* K个数。如果考虑第K+1个数X呢?如果X比最大的K个数的最小的数Y小,那么
* 最大的K个数还是保持不变。如果X比Y大,那么最大的K个数应该去掉Y,而包含
* X。如果用一个数组来存储最大的K个数,每新加入一个数X,就扫描一遍数组,得到
* 数组中最小的数Y。用X代替Y,或者保持原来数组不变。这个方法,所耗费的时间为
* O(N*K)。
*
* 进一步,可以用容量为K的最小堆来存储最大的K个数。最小堆的堆顶元素就是最
* 大K个数中最小的一个。每次新考虑一个数X,如果X比堆顶的元素Y小,则不需要
* 要改变原来的堆,因为这个元素比最大的K个数小。如果X比堆顶元素大,那么用X
* 替换堆顶的元素Y。在X替换堆顶元素Y之后,X可能破坏最小堆的结构(每个节点
* 都比它的父亲节点大),需要跟新堆来维持堆的性质。更新过程花费的时间复杂度为
* O(logK)
* @param list
* @param k
* @return
*/
public int[] method3(int[] list,int k){
int[] tmp = new int[k];
System.arraycopy(list, 0, tmp, 0, k);
printList(tmp,k);
int n = list.length - 1;
initSort(tmp,k-1);
printList(tmp,k);
while(k < n){
if(tmp[0] < list[k]){
tmp[0] = list[k];
initSort(tmp,tmp.length - 1);
printList(tmp,tmp.length);
}
++k;
}
return list;
}
public void initSort(int[] list,int n){
int m = (n + 1) / 2;
for(int i = 0;i < m;i ++){
boolean flag = buildHeap(list,n,i);
if(flag)
i = -1;
}
}
public boolean buildHeap(int tmp[],int n,int i){
int l_child = 2 * i + 1;
int r_child = 2 * i + 2;
if(r_child > n){
if(tmp[i] > tmp[l_child]){
tmp[i] = tmp[i] + tmp[l_child];
tmp[l_child] = tmp[i] - tmp[l_child];
tmp[i] = tmp[i] - tmp[l_child];
return true;
}else{
return false;
}
}
if(tmp[i] > tmp[l_child]){
if(tmp[l_child] < tmp[r_child]){
tmp[i] = tmp[i] + tmp[l_child];
tmp[l_child] = tmp[i] - tmp[l_child];
tmp[i] = tmp[i] - tmp[l_child];
return true;
}else{
tmp[i] = tmp[i] + tmp[r_child];
tmp[r_child] = tmp[i] - tmp[r_child];
tmp[i] = tmp[i] - tmp[r_child];
return true;
}
}else if(tmp[i] > tmp[r_child]){
tmp[i] = tmp[i] + tmp[r_child];
tmp[r_child] = tmp[i] - tmp[r_child];
tmp[i] = tmp[i] - tmp[r_child];
return true;
}
return false;
}
/**
* 会议一下快速排序,快排中的每一步,都是将带排数据分做两组,其中一组数据的任
* 何一个数都比另一组中的任意数大,再对两组分别做类似的操作,然后继续下去......
*
* 假设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*logK)。
* @param list
* @param k
* @return
*/
public int[] method2(int[] list,int k){
QSort2(list,0,list.length - 1,k);
return list;
}
/**
* 方法二的部分快速排序
* @param list
* @param low
* @param high
*/
public void QSort2(int[] list,int low,int high,int k){
if(low < high){
int middle = partition(list,low,high);
if(k < middle){
QSort2(list,low,middle -1,k);
}
if(k > middle){
QSort2(list,middle+1,high,k);
}
}
}
/**
* 我们先假设元素的数量不大,例如在几千个左右,在这种情况下,我们就排序吧。
* 在这里,快速排序或者堆排序都是不错的选择,,他们的平均时间复杂度都是O(N*logN)。
* 然后取出前K个,O(K)。总时间复杂度O(N*logN)+O(K)=O(N*logN)。
*
* 你一定注意到了,当K=1时,上面的算法也是O(N*logN)的复杂度,而显然我们
* 可以通过N-1次的比较和交换得到结果。上面的算法对整个数组都进行了排序,而原
* 题目只要求最大的K个数,并不需要前K个数有序,也不需要后N-K个数有序。
*
* 如何避免做后N-K个数的排序呢?我们需要部分排序算法,选择排序和交换排序都是
* 不错的选择。把N个数中的前K个数排序出来,复杂度是O(N*K)。
*
* 哪一个更好呢?O(N*logN)还是O(N*K)?这取决于K的大小,你需要弄清楚。
* 在K(<=logN)较小的情况下,可以选择部分排序。
* @param list无效数组
* @param k选出其中最大的K个数
* @return
*/
public int[] method1(int[] list,int k){
if(k <= (Math.log(list.length)/Math.log(2))){//在K<=logN的情况下,使用部分排序;在K>logN的情况下,使用快速排序。
System.out.println("使用部分排序");
selectSort(list,k);
}else{
System.out.println("使用快速排序");
QSort(list,0,list.length -1);
}
return list;
}
/**
* 快速排序
* @param list
* @param low
* @param high
*/
public void QSort(int[] list,int low,int high){
if(low < high){
int middle = partition(list,low,high);
QSort(list,low,middle -1);
QSort(list,middle+1,high);
}
}
public int partition(int[] list,int low,int high){
int pivotkey = list[low];
while(low < high){
while(low < high && list[high] <= pivotkey){
high--;
}
list[low] = list[high];
while(low < high && list[low] >= pivotkey){
low++;
}
list[high] = list[low];
}
list[low] = pivotkey;
return low;
}
/**
* 前k个数的选择排序
* @param list
* @param k
* @return
*/
public void selectSort(int[] list,int k){
int min = 0;
int tmp = 0;
for(int i = 0;i < k; i++){
min = i;
for(int j = i + 1;j < list.length;j++){
if(list[min]<list[j]){
min = j;
}
}
if(i != min){
tmp = list[min];
list[min] = list[i];
list[i] = tmp;
}
}
}
public void printList(int[] list,int n){
for(int i = 0;i < n;i++){
System.out.print(list[i]+",");
}
System.out.println();
}
}
/**
* 寻找最大的K个数
* @author ying
*
*/
public class FindTheBigestK {
/**
* 如果当N很大很大的时候,例如有100亿,这个时候数据不能全部装进内存中,所
* 以要求尽可能少地遍历所有数据。
*
* 不防假设N>K,前K个数中的最大K个数是一个退化的情况,所有K个数就是最大的
* K个数。如果考虑第K+1个数X呢?如果X比最大的K个数的最小的数Y小,那么
* 最大的K个数还是保持不变。如果X比Y大,那么最大的K个数应该去掉Y,而包含
* X。如果用一个数组来存储最大的K个数,每新加入一个数X,就扫描一遍数组,得到
* 数组中最小的数Y。用X代替Y,或者保持原来数组不变。这个方法,所耗费的时间为
* O(N*K)。
*
* 进一步,可以用容量为K的最小堆来存储最大的K个数。最小堆的堆顶元素就是最
* 大K个数中最小的一个。每次新考虑一个数X,如果X比堆顶的元素Y小,则不需要
* 要改变原来的堆,因为这个元素比最大的K个数小。如果X比堆顶元素大,那么用X
* 替换堆顶的元素Y。在X替换堆顶元素Y之后,X可能破坏最小堆的结构(每个节点
* 都比它的父亲节点大),需要跟新堆来维持堆的性质。更新过程花费的时间复杂度为
* O(logK)
* @param list
* @param k
* @return
*/
public int[] method3(int[] list,int k){
int[] tmp = new int[k];
System.arraycopy(list, 0, tmp, 0, k);
printList(tmp,k);
int n = list.length - 1;
initSort(tmp,k-1);
printList(tmp,k);
while(k < n){
if(tmp[0] < list[k]){
tmp[0] = list[k];
initSort(tmp,tmp.length - 1);
printList(tmp,tmp.length);
}
++k;
}
return list;
}
public void initSort(int[] list,int n){
int m = (n + 1) / 2;
for(int i = 0;i < m;i ++){
boolean flag = buildHeap(list,n,i);
if(flag)
i = -1;
}
}
public boolean buildHeap(int tmp[],int n,int i){
int l_child = 2 * i + 1;
int r_child = 2 * i + 2;
if(r_child > n){
if(tmp[i] > tmp[l_child]){
tmp[i] = tmp[i] + tmp[l_child];
tmp[l_child] = tmp[i] - tmp[l_child];
tmp[i] = tmp[i] - tmp[l_child];
return true;
}else{
return false;
}
}
if(tmp[i] > tmp[l_child]){
if(tmp[l_child] < tmp[r_child]){
tmp[i] = tmp[i] + tmp[l_child];
tmp[l_child] = tmp[i] - tmp[l_child];
tmp[i] = tmp[i] - tmp[l_child];
return true;
}else{
tmp[i] = tmp[i] + tmp[r_child];
tmp[r_child] = tmp[i] - tmp[r_child];
tmp[i] = tmp[i] - tmp[r_child];
return true;
}
}else if(tmp[i] > tmp[r_child]){
tmp[i] = tmp[i] + tmp[r_child];
tmp[r_child] = tmp[i] - tmp[r_child];
tmp[i] = tmp[i] - tmp[r_child];
return true;
}
return false;
}
/**
* 会议一下快速排序,快排中的每一步,都是将带排数据分做两组,其中一组数据的任
* 何一个数都比另一组中的任意数大,再对两组分别做类似的操作,然后继续下去......
*
* 假设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*logK)。
* @param list
* @param k
* @return
*/
public int[] method2(int[] list,int k){
QSort2(list,0,list.length - 1,k);
return list;
}
/**
* 方法二的部分快速排序
* @param list
* @param low
* @param high
*/
public void QSort2(int[] list,int low,int high,int k){
if(low < high){
int middle = partition(list,low,high);
if(k < middle){
QSort2(list,low,middle -1,k);
}
if(k > middle){
QSort2(list,middle+1,high,k);
}
}
}
/**
* 我们先假设元素的数量不大,例如在几千个左右,在这种情况下,我们就排序吧。
* 在这里,快速排序或者堆排序都是不错的选择,,他们的平均时间复杂度都是O(N*logN)。
* 然后取出前K个,O(K)。总时间复杂度O(N*logN)+O(K)=O(N*logN)。
*
* 你一定注意到了,当K=1时,上面的算法也是O(N*logN)的复杂度,而显然我们
* 可以通过N-1次的比较和交换得到结果。上面的算法对整个数组都进行了排序,而原
* 题目只要求最大的K个数,并不需要前K个数有序,也不需要后N-K个数有序。
*
* 如何避免做后N-K个数的排序呢?我们需要部分排序算法,选择排序和交换排序都是
* 不错的选择。把N个数中的前K个数排序出来,复杂度是O(N*K)。
*
* 哪一个更好呢?O(N*logN)还是O(N*K)?这取决于K的大小,你需要弄清楚。
* 在K(<=logN)较小的情况下,可以选择部分排序。
* @param list无效数组
* @param k选出其中最大的K个数
* @return
*/
public int[] method1(int[] list,int k){
if(k <= (Math.log(list.length)/Math.log(2))){//在K<=logN的情况下,使用部分排序;在K>logN的情况下,使用快速排序。
System.out.println("使用部分排序");
selectSort(list,k);
}else{
System.out.println("使用快速排序");
QSort(list,0,list.length -1);
}
return list;
}
/**
* 快速排序
* @param list
* @param low
* @param high
*/
public void QSort(int[] list,int low,int high){
if(low < high){
int middle = partition(list,low,high);
QSort(list,low,middle -1);
QSort(list,middle+1,high);
}
}
public int partition(int[] list,int low,int high){
int pivotkey = list[low];
while(low < high){
while(low < high && list[high] <= pivotkey){
high--;
}
list[low] = list[high];
while(low < high && list[low] >= pivotkey){
low++;
}
list[high] = list[low];
}
list[low] = pivotkey;
return low;
}
/**
* 前k个数的选择排序
* @param list
* @param k
* @return
*/
public void selectSort(int[] list,int k){
int min = 0;
int tmp = 0;
for(int i = 0;i < k; i++){
min = i;
for(int j = i + 1;j < list.length;j++){
if(list[min]<list[j]){
min = j;
}
}
if(i != min){
tmp = list[min];
list[min] = list[i];
list[i] = tmp;
}
}
}
public void printList(int[] list,int n){
for(int i = 0;i < n;i++){
System.out.print(list[i]+",");
}
System.out.println();
}
}