1. 程式人生 > >演算法與資料結構之堆的相關知識,簡單易懂。

演算法與資料結構之堆的相關知識,簡單易懂。

十、 二叉堆之Java的實現
1) 概要
前面分別通過C和C++實現了二叉堆,本章給出二叉堆的Java版本。還是那句話,它們的原理一樣,擇其一瞭解即可。
目錄
1. 二叉堆的介紹
2. 二叉堆的圖文解析
3. 二叉堆的Java實現(完整原始碼)
4. 二叉堆的Java測試程式
2) 二叉堆的介紹
二叉堆是完全二元樹或者是近似完全二元樹,按照資料的排列方式可以分為兩種:最大堆和最小堆。
最大堆:父結點的鍵值總是大於或等於任何一個子節點的鍵值;最小堆:父結點的鍵值總是小於或等於任何一個子節點的鍵值。
二叉堆一般都通過"陣列"來實現,下面是陣列實現的最大堆和最小堆的示意圖:
 
3) 二叉堆的圖文解析
圖文解析是以"最大堆"來進行介紹的。
最大堆的核心內容是"新增"和"刪除",理解這兩個演算法,二叉堆也就基本掌握了。下面對它們進行介紹,其它內容請參考後面的完整原始碼。
1. 新增
假設在最大堆[90,80,70,60,40,30,20,10,50]種新增85,需要執行的步驟如下:
 


如上圖所示,當向最大堆中新增資料時:先將資料加入到最大堆的最後,然後儘可能把這個元素往上挪,直到挪不動為止!
將85新增到[90,80,70,60,40,30,20,10,50]中後,最大堆變成了[90,85,70,60,80,30,20,10,50,40]。
最大堆的插入程式碼(Java語言)
/*
 * 最大堆的向上調整演算法(從start開始向上直到0,調整堆)
 *
 * 注:陣列實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *
 * 引數說明:
 *     start -- 被上調節點的起始位置(一般為陣列中最後一個元素的索引)
 */
protected void filterup(int start) {
    int c = start;            // 當前節點(current)的位置
    int p = (c-1)/2;        // 父(parent)結點的位置 
    T tmp = mHeap.get(c);        // 當前節點(current)的大小
    while(c > 0) {
        int cmp = mHeap.get(p).compareTo(tmp);
        if(cmp >= 0)
            break;
        else {
            mHeap.set(c, mHeap.get(p));
            c = p;
            p = (p-1)/2;   
        }       
    }
    mHeap.set(c, tmp);
}
/* 
 * 將data插入到二叉堆中
 */
public void insert(T data) {
    int size = mHeap.size();
    mHeap.add(data);    // 將"陣列"插在表尾
    filterup(size);        // 向上調整堆
}
insert(data)的作用:將資料data新增到最大堆中。mHeap是動態陣列ArrayList物件。
當堆已滿的時候,新增失敗;否則data新增到最大堆的末尾。然後通過上調演算法重新調整陣列,使之重新成為最大堆。
2. 刪除
假設從最大堆[90,85,70,60,80,30,20,10,50,40]中刪除90,需要執行的步驟如下:
 

如上圖所示,當從最大堆中刪除資料時:先刪除該資料,然後用最大堆中最後一個的元素插入這個空位;接著,把這個“空位”儘量往上挪,直到剩餘的資料變成一個最大堆。
從[90,85,70,60,80,30,20,10,50,40]刪除90之後,最大堆變成了[85,80,70,60,40,30,20,10,50]。
注意:考慮從最大堆[90,85,70,60,80,30,20,10,50,40]中刪除60,執行的步驟不能單純的用它的位元組點來替換;而必須考慮到"替換後的樹仍然要是最大堆"!
 
二叉堆的刪除程式碼(Java語言)
/* 
 * 最大堆的向下調整演算法
 *
 * 注:陣列實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
 *
 * 引數說明:
 *     start -- 被下調節點的起始位置(一般為0,表示從第1個開始)
 *     end   -- 截至範圍(一般為陣列中最後一個元素的索引)
 */
protected void filterdown(int start, int end) {
    int c = start;          // 當前(current)節點的位置
    int l = 2*c + 1;     // 左(left)孩子的位置
    T tmp = mHeap.get(c);    // 當前(current)節點的大小
    while(l <= end) {
        int cmp = mHeap.get(l).compareTo(mHeap.get(l+1));
        // "l"是左孩子,"l+1"是右孩子
        if(l < end && cmp<0)
            l++;        // 左右兩孩子中選擇較大者,即mHeap[l+1]
        cmp = tmp.compareTo(mHeap.get(l));
        if(cmp >= 0)
            break;        //調整結束
        else {
            mHeap.set(c, mHeap.get(l));
            c = l;
            l = 2*l + 1;   
        }       
    }   
    mHeap.set(c, tmp);
}
/*
 * 刪除最大堆中的data
 *
 * 返回值:
 *      0,成功
 *     -1,失敗
 */
public int remove(T data) {
    // 如果"堆"已空,則返回-1
    if(mHeap.isEmpty() == true)
        return -1;
    // 獲取data在陣列中的索引
    int index = mHeap.indexOf(data);
    if (index==-1)
        return -1;
    int size = mHeap.size();
    mHeap.set(index, mHeap.get(size-1));// 用最後元素填補
    mHeap.remove(size - 1);                // 刪除最後的元素
    if (mHeap.size() > 1)
        filterdown(index, mHeap.size()-1);    // 從index號位置開始自上向下調整為最小堆
    return 0;
}
4) 二叉堆的Java實現(完整原始碼)
二叉堆的實現同時包含了"最大堆"和"最小堆"。
二叉堆(最大堆)的實現檔案(MaxHeap.java)
/**
 * 二叉堆(最大堆)
 *
 * @author skywang
 * @date 2014/03/07
 */
