算法數據結構之樹

  有許多邏輯關係並不是簡單的線性關係,在實際場景中,常常存在着一對多,甚至是多對多的情況。其中樹和圖就是典型的非線性數據結構,我們首先講一講樹的知識。

什麼是樹呢?

  在現實生活中有很多體現樹的邏輯的例子。例如企業裏的職級關係,就是一個“樹”。

  除人與人之間的關係之外,許多抽象的東西也可以成爲一個“樹”,如一本書的目錄。

在數據結構中,樹的定義如下。

  樹(tree)是n(n≥0)個節點的有限集。當n=0時,稱爲空樹。在任意一個非空樹中,有如下特點。

  • 有且僅有一個特定的稱爲根的節點。
  • 當n>1時,其餘節點可分爲m(m>0)個互不相交的有限集,每一個集合本身又是一個樹,並稱爲根的子樹。

  下面這張圖,就是一個標準的樹結構。

  在上圖中,節點1是根節點(root);節點5、6、7、8是樹的末端,沒有“孩子”,被稱爲葉子節點(leaf)。圖中的虛線部分,是根節點1的其中一個子樹。同時,樹的結構從根節點到葉子節點,分爲不同的層級。從一個節點的角度來看,它的上下級和同級節點關係如下。

  在上圖中,節點4的上一級節點,是節點4的父節點(parent);從節點4衍生出來的節點,是節點4的孩子節點(child);和節點4同級,由同一個父節點衍生出來的節點,是節點4的兄弟節點(sibling)。樹的最大層級數,被稱爲樹的高度或深度。顯然,上圖這個樹的高度是4。

什麼是二叉樹:

  二叉樹(binary tree)是樹的一種特殊形式。二叉,顧名思義,這種樹的每個節點最多有2個孩子節點。注意,這裏是最多有2個,也可能只有1個,或者沒有孩子節點。二叉樹的結構如圖所示。

  二叉樹節點的兩個孩子節點,一個被稱爲左孩子(left child),一個被稱爲右孩子(right child)。這兩個孩子節點的順序是固定的,就像人的左手就是左手,右手就是右手,不能夠顛倒或混淆。

  此外,二叉樹還有兩種特殊形式,一個叫作滿二叉樹,另一個叫作完全二叉樹。

什麼是滿二叉樹呢?

  一個二叉樹的所有非葉子節點都存在左右孩子,並且所有葉子節點都在同一層級上,那麼這個樹就是滿二叉樹。

  簡單點說,滿二叉樹的每一個分支都是滿的。

  什麼又是完全二叉樹呢?完全二叉樹的定義很有意思。

  對一個有n個節點的二叉樹,按層級順序編號,則所有節點的編號爲從1到n。如果這個樹所有節點和同樣深度的滿二叉樹的編號爲從1到n的節點位置相同,則這個二叉樹爲完全二叉樹。看看下圖就很容易理解了。

  在上圖中,二叉樹編號從1到12的12個節點,和前面滿二叉樹編號從1到12的節點位置完全對應。因此這個樹是完全二叉樹。完全二叉樹的條件沒有滿二叉樹那麼苛刻:滿二叉樹要求所有分支都是滿的;而完全二叉樹只需保證最後一個節點之前的節點都齊全即可

  數據結構可以劃分爲物理結構和邏輯結構。二叉樹屬於邏輯結構,它可以通過多種物理結構來表達。

二叉樹可以用哪些物理存儲結構來表達呢?

  1. 鏈式存儲結構。
  2. 數組。

  首先來看一看鏈式存儲結構

  鏈式存儲是二叉樹最直觀的存儲方式。鏈表是一對一的存儲方式,每一個鏈表節點擁有data變量和一個指向下一節點的next指針。而二叉樹稍微複雜一些,一個節點最多可以指向左右兩個孩子節點,所以二叉樹的每一個節點包含3部分。

  1. 存儲數據的data變量
  2. 指向左孩子的left指針
  3. 指向右孩子的right指針

  再來看看用數組是如何存儲的

  使用數組存儲時,會按照層級順序把二叉樹的節點放到數組中對應的位置上。如果某一個節點的左孩子或右孩子空缺,則數組的相應位置也空出來。

  爲什麼這樣設計呢?因爲這樣可以更方便地在數組中定位二叉樹的孩子節點和父節點。

  假設一個父節點的下標是parent,那麼它的左孩子節點下標就是2×parent +1;右孩子節點下標就是2×parent + 2。

  反過來,假設一個左孩子節點的下標是leftChild,那麼它的父節點下標就是(leftChild-1)/ 2。

  假如節點4在數組中的下標是3,節點4是節點2的左孩子,節點2的下標可以直接通過計算得出。節點2的下標 = (3-1)/2 = 1

  顯然,對於一個稀疏的二叉樹來說,用數組表示法是非常浪費空間的。

  什麼樣的二叉樹最適合用數組表示呢?下文中將會介紹二叉堆,一種特殊的完全二叉樹,就是用數組來存儲的。

