目录
1. 数据结构树–>树基础
2. 数据结构树–>二叉树
3. 数据结构树–>二叉查找树\二叉排序树
4. 数据结构树–>平衡二叉树
5. 数据结构树–>霍夫曼树
6. 数据结构树–>红黑树
7. 数据结构树–>二叉堆
8. 数据结构树–>B树
9. 数据结构树–>B+树
平衡二叉树
平衡二叉树又叫 AVL树
前面我们介绍了二叉查找树,二叉平衡树是二叉查找树的一种,为什么会有二叉平衡树呢?这时因为二叉查找树的排序引起的,看下图。
这也是一个二叉查找树,但因为数据的原因这个二叉查找树基本成了链式结构,无法达到二叉查找树的快速查找能力,这就引入了平衡二叉树。
平衡二叉树在二叉查找树的基础上添加了平衡因子。
1. 平衡因子
平衡因子:二叉树所有节点左子树高度与右子树的高度差值的绝对值不能大于1。 只有满足这个平衡因子的二叉查找树就是一颗合格的平衡二叉树。
如图就是一个合格的平衡二叉树。
- 节点5的左子树高度为2,右子树高度为2, 2-2=0,满足
- 节点4的左子树高度为1,右子树高度为0, 1-0=1,满足
- 节点7的左子树高度为0,右子树高度为1, 0-1= -1,满足
- 节点1的左子树高度为0,右子树高度为0, 0-0= 0,满足
不是平衡二叉树
这里节点4左子树高度为2, 右子树高度为0。 2-0=2,所以该二叉树是不平衡的。
有了平衡因子我们发现平衡二叉树的高度有了很大的约束,不会再出现严重的斜树,这样对大数据的查找减少了影响。
***缺点:***有了平衡因子我们发现树的结构更合理了,查找更快速了,但我们的插入和删除变的麻烦了,插入和删除可能破坏平衡二叉树的平衡,
这就有了平衡二叉树的平衡操作,大量的数据的大量插入和删除都要进行平衡,那很耗性能,所以后面有了红黑树,后面我们介绍。
2. 左旋与右旋满足平衡二叉树的平衡
当我们向平衡二叉树中插入或删除数据时又怎么满足二叉树的平衡呢?这里引入了二叉树的左旋与右旋
2.1 左旋
节点2,左子树高度0, 右子树高度2,则这个以节点2为根节点的子树不平衡,我们对节点2进行左旋转。
旋转操作,将节点4上移,节点2变为节点4的左子树,原来的左子树节点3变为了节点2的右子树,节点4变为了整个子树的根节点。
满足的二叉树的平衡
2.2 右旋
同左旋类似,节点5不平衡,我们对节点5进行右旋,右旋完成节点3变为子树根节点,节点4变为了节点5的左节点。
3. 使用左旋和右旋
在二叉树不满足平衡时我们使用左旋与右旋来让二叉树达到平衡,在平衡二叉树中需要左旋与右旋的的状态可以分为4中。
- 左左结构—>右旋
- 右右结构—>左旋
- 左右结构---->先左旋右右旋
- 右左结构---->先右旋再左旋
我们在操作平衡二叉树的时候有个基本的顺序,总是先找到最底层的不平衡的子树,判断出它的结构在进行旋转,之后再找下一个最底层的不平衡二叉树
进行旋转。
3.1 结构判断
根据平衡二叉树的平衡因子,我们知道最底层的不平衡二叉树我们最多只判断3层就可以了。在这3层里会出现平衡因子为2的不平衡问题。
我们将不平衡树的最底层节点想象成新插入的节点。判断逻辑可以分为:我们这里称不平衡的子树的根节点为R。
- 对R的左孩子的左子树进行插入---->左左结构----->对R右旋。
- 对R的左孩子的右子树进行插入---->左右结构 ---->先对R左孩子左旋后对R右旋。
- 对R的右孩子的右子树进行插入---->右右结构----->对R左旋。
- 对R的右孩子的左子树进行插入---->右左结构----->先对R右孩子右旋后对R左旋。
这里我们把红色节点当做插入节点判断出了不平衡树的结构。
前面2.1与2.2已经介绍了左左结构的右旋,右右结构的左旋,下面我们看一下,左右结构,右右结构的旋转。
左右结构先对子树根节点的左孩子左旋转,在对根节点进行右旋转。
这里不再描述右左结构的旋转,同左右结构原理类似,对子树根节点的右节点进行右旋转,再对根节点进行左旋转。
但当最底层不平衡树的插入节点有两个怎么办?也就不平衡树子节点有两个孩子节点怎么判断呢?这种情况怎么会出现呢?是在我们进行删除操作的时候
出现的。下面我们看一下
我们以最简洁的选择达到平衡为目的,直接可以把删除节点5后形成的不平衡二叉树看成左左结构进行右旋就能达到平衡感。
如果我们看成左右结构,左旋后会发现 4 3 2 又形成一个左左结构的不平衡二叉树,右要右旋,这样就会发生逻辑循环。
上面的例子如果发生在右子树同样适用,发生在右边我们就以右右结构为标准进行左旋。
4. java 代码实现
public class BalanceTreeHelper {
public static final int LEFT_LEFT = 0; //ll型
public static final int RIGHT_RIGHT = 1; //rr型
public static final int RIGHT_LEFT = 2; //rl型
public static final int LEFT_RIGHT = 3; //lr型
/**
* 判断平衡二叉树的高度
*
* @param root
* @return
*/
public static int height(BinaryTreeNode root) {
if (root == null) {
return 0;
}
return Math.max(height(root.getLeft()), height(root.getRight())) + 1;
}
public static boolean isBalanceTree(BinaryTreeNode root) {
if (root == null) {
return true;
}
return isBalanceTree(root.getLeft()) && isBalanceTree(root.getRight()) && Math.abs(height(root.getRight()) - height(root.getLeft())) < 2;
}
/**
* 平衡二叉树插入插入
*/
public static BinaryTreeNode insert(BinaryTreeNode tree, int key) {
BinaryTreeNode t = tree;
BinaryTreeNode parent = tree;
while (t != null) {
parent = t;
if (t.getKey() < key) {
t = t.getRight();
} else if (t.getKey() == key){
return tree;
} else {
t = t.getLeft();
}
}
if (parent.getKey() < key) {
parent.setRight(new BinaryTreeNode(key));
} else {
parent.setLeft(new BinaryTreeNode(key));
}
return treeBalance(tree);
}
public static BinaryTreeNode treeBalance(BinaryTreeNode root) {
BinaryTreeNode resultRoot = root;
while (!isBalanceTree(resultRoot)) {
//找到最底层最小的不平衡树
BinaryTreeNode [] nodes = new BinaryTreeNode[2];
BinaryTreeNode t = resultRoot;
while (!isBalanceTree(t)) {
nodes[0] = nodes[1];
nodes[1] = t;
if (!isBalanceTree(t.getLeft())) {
t = t.getLeft();
} else if (!isBalanceTree(t.getRight())){
t = t.getRight();
} else {
break;
}
}
//确定最底层最小不平衡树的结构类型
int hl = height(nodes[1].getLeft());
int hr = height(nodes[1].getRight());
int type = LEFT_LEFT;
if (hl > hr && nodes[1].getLeft().getRight() != null) {
type = LEFT_RIGHT;
}
if (hr > hl && nodes[1].getRight().getLeft() == null) {
type = RIGHT_RIGHT;
}
if (hr > hl && nodes[1].getRight().getLeft() != null) {
type = RIGHT_LEFT;
}
if (nodes[0] == null) {
resultRoot = rotate(type, nodes[1], null);
} else {
rotate(type, nodes[1], nodes[0]);
}
}
return resultRoot;
}
/**
* 平衡二叉树删除
*/
public static BinaryTreeNode delete(BinaryTreeNode root, int key) {
BinaryTreeNode father = root;
BinaryTreeNode delete = null;
//delete节点属于father的那个方向
boolean left = true;
while (father != null) {
if (father.getKey() < key) {
if (father.getRight() != null && father.getRight().getKey() == key) {
delete = father.getRight();
left = false;
break;
} else {
father = father.getRight();
}
} else {
if (father.getLeft() != null && father.getLeft().getKey() == key) {
delete = father.getLeft();
left = true;
break;
} else {
father = father.getLeft();
}
}
}
if (father != null) {
System.out.println("father key=" + father.getKey() + " delete key=" + delete.getKey());
BinaryTreeNode l = delete.getLeft();
BinaryTreeNode r = delete.getRight();
if (l == null && r == null) {
//删除的节点是叶子节点
if (left) {
father.setLeft(null);
} else {
father.setRight(null);
}
} else if (l == null || r == null) {
//删除的节点只有一个子树
if (left) {
if (l == null) {
father.setLeft(delete.getRight());
} else {
father.setLeft(delete.getLeft());
}
} else {
if (l == null) {
father.setRight(delete.getRight());
} else {
father.setRight(delete.getLeft());
}
}
} else {
//删除的节点有两个子树
//找到右子树的最小值
BinaryTreeNode tempFather = delete;
BinaryTreeNode min = null;
Stack<BinaryTreeNode> temps = new Stack<>();
temps.add(tempFather);
tempFather = tempFather.getRight();
while (true) {
temps.push(tempFather);
if (tempFather.getLeft() == null) {
break;
} else {
tempFather = tempFather.getLeft();
}
}
min = temps.pop();
tempFather = temps.pop();
if (left) {
father.setLeft(min);
} else {
father.setRight(min);
}
if (temps.size() > 0) {
//这个判断 tempFather 不是为delete节点 如果最小值有右子树则赋值右子树。
if (min.getRight() != null) {
tempFather.setLeft(min.getRight());
}
min.setLeft(delete.getLeft());
min.setRight(delete.getRight());
} else {
if (min.getRight() != null) {
tempFather.setRight(min.getRight());
}
min.setLeft(delete.getLeft());
}
}
}
return treeBalance(root);
}
/**
* 旋转
* @return
*/
public static BinaryTreeNode rotate(int type, BinaryTreeNode node, BinaryTreeNode father) {
BinaryTreeNode t = null;
switch (type) {
case LEFT_LEFT:
t = node.getLeft();
node.setLeft(t.getRight());
t.setRight(node);
break;
case RIGHT_RIGHT:
t = node.getRight();
node.setRight(t.getLeft());
t.setLeft(node);
break;
case RIGHT_LEFT:
//我们知道要旋转两次,但我们知道结果,代码操作上就不再需要两次了
t = node.getRight().getLeft();
node.getRight().setLeft(t.getRight());
t.setRight(node.getRight());
node.setRight(t.getLeft());
t.setLeft(node);
break;
case LEFT_RIGHT:
t = node.getLeft().getRight();
node.getLeft().setRight(t.getLeft());
t.setLeft(node.getLeft());
node.setLeft(t.getRight());
t.setRight(node);
break;
}
if (father == null) {
return t;
} else {
if (father.getLeft() == node) {
father.setLeft(t);
} else {
father.setRight(t);
}
return null;
}
}
}