import java.util.ArrayList;
import java.util.List;
public class MaxHeap<T extends Comparable<T>> {
    private List<T> mHeap;    // 佇列(實際上是動態陣列ArrayList的例項)
    public MaxHeap() {
        this.mHeap = new ArrayList<T>();
    }
    /* 
     * 最大堆的向下調整演算法
     *
     * 注:陣列實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
     *
     * 引數說明:
     *     start -- 被下調節點的起始位置(一般為0,表示從第1個開始)
     *     end   -- 截至範圍(一般為陣列中最後一個元素的索引)
     */
    protected void filterdown(int start, int end) {
        int c = start;          // 當前(current)節點的位置
        int l = 2*c + 1;     // 左(left)孩子的位置
        T tmp = mHeap.get(c);    // 當前(current)節點的大小
        while(l <= end) {
            int cmp = mHeap.get(l).compareTo(mHeap.get(l+1));
            // "l"是左孩子,"l+1"是右孩子
            if(l < end && cmp<0)
                l++;        // 左右兩孩子中選擇較大者,即mHeap[l+1]
            cmp = tmp.compareTo(mHeap.get(l));
            if(cmp >= 0)
                break;        //調整結束
            else {
                mHeap.set(c, mHeap.get(l));
                c = l;
                l = 2*l + 1;   
            }       
        }   
        mHeap.set(c, tmp);
    }
    /*
     * 刪除最大堆中的data
     *
     * 返回值:
     *      0,成功
     *     -1,失敗
     */
    public int remove(T data) {
        // 如果"堆"已空,則返回-1
        if(mHeap.isEmpty() == true)
            return -1;
        // 獲取data在陣列中的索引
        int index = mHeap.indexOf(data);
        if (index==-1)
            return -1;
        int size = mHeap.size();
        mHeap.set(index, mHeap.get(size-1));// 用最後元素填補
        mHeap.remove(size - 1);                // 刪除最後的元素
        if (mHeap.size() > 1)
            filterdown(index, mHeap.size()-1);    // 從index號位置開始自上向下調整為最小堆
        return 0;
    }
    /*
     * 最大堆的向上調整演算法(從start開始向上直到0,調整堆)
     *
     * 注:陣列實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
     *
     * 引數說明:
     *     start -- 被上調節點的起始位置(一般為陣列中最後一個元素的索引)
     */
    protected void filterup(int start) {
        int c = start;            // 當前節點(current)的位置
        int p = (c-1)/2;        // 父(parent)結點的位置 
        T tmp = mHeap.get(c);        // 當前節點(current)的大小
        while(c > 0) {
            int cmp = mHeap.get(p).compareTo(tmp);
            if(cmp >= 0)
                break;
            else {
                mHeap.set(c, mHeap.get(p));
                c = p;
                p = (p-1)/2;   
            }       
        }
        mHeap.set(c, tmp);
    }
    /* 
     * 將data插入到二叉堆中
     */
    public void insert(T data) {
        int size = mHeap.size();
        mHeap.add(data);    // 將"陣列"插在表尾
        filterup(size);        // 向上調整堆
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int i=0; i<mHeap.size(); i++)
            sb.append(mHeap.get(i) +" ");
        return sb.toString();
    }
    public static void main(String[] args) {
        int i;
        int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80};
        MaxHeap<Integer> tree=new MaxHeap<Integer>();
        System.out.printf("== 依次新增: ");
        for(i=0; i<a.length; i++) {
            System.out.printf("%d ", a[i]);
            tree.insert(a[i]);
        }
        System.out.printf("\n== 最 大 堆: %s", tree);
        i=85;
        tree.insert(i);
        System.out.printf("\n== 新增元素: %d", i);
        System.out.printf("\n== 最 大 堆: %s", tree);
        i=90;
        tree.remove(i);
        System.out.printf("\n== 刪除元素: %d", i);
        System.out.printf("\n== 最 大 堆: %s", tree);
        System.out.printf("\n");
    }
}
二叉堆(最小堆)的實現檔案(MinHeap.java)
/**
 * 二叉堆(最小堆)
 *
 * @author skywang
 * @date 2014/03/07
 */
import java.util.ArrayList;
import java.util.List;
public class MinHeap<T extends Comparable<T>> {
    private List<T> mHeap;        // 存放堆的陣列
    public MinHeap() {
        this.mHeap = new ArrayList<T>();
    }
    /* 
     * 最小堆的向下調整演算法
     *
     * 注:陣列實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
     *
     * 引數說明:
     *     start -- 被下調節點的起始位置(一般為0,表示從第1個開始)
     *     end   -- 截至範圍(一般為陣列中最後一個元素的索引)
     */
    protected void filterdown(int start, int end) {
        int c = start;          // 當前(current)節點的位置
        int l = 2*c + 1;     // 左(left)孩子的位置
        T tmp = mHeap.get(c);    // 當前(current)節點的大小
        while(l <= end) {
            int cmp = mHeap.get(l).compareTo(mHeap.get(l+1));
            // "l"是左孩子,"l+1"是右孩子
            if(l < end && cmp>0)
                l++;        // 左右兩孩子中選擇較小者,即mHeap[l+1]
            cmp = tmp.compareTo(mHeap.get(l));
            if(cmp <= 0)
                break;        //調整結束
            else {
                mHeap.set(c, mHeap.get(l));
                c = l;
                l = 2*l + 1;   
            }       
        }   
        mHeap.set(c, tmp);
    }
    /*
     * 最小堆的刪除
     *
     * 返回值:
     *     成功,返回被刪除的值
     *     失敗,返回null
     */
    public int remove(T data) {
        // 如果"堆"已空,則返回-1
        if(mHeap.isEmpty() == true)
            return -1;
        // 獲取data在陣列中的索引
        int index = mHeap.indexOf(data);
        if (index==-1)
            return -1;
        int size = mHeap.size();
        mHeap.set(index, mHeap.get(size-1));// 用最後元素填補
        mHeap.remove(size - 1);                // 刪除最後的元素
        if (mHeap.size() > 1)
            filterdown(index, mHeap.size()-1);    // 從index號位置開始自上向下調整為最小堆
        return 0;
    }
    /*
     * 最小堆的向上調整演算法(從start開始向上直到0,調整堆)
     *
     * 注:陣列實現的堆中,第N個節點的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
     *
     * 引數說明:
     *     start -- 被上調節點的起始位置(一般為陣列中最後一個元素的索引)
     */
    protected void filterup(int start) {
        int c = start;            // 當前節點(current)的位置
        int p = (c-1)/2;        // 父(parent)結點的位置 
        T tmp = mHeap.get(c);        // 當前節點(current)的大小
        while(c > 0) {
            int cmp = mHeap.get(p).compareTo(tmp);
            if(cmp <= 0)
                break;
            else {
                mHeap.set(c, mHeap.get(p));
                c = p;
                p = (p-1)/2;   
            }       
        }
        mHeap.set(c, tmp);
    }
    /* 
     * 將data插入到二叉堆中
     */
    public void insert(T data) {
        int size = mHeap.size();
        mHeap.add(data);    // 將"陣列"插在表尾
        filterup(size);        // 向上調整堆
    }
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int i=0; i<mHeap.size(); i++)
            sb.append(mHeap.get(i) +" ");
        return sb.toString();
    }
    public static void main(String[] args) {
        int i;
        int a[] = {80, 40, 30, 60, 90, 70, 10, 50, 20};
        MinHeap<Integer> tree=new MinHeap<Integer>();
        System.out.printf("== 依次新增: ");
        for(i=0; i<a.length; i++) {
            System.out.printf("%d ", a[i]);
            tree.insert(a[i]);
        }
        System.out.printf("\n== 最 小 堆: %s", tree);
        i=15;
        tree.insert(i);
        System.out.printf("\n== 新增元素: %d", i);
        System.out.printf("\n== 最 小 堆: %s", tree);
        i=10;
        tree.remove(i);
        System.out.printf("\n== 刪除元素: %d", i);
        System.out.printf("\n== 最 小 堆: %s", tree);
        System.out.printf("\n");
    }
}
5) 二叉堆的Java測試程式
測試程式已經包含在相應的實現檔案中了,這裡只說明執行結果。
最大堆(MaxHeap.java)的執行結果:
== 依次新增: 10 40 30 60 90 70 20 50 80 
== 最 大 堆: 90 80 70 60 40 30 20 10 50 
== 新增元素: 85
== 最 大 堆: 90 85 70 60 80 30 20 10 50 40 
== 刪除元素: 90
== 最 大 堆: 85 80 70 60 40 30 20 10 50 
最小堆(MinHeap.java)的執行結果:
== 最 小 堆: 10 20 30 50 90 70 40 80 60 
== 新增元素: 15
== 最 小 堆: 10 15 30 50 20 70 40 80 60 90 
== 刪除元素: 10
== 最 小 堆: 15 20 30 50 90 70 40 80 60 
PS. 二叉堆是"堆排序"的理論基石。以後講解演算法時會講解到"堆排序",理解了"二叉堆"之後,"堆排序"就很簡單了。
十一、 左傾堆之Java的實現
1) 概要
前面分別通過C和C++實現了左傾堆,本章給出左傾堆的Java版本。還是那句老話,三種實現的原理一樣,擇其一瞭解即可。
目錄
1. 左傾堆的介紹
2. 左傾堆的圖文解析
3. 左傾堆的Java實現(完整原始碼)
4. 左傾堆的Java測試程式
2) 左傾堆的介紹
左傾堆(leftist tree 或 leftist heap),又被成為左偏樹、左偏堆,最左堆等。
它和二叉堆一樣,都是優先佇列實現方式。當優先佇列中涉及到"對兩個優先佇列進行合併"的問題時,二叉堆的效率就無法令人滿意了,而本文介紹的左傾堆,則可以很好地解決這類問題。
3) 左傾堆的定義
 