二叉樹的應用:

  二叉樹包含許多特殊的形式,每一種形式都有自己的作用,但是其最主要的應用還在於進行查找操作和維持相對順序這兩個方面。

  二叉樹的樹形結構使它很適合扮演索引的角色。這裏我們介紹一種特殊的二叉樹:二叉查找樹(binary search tree)。光看名字就可以知道,這種二叉樹的主要作用就是進行查找操作。二叉查找樹在二叉樹的基礎上增加了以下幾個條件。

  1. 如果左子樹不爲空,則左子樹上所有節點的值均小於根節點的值
  2. 如果右子樹不爲空,則右子樹上所有節點的值均大於根節點的值
  3. 左、右子樹也都是二叉查找樹

  下圖就是一個標準的二叉查找樹。

  二叉查找樹的這些條件有什麼用呢?當然是爲了查找方便。怎麼查找?相信有經驗的開發人員這裏是不需要解釋的。

  對於一個節點分佈相對均衡的二叉查找樹來說,如果節點總數是n,那麼搜索節點的時間複雜度就是O(logn),和樹的深度是一樣的。這種依靠比較大小來逐步查找的方式,和二分查找算法非常相似。

  這一點仍然要從二叉查找樹說起。二叉查找樹要求左子樹小於父節點,右子樹大於父節點,正是這樣保證了二叉樹的有序性。因此二叉查找樹還有另一個名字——二叉排序樹(binary sort tree)。新插入的節點,同樣要遵循二叉排序樹的原則。例如插入新元素5,由於5<6,5>3,5>4,所以5最終會插入到節點4的右孩子位置。

   在某種程度下,二叉查找樹慢慢的插入元素,導致成了下面這種結構:

  不只是外觀看起來變得怪異了,查詢節點的時間複雜度也退化成了O(n)。

  怎麼解決這個問題呢?這就涉及二叉樹的自平衡了。二叉樹自平衡的方式有多種,如紅黑樹、AVL樹、樹堆等。

  除二叉查找樹以外,二叉堆也維持着相對的順序。不過二叉堆的條件要寬鬆一些,只要求父節點比它的左右孩子都大。

二叉樹的遍歷:

  當我們介紹數組、鏈表時,爲什麼沒有着重研究他們的遍歷過程呢?在計算機程序中,遍歷本身是一個線性操作。所以遍歷同樣具有線性結構的數組或鏈表,是一件輕而易舉的事情。

  二叉樹的遍歷又有什麼特殊之處?反觀二叉樹,是典型的非線性數據結構,遍歷時需要把非線性關聯的節點轉化成一個線性的序列,以不同的方式來遍歷,遍歷出的序列順序也不同。

  那麼,二叉樹都有哪些遍歷方式呢?從節點之間位置關係的角度來看,二叉樹的遍歷分爲4種。

  1. 前序遍歷。
  2. 中序遍歷。
  3. 後序遍歷。
  4. 層序遍歷。

  從更宏觀的角度來看,二叉樹的遍歷歸結爲兩大類。

  1. 深度優先遍歷(前序遍歷、中序遍歷、後序遍歷)。
  2. 廣度優先遍歷(層序遍歷)

深度優先遍歷:

  深度優先和廣度優先這兩個概念不止侷限於二叉樹,它們更是一種抽象的算法思想,決定了訪問某些複雜數據結構的順序。在訪問樹、圖,或其他一些複雜數據結構時,這兩個概念常常被使用到。

  所謂深度優先,顧名思義,就是偏向於縱深,“一頭扎到底”的訪問方式。可

  能這種說法有些抽象,下面就通過二叉樹的前序遍歷、中序遍歷、後序遍歷,來看一看深度優先是怎麼回事吧。針對下面這個二叉樹的遍歷結果是怎麼樣的?

  • 前序遍歷:二叉樹的前序遍歷,輸出順序是根節點、左子樹、右子樹。結果爲 1 2 4 5 3 6
  • 中序遍歷:二叉樹的中序遍歷,輸出順序是左子樹、根節點、右子樹。結果爲 4 2 5 1 3 6
  • 後序遍歷:二叉樹的後序遍歷,輸出順序是左子樹、右子樹、根節點。結果爲  4 5 2 6 3 1 

  對於二叉樹相關遍歷的代碼如下:

