数据结构
树结构
树结构示例图
常用的树的术语:
- 1、节点的度(Degree):节点的子树个数
- 2、树的度:树的所有节点中最大的度数
- 3、叶节点(Leaf):度为0的节点(也称为叶子节点)
- 4、父节点(Parent):有子树的节点是其子树的根节点的父节点
- 5、子节点(Child):若A节点是B节点的父节点,则称B节点为A节点的子节点
- 6、兄弟节点(Sibling):有同一父节点的各节点彼此被称为兄弟节点
- 7、路径和路径长度:从节点n1到nk的路径为一个节点序列n1,n2,n3...,nk,ni是ni+1得父节点,路径所包含边的个数的路径的长度
- 8、节点层次(Level):规定根节点在1层,其他节点的层数是其父节点层数加1
- 9、树的深度(Depth):树中所有节点的最大层次是这棵树的深度
树的普通表示法
儿子-兄弟表示法
二叉树
如果树中每个节点最多只能又两个子节点,这样的书就称为“二叉树”,二叉树分为五种形态
二叉树有几个比较重要的特性,笔试中比较常见
- 1、一个二叉树第i层的最大节点数为:2^(i-1),i>=1
- 2、深度为k的二叉树有最大节点总数为:2^k - 1, k >= 1;
- 3、对任何非空二叉树T,若n0表示叶节点的个数,n2是度为2的非叶节点个数,那么两者满足关系n0 = n2 + 1
完美二叉树,也称为满二叉树:出了最下一层的叶节点外,每层节点都有2个子节点
二叉搜索树(BST,Binary Search Tree),也称二叉排序树,或二叉查找树
满足以下性质:
- 1、非空左子树的所有键值小于其根节点的键值
- 2、非空右子树的所有键值大于其根节点的键值
- 3、左、右子树本身也都是二叉搜索树 特点:二叉搜索树的特点是相对较小的值总是保存在左节点上,相对较大的值总是保存在右节点上
优点:可以快速地找到给定关键字的数据项并且可以快速的插入和删除数据项
缺陷:如果插入的数据是有序的数据时,比如有一棵初始化为9,8,12的二叉树,插入下面的数据7,6,5,4,3(限于非平衡树)
非平衡树:
- 1、比较好的二叉树搜索数据应该是左右分布均匀的
- 2、但是插入连续数据后,分布的不均匀,我称这种树为非平衡树
- 3、对于一棵平衡二叉树来说,插入/查找等操作的效率是O(logN)
- 4、对于一棵非平衡二叉树,相当于编写了衣蛾链表,查找效率变成了O(N)
平衡二叉树
- 1、树中每个节点左边的子孙节点的个数,应该尽可能的等于右边的子孙节点的个数
下面为二叉搜索树的范例:
二叉树的遍历方式:
1、先序遍历:访问根节点,先序遍历其左子树,先序遍历其右子树
2、中序遍历:中序遍历其左子树,访问根节点,中序遍历其右子树
3、后序遍历:后序遍历其左子树,后序遍历其右子树,访问根节点
二叉搜索树中,有两个特别的节点,有两个特别的名字;
- 前驱:比当前节点小一点点的节点,称为当前节点的前驱
- 后继:比当前节点大一点点的节点,称为当前节点的后继
二叉搜索树的常用操作封装
//封装二叉搜索树
function BinartSearchTree(){
function Node(key){
this.key = key
this.left = null
this.right = null
}
//属性
this.root = null
//插入方法,插入数据:对外给用户调用的方法
BinartSearchTree.prototype.insert = function(key){
// 1、根据key创建节点
let newNode = new Node(key)
// 2、判断根节点是否存在
if(this.root == null){
this.root = newNode
}else{
this.insertNode(this.root,newNode)
}
}
BinartSearchTree.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)
}
}
}
//树的遍历
//1、先序遍历
BinartSearchTree.prototype.perOrderTraversal = function(handler){
this.perOrderTraversalNode(this.root, handler)
}
BinartSearchTree.prototype.perOrderTraversalNode = function(node,handler){
if(node != null){
//1、处理经过的节点
handler(node.key)
//2、处理经过节点的子节点
this.perOrderTraversalNode(node.left,handler)
// 3、处理经过节点的右子节点
this.perOrderTraversalNode(node.right,handler)
}
}
// 2、中序遍历
BinartSearchTree.prototype.midOrderTraversal = function(handler){
this.midOrderTraversalNode(this.root,handler)
}
BinartSearchTree.prototype.midOrderTraversalNode = function(node,handler){
if(node != null){
// 1、处理左子树中的节点
this.midOrderTraversalNode(node.left, handler)
// 2、处理节点
handler(node.key)
// 3、处理右子树中的节点
this.midOrderTraversalNode(node.right, handler)
}
}
// 3、后续遍历
BinartSearchTree.prototype.postOrderTraversal = function(handler){
this.postOrderTraversalNode(this.root,handler)
}
BinartSearchTree.prototype.postOrderTraversalNode = function(node,handler){
if(node != null){
// 1、处理左子树中的节点
this.postOrderTraversalNode(node.left, handler)
// 2、处理右子树中的节点
this.postOrderTraversalNode(node.right, handler)
// 3、处理节点
handler(node.key)
}
}
//最小值查找
BinartSearchTree.prototype.min = function(){
let node = this.root
//一直向左查找
let key = null
while(node != null){
key = node.key
node = node.left
}
return key
}
//最大值查找
BinartSearchTree.prototype.max = function(){
let node = this.root
//一直向右查找
let key = null
while(node != null){
key = node.key
node = node.right
}
return key
}
//搜索特定的值
BinartSearchTree.prototype.search = function(key){
//1、获取根节点
let 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
}
//删除节点
BinartSearchTree.prototype.remove = function(key){
//1、寻找要删除的节点
//1.1定义变量,保存删除的节点
let current = this.root
let parent = null
let isLeftChild = true
//1.2开始寻找删除的节点
while(current.key != key){
parent = current
if(key < current.key){
isLeftChild = true
current = current.left
}else{
isLeftChild = false
current = current.right
}
//某种情况:已经找到最后的节点,依然没有找到key
if(current == null) return false
}
// 2、根据对应的情况删除节点
//2.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
}
}else if(current.right == null){//2.2删除的节点有一个子节点
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
}
}else{ //2.3删除的节点有两个子节点
// 1、获取后继节点
let successor = this.getSuccssor(current)
// 2、判断是否根节点
if(current == this.root){
this.root = successor
}else if(isLeftChild){ //如果删除的是父亲节点的左子树
parent.left = successor
}else{ //如果删除的是父亲节点的右子树
parent.right = successor
}
//3、将删除节点的左子树 = current.left
successor.left = current.left
}
}
//找后继的方法
BinartSearchTree.prototype.getSuccssor = function(delNode){
// 1、定义变量,保存找到后继
let successor = delNode
let current = delNode.right
let surccessorParent = delNode
//2、循环查找最小值
while(current != null){
surccessorParent = successor
successor = current
current = current.left
}
// 3、判断寻找的后继节点是否直接就是delNode的right节点
if(successor != delNode.right){
surccessorParent.left = successor.right
successor.right = delNode.right
}
return successor
}
}
//测试代码
let bst = new BinartSearchTree()
bst.insert(11)
bst.insert(7)
bst.insert(15)
bst.insert(5)
bst.insert(6)
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)
// 测试先序遍历
let resultString = ''
bst.perOrderTraversal(function(key){
resultString += key + ' '
})
console.log(resultString)
// 测试中序遍历
resultString = ''
bst.midOrderTraversal(function(key){
resultString += key + ' '
})
console.log(resultString)
// 测试后序遍历
resultString = ''
bst.postOrderTraversal(function(key){
resultString += key + ' '
})
console.log(resultString)
console.log(bst.max())
console.log(bst.min())
console.log(bst.search(24))
console.log(bst.search(25))
bst.remove(9)
bst.remove(7)
bst.remove(15)
resultString = ''
bst.postOrderTraversal(function(key){
resultString += key + ' '
})
console.log(resultString)
红黑树(平衡二叉树)
红黑树的规则
除了符合二叉搜索树的基本规则外,还添加了以下特性:
- 1、节点是红色或者黑色
- 2、根节点是黑色
- 3、每个叶子节点都是黑色的空节点(NIL节点)
- 4、每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红节点)
- 5、从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点图例 图例
插入一个新节点时,有可能树不再平衡,可以通过三种方式的变化,让树保持平衡
1、变色:为了重新符合红黑树的规则,尝试把红色节点变为黑色,或者把黑色节点变为红色
首先,需要知道插入的新节点通常都是红色节点
- (1)因为在插入节点为红色的时候,有可能插入一次是不违反红黑树任何规则的
- (2)而插入黑色节点,必然会导致有一条路径上多了个黑色节点,这是很难调整的
- (3)红色节点可能导致出现红红相连的情况,但是这种情况可以通过颜色调换和旋转来调整
2、左旋转:逆时针旋转红黑树的两个节点,使得父节点被自己的右孩子取代,而自己成为自己的左孩子。图中身为右孩子的Y取代了X的位置,而X变成了Y的左孩子,此为左旋转
3、右旋转::顺时针旋转红黑树的两个节点,使得父节点被自己的左孩子取代,而自己成为自己的右孩子。图中身为左孩子的Y取代了X的位置,而X变成了Y的右孩子,此为左旋转
// 插入情况
// 设要插入的节点为N,其父节点为P,
// 其祖父节点为G,其父亲的兄弟节点为U
// 情况一
// 新节点N位于树的根上,没有父节点
// 这种情况下,我们直接将红色变为黑色即可,这样满足性质2
// 情况二
// 新节点的父节点P是黑色
// 性质4没有失效(新节点是红色),性质5也没有任何问题
// 尽管新节点N有两个黑色的叶子节点nil,但是新节点N是红色,所以通过他它的路径中黑色节点的个数依然相同,满足性质5
// 情况三
// P为红色,U也为红色
// 变成 -> P黑 U黑 G红
// 操作方案:
// 将P和U变为黑色,并且将G变换为红色
// 现在新节点N有一个黑色父节点P,所以每条路径上黑色节点数目没有变
// 而从更高路径上,必然会经过G节点,所以那些路径的黑色节点数目也不变,符合性质5
// 可能出现的问题
// 但是,N的祖父节点G的父节点也可能是红色,这就违反了性质3,可以递归调整颜色
// 但是如果递归调整颜色到根节点,就需要进行旋转
// 情况四
// N的叔叔U是黑节点,且N是左孩子
// 父红叔黑祖黑 N是左儿子
// 变化->父黑祖红 进行右旋转
// 操作方案
// 对祖父节点进行一次右旋转
// 在旋转查收的树中,以前的父节点P现在是新节点以前祖父节点G的父节点
// 交换以前的父节点P和祖父节点G的颜色(P为黑色,G变成红色 - G原来一定是黑色,为什么?)
// B节点向右平移,称为G节点的左子节点
// 情况五
// N的叔叔U是黑色节点,且N是右孩子
// 父红叔黑,祖黑,N是右儿子
// 变化->以P为根,左旋转
// 将P作为新插入的红色节点考虑即可
// ->自己变成黑色,祖变成红色,以祖为根进行右旋转
// 操作方案
// 对P节点进行一次坐旋转,形成情况四的结果
// 对祖父节点G进行一次右旋转,并且改变颜色即可
const RED = true;
const BLACK = false;
class Node {
constructor(key, value) {
this.key = key;
this.value = value;
this.left = null;
this.right = null;
this.color = RED;
}
}
class RBT {
constructor() {
this.root = null;
this.size = 0;
}
isRed(node) {
if (!node) return BLACK;
return node.color;
}
// 左旋 右红左黑
leftRotate(node) {
let tmp = node.right;
node.right = tmp.left;
tmp.left = node;
tmp.color = node.color;
node.color = RED;
return tmp;
}
// 右旋转 左红左子红
rightRoate(node) {
let tmp = node.left;
node.left = tmp.right;
tmp.right = node;
tmp.color = node.color;
node.color = RED;
return tmp;
}
// 颜色翻转
flipColors(node) {
node.color = RED;
node.left.color = BLACK;
node.right.color = BLACK;
}
add(key, value) {
this.root = this.addRoot(this.root, key, value);
console.log(this.root)
this.root.color = BLACK; // 根节点始终是黑色
}
addRoot(node, key, value) {
if (!node) {
this.size++;
return new Node(key, value);
}
if (key < node.key) {
node.left = this.addRoot(node.left, key, value);
} else if (key > node.key) {
node.right = this.addRoot(node.right, key, value);
} else {
node.value = value;
}
if (this.isRed(node.right) && !this.isRed((node.left))) {
node = this.leftRotate(node);
}
if (this.isRed(node.left) && this.isRed((node.left.left))) {
node = this.rightRoate(node);
}
if (this.isRed(node.left) && this.isRed(node.right)) {
this.flipColors(node);
}
return node;
}
isEmpty() {
return this.size == 0 ? true : false;
}
getSize() {
return this.size;
}
contains(key) {
let ans = '';
!(function getNode(node, key) {
if (!node || key == node.key) {
ans = node;
return node;
} else if (key > node.key) {
return getNode(node.right, key);
} else {
return getNode(node.right, key);
}
})(this.root, key);
return !!ans;
}
// bst前序遍历(递归版本)
preOrder(node = this.root) {
if (node == null) return;
this.preOrder(node.left);
this.preOrder(node.right);
}
preOrderNR() {
if (this.root == null) return;
let stack = [];
stack.push(this.root);
while (stack.length > 0) {
let curNode = stack.pop();
console.log(curNode.key);
if (curNode.right != null) stack.push(curNode.right);
if (curNode.left != null) curNode.push(curNode.left);
}
}
// bst中序遍历
inOrder(node = this.root) {
if (node == null) return;
this.inOrder(node.left);
console.log(node.key);
this.inOrder(node.right);
}
// bst后续遍历
postOrder(node = this.root) {
if (node == null) return;
this.postOrder(node.left);
this.postOrder(node.right);
console.log(node.key);
}
// bsf + 队列的方式实现层次遍历
generateDepthString1() {
let queue = [];
queue.unshift(this.root);
while (queue.length > 0) {
let tmpqueue = []; let ans = [];
queue.forEach(item => {
ans.push(item.key);
item.left ? tmpqueue.push(item.left) : '';
item.right ? tmpqueue.push(item.right) : '';
});
console.log(...ans);
queue = tmpqueue;
}
}
minmun(node = this.root) {
if (node.left == null) return node;
return this.minmun(node.left);
}
maximum(node = this.root) {
if (node.right == null) return node;
return this.maximum(node.right);
}
}