上圖是一顆左傾樹,它的節點除了和二叉樹的節點一樣具有左右子樹指標外,還有兩個屬性:鍵值和零距離。
(01) 鍵值的作用是來比較節點的大小,從而對節點進行排序。
(02) 零距離(英文名NPL,即Null Path Length)則是從一個節點到一個"最近的不滿節點"的路徑長度。不滿節點是指該該節點的左右孩子至少有有一個為NULL。葉節點的NPL為0,NULL節點的NPL為-1。
左傾堆有以下幾個基本性質:
[性質1] 節點的鍵值小於或等於它的左右子節點的鍵值。
[性質2] 節點的左孩子的NPL >= 右孩子的NPL。
[性質3] 節點的NPL = 它的右孩子的NPL + 1。
4) 左傾堆的圖文解析
合併操作是左傾堆的重點。合併兩個左傾堆的基本思想如下:
(01) 如果一個空左傾堆與一個非空左傾堆合併,返回非空左傾堆。
(02) 如果兩個左傾堆都非空,那麼比較兩個根節點,取較小堆的根節點為新的根節點。將"較小堆的根節點的右孩子"和"較大堆"進行合併。
(03) 如果新堆的右孩子的NPL > 左孩子的NPL,則交換左右孩子。
(04) 設定新堆的根節點的NPL = 右子堆NPL + 1
下面通過圖文演示合併以下兩個堆的過程。
 
提示:這兩個堆的合併過程和測試程式相對應!
第1步:將"較小堆(根為10)的右孩子"和"較大堆(根為11)"進行合併。
合併的結果,相當於將"較大堆"設定"較小堆"的右孩子,如下圖所示:
 
第2步:將上一步得到的"根11的右子樹"和"根為12的樹"進行合併,得到的結果如下:
 
第3步:將上一步得到的"根12的右子樹"和"根為13的樹"進行合併,得到的結果如下:
 
第4步:將上一步得到的"根13的右子樹"和"根為16的樹"進行合併,得到的結果如下:
 
第5步:將上一步得到的"根16的右子樹"和"根為23的樹"進行合併,得到的結果如下:
 
至此,已經成功的將兩棵樹合併成為一棵樹了。接下來,對新生成的樹進行調節。
第6步:上一步得到的"樹16的右孩子的NPL > 左孩子的NPL",因此交換左右孩子。得到的結果如下:
 
第7步:上一步得到的"樹12的右孩子的NPL > 左孩子的NPL",因此交換左右孩子。得到的結果如下:
 
第8步:上一步得到的"樹10的右孩子的NPL > 左孩子的NPL",因此交換左右孩子。得到的結果如下:
 
至此,合併完畢。上面就是合併得到的左傾堆!
下面看看左傾堆的基本操作的程式碼
1. 基本定義
public class LeftistHeap<T extends Comparable<T>> {
    private LeftistNode<T> mRoot;    // 根結點
    private class LeftistNode<T extends Comparable<T>> {
        T key;                    // 關鍵字(鍵值)
        int npl;                // 零路經長度(Null Path Length)
        LeftistNode<T> left;    // 左孩子
        LeftistNode<T> right;    // 右孩子
        public LeftistNode(T key, LeftistNode<T> left, LeftistNode<T> right) {
            this.key = key;
            this.npl = 0;
            this.left = left;
            this.right = right;
        }
        public String toString() {
            return "key:"+key;
        }
    }
    ...
}
LeftistNode是左傾堆對應的節點類。
LeftistHeap是左傾堆類,它包含了左傾堆的根節點,以及左傾堆的操作。
2. 合併
/*
 * 合併"左傾堆x"和"左傾堆y"
 */
private LeftistNode<T> merge(LeftistNode<T> x, LeftistNode<T> y) {
    if(x == null) return y;
    if(y == null) return x;
    // 合併x和y時,將x作為合併後的樹的根;
    // 這裡的操作是保證: x的key < y的key
    if(x.key.compareTo(y.key) > 0) {
        LeftistNode<T> tmp = x;
        x = y;
        y = tmp;
    }
    // 將x的右孩子和y合併,"合併後的樹的根"是x的右孩子。
    x.right = merge(x.right, y);
    // 如果"x的左孩子為空" 或者 "x的左孩子的npl<右孩子的npl"
    // 則,交換x和y
    if (x.left == null || x.left.npl < x.right.npl) {
        LeftistNode<T> tmp = x.left;
        x.left = x.right;
        x.right = tmp;
    }
    if (x.right == null || x.left == null)
        x.npl = 0;
    else
        x.npl = (x.left.npl > x.right.npl) ? (x.right.npl + 1) : (x.left.npl + 1);
    return x;
}
public void merge(LeftistHeap<T> other) {
    this.mRoot = merge(this.mRoot, other.mRoot);
}
merge(x, y)是內部介面,作用是合併x和y這兩個左傾堆,並返回得到的新堆的根節點。
merge(other)是外部介面,作用是將other合併到當前堆中。
3. 新增
/* 
 * 新建結點(key),並將其插入到左傾堆中
 *
 * 引數說明:
 *     key 插入結點的鍵值
 */
