树结构
儿子兄弟表示法:
二叉树
如果树中每个节点最多只能有两个子节点,这样的树就成为“二叉树”
满二叉树(完美二叉树)
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。
完全二叉树
树的节点都是从上到下,从左到右来添加的
用数组只能表示完全二叉树,表示非完全二叉树时会造成空间的浪费
二叉搜索树BST-Binary Search Tree(二叉排序树/二叉查找树)
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。
先序遍历
先访问根节点,再访问左节点,再访问右节点
中序遍历
先访问左节点,再访问根节点,最后右节点
后序遍历
先左节点,再右节点,最后根节点
二叉搜索树的封装
二叉树的封装主要用到递归方法
以以下为例:
二叉树的删除方法比较复杂,
先找到要删除的节点,如果没有找到,则不需要删除,再判断要删除的节点的情况:
- 该节点是叶子节点
找到要删除的节点的父节点,再将父节点的左子节点或右子节点置为null - 该节点有一个子节点
- 该节点有两个子节点
找要删除节点的前驱和后继:
前驱一般是左子树的最大值,后继是右子树的最小值
封装代码:
属性:
function BinarySearchTree() {
//封装节点的属性
function Node(key) {
this.key = key;
this.left = null; //左节点
this.right = null; //右节点
}
//根节点
this.root = null;
方法:
1.insert()方法
//1.insert()方法
BinarySearchTree.prototype.insert = function(key) {
//创建节点
var newnode = new Node(key);
//判断根节点是否有值
if (this.root == null) {
this.root = newnode;
} else {
this.insertNode(this.root, newnode);
}
}
//插入的递归函数
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.search()方法
//2.search()方法
BinarySearchTree.prototype.search = function(key) {
//获取根节点
var node = this.root;
//循环搜索key
while (node != null) {
if (key < node.key) {
node = node.left;
} else if (key > node.key) {
node = node.right
} else {
return true;
}
}
return false;
}
3.inOrderTraverse()中序遍历
//3.inOrderTraverse() 中序遍历
BinarySearchTree.prototype.inOrderTraverse = function(handler) {
this.inOrderTraverseNode(this.root, handler);
}
//中序遍历递归方法
BinarySearchTree.prototype.inOrderTraverseNode = function(node, handler) {
if (node != null) {
//处理左子树的节点
this.inOrderTraverseNode(node.left, handler);
//处理节点
handler(node.key);
//处理右子树的节点
this.inOrderTraverseNode(node.right, handler);
}
}
4.preOrderTraverse()先序遍历
//4.preOrderTraverse() 先序遍历
BinarySearchTree.prototype.preOrderTraverse = function(handler) {
this.preOrderTraverseNode(this.root, handler);
}
//先序遍历递归方法
BinarySearchTree.prototype.preOrderTraverseNode = function(node, handler) {
if (node != null) {
//处理经过的节点
handler(node.key);
//处理经过的节点的左子节点
this.preOrderTraverseNode(node.left, handler);
//处理经过的节点的右子节点
this.preOrderTraverseNode(node.right, handler);
}
}
5.postOrderTraverse()后序遍历
//5.postOrderTraverse() 后序遍历
BinarySearchTree.prototype.postOrderTraverse = function(handler) {
this.postOrderTraverseNode(this.root, handler);
}
//后序遍历递归方法
BinarySearchTree.prototype.postOrderTraverseNode = function(node, handler) {
if (node != null) {
//处理经过的节点的左子节点
this.postOrderTraverseNode(node.left, handler);
//处理经过的节点的右子节点
this.postOrderTraverseNode(node.right, handler);
//处理经过的节点
handler(node.key);
}
}
6.min()返回最小值
//6.min() 返回最小值
//最小值就是树最左边的值
BinarySearchTree.prototype.min = function() {
var node = this.root;
var key = null;
while (node != null) {
key = node.key;
node = node.left;
}
return key;
}
7.max()返回最大值
//7.max() 返回最大值
//最大值就是树最右边的值
BinarySearchTree.prototype.max = function() {
//获取根节点
var node = this.root;
//不断的向右查找 直到节点为null
var key = null;
while (node != null) {
key = node.key;
node = node.right;
}
return key;
}
8.remove()方法
BinarySearchTree.prototype.remove = function(delkey) {
//寻找要删除的节点
var current = this.root;
var parent = null;
//要删除节点是父节点的左节点
var isLeftChild = true;
while (current.key != delkey) {
parent = current;
if (delkey < current.key) {
isLeftChild = true;
current = current.left;
} else {
isLeftChild = false;
current = current.right;
}
//已经找到最后一个节点还没有找到要删除的那个节点
if (current = null) {
return false;
}
}
//找到了要删除的节点 current.key=key
//1.要删除的节点是叶子节点
if (current.left == null & current.right == null) {
if (current == this.root) {
this.root = null;
} else if (isLeftChild) {
parent.left = null;
} else {
parent.right = null;
}
}
//2.要删除的节点有一个子节点
//这里是要删除的节点没有右节点
else if (current.right == null) {
//要删除的节点是根节点 但是有一个左子节点
if (current == this.root) {
this.root = current.left
} else if (isLeftChild) {
parent.left = current.left;
} else {
parent.right = current.left;
}
}
//这里是要删除的节点没有左节点
else if (current.left == null) {
//要删除的节点是根节点 但是有一个右子节点
if (current == this.root) {
this.root = current.right;
} else if (isLeftChild) {
parent.left = current.right;
} else {
parent.right = current.right;
}
}
//3.删除的节点有两个子节点
else {
//获取后继节点
var successor = this.getSuccssor(current);
//判断是否是根节点
if (current == this.root) {
this.root = successor;
} else if (isLeftChild) {
parent.left = successor;
} else {
parent.right = successor;
}
successor.left = current.left;
}
}
//获取后继结点
BinarySearchTree.prototype.getSuccssor = function(delnode) {
//定义变量 找到节点后继
var successor = delnode;
var current = delnode.right;
var successorParent = delnode;
//循环查找
while (current != null) {
successorParent = successor;
successor = current;
current = current.left;
}
//判断寻找的后继结点是否delnode的右节点
if (successor != delnode.right) {
successorParent.left = successor.right;
successor.right = delnode.right;
}
return successor;
}
}
二叉搜索树的缺陷
比较好的二叉搜索树的数据应该是左右分布均匀的,如果二叉搜索树插入的是有序的数据,则有可能分布不均匀,这种树为非平衡树。
对于平衡二叉树来说,插入/查找等操作的效率是O(logN)
对于一颗非平衡二叉树,相当于一颗链表,查找效率变成了O(N)
**平衡二叉树:**它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。