數據結構-二叉樹

數據結構-二叉樹

樹的定義

樹是n個結點的有限集合,在一個非空的樹中有且只有一個根結點,其餘結點可分爲m個互不相交的有限子集,其中每個子集又是一棵樹,並且成爲根節點的子樹。

文字定義有點繞,但圖形化很容易記住:

        |----------A----------|
        |          |          |
    |---B---|      C      |---D---|
    |       |             |   |   |
    E       F             G   H   I

樹的基本概念

樹的基本概念主要有以下幾種:

  • 雙親、孩子和兄弟

結點的子樹稱爲結點的孩子;相應地,該結點稱爲其子結點的雙親;具有相同雙親的結點互爲兄弟結點;上圖中,BCD是A的孩子,A是BCD的雙親,同時BCD互爲兄弟結點

  • 結點的度

結點的度是指結點孩子結點的個數,A有3個孩子所以度爲3,其他結點也以此類推

  • 葉子結點

度爲0的結點稱爲葉子結點,也稱爲終端結點,上圖中CEFGHI爲葉子結點

  • 內部結點

度不爲0的結點稱爲內部結點,也稱爲中間結點,上圖中BD爲中間結點

  • 結點的層次

根爲第一層結點,根的孩子爲第二層結點,結點的層次即爲目標節點與根結點構成的路徑中結點的個數

  • 樹的高度

樹中所有結點的最大層次即爲樹的高度,上圖中樹的高度爲3

  • 有序樹/無序樹

樹中每個結點的孩子結點順序不可交換爲有序樹,反之稱爲無序樹

二叉樹

二叉樹的定義:“所有結點的孩子數不超過2的有序樹”。

二叉樹的種類

  • 滿二叉樹

所有非葉子結點的孩子數均爲2的二叉樹即爲滿二叉樹,若其深度爲k則樹的結點數爲2k - 1

  • 完全二叉樹

高度爲h的二叉樹,除第h層外的每一層結點數均是滿的,且h層的結點不存在左兄弟結點爲空的情況,符合上述條件的二叉樹被稱爲完全二叉樹

  • 線索二叉樹

在結點中記錄了某種遍歷方式對應的結點線性順序關係的二叉樹

  • 最優二叉樹

最優二叉樹也稱爲哈夫曼樹,它是一類帶權路徑長度最短的樹,樹的帶權路徑長度等於所有葉子結點的帶權路徑長度只和,帶權路徑長度等於權重乘以路徑長度,經典應用-霍夫曼編碼

二叉樹的遍歷

二叉樹的遍歷,是指按某種策略訪問樹中的每個結點,主要有四種方式:先序、中序、後序與按層。爲了後文描述遍歷算法,先對樹結點做一個結構定義:

// tree node
{
    data: '',       // 結點的值
    left: null,     // 左孩子,不存在則爲null
    right: null     // 右孩子,不存在則爲null
}

先序遍歷

先序遍歷是指先訪問根結點,繼而先序遍歷左子樹再先序遍歷右子樹,算法代碼如下:

function preOrder(root) {
    if (root === null) {
        return;
    }

    console.log(root.data);
    preOrder(root.left);
    preOrder(root.right);
}

中序遍歷

中序遍歷是指先中序訪問左子樹,繼而訪問根結點再中序遍歷右子樹,算法代碼如下:

function inOrder(root) {
    if (root === null) {
        return;
    }

    inOrder(root.left);
    console.log(root.data);
    inOrder(root.right);
}

非遞歸方式:

function inOrderWithStack(root) {
    let stack = [];    

    let p = root;
    let q = null;

    while(p !== null || stack.length !== 0) {
        if (p !== null) {
            stack.push(p);              // 結點入棧
            p = p.left;                 
            continue;
        }

        q = stack.pop();                // 結點出棧
        console.log(q.data);
        p = q.right;
    }
}

後序遍歷

後序遍歷是指先後序訪問左子樹,繼而後序訪問右子樹再訪問根結點,算法代碼如下:

function postOrder(root) {
    if (root === null) {
        return;
    }

    postOrder(root.left);
    postOrder(root.right);
    console.log(root.data);
}

按層遍歷

按層遍歷是指根據樹的層從小到大,每一層從左向右訪問各結點,刷法代碼如下:

function levelOrder(root) {
    let p = null;       // 用於保存隊列出來的結點
    let queue = [];

    queue.push(root);
    while((p = queue.shift())) {            // queue爲空時表達式的值爲undefined,即爲false
        consolg.log(p.data);
        
        p.left && queue.push(p.left);       // if的一種簡寫
        p.right && queue.push(p.right);
    }
}

樹的應用

查找二叉樹

查找二叉樹也稱爲二叉排序書,它滿足以下兩個條件:

  • 所有結點的左子樹結點值均小於自己
  • 所有結點的右子樹結點值均大於自己

查找二叉樹主要作用就是可以進行快速查找,從某查找二叉樹中查找值的算法如下:

function searchBST(root, key) {
    let p = root;

    while(p !== null && p.data !== key) {
        p = key < p.data ? p.left : p.right;
    }

    return p;
}

查找二叉樹的插入算法與查找算法相似,如下:

function insertBST(root, key) {
    let node = treeNodeFrom(key);

    let p = root;
    let q = null;   // 指向p的雙親結點

    while(p !== null && p.data !== key) {
        q = p;
        p = key < p.data ? p.left : p.right;
    }

    if (p !== null) {
        throw new Error('duplicate key');
    }

    if (q.data > key) {
        q.left = node;
    }
    else {
        q.right = node;
    }
}

平衡二叉樹

平衡樹滿足以下兩個條件:

  • 左子樹和右子樹都是平衡樹
  • 左子樹與右子樹的高度差其絕對值不超過1

一顆平衡查找二叉樹的查找效率會優於不平衡的查找二叉樹,但是插入和刪除結點可能涉及需要對樹做調整使其保持平衡,這其中的操作用代碼表示會比較複雜,暫時不展開。

B樹

B樹是一個一般化的二叉查找樹,主要用在數據庫和文件系統上,相信內容移步wiki

博客原文

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