public void insert(T key) {
    LeftistNode<T> node = new LeftistNode<T>(key,null,null);


    // 如果新建結點失敗,則返回。
    if (node != null)
        this.mRoot = merge(this.mRoot, node);
}
insert(key)的作用是新建鍵值為key的節點,並將其加入到當前左傾堆中。
4. 刪除
/* 
 * 刪除根結點
 * 
 * 返回值:
 *     返回被刪除的節點的鍵值
 */
public T remove() {
    if (this.mRoot == null)
        return null;
    T key = this.mRoot.key;
    LeftistNode<T> l = this.mRoot.left;
    LeftistNode<T> r = this.mRoot.right;
    this.mRoot = null;          // 刪除根節點
    this.mRoot = merge(l, r);   // 合併左右子樹
    return key;
}
remove()的作用是刪除左傾堆的最小節點。
注意:關於左傾堆的"前序遍歷"、"中序遍歷"、"後序遍歷"、"列印"、"銷燬"等介面就不再單獨介紹了。後文的原始碼中有給出它們的實現程式碼,Please RTFSC(Read The Fucking Source Code)!
5) 左傾堆的Java實現(完整原始碼)
左傾堆的實現檔案(LeftistHeap.java)
/**
 * Java 語言: 左傾堆
 *
 * @author skywang
 * @date 2014/03/31
 */
public class LeftistHeap<T extends Comparable<T>> {
    private LeftistNode<T> mRoot;    // 根結點
    private class LeftistNode<T extends Comparable<T>> {
        T key;                    // 關鍵字(鍵值)
        int npl;                // 零路經長度(Null Path Length)
        LeftistNode<T> left;    // 左孩子
        LeftistNode<T> right;    // 右孩子
        public LeftistNode(T key, LeftistNode<T> left, LeftistNode<T> right) {
            this.key = key;
            this.npl = 0;
            this.left = left;
            this.right = right;
        }
        public String toString() {
            return "key:"+key;
        }
    }
    public LeftistHeap() {
        mRoot = null;
    }
    /*
     * 前序遍歷"左傾堆"
     */
    private void preOrder(LeftistNode<T> heap) {
        if(heap != null) {
            System.out.print(heap.key+" ");
            preOrder(heap.left);
            preOrder(heap.right);
        }
    }
    public void preOrder() {
        preOrder(mRoot);
    }
    /*
     * 中序遍歷"左傾堆"
     */
    private void inOrder(LeftistNode<T> heap) {
        if(heap != null) {
            inOrder(heap.left);
            System.out.print(heap.key+" ");
            inOrder(heap.right);
        }
    }
    public void inOrder() {
        inOrder(mRoot);
    }
    /*
     * 後序遍歷"左傾堆"
     */
    private void postOrder(LeftistNode<T> heap) {
        if(heap != null)
        {
            postOrder(heap.left);
            postOrder(heap.right);
            System.out.print(heap.key+" ");
        }
    }
    public void postOrder() {
        postOrder(mRoot);
    }
    /*
     * 合併"左傾堆x"和"左傾堆y"
     */
    private LeftistNode<T> merge(LeftistNode<T> x, LeftistNode<T> y) {
        if(x == null) return y;
        if(y == null) return x;
        // 合併x和y時,將x作為合併後的樹的根;
        // 這裡的操作是保證: x的key < y的key
        if(x.key.compareTo(y.key) > 0) {
            LeftistNode<T> tmp = x;
            x = y;
            y = tmp;
        }
        // 將x的右孩子和y合併,"合併後的樹的根"是x的右孩子。
        x.right = merge(x.right, y);
        // 如果"x的左孩子為空" 或者 "x的左孩子的npl<右孩子的npl"
        // 則,交換x和y
        if (x.left == null || x.left.npl < x.right.npl) {
            LeftistNode<T> tmp = x.left;
            x.left = x.right;
            x.right = tmp;
        }
        if (x.right == null || x.left == null)
            x.npl = 0;
        else
            x.npl = (x.left.npl > x.right.npl) ? (x.right.npl + 1) : (x.left.npl + 1);
        return x;
    }
    public void merge(LeftistHeap<T> other) {
        this.mRoot = merge(this.mRoot, other.mRoot);
    }
    /* 
     * 新建結點(key),並將其插入到左傾堆中
     *
     * 引數說明:
     *     key 插入結點的鍵值
     */
    public void insert(T key) {
        LeftistNode<T> node = new LeftistNode<T>(key,null,null);
        // 如果新建結點失敗,則返回。
        if (node != null)
            this.mRoot = merge(this.mRoot, node);
    }
    /* 
     * 刪除根結點
     * 
     * 返回值:
     *     返回被刪除的節點的鍵值
     */
    public T remove() {
        if (this.mRoot == null)
            return null;
        T key = this.mRoot.key;
        LeftistNode<T> l = this.mRoot.left;
        LeftistNode<T> r = this.mRoot.right;
        this.mRoot = null;          // 刪除根節點
        this.mRoot = merge(l, r);   // 合併左右子樹
        return key;
    }
    /*
     * 銷燬左傾堆
     */
    private void destroy(LeftistNode<T> heap) {
        if (heap==null)
            return ;
        if (heap.left != null)
            destroy(heap.left);
        if (heap.right != null)
            destroy(heap.right);
        heap=null;
    }
    public void clear() {
        destroy(mRoot);
        mRoot = null;
    }
    /*
     * 列印"左傾堆"
     *
     * key        -- 節點的鍵值 
     * direction  --  0,表示該節點是根節點;
     *               -1,表示該節點是它的父結點的左孩子;
     *                1,表示該節點是它的父結點的右孩子。
     */
    private void print(LeftistNode<T> heap, T key, int direction) {
        if(heap != null) {
            if(direction==0)    // heap是根節點
                System.out.printf("%2d(%d) is root\n", heap.key, heap.npl);
            else                // heap是分支節點
                System.out.printf("%2d(%d) is %2d's %6s child\n", heap.key, heap.npl, key, direction==1?"right" : "left");
            print(heap.left, heap.key, -1);
            print(heap.right,heap.key,  1);
        }
    }
    public void print() {
        if (mRoot != null)
            print(mRoot, mRoot.key, 0);
    }
}
左傾堆的測試程式(LeftistHeapTest.java)
/**
 * Java 語言: 左傾堆
 *
 * @author skywang
 * @date 2014/03/31
 */