public class TreeNode {

    int data;
    TreeNode leftChild;
    TreeNode rightChild;

    TreeNode(int data) {
        this.data = data;
    }

    /**
     * 構建二叉樹
     *
     * @param inputList 輸入序列
     */
    public static TreeNode createBinaryTree(LinkedList<Integer> inputList) {
        TreeNode node = null;
        if (inputList == null || inputList.isEmpty()) {
            return null;
        }
        Integer data = inputList.removeFirst();
        if (data != null) {
            node = new TreeNode(data);
            node.leftChild = createBinaryTree(inputList);
            node.rightChild = createBinaryTree(inputList);
        }
        return node;
    }

    /**
     * 二叉樹前序遍歷
     *
     * @param node 二叉樹節點
     */
    public static void preOrderTraveral(TreeNode node) {
        if (node == null) {
            return;
        }
        System.out.println(node.data);
        preOrderTraveral(node.leftChild);
        preOrderTraveral(node.rightChild);
    }

    /**
     * 二叉樹中序遍歷
     *
     * @param node 二叉樹節點
     */
    public static void inOrderTraveral(TreeNode node) {
        if (node == null) {
            return;
        }
        inOrderTraveral(node.leftChild);
        System.out.println(node.data);
        inOrderTraveral(node.rightChild);
    }

    /**
     * 二叉樹後序遍歷
     *
     * @param node 二叉樹節點
     */
    public static void postOrderTraveral(TreeNode node) {
        if (node == null) {
            return;
        }
        postOrderTraveral(node.leftChild);
        postOrderTraveral(node.rightChild);
        System.out.println(node.data);
    }

    public static void main(String[] args) {
        LinkedList<Integer> inputList = new LinkedList<Integer>(
                Arrays.asList(new Integer[]{3, 2, 9, null, null, 10, null, null, 8, null, 4}));
        TreeNode treeNode = createBinaryTree(inputList);
        System.out.println(" 前序遍歷:");
        preOrderTraveral(treeNode);
        System.out.println(" 中序遍歷:");
        inOrderTraveral(treeNode);
        System.out.println(" 後序遍歷:");
        postOrderTraveral(treeNode);
    }
}

  二叉樹用遞歸方式來實現前序、中序、後序遍歷,是最爲自然的方式,因此代碼也非常簡單。這3種遍歷方式的區別,僅僅是輸出的執行位置不同:前序遍歷的輸出在前,中序遍歷的輸出在中間,後序遍歷的輸出在最後。代碼中值得注意的一點是二叉樹的構建。二叉樹的構建方法有很多,這裏把一個線性的鏈表轉化成非線性的二叉樹,鏈表節點的順序恰恰是二叉樹前序遍歷的順序。鏈表中的空值,代表二叉樹節點的左孩子或右孩子爲空的情況。在代碼的main函數中,通過{3,2,9,null,null,10,null,null,8,null,4}這樣一個線性序列,構建成的二叉樹如下。

  絕大多數可以用遞歸解決的問題,其實都可以用另一種數據結構來解決,這種數據結構就是棧。因爲遞歸和棧都有回溯的特性。

  二叉樹非遞歸前序遍歷的代碼如下:

/**
     * 二叉樹非遞歸前序遍歷
     *
     * @param root 二叉樹根節點
     */
    public static void preOrderTraveralWithStack(TreeNode root) {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode treeNode = root;
        while (treeNode != null || !stack.isEmpty()) {
            //迭代訪問節點的左孩子,併入棧
            while (treeNode != null) {
                System.out.println(treeNode.data);
                stack.push(treeNode);
                treeNode = treeNode.leftChild;
            }
            //如果節點沒有左孩子,則彈出棧頂節點,訪問節點右孩子
            if (!stack.isEmpty()) {
                treeNode = stack.pop();
                treeNode = treeNode.rightChild;
            }
        }
    }

  至於二叉樹的中序、後序遍歷的非遞歸實現,思路和前序遍歷差不太多,都是利用棧來進行回溯。

