數據結構和算法之路---堆和堆排序

1.是一種完全二叉樹,第N層至少有 2 ^(n-1)個結點
2.大頂堆:父節點 大於左右子結點;小頂堆:父節點小於左右子結點
3.用一維數組來表示
4.最後一個非葉結點的下標 = length / 2 -1
   parent = floor( ( (i -1) / 2 ) i > 1
   left = i * 2+1;
   right = i * 2 + 2;

堆的用途

  • 構建優先隊列
  • 快速找到一個集合的最大值或者最小值
  • 堆排序
  • 斐波那契數列,Huffman數
  • 朋友面前裝逼

堆的主要方法

1.shiftUp( i ):跟父結點比較,將第i個結點往上冒,將結點在數組中的更靠前,主要用於insert
2.shiftDown( i , n) : n代表當前參與構建堆的結點數量,跟左右子結點比較,與shiftUp操作相反,主要用於remove,又稱爲 堆化(heapify)
shiftUp 或者 shiftDown 是一個遞歸的過程,所以它的時間複雜度是 O(log n),這兩個是堆的基本方法,其他都是基於它們產生的
3.insert( value ):將value添加到堆尾,然後調用shiftUp調整堆
4.remove( ):移除堆頂結點,然後將堆尾結點放在堆頂,調用shiftDown調整堆

// shiftUp 
/**
 * 往上冒 (小頂堆)
 * @param arr 堆數組
 * @param i   往上冒的結點下標
 */
function shiftUp(arr, i) {
    if (i <= 0 || !arr) {
        return;
    }
    // 爲交換騰出坑
    let tmpValue = arr[i];
    while (i === 1 || Math.floor((i - 1) / 2) >= 0) {
        let parent = i === 1 ? 0 : Math.floor((i - 1) / 2);
        if (arr[parent] > tmpValue) {
            arr[i] = arr[parent];
            i = parent;
        } else {
            break;
        }
    }
    arr[i] = tmpValue;
}

堆排序

1.基本思想:
   a.先將無序數組構建成大頂堆(小頂堆)— 從最後的一個非葉結點開始,從下往上循環調整每個小二叉樹,使其形成局部最優解
   b.將根結點與葉結點交換位置 — 爲了下次調整堆得時候,將最大(最小)的結點排除
   c.循環的重新構建一次堆 — 因爲已經構成了局部最優解,這次從上往下調整,最多調整一個分支
2.選擇排序,所以是不穩定的
3.O(nlogn) — 第一次構建堆,用一次for循環爲O(n),以後每次循環調整 最後再遍歷一個分支,即O(logn)
4.注:堆排序的主要應用並不是全排(快速排序更方便)更適用最優N解得問題,或者動態的數據;
          對於已經構建好的堆,主要有兩個操作,插入和刪除
          插入:插入到堆尾,然後從下往上調整堆
          刪除:將堆頂移動到堆尾,然後從上往下調整堆

// 堆排序
function heapSort(arr) {
    if (!arr || typeof arr !== "object" || arr.constructor !== Array) {
        return;
    }
    // 最後一個非葉節點的下標
    let i = Math.floor(arr.length / 2) - 1;
    // 構建大堆序
    for (; i >= 0; i--) {
        adjustHeap(arr, i, arr.length);
    }
    // 調換堆頂和結點位置,再從上往下調整堆
    for (let j = arr.length - 1; j > 0; j--) {
        let temp = arr[0];
        arr[0] = arr[j];
        arr[j] = temp;
        adjustHeap(arr, 0, j)
    }
}

/**
 * 調整堆結構
 * 從左往右,從下到上
 */
function adjustHeap(arr, i, length) {
    let temp = arr[i];
    for (let k = 2 * i + 1; k < length; k = 2 * k + 1) {
        if (k + 1 < length && arr[k] > arr[k + 1]) {
            k++;
        }
        if (arr[k] < temp) {
            arr[i] = arr[k];
            i = k;
        } else {
            break;
        }
    }
    arr[i] = temp;

}

let arr = [12, 34, 2, 34, 5, 1, 0, 2, 5, 8, 9, 6, 3, 2, 33, 3, 3, 3, 5, 6, 8];
heapSort(arr);
console.log(arr);

堆代碼實現

用JS 手動實現了堆結構,以及主要方法

/**
 * 堆結構(小頂堆爲例)
 */
class Heap {
    constructor(arr) {
        this.arr = arr;
    }

    get size() {
        if (!this.arr || typeof this.arr !== "object" || this.arr.constructor !== Array) {
            return 0;
        }
        return this.arr.length;
    }

    /**
     * 將i位置的元素往上冒
     * @param i
     */
    shiftUp(i) {
        if (this.size < 0) {
            return;
        }
        let tempValue = this.arr[i];
        while (i === 1 || Math.floor((i - 1) / 2) >= 0) {
            let parent = i === 1 ? 0 : Math.floor((i - 1) / 2);
            if (this.arr[parent] > tempValue) {
                this.arr[i] = parent;
                i = parent;
            } else {
                break;
            }
        }
        this.arr[i] = tempValue;
    }

    /**
     * 往下沉  (堆化heapify)
     * @param i
     */
    shiftDown(i, size) {
        let length = size || this.size;
        if (i < 0) {
            return;
        }
        let tempValue = this.arr[i];
        let k = i * 2 + 1;
        while (k < length) {
            if (k + 1 < length && this.arr[k] > this.arr[k + 1]) {
                k++;
            }
            if (this.arr[k] < tempValue) {
                this.arr[i] = this.arr[k];
                i = k;
                k = 2 * k + 1;
            } else {
                break;
            }
        }
        this.arr[i] = tempValue;
    }

    /**
     * 插入新元素
     * 放到堆尾,然後往上冒
     * @param value
     */
    insert(value) {
        this.arr.push(value);
        this.shiftUp(this.size - 1);
    }

    /**
     * 刪除堆頂元素
     * 1.移除堆頂
     * 2.將堆尾的結點移到堆頂
     * 3.往下沉
     */
    remove() {
        if (this.size <= 0) {
            return;
        }
        if (this.size === 1) {
            return this.arr.splice(0, 1);
        } else {
            let firstValue = this.arr[0];
            this.arr[0] = this.arr[this.size - 1];
            this.arr.splice(this.size - 1, 1);
            this.shiftDown(0);
            return firstValue;
        }
    }

    /**
     * 堆排序
     * 1.先構建堆
     * 2.再循環remove堆頂
     * @param heap
     */
    sort() {
        Heap.createHeap(this);
        for (let j = this.size - 1; j > 0; j--) {
            let temp = this.arr[0];
            this.arr[0] = this.arr[j];
            this.arr[j] = temp;
            this.shiftDown(0, j);
        }

    }

    /**
     * 構建堆
     *
     * @param heap
     */
    static createHeap(heap) {
        let arr = heap.arr;
        if (!arr || typeof arr !== "object" || arr.constructor !== Array) {
            return false;
        }
        //從最後一個非葉結點開上往上堆化
        let node = Math.floor(arr.length / 2) - 1;
        while (node >= 0) {
            heap.shiftDown(node);
            node--;
        }
        return true;
    }
}

let arr = [12, 34, 2, 34, 5, 1, 0, 2, 5, 8, 9, 6, 3, 2, 33, 3, 3, 3, 5, 6, 8];

let heap = new Heap(arr);
Heap.createHeap(heap);
// heap.sort();
console.log(heap.arr);
// heap.remove();
heap.insert(-1);
console.log(heap.arr);

最優隊列

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章