数据结构(四)
说明:本文基于哔哩哔哩视频【JavaScript数据结构与算法】整理
树(tree)
-
概念:n(n>=0) 个节点构成的有限集合
-
特性:非线性,一对多。查找较快
-
概念术语:
- root:根,用 r 表示
- 空树:n=0
- 子树:subtree
- 节点的度 degree :子树个数
- 父节点,子节点,兄弟节点
- 节点度: 子节点的个数
- 树的最大度:树的深度
- 叶子节点:没有子节点的节点
- 路径和路径长度:路径包含的边为路径长度
- 节点层次:规定 根节点在一层,其他的是其父节点加一
- 树的深度:树的最大层次
-
实现方式:普通树结构—从上到下;儿子兄弟表示法
-
分类
- 二叉树(所有的树都可以转化为二叉树)
-
形态:空,只有左节点TL,或者只有右节点TR,有两个节点;
-
特性
- 一个二叉树第 i 层的最大结点数为:2^(i-1), i >= 1;
- 深度为k的二叉树有最大结点总数为: 2^k - 1, k >= 1;
- 对任何非空二叉树 T,若n0表示叶结点的个数、n2是度为2的非叶结点个数,那么两者满足关系n0 = n2 + 1。
-
特殊二叉树
- 满二叉树:又叫完美二叉树:所有的子节点都是两个
- 完全二叉树:叶子节点的子节点只缺少 TR,但是有 TL,其他节点都是满的
-
二叉树的存储
- 数组:完全二叉树:从上到下,从左到右;(非完全:转化为完全二叉树,效率低,不用)
- 链表:最常见的二叉树表示方法
-
二叉搜索树(Binary Search Tree,简称 BST):最常用的二叉树
- 性质:左子树 的键值小于其父节点的键值,右子树 的键值大于其父节点的键值,左右子树本身也是二叉树;(查找效率高,类似于二分查找的思想)
- 遍历二叉树:(看什么时候处理 根节点)
- 先序遍历:根节点—左子树—左子树— (左子树遍历完成)父节点的右子树—父节点的右子树----根节点的右子树—左子树—左子树— (左子树遍历完成)父节点的右子树—父节点的右子树
- 中序遍历:先处理左子树,根节点中间处理
- 后序遍历;先处理左子树和右子树,根节点最后处理
- 层序遍历:较少用。
- 操作方法
- insert(key):向树中插入一个新的键。
- search(key):在树中查找一个键,如果结点存在,则返回true;如果不存在,则返回false。
- inOrderTraverse:通过中序遍历方式遍历所有结点。preOrderTraverse:通过先序遍历方式遍历所有结点。
- postOrderTraverse:通过后序遍历方式遍历所有结点。
- min:返回树中最小的值/键。
- max:返回树中最大的值/键。
- remove(key):从树中移除某个键(特别麻烦)找 前驱或者后继 替换当前节点。
- 二叉树中与删除相关的概念:
- 前驱:比当前要删除的节点小一点点的节点,左子树中的最大值
- 后继:比当前要删除的节点大一点点的节点,右子树中的最小值
- 二叉树中与删除相关的概念:
- 优点:查询,插入删除效率特别高(取决于深度)
- 缺点 :如果插入的是均匀分布的数(765432),深度很大
- 平衡树:效率高O(logN)
- 非平衡树:效率低 O(N)
-
- 二叉树(所有的树都可以转化为二叉树)
- 二叉搜索树的基本操作代码
function BinarySearchTree() {
// 子节点
function Node(key) {
this.key = key;
this.left = null;
this.right = null;
}
// 属性
this.root = null;
// 方法
// 插入 ======主要用到递归
BinarySearchTree.prototype.insert = function(key) {
var newNode = new Node(key);
this.root == null ?
(this.root = newNode) :
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);
}
}
};
// 获取最大值(叶节点的左边值)和最小值(叶节点的最右边)
BinarySearchTree.prototype.max = function() {
var node = this.root;
while (node.right != null) {
node = node.right;
}
return node.key;
};
BinarySearchTree.prototype.min = function() {
var node = this.root;
while (node.left != null) {
node = node.left;
}
return node.key;
};
// 获取特定的 key
BinarySearchTree.prototype.search = function(key) {
var node = this.root;
while (node != null) {
if (key < node.key) {
node = node.left;
} else if (key > node.key) {
node = node.right;
} else {
return true;
}
}
return false;
};
// 删除节点
// 找前驱
// 找后继
}
// 测试代码
var bst = new BinarySearchTree();
// 插入数据
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);
// bst.insert(6);
console.log(bst, "insert");
var res = "";
// 处理遍历之后得到的每个节点
bst.preOrderTraverse(function(key) {
return (res += key + " ");
});
console.log(res, "pre");
res = "";
bst.midOrderTraverse(function(key) {
return (res += key + " ");
});
console.log(res, "mid");
res = "";
bst.postOrderTraverse(function(key) {
return (res += key + " ");
});
console.log(res, "post");
console.log(bst.max(), "max");
console.log(bst.min(), "min");
console.log(bst.search(99), "search");
bst.remove(9);
bst.remove(7);
bst.remove(15);
console.log(bst, "remove");
res = "";
bst.postOrderTraverse(function(key) {
return (res += key + " ");
});
console.log(res, "post111");
上述测试中最终形成的树的结构
- 树的遍历
// 树的遍历
// 先序遍历 用到递归(函数内部自调用) 和 闭包(函数作为参数)
BinarySearchTree.prototype.preOrderTraverse = function(handler) {
// 递归:依次处理子节点
this.preOrderTraverseNode(this.root, handler);
};
// 11--7
BinarySearchTree.prototype.preOrderTraverseNode = function(
node,
handler
) {
if (node != null) {
// 先序 先处理经过的 父节点
handler(node.key);
// 处理经过的左节点
//
this.preOrderTraverseNode(node.left, handler);
// 处理经过的右节点
this.preOrderTraverseNode(node.right, handler);
}
};
// 中序遍历
BinarySearchTree.prototype.midOrderTraverse = function(handler) {
// 递归:依次处理子节点
this.midOrderTraverseNode(this.root, handler);
};
BinarySearchTree.prototype.midOrderTraverseNode = function(
node,
handler
) {
if (node != null) {
// 处理经过的左节点
//
this.midOrderTraverseNode(node.left, handler);
// 中序 中间处理经过的 父节点
handler(node.key);
// 处理经过的右节点
this.midOrderTraverseNode(node.right, handler);
}
};
// 后序遍历
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);
}
};
树的三种遍历方式图解
-
先序
-
中序
-
后序
-
树的删除(由于比较麻烦,所以抽离出来)
// 删除节点
BinarySearchTree.prototype.remove = function(key) {
var current = this.root;
var parent = null;
var isLeftChild = true;
// 找到
while (!current.key == key) {
parent = current;
if (key < current.key) {
current = current.left;
} else {
current = current.right;
isLeftChild = false;
}
// 遍历完都没有找到
if (current == null) return false;
}
// 判断有几个几点 0,1,2
// 叶子节点
if (current.left == null && current.right == null) {
// 根节点
if (current == this.root) {
this.root = null;
} else if (isLeftChild) {
parent.left = null;
} else {
parent.right = null;
}
}
// 一个子节点
else if (current.left != null && 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.right != null && current.left == null) {
if (current == this.root) {
this.root = current.right;
} else if (isLeftChild) {
parent.left = current.right;
} else {
parent.right = current.right;
}
}
// 两个子节点
else {
var successor = this.getSuccessor(current)
if (current == this.root) {
this.root = current.right;
} else if (isLeftChild) {
parent.left = successor
} else {
parent.right = successor
}
// 删除节点的左子树指向
successor.left = current.left
}
};
// 找前驱
// 找后继
BinarySearchTree.prototype.getSuccessor = function(delNode) {
// 存储找到的后继
var successor = delNode;
var successorParent = delNode
// 当前要删除的节点的后继
var current = delNode.right;
// 循环查找
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
};
-
为了保持二叉搜索树的平衡性,衍生出了下面两种树
- 平衡树(AVL):应用较少,效率不比红黑树
- 红黑树(见下章)
其他数据结构可访问以下地址: