1.0 問題描述
實現資料結構:堆。
2.0 問題分析
- 堆一般使用陣列來表示,其中某個節點下標i的兩個子節點的下標為 2i+1 和 2i+2。堆是一棵完全二叉樹。
- 堆有3種基本操作:建立,插入,刪除。
- 這3種操作都需要通過“調整堆”的方式來實現。調整堆是指,對堆中的某個節點,若它的值和它所有子節點相比,不是最大/最小,那麼就需要將最大/最小的元素和當前節點交換,這種操作成為“調整堆”。
- 建立可以用插入來實現(複雜度O(nlogn)),也可以使用陣列直接轉換成堆(複雜度O(n))。
- 假設陣列長度為n,那麼這個陣列轉成堆,需要從第一個有子節點的節點((n - 1)/2或(n - 2)/2開始,倒序“調整堆”,直到下標為0。
- 插入操作可以先將資料插入到陣列尾部,然後依次調整該資料的父節點,父節點的父節點......直到根節點。
- 刪除操作可以先將陣列首尾節點互換,然後刪除最後面的元素,最後再調整根節點即可。
3.0 程式碼實現
3.1使用swift實現
/*
* 堆型別
* small 小根堆
* big 大根堆
*/
enum HeapType {
case small, big
}
/*
* 堆
*/
class Heap<T: Comparable> {
private var _arr: [T];
private var _type: HeapType;
/*
* 使用陣列建立堆
* @param {[T]} - arr 建立堆的陣列
* @param {HeapType} - type 堆型別
*/
init(arr: [T], type: HeapType = .big) {
self._arr = arr;
self._type = type;
self._create();
}
/*
* 調整堆:調整以idx為頂的3元素子堆,若頂部元素不是最大/最小,則調整為最大/最小
* @param {Int} - idx 表示調整以idx為頂的3元素子堆
* @return {Bool} - 調整成功返回true,無需調整返回false
*/
@discardableResult
private func _adjust(_ idx: Int) -> Bool{
let maxAdjustIdx = Int(ceilf(Float(_arr.count) / 2) - 1);
if idx > maxAdjustIdx || idx < 0{
return false;
}
let leftIdx = 2 * idx + 1;
let rightIdx = 2 * idx + 2;
let top: T? = _arr[idx];
let left: T? = leftIdx < _arr.count ? _arr[leftIdx]: nil;
let right: T? = rightIdx < _arr.count ? _arr[rightIdx]: nil;
if let t = top {
if let l = left, let r = right {
let v = self._type == .big ? max(t, l, r) : min(t, l, r);
switch v {
case l:
swapArr(arr: &_arr, f: leftIdx, t: idx);
_adjust(leftIdx);
return true;
case r:
swapArr(arr: &_arr, f: rightIdx, t: idx);
_adjust(rightIdx);
return true;
default:
break;
}
}else if let l = left {
let b = self._type == .big ? (l > t) : (l < t);
if b {
swapArr(arr: &_arr, f: leftIdx, t: idx);
_adjust(leftIdx);
return true;
}
}else if let r = right {
let b = self._type == .big ? (r > t) : (r < t);
if b {
swapArr(arr: &_arr, f: rightIdx, t: idx);
_adjust(rightIdx);
return true;
}
}
}
return false;
}
/**
* 建立堆,依次調整 n/2 -> 0 元素
*/
private func _create(){
let maxAdjustIdx = Int(ceilf(Float(_arr.count) / 2) - 1);
for i in stride(from: maxAdjustIdx, to: -1, by: -1){
_adjust(i);
}
}
/**
* 彈出一個元素,移除頂部元素,然後令頂部元素等於最後一個元素,最後調整頂部元素
* @return [T?] 返回頂部元素
*/
func pop() -> T? {
let first = _arr.first;
if first != nil {
if _arr.count <= 1 {
_arr.removeLast();
}else{
swapArr(arr: &_arr, f: 0, t: _arr.count - 1);
_arr.removeLast();
_adjust(0);
}
}
return first;
}
/**
* 插入一個元素,插入到尾部,依次調整該元素的頂部元素,直到無需調整或下標為0
* @param {T} - t 插入的元素
*/
func push(_ t: T){
_arr.append(t);
var idx = Int(ceilf(Float(_arr.count - 1) / 2) - 1);
while true {
if !_adjust(idx){
break;
}
if idx == 0{
break;
}
idx = Int(ceilf(Float(idx) / 2) - 1);
}
}
/**
* 返回當前元素數量
* @return {Int} 返回堆內元素數量
*/
func count() -> Int{
return _arr.count;
}
}
複製程式碼
3.2使用js實現
//常量
const BigHeap = 1;
const SmallHeap = 2;
//建構函式
function Heap(type, arr, compareFunction){
this.type = type;
this.arr = arr;
this.compareFunction = compareFunction;
this._createHeap(arr);
}
//陣列交換
Heap._swap = function(i,j,arr){
let tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
//比較值
Heap.prototype._compare = function(v1, v2){
if(this.type == SmallHeap){
if(v2 == null){
return true;
}else{
if(this.compareFunction){
return this.compareFunction(v1, v2) == -1;
}else{
return v1 < v2;
}
}
}else{
if(v2 == null){
return true;
}else{
if(this.compareFunction){
return this.compareFunction(v1, v2) == 1;
}else{
return v1 > v2;
}
}
}
}
//調整堆的第i個結點
Heap.prototype._adjustNode = function(i, arr){
let leftChildIdx = 2 * i + 1;
let leftValue = null;
if(leftChildIdx < arr.length){
leftValue = arr[leftChildIdx];
}
let rightChildIdx = 2 * i + 2;
let rightValue = null;
if(rightChildIdx < arr.length){
rightValue = arr[rightChildIdx];
}
if(!leftValue && !rightValue){
return;
}
let exchangeIdx = null;
//左值存在並且大於根結點
if(leftValue && this._compare(leftValue, arr[i])){
//右值存在並且大於左結點
if(rightValue && this._compare(rightValue, leftValue)){
//右值交換
exchangeIdx = rightChildIdx;
}else{
//左值交換
exchangeIdx = leftChildIdx;
}
}else if(rightValue && this._compare(rightValue, leftValue)){
//右值交換
exchangeIdx = rightChildIdx;
}
if(exchangeIdx != null){
Heap._swap(exchangeIdx, i, arr);
//交換完畢後,新的子結點也需要調整
this._adjustNode(exchangeIdx, arr);
}
}
//根據陣列建立堆
Heap.prototype._createHeap = function(arr){
let len = arr.length;
if(len <= 1){
return;
}
//最後一個非葉子結點
let lastNonLeafIdx = Math.floor(len / 2) - 1;
//依次調整每個結點
for (let index = lastNonLeafIdx; index >= 0; index--) {
this._adjustNode(index, arr);
}
}
//插入
Heap.prototype.insert = function(ele){
this.arr.push(ele);
let adjustIdx = this.arr.length;
while(adjustIdx > 0){
adjustIdx = Math.ceil(adjustIdx / 2) - 1;
this._adjustNode(adjustIdx, this.arr);
if(adjustIdx <= 0){
break;
}
}
}
//刪除
Heap.prototype.remove = function(){
if(this.arr.length <= 0){
return null;
}
let value = this.arr[0];
if(this.arr.length > 1){
this.arr[0] = this.arr.pop();
this._adjustNode(0, this.arr);
}else{
this.arr.pop();
}
return value;
}
//是否為空
Heap.prototype.empty = function(){
return this.arr.length == 0 || this.arr[0] == null;
}
複製程式碼
4.0 複雜度分析
- 陣列轉堆的複雜度
- 首先需要進行n/2次堆的調整
- 每次調整堆最多可能需要 logn次的二次調整
- 所以時間複雜度小於 O(nlogn)
- 每次調整堆最少可能需要1次調整
- 所以時間複雜度大於O(n/2)
- 實際上,需要少量調整的運算元遠遠大於需要多次調整的操作。
- 根據調整規則,設最高高度為
h = logn
- 根節點需要調整次數為:
2^0*h
- 第二層需要調整次數為:
2^1*(h-1)
- ... ...
- 第logn層需要調整次數為:
2^(h-1)*1
- 總次數
count=2^0*h + 2^1*(h-1) + 2^2*(h-2) + ... + 2^(h-1)*(h-(h-1))
//使用錯位相減 count*2 = 2^1*h + 2^2*(h-1) + 2^3*(h-2) + ... + 2^h*(h-(h-1))
count = count*2-count = 2^1 + 2^2 + ... + 2^(h-1) + 2^h - 2^0*h
count = 2^(h+1) - 2 - h
count = 2n - 2 - logn
- 所以時間複雜度為O(n)
- 根據調整規則,設最高高度為
- 堆的插入:O(logn)
- 堆的刪除:O(logn)