input为数组,k为求的top值
// 初始化建堆的时间复杂度为O(n),排序重建堆的时间复杂度为nlog(n),所以总的时间复杂度为O(n+nlogn)=O(nlogn)
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
ArrayList<Integer> it = new ArrayList();
// 临界判断
if (k <= 0) {
return it;
}
if (input.length == 0) {
return it;
}
if (input.length < k) {
return it;
}
if (input.length == 1) {
it.add(input[0]);
return it;
}
buildMinHeap(input);
System.out.println("---第一次过后---" + new Gson().toJson(input));
// 每次位置交换定位
int place = input.length - 1;
// 交换记录暂存
int save = 0;
// 交换下沉
for (int i = 0; i < k; i++) {
place = input.length - 1 - i;
int start = 0;
// 交换树顶和末节点,已排序位置左移
while (start <= place) {
//求出左右位置
int left = (start << 1) + 1;
int right = (start << 1) + 2;
if (left > place) {
break;
}
// 有双节点
if (right <= place) {
//符合该位置规则,直接返回
if (input[start] <= input[left] && input[start] <= input[right]) {
break;
} else {
boolean smallL = false;
if (input[left] <= input[right]) {
smallL = true;
}
// 和最小的节点交换
if (smallL) {
change(input, left, start);
start = left;
} else {
change(input, right, start);
start = right;
}
}
}
// 只剩下左节点
else {
if (input[left] < input[start]) {
change(input, left, start);
}
break;
}
}
// 移除顶结点
save = input[0];
input[0] = input[input.length - 1 - i];
input[input.length - 1 - i] = save;
it.add(save);
}
System.out.println(new Gson().toJson(input));
return it;
}
public void change(int[] input,int i,int j) {
int c = input[i];
input[i] = input[j];
input[j] = c;
}
// build
public void buildMinHeap(int[] input) {
// 每次位置交换定位
int place = input.length - 1;
// 交换记录暂存
int save = 0;
// 构建最小堆
while (place > 0) {
// 判断奇数,偶数,奇数左指数,偶右指数,左树开始下次跳一格,右树开始,跳两格,后面都以右结点开始
boolean dou = false;
if (place % 2 == 0) {
dou = true;
}
// 找出父节点位置,偶数左移减一,奇数左移
// 交换位置,调整指针,因为排列完全二叉树,定位位置是奇数,必定无右节点(只会在最开始出现单一节点)
// 父节点位置
int placeroot = 0;
if (!dou) {
placeroot = place >> 1;
// 奇数只与父节点比较
if (input[place] < input[placeroot]) {
save = input[place];
input[place] = input[placeroot];
input[placeroot] = save;
}
place--;
} else {
placeroot = (place >> 1) - 1;
// 偶数必有左兄弟节点,两子节点都与父节点比较
if (input[place] < input[placeroot]) {
save = input[place];
input[place] = input[placeroot];
input[placeroot] = save;
}
if (input[place - 1] < input[placeroot]) {
save = input[place - 1];
input[place - 1] = input[placeroot];
input[placeroot] = save;
}
// 跳两格
place -= 2;
}
}
}
初始化建堆只需要对二叉树的非叶子节点调用adjusthead()函数,由下至上,由右至左选取非叶子节点来调用adjusthead()函数。那么倒数第二层的最右边的非叶子节点就是最后一个非叶子结点。
假设高度为k,则从倒数第二层右边的节点开始,这一层的节点都要执行子节点比较然后交换(如果顺序是对的就不用交换);倒数第三层呢,则会选择其子节点进行比较和交换,如果没交换就可以不用再执行下去了。如果交换了,那么又要选择一支子树进行比较和交换;高层也是这样逐渐递归。
那么总的时间计算为:s = 2^( i - 1 ) * ( k - i );其中 i 表示第几层,2^( i - 1) 表示该层上有多少个元素,( k - i) 表示子树上要下调比较的次数。
S = 2^(k-2) * 1 + 2(k-3)2……+2(k-2)+2(0)*(k-1) ===> 因为叶子层不用交换,所以i从 k-1 开始到 1;
S = 2^k -k -1;又因为k为完全二叉树的深度,而log(n) =k,把此式带入;
得到:S = n - log(n) -1,所以时间复杂度为:O(n)