學習 javascript 數據結構 (四)——樹

前言

總括: 本文講解了數據結構中的[樹]的概念,儘量通俗易懂的解釋樹這種數據結構的概念,使用javascript實現了樹,若有紕漏,歡迎批評指正。javascript

人之所能,不能兼備,棄其所短,取其所長。php

正文

樹簡介

在上一篇學習javascript數據結構(三)——集合中咱們說了集合這種數據結構,在學習javascript數據結構(一)——棧和隊列學習javascript數據結構(二)——鏈表說了棧和隊列以及鏈表這類線性表數據結構。接下來這一篇說的是這種數據結構。首先想讓你們明白的是數據結構是個什麼玩意兒,數據結構能夠分爲數據的邏輯結構和數據的物理結構,所謂的數據邏輯結構在我理解就是計算機對於數據的組織方式的研究。也就是說研究的是數據和數據之間的關係。而數據的物理結構是數據的邏輯結構在計算機中的具體實現,也就是說一種邏輯結構可能會有多種存儲結構與之相對應。前端

那麼咱們這一篇所說的就是一種數據邏輯結構,即研究的是數據和數據之間的關係。以前所說的隊列鏈表都是一種線性結構,相信你們也能發現這種線性結構的數據關係有一個共同點,就是數據都是一對一的,而上一篇說到的集合這種數據結構,數據是散亂的,他們之間的關係就是隸屬於同一個集合,如上一篇例子所說,這些小孩子都是同一個幼兒園的,可是這些小孩子之間的關係咱們並不知道。線性表(棧、隊列、鏈表)就是對這些小孩子關係的一種表達(一對一)。而集合也是對於這些小孩子關係的一種表達。和線性表不一樣的是,樹這種數據結構是一對多的,也就是說他所描述的是某個小孩子和其它小孩子之間的關係。java

樹這種結構實際上咱們平時也有見到,好比下圖這種簡單的思惟導圖:node

思惟導圖

以下也是一棵樹:git

關於樹概念總結以下:github

1)樹形結構是一對多的非線性結構。
2)樹形結構有樹和二叉樹兩種,樹的操做實現比較複雜,但樹能夠轉換爲二叉樹進行處理。
3)樹的定義:樹(Tree)是 n(n≥0)個相同類型的數據元素的有限集合。
4)樹中的數據元素叫節點(Node)。
5)n=0 的樹稱爲空樹(Empty Tree);
6)對於 n>0 的任意非空樹 T 有:
(1)有且僅有一個特殊的節點稱爲樹的根(Root)節點,根沒有前驅節點;
(2)若n>1,則除根節點外,其他節點被分紅了m(m>0)個互不相交的集合
T1,T2,。。。,Tm,其中每個集合Ti(1≤i≤m)自己又是一棵樹。樹T1,T2,。。。,Tm稱爲這棵樹的子樹(Subtree)。
7)樹的定義是遞歸的,用樹來定義樹。所以,樹(以及二叉樹)的許多算法都使用了遞歸。 面試

參看維基百科對於的定義:算法

在計算機科學中,(英語:tree)是一種抽象數據類型(ADT)或是實做這種抽象數據類型的數據結構,用來模擬具有樹狀結構性質的數據集合。它是由n(n>=1)個有限節點組成一個具備層次關係的集合。把它叫作「樹」是由於它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具備如下的特色:數據結構

  • 每一個節點有零個或多個子節點;
  • 沒有父節點的節點稱爲根節點;
  • 每個非根節點有且只有一個父節點;
  • 除了根節點外,每一個子節點能夠分爲多個不相交的子樹;

樹的種類:

  • 無序樹:樹中任意節點的子節點之間沒有順序關係,這種樹稱爲無序樹,也稱爲自由樹
  • 有序樹:樹中任意節點的子節點之間有順序關係,這種樹稱爲有序樹;
    • 二叉樹:每一個節點最多含有兩個子樹的樹稱爲二叉樹;徹底二叉樹:對於一顆二叉樹,假設其深度爲d(d>1)。除了第d層外,其它各層的節點數目均已達最大值,且第d層全部節點從左向右連續地緊密排列,這樣的二叉樹被稱爲徹底二叉樹;滿二叉樹:全部葉節點都在最底層的徹底二叉樹;平衡二叉樹AVL樹):當且僅當任何節點的兩棵子樹的高度差不大於1的二叉樹;排序二叉樹(二叉查找樹(英語:Binary Search Tree),也稱二叉搜索樹、有序二叉樹);
    • 霍夫曼樹帶權路徑最短的二叉樹稱爲哈夫曼樹或最優二叉樹;
    • B樹:一種對讀寫操做進行優化的自平衡的二叉查找樹,可以保持數據有序,擁有多餘兩個子樹。