廣度優先遍歷:

  如果說深度優先遍歷是在一個方向上“一頭扎到底”,那麼廣度優先遍歷則恰恰相反:先在各個方向上各走出1步,再在各個方向上走出第2步、第3步……一直到各個方向全部走完。聽起來有些抽象,下面讓我們通過二叉樹的層序遍歷,來看一看廣度優先是怎麼回事。層序遍歷,顧名思義,就是二叉樹按照從根節點到葉子節點的層次關係,一層一層橫向遍歷各個節點。

 

  上圖就是一個二叉樹的層序遍歷,每個節點左側的序號代表該節點的輸出順序。

  可是,二叉樹同一層次的節點之間是沒有直接關聯的,如何實現這種層序遍歷呢?這裏同樣需要藉助一個數據結構來輔助工作,這個數據結構就是隊列。詳細遍歷步驟如下。

  1. 根節點1進入隊列。
  2. 節點1出隊,輸出節點1,並得到節點1的左孩子節點2、右孩子節點3。讓節點2和節點3入隊。
  3. 節點2出隊,輸出節點2,並得到節點2的左孩子節點4、右孩子節點5。讓節點4和節點5入隊。
  4. 節點3出隊,輸出節點3,並得到節點3的右孩子節點6。讓節點6入隊。
  5. 節點4出隊,輸出節點4,由於節點4沒有孩子節點,所以沒有新節點入隊。
  6. 節點5出隊,輸出節點5,由於節點5同樣沒有孩子節點,所以沒有新節點入隊。
  7. 節點6出隊,輸出節點6,節點6沒有孩子節點,沒有新節點入隊。到此爲止,所有的節點都遍歷輸出完畢。

  代碼如下:

/**
     * 二叉樹層序遍歷
     *
     * @param root 二叉樹根節點
     */
    public static void levelOrderTraversal(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            System.out.println(node.data);
            if (node.leftChild != null) {
                queue.offer(node.leftChild);
            }
            if (node.rightChild != null) {
                queue.offer(node.rightChild);
            }
        }
    }

  遞歸實現:

/**
* 二叉樹層序遍歷遞歸實現
*
* @param root 二叉樹根節點
*/
public static void levelOrderTraversalDigui(TreeNode root, int level) {
        if (root == null) {
            return;
        }
        if (level == 0) {
            System.out.println(root.data);
        }
        if (root.leftChild !=null) {
            System.out.println(root.leftChild.data);
        }
        if (root.rightChild !=null) {
            System.out.println(root.rightChild.data);
        }
        levelOrderTraversalDigui(root.leftChild,level+1);
        levelOrderTraversalDigui(root.rightChild,level+1);

}

什麼是二叉堆:

  二叉堆本質上是一種完全二叉樹,它分爲兩個類型。

  1. 最大堆。
  2. 最小堆。

  什麼是最大堆呢?最大堆的任何一個父節點的值,都大於或等於它左、右孩子節點的值。

 

  什麼是最小堆呢?最小堆的任何一個父節點的值,都小於或等於它左、右孩子節點的值。

 

  二叉堆的根節點叫作堆頂。最大堆和最小堆的特點決定了:最大堆的堆頂是整個堆中的最大元素;最小堆的堆頂是整個堆中的最小元素。

二叉堆的自我調整:

  我們如何構建一個堆呢?這就需要依靠二叉堆的自我調整了。對於二叉堆,有如下幾種操作。

  1. 插入節點。
  2. 刪除節點。
  3. 構建二叉堆。

  這幾種操作都基於堆的自我調整。所謂堆的自我調整,就是把一個不符合堆性質的完全二叉樹,調整成一個堆。下面讓我們以最小堆爲例,看一看二叉堆是如何進行自我調整的。

插入節點:

1.插入節點當二叉堆插入節點時,插入位置是完全二叉樹的最後一個位置。例如插入一個新節點,值是 0。

2.這時,新節點的父節點5比0大,顯然不符合最小堆的性質。於是讓新節點“上浮”,和父節點交換位置。

3.繼續用節點0和父節點3做比較,因爲0小於3,則讓新節點繼續“上浮”。

繼續比較,最終新節點0“上浮”到了堆頂位置。

刪除節點:

  二叉堆刪除節點的過程和插入節點的過程正好相反,所刪除的是處於堆頂的節點。例如刪除最小堆的堆頂節點1。

