數據結構《學習筆記》——二叉樹

這是我參與8月更文挑戰的第8天,活動詳情查看:8月更文挑戰node

1、樹的介紹

  • 樹:n(n >= 0)個節點構成的有限集合
  • 對於任一棵非空樹(n > 0),具有如下性質:
    • 樹中有一個稱爲**"根"**的特殊節點
    • 其他節點可分爲m(m > 0)個互不相交的有限集T一、T二、...、Tm,其中每一個集合自己又是一棵樹,稱爲原來樹的**"子樹"**

樹的性質是不少的...數組

  • 節點的度:樹的節點個數
  • 葉節點:度爲0的節點(也稱爲葉子節點)
  • 節點的層次:規定根節點在1層,其它任一節點的層數是其父節點的層數加1

2、二叉樹

二叉樹的特性

  • 一個二叉樹第i層的最大節點數爲:2^(i-1), i>=1markdown

    好比第一層(根節點)爲1個;第二層最多爲2節點函數

  • 深度爲k的二叉樹有最大節點總數爲:2^k-1, k>=1post

  • 對任何非空二叉樹T,若n0表示葉節點(度爲0)的個數,n2表示度爲2的非葉節點個數,那麼二者知足關係n0 = n2 + 1ui

a. 徹底二叉樹

  • 除二叉樹最後一層外,其餘各層的節點數都達到最大個數this

  • 且最後一層從左向右達到葉節點連續存在,只缺右側若干節點spa

  • 下面的圖從左到右看,E有右子節點可是D沒有,因此不是徹底二叉樹。若是給D加一個右子節點,那麼它就是徹底二叉樹prototype

徹底二叉樹.png

b. 完美二叉樹(滿二叉樹)

  • 在二叉樹中,除了最下一層的葉節點外,每層節點都有2個子節點,就構成了滿二叉樹

滿二叉樹.png

二叉樹的存儲方式能夠是數組也能夠是鏈表,可是咱們通常用鏈表。由於使用數組的是否若是該二叉樹不是滿二叉樹,會形成不少空間的浪費。指針

3、二叉搜索樹

  • 二叉搜索樹是一顆二叉樹,能夠爲空;若是不爲空,知足如下性質:
  • 非空子樹的全部鍵值小於其根節點的鍵值
  • 非空子樹的全部鍵值大於其根節點的鍵值
  • 左、右子樹自己也都是二叉搜索樹

二叉搜索樹初始化

  • 首先,二叉搜索樹有一個根節點,初始化讓這個根節點指向空;this.root = null;

  • 其次,二叉搜索樹的每個節點包含3個元素,左子節點、右子節點和鍵值;初始狀態下指針指向空

    function Node() {
        this.key = key;
        this.left = null;
        this.right = null;
    }
    複製代碼

4、二叉搜索樹常見方法

  • insert(key):向樹中插入一個新的鍵
  • search(key):在樹中查找一個鍵,若是節點存在,則返回true;若是不存在,返回false
  • inOrderTraverse:經過中序遍歷方式遍歷全部節點
  • preOrderTraverse:經過先序遍歷方式遍歷全部節點
  • postOrderTraverse:經過後序遍歷方式遍歷全部節點
  • min:返回樹中的最小的值/鍵
  • max:返回樹中的最大的值/鍵
  • remove(key):從樹中移除某個鍵

1. insert方法

  1. 根據key建立節點

  2. 判斷根節點是否有值;若是根節點爲null時直接插入,不然進行下一步操做

    BinarySearchTree.prototype.insert = function (key) {
        var newNode = new Node(key);
        if (this.root == null) {
            this.root = newNode;
        } else {
            this.insertNode(this.root, newNode);
        }
    }
    複製代碼
  3. 在搜索樹進行插入操做的時候,若是插入的值大於根節點,那麼就要把該節點插到根節點的右子節點;若是此時根節點已經有右子節點了,那就應該繼續讓這個要插入的節點的值和該右子節點進行比較插入;這個過程其實就是在套娃,一直到這個左或者右節點爲空的時候就中止,因此這裏用遞歸來實現,用另外一個函數來進行對節點的插入。

    BinarySearchTree.prototype.insertNode = function (node, newNode) {
        if (newNode.key < node.key) {
            if (node.left == null) {
                node.left = newNode;
            } else {
                this.insertNode(node.left, newNode);
            }
        } else {
            if (node.right == null) {
                node.right = newNode;
            } else {
                this.insertNode(node.right, newNode);
            }
        }
    }
    複製代碼