public class LeftistHeapTest {
    public static void main(String[] args) {
        int a[]= {10,40,24,30,36,20,12,16};
        int b[]= {17,13,11,15,19,21,23};
        LeftistHeap<Integer> ha=new LeftistHeap<Integer>();
        LeftistHeap<Integer> hb=new LeftistHeap<Integer>();
        System.out.printf("== 左傾堆(ha)中依次新增: ");
        for(int i=0; i<a.length; i++) {
            System.out.printf("%d ", a[i]);
            ha.insert(a[i]);
        }
        System.out.printf("\n== 左傾堆(ha)的詳細資訊: \n");
        ha.print();
        System.out.printf("\n== 左傾堆(hb)中依次新增: ");
        for(int i=0; i<b.length; i++) {
            System.out.printf("%d ", b[i]);
            hb.insert(b[i]);
        }
        System.out.printf("\n== 左傾堆(hb)的詳細資訊: \n");
        hb.print();
        // 將"左傾堆hb"合併到"左傾堆ha"中。
        ha.merge(hb);
        System.out.printf("\n== 合併ha和hb後的詳細資訊: \n");
        ha.print();
    }
}
6) 左傾堆的Java測試程式
左傾堆的測試程式已經包含在它的實現檔案(LeftistHeapTest.java)中了,這裡僅給出它的執行結果:
== 左傾堆(ha)中依次新增: 10 40 24 30 36 20 12 16 
== 左傾堆(ha)的詳細資訊: 
10(2) is root
24(1) is 10's   left child
30(0) is 24's   left child
36(0) is 24's  right child
12(1) is 10's  right child
20(0) is 12's   left child
40(0) is 20's   left child
16(0) is 12's  right child
== 左傾堆(hb)中依次新增: 17 13 11 15 19 21 23 
== 左傾堆(hb)的詳細資訊: 
11(2) is root
15(1) is 11's   left child
19(0) is 15's   left child
21(0) is 15's  right child
13(1) is 11's  right child
17(0) is 13's   left child
23(0) is 13's  right child
== 合併ha和hb後的詳細資訊: 
10(2) is root
11(2) is 10's   left child
15(1) is 11's   left child
19(0) is 15's   left child
21(0) is 15's  right child
12(1) is 11's  right child
13(1) is 12's   left child
17(0) is 13's   left child
16(0) is 13's  right child
23(0) is 16's   left child
20(0) is 12's  right child
40(0) is 20's   left child
24(1) is 10's  right child
30(0) is 24's   left child
36(0) is 24's  right child
十二、 斜堆之Java的實現
1) 概要
前面分別通過C和C++實現了斜堆,本章給出斜堆的Java版本。還是那句老話,三種實現的原理一樣,擇其一瞭解即可。
目錄
1. 斜堆的介紹
2. 斜堆的基本操作
3. 斜堆的Java實現(完整原始碼)
4. 斜堆的Java測試程式
2) 斜堆的介紹
斜堆(Skew heap)也叫自適應堆(self-adjusting heap),它是左傾堆的一個變種。和左傾堆一樣,它通常也用於實現優先佇列;作為一種自適應的左傾堆,它的合併操作的時間複雜度也是O(lg n)。
它與左傾堆的差別是:
(01) 斜堆的節點沒有"零距離"這個屬性,而左傾堆則有。
(02) 斜堆的合併操作和左傾堆的合併操作演算法不同。
3) 斜堆的合併操作
(01) 如果一個空斜堆與一個非空斜堆合併,返回非空斜堆。
(02) 如果兩個斜堆都非空,那麼比較兩個根節點,取較小堆的根節點為新的根節點。將"較小堆的根節點的右孩子"和"較大堆"進行合併。
(03) 合併後,交換新堆根節點的左孩子和右孩子。
        第(03)步是斜堆和左傾堆的合併操作差別的關鍵所在,如果是左傾堆,則合併後要比較左右孩子的零距離大小,若右孩子的零距離 > 左孩子的零距離,則交換左右孩子;最後,在設定根的零距離。
斜堆的基本操作
1. 基本定義
public class SkewHeap<T extends Comparable<T>> {
    private SkewNode<T> mRoot;    // 根結點
    private class SkewNode<T extends Comparable<T>> {
        T key;                // 關鍵字(鍵值)
        SkewNode<T> left;    // 左孩子
        SkewNode<T> right;    // 右孩子
        public SkewNode(T key, SkewNode<T> left, SkewNode<T> right) {
            this.key = key;
            this.left = left;
            this.right = right;
        }
        public String toString() {
            return "key:"+key;
        }
    }
    ...
}
SkewNode是斜堆對應的節點類。
SkewHeap是斜堆類,它包含了斜堆的根節點,以及斜堆的操作。
2. 合併
/*
 * 合併"斜堆x"和"斜堆y"
 */
private SkewNode<T> merge(SkewNode<T> x, SkewNode<T> y) {
    if(x == null) return y;
    if(y == null) return x;
    // 合併x和y時,將x作為合併後的樹的根;
    // 這裡的操作是保證: x的key < y的key
    if(x.key.compareTo(y.key) > 0) {
        SkewNode<T> tmp = x;
        x = y;
        y = tmp;
    }
    // 將x的右孩子和y合併,
    // 合併後直接交換x的左右孩子,而不需要像左傾堆一樣考慮它們的npl。
    SkewNode<T> tmp = merge(x.right, y);
    x.right = x.left;
    x.left = tmp;
    return x;
}
public void merge(SkewHeap<T> other) {
    this.mRoot = merge(this.mRoot, other.mRoot);
}
merge(x, y)是內部介面,作用是合併x和y這兩個斜堆,並返回得到的新堆的根節點。
merge(other)是外部介面,作用是將other合併到當前堆中。
3. 新增
/* 
 * 新建結點(key),並將其插入到斜堆中
 *
 * 引數說明:
 *     key 插入結點的鍵值
 */
public void insert(T key) {
    SkewNode<T> node = new SkewNode<T>(key,null,null);
    // 如果新建結點失敗,則返回。
    if (node != null)
        this.mRoot = merge(this.mRoot, node);
}
insert(key)的作用是新建鍵值為key的節點,並將其加入到當前斜堆中。
4. 刪除
/* 
 * 刪除根結點
 * 
 * 返回值:
 *     返回被刪除的節點的鍵值
 */
