js高級進階之數據結構“二叉排序樹”的es5和es6實現與應用

“樹”是一種較爲複雜也非常重要的數據結構,特別是目前流行的vue react框架虛擬dom操作時都是以“樹”爲數據結構模型進行一系列的操作的,弄清楚樹數據結構算法對閱讀源碼有很大的幫助。

認識樹形結構

1.1樹結構

一個樹結構包含一系列存在父子關係的節點。每個節點都有一個父節點(除了頂部的第一個節點)以及零個或多個子節點:

在這裏插入圖片描述

1.2 樹的定義

  • 節點:樹中的每個元素都叫作節點;
  • 根節點:位於樹頂部的節點叫作根節點;
  • 內部節點/分支節點:至少有一個子節點的節點稱爲內部節點或;外部節點/葉節點:沒有子元素的節點稱爲外部節點或葉節點;
  • 子節點:P和Q爲J的子節點;
  • 父節點:J爲P和1Q的父節點;
  • 兄弟節點:同一個父節點的子節點互稱爲兄弟;P和Q互爲兄弟節點
  • 祖先節點:從根節點到該節點所經過分支上的所有節點;如節點P的祖先節點爲 J,E,A;
  • 子孫節點:以某一節點構成的子樹,其下所有節點均爲其子孫節點;如P和Q爲A的子孫節點;
  • 節點所在層次:根節點爲1層,依次向下;
  • 樹的深度:樹中距離根節點最遠的節點所處的層次就是樹的深度;上圖中,樹的深度是4;
  • 節點的度:結點擁有子結點的數量;
  • 樹的度:樹中節點的度的最大值;
  • 有序樹:樹中任意節點的子結點之間有順序關係,這種樹稱爲有序樹;
  • 無序樹:樹中任意節點的子結點之間沒有順序關係,這種樹稱爲無序樹,也稱爲自由樹
  • 二叉樹:在計算機科學中,二叉樹是每個結點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。二叉樹常被用於實現二叉查找樹和二叉堆。
  • 二叉排序樹,二叉查找樹:
    二叉排序樹或者是一顆空樹,或者是具有下列性質的二叉樹:

(1)若它的左子樹不空,則左子樹上的所有結點的值均小於它的根結點的值。
(2)若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值。
(3)它的左、右子樹葉分別是二叉排序樹。

算法結構中重點實現的就是二叉排序樹/二叉查找樹,以下的內容我們都是針對二叉排序樹/二叉查找樹進行說明;

1.3 二叉排序樹的定義

下圖就是一個二叉排序樹(搜索樹)
在這裏插入圖片描述

定義
二叉樹是樹的一種特殊情況,每個節點最多有有兩個子女,分別稱爲該節點的左子女和右子女,就是說,在二叉樹中,不存在度大於2的節點。

二叉搜索樹(BST)是二叉樹的一種,但是它只允許你在左側節點存儲(比父節點)小的值, 在右側節點存儲(比父節點)大(或者等於)的值。

特點
同一層,數值從左到右依次增加以某一祖先節點爲參考,該節點左側值均小於節點值,右側值均大於節點值在二叉樹的第i(i>=1)層,最多有x^(i-1)
個節點深度爲k(k>=1)的二叉樹,最少有k個節點,最多有2^k-1個節點
對於一棵非空二叉樹,葉節點的數量等於度爲2的節點數量加1。

滿二叉樹:深度爲k的滿二叉樹,是有2^k-1個節點的二叉樹,每一層都達到了可以容納的最大數量的節點;

因爲樹有很多用法太過靈活,就前端而言最爲常用的就是二叉排序樹。本文後續內容只關注二叉排序樹

二叉排序樹的方法

  1. insert(key): 向樹中插入一個新的鍵;
  2. inOrderTraverse: 通過中序遍歷方式遍歷所有節點;
  3. preOrderTraverse: 通過先序遍歷方式遍歷所有節點;
  4. postOrderTraverse: 通過後序遍歷方式遍歷所有節點;
  5. getMin: 返回樹中最小的值/鍵;
  6. getMax: 返回樹中最大的值/鍵;
  7. find(key): 在樹中查找一個鍵,如果節點存在則返回該節點不存在則返回null;
  8. remove(key): 從樹中移除某個鍵;

1.4 二叉排序樹的作用

到這裏,相信很多朋友已經迷糊了,既然樹這麼複雜,而且還有這麼多類型,爲什麼要學習樹而且還是排序二叉樹。