這時,爲了繼續維持完全二叉樹的結構,我們把堆的最後一個節點10臨時補到原本堆頂的位置。

接下來,讓暫處堆頂位置的節點10和它的左、右孩子進行比較,如果左、右孩子節點中最小的一個(顯然是節點2)比節點10小,那麼讓節點10“下沉”。

繼續讓節點10和它的左、右孩子做比較,左、右孩子中最小的是節點7,由於10大於7,讓節點10繼續“下沉”。

這樣一來,二叉堆重新得到了調整。

構建二叉堆:

  構建二叉堆,也就是把一個無序的完全二叉樹調整爲二叉堆,本質就是讓所有非葉子節點依次“下沉”。下面舉一個無序完全二叉樹的例子,如下圖所示。

  1. 首先,從最後一個非葉子節點開始,也就是從節點10開始。如果節點10大於它左、右孩子節點中最小的一個,則節點10“下沉”。
  2. 接下來輪到節點3,如果節點3大於它左、右孩子節點中最小的一個,則節點3“下沉”。
  3. 然後輪到節點1,如果節點1大於它左、右孩子節點中最小的一個,則節點1“下沉”。事實上節點1小於它的左、右孩子,所以不用改變。
  4. 接下來輪到節點7,如果節點7大於它左、右孩子節點中最小的一個,則節點7“下沉”。
  5. 節點7繼續比較,繼續“下沉”。
  6. 經過上述幾輪比較和“下沉”操作,最終每一節點都小於它的左、右孩子節點,一個無序的完全二叉樹就被構建成了一個最小堆。

  關於堆的插入和刪除操作,時間複雜度是O(logn)。構建堆的時間複雜度是O(n)。

二叉堆的代碼實現:

  需要明確一點:二叉堆雖然是一個完全二叉樹,但它的存儲方式並不是鏈式存儲,而是順序存儲。換句話說,二叉堆的所有節點都存儲在數組中。

  在數組中,在沒有左、右指針的情況下,如何定位一個父節點的左孩子和右孩子呢?

  像上圖那樣,可以依靠數組下標來計算。假設父節點的下標是parent,那麼它的左孩子下標就是 2×parent+1;右孩子下標就是2×parent+2。

  例如上面的例子中,節點6包含9和10兩個孩子節點,節點6在數組中的下標是3,節點9在數組中的下標是7,節點10在數組中的下標是8。那麼,7 = 3×2+1,8 = 3×2+2,剛好符合規律。

   (array.length-2)/2 就能計算出最後一個非葉子節點

public class BinaryHeap {
    /**
     * “上浮”調整
     *
     * @param array 待調整的堆
     */
    public static void upAdjust(int[] array) {
        int childIndex = array.length - 1;
        int parentIndex = (childIndex - 1) / 2;
        // temp 保存插入的葉子節點值,用於最後的賦值
        int temp = array[childIndex];
        while (childIndex > 0 && temp < array[parentIndex]) {
            //無須真正交換,單向賦值即可
            array[childIndex] = array[parentIndex];
            childIndex = parentIndex;
            parentIndex = (parentIndex - 1) / 2;
        }
        array[childIndex] = temp;
    }

    /**
     * “下沉”調整
     *
     * @param array       待調整的堆
     * @param parentIndex 要“下沉”的父節點
     * @param length      堆的有效大小
     */
    public static void downAdjust(int[] array, int parentIndex,
                                  int length) {
        // temp 保存父節點值,用於最後的賦值
        int temp = array[parentIndex];
        int childIndex = 2 * parentIndex + 1;
        while (childIndex < length) {
            // 如果有右孩子,且右孩子小於左孩子的值,則定位到右孩子
            if (childIndex + 1 < length && array[childIndex + 1] <
                    array[childIndex]) {
                childIndex++;
            }
            // 如果父節點小於任何一個孩子的值,則直接跳出
            if (temp <= array[childIndex])
                break;
            //無須真正交換,單向賦值即可
            array[parentIndex] = array[childIndex];
            parentIndex = childIndex;
            childIndex = 2 * childIndex + 1;
        }
        array[parentIndex] = temp;
    }

    /**
     * 構建堆
     *
     * @param array 待調整的堆
     */
    public static void buildHeap(int[] array) {
        // 從最後一個非葉子節點開始,依次做“下沉”調整
        for (int i = (array.length - 2) / 2; i >= 0; i--) {
            downAdjust(array, i, array.length);
        }
    }

    public static void main(String[] args) {
        int[] array = new int[]{1, 3, 2, 6, 5, 7, 8, 9, 10, 0};
        upAdjust(array);
        System.out.println(Arrays.toString(array));

        array = new int[]{7, 1, 3, 10, 5, 2, 8, 9, 6};
        buildHeap(array);
        System.out.println(Arrays.toString(array));
    }
}

  代碼中有一個優化的點,就是在父節點和孩子節點做連續交換時,並不一定要真的交換,只需要先把交換一方的值存入temp變量,做單向覆蓋,循環結束後,再把temp的值存入交換後的最終位置即可。

什麼是優先隊列:

  隊列的特點是先進先出(FIFO)。入隊列,將新元素置於隊尾。出隊列,隊頭元素最先被移出。

  那麼,優先隊列又是什麼樣子呢?優先隊列不再遵循先入先出的原則,而是分爲兩種情況。

  1. 最大優先隊列,無論入隊順序如何,都是當前最大的元素優先出隊
  2. 最小優先隊列,無論入隊順序如何,都是當前最小的元素優先出隊

  例如有一個最大優先隊列,其中的最大元素是8,那麼雖然8並不是隊頭元素,但出隊時仍然讓元素8首先出隊。

 

   因此,可以用最大堆來實現最大優先隊列,這樣的話,每一次入隊操作就是堆的插入操作,每一次出隊操作就是刪除堆頂節點。

  二叉堆節點“上浮”和“下沉”的時間複雜度都是O(log2(n)),所以優先隊列入隊和出隊的時間複雜度也是O(log2(n))

public class PriorityQueue {
    private int[] array;
    private int size;

    public PriorityQueue() {
        //隊列初始長度爲32
        array = new int[32];
    }

    /**
     * 入隊
     *
     * @param key 入隊元素
     */
    public void enQueue(int key) {
        //隊列長度超出範圍,擴容
        if (size >= array.length) {
            resize();
        }
        array[size++] = key;
        upAdjust();
    }

    /**
     * 出隊
     */
    public int deQueue() throws Exception {
        if (size <= 0) {
            throw new Exception("the queue is empty !");
        }
        //獲取堆頂元素
        int head = array[0];
        //讓最後一個元素移動到堆頂
        array[0] = array[--size];
        downAdjust();
        return head;
    }

    /**
     * “上浮”調整
     */
    private void upAdjust() {
        int childIndex = size - 1;
        int parentIndex = (childIndex - 1) / 2;
        // temp 保存插入的葉子節點值,用於最後的賦值
        int temp = array[childIndex];
        while (childIndex > 0 && temp > array[parentIndex]) {
            //無須真正交換,單向賦值即可
            array[childIndex] = array[parentIndex];
            childIndex = parentIndex;
            parentIndex = parentIndex / 2;
        }
        array[childIndex] = temp;
    }

    /**
     * “下沉”調整
     */
    private void downAdjust() {
        // temp 保存父節點的值,用於最後的賦值
        int parentIndex = 0;
        int temp = array[parentIndex];
        int childIndex = 1;
        while (childIndex < size) {
            // 如果有右孩子,且右孩子大於左孩子的值,則定位到右孩子
            if (childIndex + 1 < size && array[childIndex + 1] >
                    array[childIndex]) {
                childIndex++;
            }
            // 如果父節點大於任何一個孩子的值,直接跳出
            if (temp >= array[childIndex])
                break;
            //無須真正交換,單向賦值即可
            array[parentIndex] = array[childIndex];
            parentIndex = childIndex;
            childIndex = 2 * childIndex + 1;
        }
        array[parentIndex] = temp;
    }

    /**
     * 隊列擴容
     */
    private void resize() {
        //隊列容量翻倍
        int newSize = this.size * 2;
        this.array = Arrays.copyOf(this.array, newSize);
    }

    public static void main(String[] args) throws Exception {
        PriorityQueue priorityQueue = new PriorityQueue();
        priorityQueue.enQueue(3);
        priorityQueue.enQueue(5);
        priorityQueue.enQueue(10);
        priorityQueue.enQueue(2);
        priorityQueue.enQueue(7);
        System.out.println(" 出隊元素:" + priorityQueue.deQueue());
        System.out.println(" 出隊元素:" + priorityQueue.deQueue());
    }
}

 

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