public T remove() {
    if (this.mRoot == null)
        return null;
    T key = this.mRoot.key;
    SkewNode<T> l = this.mRoot.left;
    SkewNode<T> r = this.mRoot.right;
    this.mRoot = null;          // 刪除根節點
    this.mRoot = merge(l, r);   // 合併左右子樹
    return key;
}
remove()的作用是刪除斜堆的最小節點。
注意:關於斜堆的"前序遍歷"、"中序遍歷"、"後序遍歷"、"列印"、"銷燬"等介面就不再單獨介紹了。後文的原始碼中有給出它們的實現程式碼,Please RTFSC(Read The Fucking Source Code)!
4) 斜堆的Java實現(完整原始碼)
斜堆的實現檔案(SkewHeap.java)
/**
 * Java 語言: 斜堆
 *
 * @author skywang
 * @date 2014/03/31
 */


public class SkewHeap<T extends Comparable<T>> {
    private SkewNode<T> mRoot;    // 根結點
    private class SkewNode<T extends Comparable<T>> {
        T key;                // 關鍵字(鍵值)
        SkewNode<T> left;    // 左孩子
        SkewNode<T> right;    // 右孩子
        public SkewNode(T key, SkewNode<T> left, SkewNode<T> right) {
            this.key = key;
            this.left = left;
            this.right = right;
        }
        public String toString() {
            return "key:"+key;
        }
    }
    public SkewHeap() {
        mRoot = null;
    }
    /*
     * 前序遍歷"斜堆"
     */
    private void preOrder(SkewNode<T> heap) {
        if(heap != null) {
            System.out.print(heap.key+" ");
            preOrder(heap.left);
            preOrder(heap.right);
        }
    }
    public void preOrder() {
        preOrder(mRoot);
    }
    /*
     * 中序遍歷"斜堆"
     */
    private void inOrder(SkewNode<T> heap) {
        if(heap != null) {
            inOrder(heap.left);
            System.out.print(heap.key+" ");
            inOrder(heap.right);
        }
    }
    public void inOrder() {
        inOrder(mRoot);
    }
    /*
     * 後序遍歷"斜堆"
     */
    private void postOrder(SkewNode<T> heap) {
        if(heap != null)
        {
            postOrder(heap.left);
            postOrder(heap.right);
            System.out.print(heap.key+" ");
        }
    }
    public void postOrder() {
        postOrder(mRoot);
    }
    /*
     * 合併"斜堆x"和"斜堆y"
     */
    private SkewNode<T> merge(SkewNode<T> x, SkewNode<T> y) {
        if(x == null) return y;
        if(y == null) return x;
        // 合併x和y時,將x作為合併後的樹的根;
        // 這裡的操作是保證: x的key < y的key
        if(x.key.compareTo(y.key) > 0) {
            SkewNode<T> tmp = x;
            x = y;
            y = tmp;
        }
        // 將x的右孩子和y合併,
        // 合併後直接交換x的左右孩子,而不需要像左傾堆一樣考慮它們的npl。
        SkewNode<T> tmp = merge(x.right, y);
        x.right = x.left;
        x.left = tmp;
        return x;
    }
    public void merge(SkewHeap<T> other) {
        this.mRoot = merge(this.mRoot, other.mRoot);
    }
    /* 
     * 新建結點(key),並將其插入到斜堆中
     *
     * 引數說明:
     *     key 插入結點的鍵值
     */
    public void insert(T key) {
        SkewNode<T> node = new SkewNode<T>(key,null,null);
        // 如果新建結點失敗,則返回。
        if (node != null)
            this.mRoot = merge(this.mRoot, node);
    }
    /* 
     * 刪除根結點
     * 
     * 返回值:
     *     返回被刪除的節點的鍵值
     */
    public T remove() {
        if (this.mRoot == null)
            return null;
        T key = this.mRoot.key;
        SkewNode<T> l = this.mRoot.left;
        SkewNode<T> r = this.mRoot.right;
        this.mRoot = null;          // 刪除根節點
        this.mRoot = merge(l, r);   // 合併左右子樹
        return key;
    }
    /*
     * 銷燬斜堆
     */
    private void destroy(SkewNode<T> heap) {
        if (heap==null)
            return ;
        if (heap.left != null)
            destroy(heap.left);
        if (heap.right != null)
            destroy(heap.right);
        heap=null;
    }
    public void clear() {
        destroy(mRoot);
        mRoot = null;
    }
    /*
     * 列印"斜堆"
     *
     * key        -- 節點的鍵值 
     * direction  --  0,表示該節點是根節點;
     *               -1,表示該節點是它的父結點的左孩子;
     *                1,表示該節點是它的父結點的右孩子。
     */
    private void print(SkewNode<T> heap, T key, int direction) {
        if(heap != null) {
            if(direction==0)    // heap是根節點
                System.out.printf("%2d is root\n", heap.key);
            else                // heap是分支節點
                System.out.printf("%2d is %2d's %6s child\n", heap.key, key, direction==1?"right" : "left");
            print(heap.left, heap.key, -1);
            print(heap.right,heap.key,  1);
        }
    }
    public void print() {
        if (mRoot != null)
            print(mRoot, mRoot.key, 0);
    }
}
斜堆的測試程式(SkewHeapTest.java)
/**
 * Java 語言: 斜堆
 *
 * @author skywang
 * @date 2014/03/31
 */