有關樹的術語:

  1. 節點的度:一個節點含有的子樹的個數稱爲該節點的度;
  2. 樹的度:一棵樹中,最大的節點的度稱爲樹的度;
  3. 葉節點終端節點:度爲零的節點;
  4. 非終端節點分支節點:度不爲零的節點;
  5. 父親節點父節點:若一個節點含有子節點,則這個節點稱爲其子節點的父節點;
  6. 孩子節點子節點:一個節點含有的子樹的根節點稱爲該節點的子節點;
  7. 兄弟節點:具備相同父節點的節點互稱爲兄弟節點;
  8. 節點的層次:從根開始定義起,根爲第1層,根的子節點爲第2層,以此類推;
  9. 樹的高度深度:樹中節點的最大層次;
  10. 堂兄弟節點:父節點在同一層的節點互爲堂兄弟;
  11. 節點的祖先:從根到該節點所經分支上的全部節點;
  12. 子孫:以某節點爲根的子樹中任一節點都稱爲該節點的子孫。
  13. 森林:由m(m>=0)棵互不相交的樹的集合稱爲森林;

(我是維基百科搬運工,哈哈哈)

二叉樹

二叉樹(英語:Binary tree)是每一個節點最多有兩個子樹的樹結構。一般子樹被稱做「左子樹」(left subtree)和「右子樹」(right subtree)。二叉樹常被用於實現二叉查找樹二元堆積

咱們主要研究的就是二叉樹,也就是數據爲一對二的關係。那麼在二叉樹中又有些分類;

二叉樹

二叉樹分類:

  • 一棵深度爲k,且有
    {\displaystyle 2^{\begin{aligned}k+1\end{aligned}}-1}
    個節點稱之爲滿二叉樹
  • 深度爲k,有n個節點的二叉樹,當且僅當其每個節點都與深度爲k的滿二叉樹中,序號爲1至n的節點對應時,稱之爲徹底二叉樹
  • 平衡二叉樹又被稱爲AVL樹(區別於AVL算法),它是一棵二叉排序樹,且具備如下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,而且左右兩個子樹都是一棵平衡二叉樹

二叉樹的遍歷

1)一棵二叉樹由根結點、左子樹和右子樹三部分組成,
2) D、L、R 分別表明遍歷根結點、遍歷左子樹、遍歷右子樹,則二叉樹的
3) 遍歷方式有6 種:DLR、DRL、LDR、LRD、RDL、RLD。先左或先右算法基本同樣,因此就剩下三種DLR(先序或是前序)、LDR(中序)、LRD(後序)。

  • 前序遍歷:首先訪問根節點,而後遍歷左子樹,最後遍歷右子樹,可記錄爲根—左—右;
  • 中序遍歷:首先訪問左子樹,而後訪問根節點,最後遍歷右子樹,可記錄爲左—根—右;
  • 後序遍歷:首先遍歷左子樹,而後遍歷右子樹,最後遍歷根節點,可記錄爲左—右—根。

二叉樹的遍歷

二叉樹遍歷

以上圖1爲例解釋前序遍歷:

首先訪問根節點a=>而後遍歷左子樹b=>左子樹b的左子樹d=>d的右孩子e>此時b的左子樹遍歷完,遍歷b的右子樹f=>f的左孩子g=>左子樹b遍歷完,遍歷根節點的右孩子c,完成=>abdefgc

中序遍歷,後序遍歷就很少說了,不一樣的只是訪問的順序。

注意:

(1)已知前序、後序遍歷結果,不能推導出一棵肯定的樹;

(2)已知前序、中序遍歷結果,可以推導出後序遍歷結果;

(3)已知後序、中序遍歷結果,可以推導出前序遍歷結果;

二叉搜索樹的建立

二叉查找樹(BinarySearch Tree,也叫二叉搜索樹,或稱二叉排序樹Binary Sort Tree)或者是一棵空樹,或者是具備下列性質的二叉樹:

1)若它的左子樹不爲空,則左子樹上全部結點的值均小於它的根結點的值;

(2)若它的右子樹不爲空,則右子樹上全部結點的值均大於它的根結點的值;

(3)它的左、右子樹也分別爲二叉查找樹。複製代碼