現在我來回答這個問題,假設現在要對全班的總成績進行錄入並排名,在後一名學生成績還未錄入前求出當前已錄入學生的第一名和最後一名(不要想着數據庫sql查詢語句實現),此時初學者想到的肯定是數組,ok我們看下如果數組來實現,每次找到第N名學生的成績應該非常容易arry[i]就搞定,但插入時立即得出當前排序名次,然後將該名同學交換位置到排名位置,這是一件很浪費性能的事情,如果改用鏈表實現插入非常容易,但尋找查詢成績將消耗性能。

綜上我們考慮到一種數據結構,它既滿足方便的插入同時也具備快速的查找,那就是二叉排序樹。

最重要的二叉排序樹的前中後遍歷在實際應用中非常有用。

前序遍歷:快速展示結果;
中序遍歷:排序輸出結果;
後續遍歷:實現目錄結構或刪除節點;

2 二叉排序樹的實現

2.1二叉樹基類

ES5實現:

 /**
         * 二叉排序樹類
         * */
        var BinarySearchTree = function() {
            var Node = function(key,value) {
                this.key = key;
                this.value = value;
                this.left = null;
                this.right = null;
            };
            var root = null;
            this.util = util;

            this.insert = function(key,value) {
                var newNode = new Node(key,value);
                if (root === null) {
                    root = newNode;
                } else {
                    this.util.insertNode(root, newNode);
                }
            };

            // 中序遍歷是一種以上行順序訪問BST所有節點的遍歷方式,也就是以從最小到最大的順序訪
            // 問所有節點。中序遍歷的一種應用就是對樹進行排序操作。
            this.inOrderTraverse = function(cb) {
                this.util.orderTraverseNode("in",root, cb);
            };

            // 先序遍歷是以優先於後代節點的順序訪問每個節點的。先序遍歷的一種應用是打印一個結構化的文檔。
            this.preOrderTraverse = function(cb) {
                this.util.orderTraverseNode("per",root, cb);
            };

            // 後序遍歷則是先訪問節點的後代節點,再訪問節點本身。後序遍歷的一種應用是計算一個目
            // 錄和它的子目錄中所有文件所佔空間的大小。
            this.postOrderTraverse = function(cb) {
                this.util.orderTraverseNode("post",root, cb);
            };

            // Breadth-First-Search
            // 可以用來解決尋路徑的問題。
            this.levelOrderTraverse = function(cb) {
                this.util.levelOrderTraverseNode(root, cb);
            }

            // Breadth-First-Search
            // 區分層次
            this.separateByLevel = function(cb) {
                this.util.separateByLevelFn(root, cb,"******");
            }

            this.min = function() {
                return this.util.minNode(root);
            };

            this.max = function() {
                return this.util.maxNode(root);
            };

            this.search = function(val) {
                this.util.searchNode(root, val);
            };

            this.remove = function(key) {
                root = this.util.removeNode(root, key);
            };
        };

樹操作的工具方法集合:

       /**
         * 樹的工具方法集合
         * */
        var util = {
            insertNode : function(node, newNode) {
                if (newNode.value < node.value) {
                    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);
                    }
                }
            },

            orderTraverseNode : function(type,node, cb) {
                if (node !== null) {
                    if(type === "in"){
                        this.orderTraverseNode(type,node.left, cb);
                        cb(node);
                        this.orderTraverseNode(type,node.right, cb);
                    }else if(type === "per"){
                        cb(node);
                        this.orderTraverseNode(type,node.left, cb);
                        this.orderTraverseNode(type,node.right, cb);
                    }else if(type === "post"){
                        this.orderTraverseNode(type,node.left, cb);
                        this.orderTraverseNode(type,node.right, cb);
                        cb(node);
                    }                   
                }
            },

            levelOrderTraverseNode : function(node, cb) {
                if (node === null) {
                    return null;
                }
                var list = [node];
                while (list.length > 0) {
                    node = list.shift();
                    cb(node);
                    if (node.left) {
                        list.push(node.left);
                    }
                    if (node.right) {
                        list.push(node.right);
                    }
                }
            },

            separateByLevelFn : function(node, cb, separator) {
                var list = [];
                var lev = 0;
                var END_FLAG = 'END_FLAG';
                list.push(node);
                list.push(END_FLAG);
                separator = separator || '---*---';
                while (list.length > 0) {
                    node = list.shift();
                    // 遇到結束信號,表示已經遍歷完一層;若隊列中還有元素,說明它們是剛剛遍歷完的這一層的所有子元素。
                    if (node === END_FLAG && list.length > 0) {
                        lev++;
                        list.push(END_FLAG);
                        cb("第"+(lev)+"層"+separator+"有"+(list.length-1)+"個元素");
                    } else {
                        cb(node);
                        if (node.left) {
                        list.push(node.left)
                        }
                        if (node.right) {
                        list.push(node.right);
                        }
                    }
                }
            },

            minNode : function(node) {
                if (node) {
                    while (node.left !== null) {
                        node = node.left;
                    }
                    return node;
                }
                return null;
            },

            maxNode : function(node) {
                if (node) {
                    while (node.right !== null) {
                        node = node.right;
                    }
                    return node;
                }
                return null;
            },

            searchNode : function(node, val) {
                if (node === null) {
                    return false;
                }
                if (val < node.value) {
                    return this.searchNode(node.left, val);
                } else if (val > node.value) {
                    return this.searchNode(node.right, val);
                } else {
                    return true;
                }
            },

            findMinNode : function(node) {
                if (node) {
                    while (node.left !== null) {
                        node = node.left;
                    }
                    return node;
                }
                return null;
            },

            removeNode : function(node, value) {
                if (node === null) {
                    return null;
                }
                if (value < node.value) {
                    node.left = this.removeNode(node.left, value);
                    return node;
                } else if (value > node.value) {
                    node.right = this.removeNode(node.right, value);
                    return node;
                } else {
                    if (node.left === null && node.right === null) {
                        node = null;
                        return node;
                    }
                    if (node.left === null) {
                        node = node.right;
                        return node;
                    }
                    if (node.right === null) {
                        node = node.left;
                        return node;
                    }
                    var aux = this.findMinNode(node.right);
                    node.value = aux.value;
                    node.key = aux.key;
                    node.right = this.removeNode(node.right, aux.value);
                    return node;
                }
            },
            getObjType(obj) {
                if(obj === null){
                    return "null";
                }else if(typeof obj === "object"){
                    if(obj instanceof Array){
                        return "array";
                    } else if(obj instanceof RegExp){
                        return "regExp";
                    }else if(obj instanceof Date){
                        return "date";
                    }else if(obj instanceof Error){
                        return "error";
                    }  else{
                        return "object";
                    }
                }else{
                    return typeof obj;
                }
            }
        };