public class SkewHeapTest {
    public static void main(String[] args) {
        int a[]= {10,40,24,30,36,20,12,16};
        int b[]= {17,13,11,15,19,21,23};
        SkewHeap<Integer> ha=new SkewHeap<Integer>();
        SkewHeap<Integer> hb=new SkewHeap<Integer>();
        System.out.printf("== 斜堆(ha)中依次新增: ");
        for(int i=0; i<a.length; i++) {
            System.out.printf("%d ", a[i]);
            ha.insert(a[i]);
        }
        System.out.printf("\n== 斜堆(ha)的詳細資訊: \n");
        ha.print();
        System.out.printf("\n== 斜堆(hb)中依次新增: ");
        for(int i=0; i<b.length; i++) {
            System.out.printf("%d ", b[i]);
            hb.insert(b[i]);
        }
        System.out.printf("\n== 斜堆(hb)的詳細資訊: \n");
        hb.print();
        // 將"斜堆hb"合併到"斜堆ha"中。
        ha.merge(hb);
        System.out.printf("\n== 合併ha和hb後的詳細資訊: \n");
        ha.print();
    }
}
5) 斜堆的Java測試程式
斜堆的測試程式已經包含在它的實現檔案(SkewHeapTest.java)中了,這裡僅給出它的執行結果:
== 斜堆(ha)中依次新增: 10 40 24 30 36 20 12 16 
== 斜堆(ha)的詳細資訊: 
10 is root
16 is 10's   left child
20 is 16's   left child
30 is 20's   left child
40 is 30's   left child
12 is 10's  right child
24 is 12's   left child
36 is 24's   left child
== 斜堆(hb)中依次新增: 17 13 11 15 19 21 23 
== 斜堆(hb)的詳細資訊: 
11 is root
13 is 11's   left child
17 is 13's   left child
23 is 17's   left child
19 is 13's  right child
15 is 11's  right child
21 is 15's   left child
== 合併ha和hb後的詳細資訊: 
10 is root
11 is 10's   left child
12 is 11's   left child
15 is 12's   left child
21 is 15's   left child
24 is 12's  right child
36 is 24's   left child
13 is 11's  right child
17 is 13's   left child
23 is 17's   left child
19 is 13's  right child
16 is 10's  right child
20 is 16's   left child
30 is 20's   left child
40 is 30's   left child
十三、 二項堆之Java的實現
1) 概要
前面分別通過C和C++實現了二項堆,本章給出二項堆的Java版本。還是那句老話,三種實現的原理一樣,擇其一瞭解即可。
目錄
1. 二項樹的介紹
2. 二項堆的概述
3. 二項堆的基本操作
4. 二項堆的Java實現(完整原始碼)
5. 二項堆的Java測試程式
2) 二項樹的介紹
二項樹的定義
二項堆是二項樹的集合。在瞭解二項堆之前,先對二項樹進行介紹。
二項樹是一種遞迴定義的有序樹。它的遞迴定義如下:
(01) 二項樹B0只有一個結點;
(02) 二項樹Bk由兩棵二項樹B(k-1)組成的,其中一棵樹是另一棵樹根的最左孩子。
如下圖所示:
 
上圖的B0、B1、B2、B3、B4都是二項樹。對比前面提到的二項樹的定義:B0只有一個節點,B1由兩個B0所組成,B2由兩個B1所組成,B3由兩個B2所組成,B4由兩個B3所組成;而且,當兩顆相同的二項樹組成另一棵樹時,其中一棵樹是另一棵樹的最左孩子。
二項樹的性質
二項樹有以下性質:
[性質一] Bk共有2k個節點。
               如上圖所示,B0有20=1節點,B1有21=2個節點,B2有22=4個節點,...
[性質二] Bk的高度為k。
               如上圖所示,B0的高度為0,B1的高度為1,B2的高度為2,...
[性質三] Bk在深度i處恰好有C(k,i)個節點,其中i=0,1,2,...,k。
              C(k,i)是高中數學中階乘元素,例如,C(10,3)=(10*9*8) / (3*2*1)=240
              B4中深度為0的節點C(4,0)=1
              B4中深度為1的節點C(4,1)= 4 / 1 = 4
              B4中深度為2的節點C(4,2)= (4*3) / (2*1) = 6
              B4中深度為3的節點C(4,3)= (4*3*2) / (3*2*1) = 4
              B4中深度為4的節點C(4,4)= (4*3*2*1) / (4*3*2*1) = 1
             合計得到B4的節點分佈是(1,4,6,4,1)。
[性質四] 根的度數為k,它大於任何其它節點的度數。
              節點的度數是該結點擁有的子樹的數目。
注意:樹的高度和深度是相同的。關於樹的高度的概念,《演算法導論》中只有一個節點的樹的高度是0,而"維基百科"中只有一個節點的樹的高度是1。本文使用了《演算法導論中》"樹的高度和深度"的概念。
3) 二項堆的概述
二項堆和之前所講的堆(二叉堆、左傾堆、斜堆)一樣,也是用於實現優先佇列的。二項堆是指滿足以下性質的二項樹的集合:
(01) 每棵二項樹都滿足最小堆性質。即,父節點的關鍵字 <= 它的孩子的關鍵字。
(02) 不能有兩棵或以上的二項樹具有相同的度數(包括度數為0)。換句話說,具有度數k的二項樹有0個或1個。
 
上圖就是一棵二項堆,它由二項樹B0、B2和B3組成。對比二項堆的定義:(01)二項樹B0、B2、B3都是最小堆;(02)二項堆不包含相同度數的二項樹。
         二項堆的第(01)個性質保證了二項堆的最小節點就是某個二項樹的根節點,第(02)個性質則說明結點數為n的二項堆最多隻有log{n} + 1棵二項樹。實際上,將包含n個節點的二項堆,表示成若干個2的指數和(或者轉換成二進位制),則每一個2個指數都對應一棵二項樹。例如,13(二進位制是1101)的2個指數和為13=23 + 22+ 20, 因此具有13個節點的二項堆由度數為3, 2, 0的三棵二項樹組成。
4) 二項堆的基本操作
二項堆是可合併堆,它的合併操作的複雜度是O(log n)。
5) 1. 基本定義
public class BinomialHeap<T extends Comparable<T>> {
    private BinomialNode<T> mRoot;    // 根結點
    private class BinomialNode<T extends Comparable<T>> {
        T key;                // 關鍵字(鍵值)
        int degree;            // 度數
        BinomialNode<T> child;    // 左孩子
        BinomialNode<T> parent;    // 父節點
        BinomialNode<T> next;    // 兄弟節點
        public BinomialNode(T key) {
            this.key = key;
            this.degree = 0;
            this.child = null;
            this.parent = null;
            this.next = null;
        }
        public String toString() {
            return "key:"+key;
        }
    }
    ...
}
BinomialNode是二項堆的節點。它包括了關鍵字(key),用於比較節點大小;度數(degree),用來表示當前節點的度數;左孩子(child)、父節點(parent)以及兄弟節點(next)。
BinomialHeap是二項堆對應的類,它包括了二項堆的根節點mRoot以及二項堆的基本操作的定義。
下面是一棵二項堆的樹形圖和它對應的記憶體結構關係圖。
 
6) 2. 合併操作
合併操作是二項堆的重點,它的新增操作也是基於合併操作來實現的。合併兩個二項堆,需要的步驟概括起來如下:
(01) 將兩個二項堆的根連結串列合併成一個連結串列。合併後的新連結串列按照"節點的度數"單調遞增排列。
(02) 將新連結串列中"根節點度數相同的二項樹"連線起來,直到所有根節點度數都不相同。
下面,先看看合併操作的程式碼;然後再通過示意圖對合並操作進行說明。
merge()程式碼(Java)
/*
 * 將h1, h2中的根表合併成一個按度數遞增的連結串列,返回合併後的根節點
 */