首先咱們聲明一個BinarySearchTree類:

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

二叉樹

和鏈表同樣,二叉樹也經過指針來表示節點之間的關係。在雙向鏈表中,每個節點有兩個指針,一個指向下一個節點,一個指向上一個節點。對於樹,使用一樣的方式,只不過一個指向左孩子,一個指向右孩子。如今咱們給這棵樹弄一些方法:

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

BinarySearchTree類的完整代碼(充分添加註釋):

function BinarySearchTree() {
    var Node = function(key){
        this.key = key;
        this.left = null;
        this.right = null;
    };
    var root = null;
    this.insert = function(key){

        var newNode = new Node(key);

        //判斷是不是第一個節點,若是是做爲根節點保存。不是調用inserNode方法
        if (root === null){
            root = newNode;
        } else {
            insertNode(root,newNode);
        }
    };
    var insertNode = function(node, newNode){
      //判斷兩個節點的大小,根據二叉搜索樹的特色左子樹上全部結點的值均小於它的根結點的值,右子樹上全部結點的值均大於它的根結點的值
        if (newNode.key < node.key){
            if (node.left === null){
                node.left = newNode;
            } else {
                insertNode(node.left, newNode);
            }
        } else {
            if (node.right === null){
                node.right = newNode;
            } else {
                insertNode(node.right, newNode);
            }
        }
    };
    this.getRoot = function(){
        return root;
    };
    this.search = function(key){
        return searchNode(root, key);
    };

    var searchNode = function(node, key){
        if (node === null){
            return false;
        }
        if (key < node.key){
            return searchNode(node.left, key);
        } else if (key > node.key){
            return searchNode(node.right, key);
        } else { //element is equal to node.item
            return true;
        }
    };
    this.inOrderTraverse = function(callback){
        inOrderTraverseNode(root, callback);
    };
    var inOrderTraverseNode = function (node, callback) {
        if (node !== null) {
            inOrderTraverseNode(node.left, callback);
            callback(node.key);
            inOrderTraverseNode(node.right, callback);
        }
    };
    this.preOrderTraverse = function(callback){
        preOrderTraverseNode(root, callback);
    };
    var preOrderTraverseNode = function (node, callback) {
        if (node !== null) {
            callback(node.key);
            preOrderTraverseNode(node.left, callback);
            preOrderTraverseNode(node.right, callback);
        }
    };
    this.postOrderTraverse = function(callback){
        postOrderTraverseNode(root, callback);
    };
    var postOrderTraverseNode = function (node, callback) {
        if (node !== null) {
            postOrderTraverseNode(node.left, callback);
            postOrderTraverseNode(node.right, callback);
            callback(node.key);
        }
    };
    this.min = function() {
        return minNode(root);
    };
    var minNode = function (node) {
        if (node){
            while (node && node.left !== null) {
                node = node.left;
            }

            return node.key;
        }
        return null;
    };
    this.max = function() {
        return maxNode(root);
    };
    var maxNode = function (node) {
        if (node){
            while (node && node.right !== null) {
                node = node.right;
            }

            return node.key;
        }
        return null;
    };
    this.remove = function(element){
        root = removeNode(root, element);
    };
    var findMinNode = function(node){
        while (node && node.left !== null) {
            node = node.left;
        }
        return node;
    };
    var removeNode = function(node, element){
        if (node === null){
            return null;
        }
        if (element < node.key){
            node.left = removeNode(node.left, element);
            return node;
        } else if (element > node.key){
            node.right = removeNode(node.right, element);
            return node;
        } else { 
            //處理三種特殊狀況
            //1 - 葉子節點
            //2 - 只有一個孩子的節點
            //3 - 有兩個孩子的節點
            //case 1
            if (node.left === null && node.right === null){
                node = null;
                return node;
            }
            //case 2
            if (node.left === null){
                node = node.right;
                return node;
            } else if (node.right === null){
                node = node.left;
                return node;
            }
            //case 3
            var aux = findMinNode(node.right);
            node.key = aux.key;
            node.right = removeNode(node.right, aux.key);
            return node;
        }
    };
}複製代碼

後記

樹是一種比較常見的數據結構,不論是考試仍是平常編碼或是面試都是無法避免的一個知識點,此篇總結不甚完善,紕漏之處還望指出方便以後更改。敬請期待數據結構篇最後一篇文章:[學習javascript數據結構(五)——圖]

參考文章

相關文章
相關標籤/搜索