二叉搜尋樹的簡明實現(ES5 & ES6)

天方夜發表於2017-11-24

二叉樹 & 二叉搜尋樹

二叉樹(Binary Tree)是 n(n >= 0)個節點的有限集合,集合為空集時,叫作空二叉樹;不為空時,由根節點及左子樹、右子樹組成,左子樹、右子樹也都是二叉樹。

從這個描述,可以看出樹的結構與遞迴之間存在密切關係,這種密切關係在樹的遍歷時能夠得到充分體現。

二叉搜尋樹(Binary Search Tree),又叫二叉查詢樹;也稱為有序二叉樹(Ordered Binary Tree),排序二叉樹(Sorted Binary Tree)。

這是維基百科上歸納的一些二叉搜尋樹的性質:

  1. 若任意節點的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值;
  2. 若任意節點的右子樹不空,則右子樹上所有節點的值均大於它的根節點的值;
  3. 任意節點的左、右子樹也分別為二叉查詢樹;
  4. 沒有鍵值相等的節點。

本次實現中有點不一樣的地方,右節點是大於或等於父節點的。不過對示例沒有影響,而且很容易改成只能大於父節點。

關於《學習JavaScript資料結構與演算法(第2版)》

當年看過這本書的第一版,最近打算複習一下資料結構與演算法,於是看了第二版。

這是難得的一本用 JavaScript 實現的資料結構與演算法的書,它的講解十分清晰,相對來說質量較高,但問題也有很多。應該說第一版的內容還是比較可靠的,第二版新增的內容可靠性就差了很多。總的來說,這是一本從非常淺顯的層面講解資料結構與演算法的書,想獲得比較全面的知識還是需要閱讀更專業的資料。

本文的 ES5 實現參考了這本書,因為我覺得它是比較工整的實現,用於學習和理解時,好過我看到的其他一些實現方式。在原書示例程式碼的基礎上,我做了一些小調整。本書第二版號稱擁抱 ES6,但我看過之後發現至少樹這一章沒有改為 ES6 的實現,於是自己寫了一遍,正好當作練習的機會。

另外,這本書中提到的樹的遍歷方式包括中序、先序、後序遍歷,這些都屬於深度優先遍歷。在本文的程式碼中,我補充了廣度優先遍歷以及按照層次遍歷二叉樹的實現。

Binary Search Tree - ES5

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

  var root = null;

  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);
      }
    }
  };

  var inOrderTraverseNode = function(node, cb) {
    if (node !== null) {
      inOrderTraverseNode(node.left, cb);
      cb(node.key);
      inOrderTraverseNode(node.right, cb);
    }
  };

  var preOrderTraverseNode = function(node, cb) {
    if (node !== null) {
      cb(node.key);
      preOrderTraverseNode(node.left, cb);
      preOrderTraverseNode(node.right, cb);
    }
  };

  var postOrderTraverseNode = function(node, cb) {
    if (node !== null) {
      postOrderTraverseNode(node.left, cb);
      postOrderTraverseNode
    }
  };

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

    var 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);
      }
    }
  };

  var separateByLevelFn = function(node, cb, separator) {
    var list = [];
    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) {
        list.push(END_FLAG);
        cb(separator);
      } else {
        cb(node.key);

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

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

  var minNode = function(node) {
    if (node) {
      while (node.left !== null) {
        node = node.left;
      }

      return node.key;
    }

    return null;
  };

  var maxNode = function(node) {
    if (node) {
      while (node.right !== null) {
        node = node.right;
      }

      return node.key;
    }

    return null;
  };

  var searchNode = function(node, val) {
    if (node === null) {
      return false;
    }

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

  var findMinNode = function(node) {
    if (node) {
      while (node.left !== null) {
        node = node.left;
      }

      return node;
    }

    return null;
  };

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

    if (key < node.key) {
      node.left = removeNode(node.left, key);
      return node;
    } else if (key > node.key) {
      node.right = removeNode(node.right, key);
      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 = findMinNode(node.right);
      node.key = aux.key;
      node.right = removeNode(node.right, aux.key);
      return node;
    }
  };

  this.insert = function(key) {
    var newNode = new Node(key);

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

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

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

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

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

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

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

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

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

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


/* ========== test case ========== */


var tree = new BinarySearchTree();

/**
 *
 *               11
 *              /  \
 *             /    \
 *            /      \
 *           /        \
 *          /          \
 *         /            \
 *        7             15
 *       / \           /   \
 *      /   \         /     \
 *     5     9       13     20
 *    / \   / \     /  \   /  \
 *   3   6 8  10   12  14 18  25
 *
 */
tree.insert(11);
tree.insert(7);
tree.insert(15);
tree.insert(5);
tree.insert(3);
tree.insert(9);
tree.insert(8);
tree.insert(10);
tree.insert(13);
tree.insert(12);
tree.insert(14);
tree.insert(20);
tree.insert(18);
tree.insert(25);
tree.insert(6);

var printNode = function(val) {
  console.log(val);
};

tree.inOrderTraverse(printNode);

console.log('\n')
tree.levelOrderTraverse(printNode);

console.log('\n')
tree.separateByLevel(printNode);

console.log('\n')
tree.remove(7)
tree.inOrderTraverse(printNode);

console.log('\n')
tree.preOrderTraverse(printNode);

console.log('\n')
tree.postOrderTraverse(printNode);

Binary Search Tree - ES6

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);
  }
}


/* ========== test case ========== */


const tree = new BinarySearchTree();

/**
 *
 *               11
 *              /  \
 *             /    \
 *            /      \
 *           /        \
 *          /          \
 *         /            \
 *        7             15
 *       / \           /   \
 *      /   \         /     \
 *     5     9       13     20
 *    / \   / \     /  \   /  \
 *   3   6 8  10   12  14 18  25
 *
 */
tree.insert(11);
tree.insert(7);
tree.insert(15);
tree.insert(5);
tree.insert(3);
tree.insert(9);
tree.insert(8);
tree.insert(10);
tree.insert(13);
tree.insert(12);
tree.insert(14);
tree.insert(20);
tree.insert(18);
tree.insert(25);
tree.insert(6);

tree.inOrderTraverse();

console.log('\n')
tree.levelOrderTraverse();

console.log('\n')
tree.separateByLevel();

console.log('\n')
tree.remove(7)
tree.inOrderTraverse();

console.log('\n')
tree.preOrderTraverse();

console.log('\n')
tree.postOrderTraverse();

相關文章