private BinomialNode<T> merge(BinomialNode<T> h1, BinomialNode<T> h2) {
    if (h1 == null) return h2;
    if (h2 == null) return h1;
    // root是新堆的根,h3用來遍歷h1和h3的。
    BinomialNode<T> pre_h3, h3, root=null;
    pre_h3 = null;
    //整個while,h1, h2, pre_h3, h3都在往後順移
    while ((h1!=null) && (h2!=null)) {
        if (h1.degree <= h2.degree) {
            h3 = h1;
            h1 = h1.next;
        } else {
            h3 = h2;
            h2 = h2.next;
        }
        if (pre_h3 == null) {
            pre_h3 = h3;
            root = h3;
        } else {
            pre_h3.next = h3;
            pre_h3 = h3;
        }
        if (h1 != null) {
            h3.next = h1;
        } else {
            h3.next = h2;
        }
    }
    return root;
}
link()程式碼(Java)
/*
 * 合併兩個二項堆:將child合併到root中
 */
private void link(BinomialNode<T> child, BinomialNode<T> root) {
    child.parent = root;
    child.next   = root.child;
    root.child = child;
    root.degree++;
}
合併操作程式碼(Java)
/*
 * 合併二項堆:將h1, h2合併成一個堆,並返回合併後的堆
 */
private BinomialNode<T> union(BinomialNode<T> h1, BinomialNode<T> h2) {
    BinomialNode<T> root;
    // 將h1, h2中的根表合併成一個按度數遞增的連結串列root
    root = merge(h1, h2);
    if (root == null)
        return null;
    BinomialNode<T> prev_x = null;
    BinomialNode<T> x      = root;
    BinomialNode<T> next_x = x.next;
    while (next_x != null) {
        if (   (x.degree != next_x.degree) 
            || ((next_x.next != null) && (next_x.degree == next_x.next.degree))) {
            // Case 1: x.degree != next_x.degree
            // Case 2: x.degree == next_x.degree == next_x.next.degree
            prev_x = x;
            x = next_x;
        } else if (x.key.compareTo(next_x.key) <= 0) {
            // Case 3: x.degree == next_x.degree != next_x.next.degree
            //      && x.key    <= next_x.key
            x.next = next_x.next;
            link(next_x, x);
        } else {
            // Case 4: x.degree == next_x.degree != next_x.next.degree
            //      && x.key    >  next_x.key
            if (prev_x == null) {
                root = next_x;
            } else {
                prev_x.next = next_x;
            }
            link(x, next_x);
            x = next_x;
        }
        next_x = x.next;
    }
    return root;
}
/*
 * 將二項堆other合併到當前堆中
 */
public void union(BinomialHeap<T> other) {
    if (other!=null && other.mRoot!=null)
        mRoot = union(mRoot, other.mRoot);
}
合併函式combine(h1, h2)的作用是將h1和h2合併,並返回合併後的二項堆。在combine(h1, h2)中,涉及到了兩個函式merge(h1, h2)和link(child, root)。
merge(h1, h2)就是我們前面所說的"兩個二項堆的根連結串列合併成一個連結串列,合併後的新連結串列按照'節點的度數'單調遞增排序"。
link(child, root)則是為了合併操作的輔助函式,它的作用是將"二項堆child的根節點"設為"二項堆root的左孩子",從而將child整合到root中去。
        在combine(h1, h2)中對h1和h2進行合併時;首先通過 merge(h1, h2) 將h1和h2的根連結串列合併成一個"按節點的度數單調遞增"的連結串列;然後進入while迴圈,對合並得到的新連結串列進行遍歷,將新連結串列中"根節點度數相同的二項樹"連線起來,直到所有根節點度數都不相同為止。在將新聯表中"根節點度數相同的二項樹"連線起來時,可以將被連線的情況概括為4種。
x是根連結串列的當前節點,next_x是x的下一個(兄弟)節點。
Case 1: x->degree != next_x->degree
             即,"當前節點的度數"與"下一個節點的度數"相等時。此時,不需要執行任何操作,繼續檢視後面的節點。
Case 2: x->degree == next_x->degree == next_x->next->degree
             即,"當前節點的度數"、"下一個節點的度數"和"下下一個節點的度數"都相等時。此時,暫時不執行任何操作,還是繼續檢視後面的節點。實際上,這裡是將"下一個節點"和"下下一個節點"等到後面再進行整合連線。
Case 3: x->degree == next_x->degree != next_x->next->degree
        && x->key <= next_x->key
             即,"當前節點的度數"與"下一個節點的度數"相等,並且"當前節點的鍵值"<="下一個節點的度數"。此時,將"下一個節點(對應的二項樹)"作為"當前節點(對應的二項樹)的左孩子"。
Case 4: x->degree == next_x->degree != next_x->next->degree
        && x->key > next_x->key
             即,"當前節點的度數"與"下一個節點的度數"相等,並且"當前節點的鍵值">"下一個節點的度數"。此時,將"當前節點(對應的二項樹)"作為"下一個節點(對應的二項樹)的左孩子"。
下面通過示意圖來對合並操作進行說明。
 
第1步:將兩個二項堆的根連結串列合併成一個連結串列
          執行完第1步之後,得到的新連結串列中有許多度數相同的二項樹。實際上,此時得到的是對應"Case 4"的情況,"樹41"(根節點為41的二項樹)和"樹13"的度數相同,且"樹41"的鍵值 > "樹13"的鍵值。此時,將"樹41"作為"樹13"的左孩子。
第2步:合併"樹41"和"樹13"
         執行完第2步之後,得到的是對應"Case 3"的情況,"樹13"和"樹28"的度數相同,且"樹13"的鍵值 < "樹28"的鍵值。此時,將"樹28"作為"樹13"的左孩子。
第3步:合併"樹13"和"樹28"
         執行完第3步之後,得到的是對應"Case 2"的情況,"樹13"、"樹28"和"樹7"這3棵樹的度數都相同。此時,將x設為下一個節點。
第4步:將x和next_x往後移
         執行完第4步之後,得到的是對應"Case 3"的情況,"樹7"和"樹11"的度數相同,且"樹7"的鍵值 < "樹11"的鍵值。此時,將"樹11"作為"樹7"的左孩子。
第5步:合併"樹7"和"樹11"
         執行完第5步之後,得到的是對應"Case 4"的情況,"樹7"和"樹6"的度數相同,且"樹7"的鍵值 > "樹6"的鍵值。此時,將"樹7"作為"樹6"的左孩子。
第6步:合併"樹7"和"樹6"
         此時,合併操作完成!
PS. 合併操作的圖文解析過程與"測試程式(Main.java)中的testUnion()函式"是對應的!
7) 3. 插入操作
理解了"合併"操作之後,插入操作就相當簡單了。插入操作可以看作是將"要插入的節點"和當前已有的堆進行合併。
插入操作程式碼(Java)
/*
 * 新建key對應的節點,並將其插入到二項堆中。
 */
public void insert(T key) {
    BinomialNode<T> node;
    // 禁止插入相同的鍵值
    if