玩轉資料結構(14)-- 堆中的Heapify 和 Replace
阿新 • • 發佈:2018-12-10
Heapify 和 Replace
一、replace
定義:取出最大元素後,放入一個新元素【堆中總數沒有變化】
實現方法:1.可以先 extractMax,再 add,兩次O(log n)的操作;
2.可以直接將堆頂元素替換以後 Sift Down,一次 O(log n)的操作;
程式碼實現:MaxHeap.java
public class MaxHeap<E extends Comparable<E>> { private Array<E> data; public MaxHeap(int capacity){ data = new Array<>(capacity); } public MaxHeap(){ data = new Array<>(); } public MaxHeap(E[] arr){ data = new Array<>(arr); for(int i = parent(arr.length - 1) ; i >= 0 ; i --) siftDown(i); } // 返回堆中的元素個數 public int size(){ return data.getSize(); } // 返回一個布林值, 表示堆中是否為空 public boolean isEmpty(){ return data.isEmpty(); } // 返回完全二叉樹的陣列表示中,一個索引所表示的元素的父親節點的索引 private int parent(int index){ if(index == 0) throw new IllegalArgumentException("index-0 doesn't have parent."); return (index - 1) / 2; } // 返回完全二叉樹的陣列表示中,一個索引所表示的元素的左孩子節點的索引 private int leftChild(int index){ return index * 2 + 1; } // 返回完全二叉樹的陣列表示中,一個索引所表示的元素的右孩子節點的索引 private int rightChild(int index){ return index * 2 + 2; } // 向堆中新增元素 public void add(E e){ data.addLast(e); siftUp(data.getSize() - 1); } private void siftUp(int k){ while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0 ){ data.swap(k, parent(k)); k = parent(k); } } // 看堆中的最大元素 public E findMax(){ if(data.getSize() == 0) throw new IllegalArgumentException("Can not findMax when heap is empty."); return data.get(0); } // 取出堆中最大元素 public E extractMax(){ E ret = findMax(); data.swap(0, data.getSize() - 1); data.removeLast(); siftDown(0); return ret; } private void siftDown(int k){ while(leftChild(k) < data.getSize()){ int j = leftChild(k); // 在此輪迴圈中,data[k]和data[j]交換位置 if( j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0 ) j ++; // data[j] 是 leftChild 和 rightChild 中的最大值 if(data.get(k).compareTo(data.get(j)) >= 0 ) break; data.swap(k, j); k = j; } } // 取出堆中的最大元素,並且替換成元素e(新增程式碼) public E replace(E e){ E ret = findMax(); data.set(0, e); siftDown(0); return ret; } }
二、Repalce
定義:將任意陣列整理成堆的形狀;
方法:將當前陣列看做完全二叉樹,從當前最後一個非葉子節點開始;即圖中的 22 ;【最後一個非葉子節點的索引:拿到最後一個葉子節點,根據這個葉子節點來計算其父親節點的索引即可】
從 22 開始,不斷進行下沉操作
索引為3 的是13,對其進行下沉操作,交換 41 和 13;
索引為 2 的是19,對其進行下沉操作
對索引為 1 、0 的繼續進行下沉,得到最終的二叉樹
Heapify 的演算法複雜度:
將 n 個元素逐個插入到一個空堆中,演算法複雜度:O(nlog n);
使用 heapify ,演算法複雜度:O(n)
程式碼實現heapify:Array.java
public class Array<E> { private E[] data; private int size; // 建構函式,傳入陣列的容量capacity構造Array public Array(int capacity){ data = (E[])new Object[capacity]; size = 0; } // 無引數的建構函式,預設陣列的容量capacity=10 public Array(){ this(10); } public Array(E[] arr){ //新增程式碼 data = (E[])new Object[arr.length]; for(int i = 0 ; i < arr.length ; i ++) data[i] = arr[i]; size = arr.length; } // 獲取陣列的容量 public int getCapacity(){ return data.length; } // 獲取陣列中的元素個數 public int getSize(){ return size; } // 返回陣列是否為空 public boolean isEmpty(){ return size == 0; } // 在index索引的位置插入一個新元素e public void add(int index, E e){ if(index < 0 || index > size) throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size."); if(size == data.length) resize(2 * data.length); for(int i = size - 1; i >= index ; i --) data[i + 1] = data[i]; data[index] = e; size ++; } // 向所有元素後新增一個新元素 public void addLast(E e){ add(size, e); } // 在所有元素前新增一個新元素 public void addFirst(E e){ add(0, e); } // 獲取index索引位置的元素 public E get(int index){ if(index < 0 || index >= size) throw new IllegalArgumentException("Get failed. Index is illegal."); return data[index]; } // 修改index索引位置的元素為e public void set(int index, E e){ if(index < 0 || index >= size) throw new IllegalArgumentException("Set failed. Index is illegal."); data[index] = e; } // 查詢陣列中是否有元素e public boolean contains(E e){ for(int i = 0 ; i < size ; i ++){ if(data[i].equals(e)) return true; } return false; } // 查詢陣列中元素e所在的索引,如果不存在元素e,則返回-1 public int find(E e){ for(int i = 0 ; i < size ; i ++){ if(data[i].equals(e)) return i; } return -1; } // 從陣列中刪除index位置的元素, 返回刪除的元素 public E remove(int index){ if(index < 0 || index >= size) throw new IllegalArgumentException("Remove failed. Index is illegal."); E ret = data[index]; for(int i = index + 1 ; i < size ; i ++) data[i - 1] = data[i]; size --; data[size] = null; // loitering objects != memory leak if(size == data.length / 4 && data.length / 2 != 0) resize(data.length / 2); return ret; } // 從陣列中刪除第一個元素, 返回刪除的元素 public E removeFirst(){ return remove(0); } // 從陣列中刪除最後一個元素, 返回刪除的元素 public E removeLast(){ return remove(size - 1); } // 從陣列中刪除元素e public void removeElement(E e){ int index = find(e); if(index != -1) remove(index); } public void swap(int i, int j){ if(i < 0 || i >= size || j < 0 || j >= size) throw new IllegalArgumentException("Index is illegal."); E t = data[i]; data[i] = data[j]; data[j] = t; } @Override public String toString(){ StringBuilder res = new StringBuilder(); res.append(String.format("Array: size = %d , capacity = %d\n", size, data.length)); res.append('['); for(int i = 0 ; i < size ; i ++){ res.append(data[i]); if(i != size - 1) res.append(", "); } res.append(']'); return res.toString(); } // 將陣列空間的容量變成newCapacity大小 private void resize(int newCapacity){ E[] newData = (E[])new Object[newCapacity]; for(int i = 0 ; i < size ; i ++) newData[i] = data[i]; data = newData; } }
MaxHeap.java
public class MaxHeap<E extends Comparable<E>> {
private Array<E> data;
public MaxHeap(int capacity){
data = new Array<>(capacity);
}
public MaxHeap(){ //新增程式碼
data = new Array<>();
}
public MaxHeap(E[] arr){
data = new Array<>(arr);
for(int i = parent(arr.length - 1) ; i >= 0 ; i --)
// 從最後一個非葉子節點開始,一直到根節點遍歷
siftDown(i); //進行下沉操作
}
// 返回堆中的元素個數
public int size(){
return data.getSize();
}
// 返回一個布林值, 表示堆中是否為空
public boolean isEmpty(){
return data.isEmpty();
}
// 返回完全二叉樹的陣列表示中,一個索引所表示的元素的父親節點的索引
private int parent(int index){
if(index == 0)
throw new IllegalArgumentException("index-0 doesn't have parent.");
return (index - 1) / 2;
}
// 返回完全二叉樹的陣列表示中,一個索引所表示的元素的左孩子節點的索引
private int leftChild(int index){
return index * 2 + 1;
}
// 返回完全二叉樹的陣列表示中,一個索引所表示的元素的右孩子節點的索引
private int rightChild(int index){
return index * 2 + 2;
}
// 向堆中新增元素
public void add(E e){
data.addLast(e);
siftUp(data.getSize() - 1);
}
private void siftUp(int k){
while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0 ){
data.swap(k, parent(k));
k = parent(k);
}
}
// 看堆中的最大元素
public E findMax(){
if(data.getSize() == 0)
throw new IllegalArgumentException("Can not findMax when heap is empty.");
return data.get(0);
}
// 取出堆中最大元素
public E extractMax(){
E ret = findMax();
data.swap(0, data.getSize() - 1);
data.removeLast();
siftDown(0);
return ret;
}
private void siftDown(int k){
while(leftChild(k) < data.getSize()){
int j = leftChild(k); // 在此輪迴圈中,data[k]和data[j]交換位置
if( j + 1 < data.getSize() &&
data.get(j + 1).compareTo(data.get(j)) > 0 )
j ++;
// data[j] 是 leftChild 和 rightChild 中的最大值
if(data.get(k).compareTo(data.get(j)) >= 0 )
break;
data.swap(k, j);
k = j;
}
}
// 取出堆中的最大元素,並且替換成元素e(新增程式碼)
public E replace(E e){
E ret = findMax();
data.set(0, e);
siftDown(0);
return ret;
}
}
測試時間複雜度:
Main.java
import java.util.Random;
public class Main {
//測試
private static double testHeap(Integer[] testData, boolean isHeapify){
long startTime = System.nanoTime();
MaxHeap<Integer> maxHeap;
if(isHeapify)
maxHeap = new MaxHeap<>(testData);
else{
maxHeap = new MaxHeap<>();
for(int num: testData)
maxHeap.add(num);
}
int[] arr = new int[testData.length];
for(int i = 0 ; i < testData.length ; i ++)
arr[i] = maxHeap.extractMax();
for(int i = 1 ; i < testData.length ; i ++)
if(arr[i-1] < arr[i])
throw new IllegalArgumentException("Error");
System.out.println("Test MaxHeap completed.");
long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
int n = 1000000;
Random random = new Random();
Integer[] testData = new Integer[n];
for(int i = 0 ; i < n ; i ++)
testData[i] = random.nextInt(Integer.MAX_VALUE);
double time1 = testHeap(testData, false);
System.out.println("Without heapify: " + time1 + " s");
double time2 = testHeap(testData, true);
System.out.println("With heapify: " + time2 + " s");
}
}
輸出: