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)