測試:

  tree.insert("zhan",110);
        tree.insert("wang",37);
        tree.insert("li",145);
        tree.insert("zhao",55);
        tree.insert("feng",68);
        tree.insert("sui",130);
        tree.insert("bai",46);
        tree.insert("hubf",130);

        var printNode = function(node) {
            if(this.util.getObjType(node) === "string"){
                console.log(node);
            }else if(this.util.getObjType(node) === "object"){
                console.log("key:"+node.key+"-------value:"+node.value);
            }  
        };
        console.log('中序遍歷');
        tree.inOrderTraverse(printNode);
        console.log('\n');
        console.log('層級遍歷');
        tree.levelOrderTraverse(printNode);
        console.log('\n')
        console.log('層級統計遍歷');
        tree.separateByLevel(printNode);
        console.log('\n')        
        console.log('前序遍歷');
        tree.preOrderTraverse(printNode);
        console.log('\n')        
        console.log('後序遍歷');
        tree.postOrderTraverse(printNode);
        console.log('\n')        
        console.log('最大值:')
        printNode(tree.max());
        console.log('\n')        
        console.log('最小值:');
        printNode(tree.min());

結果:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
es6的實現:

“use strict”;

class Node {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}

// insert(key):向樹中插入一個新的鍵。
// search(key):在樹中查找一個鍵,如果節點存在,則返回true;如果不存在,則返回false。
// inOrderTraverse:通過中序遍歷方式遍歷所有節點。
// preOrderTraverse:通過先序遍歷方式遍歷所有節點。
// postOrderTraverse:通過後序遍歷方式遍歷所有節點。
// min:返回樹中最小的值/鍵。
// max:返回樹中最大的值/鍵。
// remove(key):從樹中移除某個鍵。
class BinarySearchTree {

constructor() {
  this.root = null;
}

static insertNode(node, newNode) {
  if (node.key > newNode.key) {
    if (node.left === null) {
      node.left = newNode;
    } else {
      BinarySearchTree.insertNode(node.left, newNode);
    }
  } else {
    if (node.right === null) {
      node.right = newNode;
    } else {
      BinarySearchTree.insertNode(node.right, newNode);
    }
  }
}

static searchNode(node, key) {
  if (node === null) {
    return false;
  }

  if (node.key === key) {
    return true;
  } else if (node.key > key) {
    BinarySearchTree.searchNode(node.left, key);
  } else if (node.key < key) {
    BinarySearchTree.searchNode(node.right, key);
  }
}

static inOrderTraverseNode(node, cb) {
  if (node === null) {
    return;
  }
  BinarySearchTree.inOrderTraverseNode(node.left, cb);
  cb(node.key);
  BinarySearchTree.inOrderTraverseNode(node.right, cb);
}

static preOrderTraverseNode(node, cb) {
  if (node === null) {
    return;
  }
  cb(node.key);
  BinarySearchTree.preOrderTraverseNode(node.left, cb);
  BinarySearchTree.preOrderTraverseNode(node.right, cb);
}

static postOrderTraverseNode(node, cb) {
  if (node === null) {
    return;
  }
  BinarySearchTree.postOrderTraverseNode(node.left, cb);
  BinarySearchTree.postOrderTraverseNode(node.right, cb);
  cb(node.key);
}

static levelOrderTraverseNode(node, cb) {
  if (node === null) {
    return null;
  }

  const list = [node];

  while (list.length > 0) {
    node = list.shift();
    cb(node.key);
    if (node.left) {
      list.push(node.left);
    }
    if (node.right) {
      list.push(node.right);
    }
  }
}

static separateByLevelFn(node, cb, separator = '---*---') {
  const list = [];
  const END_FLAG = 'END_FLAG';

  list.push(node);
  list.push(END_FLAG);

  while (list.length > 0) {
    node = list.shift();

    // 遇到結束信號,表示已經遍歷完一層;若隊列中還有元素,說明它們是剛剛遍歷完的這一層的所有子元素。
    if (node === END_FLAG && list.length > 0) {
      list.push(END_FLAG);
      cb(separator);
    } else {
      cb(node.key);

      if (node.left) {
        list.push(node.left)
      }

      if (node.right) {
        list.push(node.right);
      }
    }
  }
}

static removeNode(node, key) {
  if (node === null) {
    return null;
  }

  if (node.key === key) {

    if (node.left === null && node.right === null) {
      node = null;
      return node;
    } else if (node.left === null) {
      node = node.right;
      return node;
    } else if (node.right === null) {
      node = node.left;
      return node;
    } else if (node.left && node.right) {
      let rightMinNode = node.right;

      while (rightMinNode.left !== null) {
        rightMinNode = rightMinNode.left;
      }

      node.key = rightMinNode.key;
      node.right = BinarySearchTree.removeNode(node.right, rightMinNode.key);
      return node;
    }

  } else if (node.key > key) {
    node.left = BinarySearchTree.removeNode(node.left, key);
    return node;
  } else if (node.key < key) {
    node.right = BinarySearchTree.removeNode(node.right, key);
    return node;
  }
}

static printNode(val) {
  console.log(val);
}

insert(key) {
  const newNode = new Node(key);

  if (this.root === null) {
    this.root = newNode;
  } else {
    BinarySearchTree.insertNode(this.root, newNode);
  }
}

search(key) {
  return BinarySearchTree.searchNode(key);
}

// 中序遍歷是一種以上行順序訪問BST所有節點的遍歷方式,也就是以從最小到最大的順序訪
// 問所有節點。中序遍歷的一種應用就是對樹進行排序操作。
inOrderTraverse(cb = BinarySearchTree.printNode) {
  BinarySearchTree.inOrderTraverseNode(this.root, cb);
}

// 先序遍歷是以優先於後代節點的順序訪問每個節點的。先序遍歷的一種應用是打印一個結構化的文檔。
preOrderTraverse(cb = BinarySearchTree.printNode) {
  BinarySearchTree.preOrderTraverseNode(this.root, cb);
}

// 後序遍歷則是先訪問節點的後代節點,再訪問節點本身。後序遍歷的一種應用是計算一個目
// 錄和它的子目錄中所有文件所佔空間的大小。
postOrderTraverse(cb = BinarySearchTree.printNode) {
  BinarySearchTree.postOrderTraverseNode(this.root, cb);
}

// Breadth-First-Search
// 可以用來解決尋路徑的問題。
levelOrderTraverse(cb = BinarySearchTree.printNode) {
  BinarySearchTree.levelOrderTraverseNode(this.root, cb);
}

// Breadth-First-Search
// 區分層次
separateByLevel(cb = BinarySearchTree.printNode) {
  BinarySearchTree.separateByLevelFn(this.root, cb);
}

min() {
  let node = this.root;

  if (node === null) {
    return null;
  }

  while (node.left !== null) {
    node = node.left;
  }

  return node.key;
}

max() {
  let node = this.root;

  if (node === null) {
    return null;
  }

  while (node.right !== null) {
    node = node.right;
  }

  return node.key;
}

remove(key) {
  this.root = BinarySearchTree.removeNode(this.root, key);
}

}

export {BinarySearchTree};

總結以上二叉排序樹可以方便的來處理排序問題,求最大值最小值,我對node節點的改造key–value方式可以方便傳任何對象進行操作。

發佈了77 篇原創文章 · 獲贊 8 · 訪問量 6878
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章