树是计算机科学中经常用到的一种数据结构。树是一种非线性的数据结构,以分层的方式存储数据。这里介绍一种特殊的树:二叉树。 二叉树的优点:添加、删除、查找等操作非常快。
二叉树和二叉查找树
二叉树是指树的节点不超过2个,分别为左节点和右节点。二叉查找树是一种特殊的二叉树,相对较小的值保存在左节点,较大的值保存在右节点中,即左节点小于当前节点节点小于右节点。
实现二叉查找树
二叉查找树由节点组成,所以要定义的第一个对象就是Node,其次建立一个二叉查找树BST,BST要有一个insert方法用来向树中插入新节点。步骤如下:
- 设根节点为当前节点。
- 如果待插入节点保存的数据小于当前节点,则设新的当前节点为原节点的左节点;反之,执行第 4 步。
- 如果当前节点的左节点为 null,就将新的节点插入这个位置,退出循环;反之,继续执行下一次循环。
- 设新的当前节点为原节点的右节点。
- 如果当前节点的右节点为 null,就将新的节点插入这个位置,退出循环;反之,继续执行下一次循环。
function Node(data,left,right) {
this.data = data;
this.left = left;
this.right = right;
this.show = show;
}
function BST() {
this.root = null;
this.insert = insert;
}
function show() {
return this.data;
}
function insert(data) {
const n = new Node(data,null,null);
if(this.root === null) {
this.root = n;
} else {
let current = this.root;
let parent;
while(true) {
parent = current;
if(data < current.data) {
current = current.left;
if(current === null) {
parent.left = n
}
} else {
current =current.right;
if(current === null) {
parent.right = n
}
}
}
}
}
遍历二叉查找树
遍历BST的三种方式:中序、先序和后序。中序遍历按照节点上的键值,以升序访问BST上的所有节点。先序遍历先访问根节点,然后以同样方式访问左子树和右子树。后序遍历先访问叶子节点,从左子树到右子树,再到根节点。
中序遍历
function inOrder(node) {
if(!(node === null)) {
inOrder(node.left);
console.log(node.show());
inOrder(node.right);
}
}
先序遍历
function preOrder(node) {
if(!(node == null)) {
console.log(node.show());
preOrder(node.left);
preOrder(node.right);
}
}
后序遍历
function postOrder(node) {
if(!(node == null)) {
postOrder(node.left);
postOrder(node.right);
console.log(node.show());
}
}
在二叉查找树上进行查找
BST通常有下列三种类型的查找:查找给定值、查找最小值、查找最大值。
查找最小值
较小的值总是在左子节点上,在BST上查找最小值,只需要遍历左子树,直到找到最后一个节点。
function getMins() {
var current = this.root;
while(current.left !== null) {
current = current.left;
}
return current.data;
}
查找最大值
查找最大值同理。
function getMaxs() {
var current = this.root;
while(current.right !== null) {
current = current.right;
}
return current.data;
}
查找给定值
查找给定值,需要比较该值和当前节点上的值的大小。
function find(data) {
let current = this.root;
while(current !== null) {
if(current.data === data) {
return current;
} else if(data < current.data) {
current = current.left
} else {
current = current.right
}
}
return null
}
删除节点
删除节点的操作比较复杂,复杂程度取决于删除哪个节点。为了管理删除操作的复杂度,我们使用递归操作,同时定义两个方法:remove()和removeNode()。
- 判断当前节点是否包含待删除的数据,如果包含,则删除该节点;如果不包含,比较当前节点上的数据和待删除的数据。如果待删除数据小于当前节点上的数据,则移至当前节点的左子节点继续比较,如果删除数据大于当前节点上的数据,则移至当前节点的右子节点继续比较。
- 待删除节点是叶子节点(没有子节点的节点),只需要从父节点指向它的链接指向null。
- 待删除节点只包含一个子节点,将原本指向它的节点指向它的子节点。
- 待删除节点包含两个子节点,可以选用两种方式:一个是查找待删除节点左子树上的最大值,另一个是查找其右子树上的最小值。
function remove(data) {
this.root = removeNode(this.root,data);
}
function getSmallest(node) {
if (node === null) {
return null;
}
while (node.left !== null) {
node = node.left;
}
return node;
}
function removeNode(node,data) {
if(node === null) {
return null;
}
if(data === node.data) {
// node是叶子节点
if(node.left === null && node.right === null) {
return null;
}
// node没有左子节点
else if(node.left === null) {
return node.right;
}
// node没有右子节点
else if(node.right === null) {
return node.left;
}
// node有两个子节点,查找右子节点的最小值
const tempNode = getSmallest(node.right);
node.data = tempNode.data;
node.right = removeNode(node.right,data);
return node;
} else if(data < node.data) {
node.left = removeNode(node.left,data);
return node;
} else {
node.right = removeNode(node.right,data);
return node;
}
}