2. 遍歷二叉搜索樹

因爲樹的結構是比較特殊的,在遍歷節點的時候咱們每每都是採用遞歸的方法來實現。

a. 先序遍歷

  • 訪問根節點
  • 先先序遍歷其左子樹,再先序遍歷右子樹
BinarySearchTree.prototype.preOrderTraversalNode = function (node, handler) {
    if (node != null) {
        handler(node.key);
        this.preOrderTraversalNode(node.left, handler);
        // 3. 處理通過節點的右子節點
        this.preOrderTraversalNode(node.right, handler);
    }
}
//--------
var resultString = ''
tree.preOrderTraversal(function (key) {
    resultString += key + ' ';
})
複製代碼

這裏的handler是一個處理結點的回調函數,便於咱們看遍歷的結果;在先序遍歷中,咱們要先遍歷全部的左子節點,再處理通過節點的右子節點;好比下面這張圖:

這裏用的遞歸思路仍是有一點複雜的,是一個個函數的嵌套,遍歷結束以後一個個跳出來的,須要花一點時間來理解。

遍歷7的左節點到5的時候,先遍歷3,3是葉節點,繼續遍歷6(此時的node是5),遍歷完5的左右節點以後,就繼續遍歷7的右子節點。

先序遍歷.png

b. 中序遍歷

  • 中序遍歷其左子樹
  • 訪問根節點
  • 中序遍歷右子樹
if (node != null) {
    this.inOrderTraversalNode(node.left, handler);
    handler(node.key);
    this.inOrderTraversalNode(node.right, handler);
}
複製代碼

c. 後序遍歷

  • 後序遍歷其左子樹,再後序遍歷其右子樹
  • 訪問根節點
if (node != null) {
    this.inOrderTraversalNode(node.left, handler);
    this.inOrderTraversalNode(node.right, handler);
    handler(node.key);
}
複製代碼

理解了先序遍歷以後,中序遍歷和後序遍歷都是用遞歸的方式實現,不一樣之處在於什麼時候來處理節點。

3. 最大值&最小值

最大值和最小值在數中是很容易找的。最左邊的子節點(左下)就是最小值;最右邊的子節點(右下)就是最大值。這裏演示查找最大值。

  • 獲取根節點
  • 依次向右查找,直到節點爲null
BinarySearchTree.prototype.max = function () {
    var node = this.root;
    var key = null;
    while (node != null) {
        key = node.key
        node = node.right
    }
    return node.key
}
複製代碼

在查找的時候,一直都找右節點,直到這個右節點的值爲空的時候返回這個節點的鍵值。

4. 搜索特定的值

這個方法能夠經過遞歸的方法實現,也能夠經過循環來進行搜索

  • 遞歸;將查找值和當前節點的值進行對比,若是較小就往左子樹找,若是較大就往右子樹上查找,直到node.key == key時,返回true,若是通過這些步驟以後仍是找不到的話就返回false。

    BinarySearchTree.prototype.searchNode = function (node, key) {
        if (node == null) {
            return false;
        }
        if (node.key > key) {
            return this.searchNode(node.left, key);
        } else if (node.key < key) {
            return this.searchNode(node.right, key);
        } else {
            return true;
        }
        return false;
    }
    複製代碼
  • 循環 先獲取根節點,再循環搜索key

    循環的話會比較好理解一點,就是鍵值比較,決定往左子樹仍是右子樹遍歷,直到找到目標值。

    var node = this.root;
    while (node != null) {
        if (node.key > key) {
            node = node.left;
        } else if (node.key < key) {
            node = node.right;
        } else {
            return true;
        }
    }
    return false;
    複製代碼

刪除操做是最複雜的,那就放在下一篇文章吧!今天學不下去了aaa...codewhy老師真的講的好棒!

相關文章
相關標籤/搜索