大根堆和堆排序的原理與實現
shift up
- 插入一個元素時,把元素追加到列表的最後,也就是堆的葉子節點上。然後執行shifup操作,對新元素進行從下往上的調整。
- 判斷當前元素是否比父節點更大,如果是,則交換。否則就終止。
- 因為插入一個元素時,列表已經是一個大根堆,所以當出現父元素大於自己時,就沒有必要繼續,因為父元素的父元素值更大。
shift down
-
刪除一個元素時,把該元素和列表的最後一個元素交換。然後列表的長度減一(如果用
count
計數的話)。剩餘的元素進行shiftdown操作。 -
shiftdown,如果兩個孩子中存在比自己更大的元素,就和那個孩子交換值。然後這個孩子作為根,繼續這個操作。
-
如下: 插入時執行shiftUp,刪除時執行shiftDown
// 堆排序
class Heap{
private List<Integer> data;
public Heap(){
this.data = new ArrayList<>();
}
public void insert(int value){
this.data.add(value);
this.shiftUp(this.data.size() - 1);
}
public int pop(){
this.swap(0, this.data.size() - 1);
int res = this.data.remove(this.data.size() - 1);
this.shiftDown(0);
return res;
}
/**
* 上移,元素加入時執行
* @param k
*/
public void shiftUp(int k){
// 0 元素沒有父元素
// 當出現k比父元素小的時候,就沒有必要繼續下去,因為,父元素的父元素一定是更大的數
while(k >= 1 && data.get(k) > data.get((k - 1) / 2)){
swap(k, (k - 1) / 2);
k = (k - 1) / 2;
}
}
/**
* 下移,調整堆時執行
* @param k
*/
public void shiftDown(int k){
// 省略了對左孩子的判斷
while(2 * k + 1 < data.size()){
int child = 2 * k + 1;
if(child + 1 < this.data.size() && this.data.get(child) < this.data.get(child + 1)){
child += 1;
}
if(this.data.get(child) > this.data.get(k)){
this.swap(k, child);
}
k = child;
}
}
public void swap(int left, int right){
int temp = this.data.get(left);
this.data.set(left, this.data.get(right));
this.data.set(right, temp);
}
}
// main
_215_陣列中的第k個最大元素.Heap2 heap = handle.new Heap2();
// 建立堆
for(int item : list){
heap.insert(item);
}
for(int item : list){
System.out.print(heap.pop() + " ");
}
heapify
- 不需要一個一個的插入元素,可以直接對原陣列的所有非葉節點進行heapify,即可構成一個大根堆。
((size - 1) - 1) / 2
表示最大下標減一。當然,用size/2
也行,多出的幾個元素都是葉子節點,都可以當成是隻有一個元素的堆。
public Heap2(List<Integer> list){
this.data = new ArrayList<>(list);
heapify();
}
public void heapify(){
for(int i = (data.size() - 1 - 1) / 2; i >= 0; --i){
shiftDown(i);
}
}
就地排序
- 這裡的nums陣列的資料從0開始拍,這樣的話,按照如下方式計算父子節點
- 左孩子:
index * 2 + 1
- 右孩子:
index * 2 + 2
- 父節點:
(index - 1) / 2
或者(length -1 -1)/2
。有的演算法直接用(length / 2)
也不會有錯,因為最右邊的葉子節點都可以看成是單個的大根堆。
- 左孩子:
class Heap {
public void heapSort(int [] nums){
// 從第一個非葉節點開始,執行shiftdown操作
int size = nums.length;
// 建立一個堆
// 很多攜程(size-1)/2 或者 size/2都行
for( int i = (size -1 - 1)/2; i >= 0; --i){
shiftDown(nums, i, size);
}
// 從堆中取元素
for (int i = size - 1; i > 0; i --){
swap(nums, 0, i); // i 元素被放到0位置
shiftDown(nums, 0, i); // 重新堆0位置的元素shiftdown
}
}
/**
* 對第i個元素執行下移操作。大根堆
* @param nums
* @param i
*/
public void shiftDown(int [] nums, int k, int size){
// 用while 少了一次迴圈,避免了遞迴
while( 2 * k + 1 < size){
int child = 2 * k + 1; // 左孩子
if ( child + 1 < size && nums[child+1] > nums[child])
child += 1; // 右孩子
if (nums[k] < nums[child]){
swap(nums, child, k);
}
k = child; // 從被交換的孩子開始,繼續往下迭代。直到到達葉子節點
}
}
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
相關文章
- 大根堆和小根堆的介紹
- 大根堆
- 堆的原理與實現
- java使用PriorityQueue即優先佇列實現大根堆和小根堆Java佇列
- PHP 實現堆, 堆排序以及索引堆PHP排序索引
- 看懂堆排序——堆與堆排序(三)排序
- 與堆和堆排序相關的問題排序
- 堆與堆排序(一)排序
- 堆操作與堆排序排序
- 堆和堆的應用:堆排序和優先佇列排序佇列
- 線性建堆法與堆排序排序
- 堆的原理以及實現O(lgn)
- 堆和索引堆的python實現索引Python
- 堆、堆排序和優先佇列的那些事排序佇列
- 堆的基本操作及堆排序排序
- 實現堆排序排序
- 串的堆分配表示與實現
- 高階資料結構---堆樹和堆排序資料結構排序
- PHP面試:說下什麼是堆和堆排序?PHP面試排序
- 圖的深度優先遍歷(堆疊實現和非堆疊實現)
- 二叉堆及堆排序排序
- js 實現堆排序JS排序
- 堆排序(php實現)排序PHP
- 堆的實現
- 服務註冊與發現的原理和實現
- 堆排序(實現c++)排序C++
- 使用 Swift 實現堆排序Swift排序
- 堆排序c++實現排序C++
- 《排序演算法》——堆排序(大頂堆,小頂堆,Java)排序演算法Java
- Python實現堆疊與佇列Python佇列
- 富集分析的原理與實現
- 前端模板的原理與實現前端
- jsonp的原理與實現JSON
- Projective Texture的原理與實現Project
- [SentencePiece]Tokenizer的原理與實現
- Lombok 原理與實現Lombok
- LRU原理與實現
- 基於"堆"的底層實現和應用