二叉树(BST:Binary Search Tree)
平衡二叉树可参考:
sheng的学习笔记-平衡二叉树(AVL)和3+4重构_coldstarry的博客-CSDN博客
动画演示网站
下面是动画网站,用于演示二叉树的查找,插入,删除等操作的过程
http://btv.melezinek.cz/binary-search-tree.html
定义:
二叉树(binary tree)是指树中节点的度不大于2的有序树,它是一种最简单且最重要的树。二叉树的递归定义为:二叉树是一棵空树,或者是一棵由一个根节点和两棵互不相交的,分别称作根的左子树和右子树组成的非空树;左子树和右子树又同样都是二叉树
遍历
前序遍历:根节点->左子树->右子树(根->左->右) GDAFEMHZ
中序遍历:左子树->根节点->右子树(左->根->右) ADEFGHMZ
后序遍历:左子树->右子树->根节点(左->右->根) AEFDHZMG
二叉树的操作时间与高度成正比,a的速度会比b快(因为a的高度小)
查找数据
下面是用递归函数实现
扩展为while循环,这个版本会快一些
插入
删除
后继的定义看下面的《其他》章节
transplant解释:
tree-delete解释,其中tree-minimum函数见下面最小关键字元素部分
其他
最小关键字元素和最大关键字元素
最小关键字元素:通过从树根开始沿着left孩子指针直到遇到一个NIL(空),最左下角的那个叶子结点,就是这颗树的最小关键字节点,这一颗树中这个节点的值最小,如图 2 这个节点就是最小关键字元素
最大关键字元素:跟最小关键字元素对称的,最右边的叶子结点就是最大关键字元素
后继和前驱
下面是后继的代码,如果x的右子树非空,找到x的右子树的最左节点(15的后继是17)。如果x的右子树是空的,通过x开始沿着树网上直到遇到其双亲有左孩子的节点(下图3~7行,其中x.p是x的父节点,13的后继是15)
优缺点
二叉树的性能损耗,跟二叉树的高度有关,越高的树越慢。在随机数据模型中,查找和插入都是2lgN左右,但如果是顺序插入,二叉树会退化为链表,比如下图,所以出现平衡二叉树,红黑树等改进方案
代码
照着书里的逻辑自己试着写了个,留个纪念
先弄个abstract的类,便于做不同的实现来进行比较
package algorithm.BST;
//二叉树
public abstract class BSTAbstract {
public int count = 0;
// 二叉树的根节点
public MyNode root;
/**
* 查询
*
* @return
*/
public abstract MyNode find(int o);
/**
* 插入
*
* @param o
*/
public abstract void insert(MyNode o);
/**
* 删除
*
* @param o
*/
public abstract boolean delete(MyNode o);
/**
* 节点个数
*
* @return
*/
public int count() {
return this.count;
}
/**
* 打印整个树
*
* @return
*/
public abstract void inorderTreeWalk(MyNode myNode);
}
class MyNode {
public int data;
public MyNode leftNode;
public MyNode rightNode;
public MyNode parent;
public MyNode(int key) {
data = key;
}
@Override
public String toString() {
return "MyNode{" +
"data=" + data +
'}';
}
}
实现类
package algorithm.BST;
public class MyBST extends BSTAbstract {
@Override
public MyNode find(int o) {
System.out.println("find函数路径为");
MyNode node = root;
System.out.println("途径节点:" + node);
while (node != null && o != node.data) {
if (o < node.data) {
node = node.leftNode;
} else {
node = node.rightNode;
}
System.out.println("途径节点:" + node);
}
System.out.println("找到节点:" + node);
return node;
}
@Override
public void insert(MyNode o) {
MyNode x = this.root;
MyNode parent = null;
while (x != null) {
parent = x;
if (o.data < x.data) {
x = x.leftNode;
} else {
x = x.rightNode;
}
}
o.parent = parent;
// 空列表
if (parent == null) {
root = o;
} else if (o.data < parent.data) {
parent.leftNode = o;
} else {
parent.rightNode = o;
}
}
@Override
public boolean delete(MyNode o) {
MyNode current = root;
MyNode parent = null;
//当前节点是否为左节点
boolean isLeftNode = false;
// 定位需要删除的节点
if (current == null) {
return false;
}
while (current.data != o.data) {
if (o.data < current.data) {
isLeftNode = true;
current = current.leftNode;
} else {
isLeftNode = false;
current = current.rightNode;
}
if (current == null) {
return false;
}
}
parent = current.parent;
// 如果待删除节点是叶子结点,将节点改为空
if (current.leftNode == null && current.rightNode == null) {
if (current == root) {
root = null;
} else if (isLeftNode) {
parent.leftNode = null;
} else {
parent.rightNode = null;
}
return true;
}
// 当前有一个节点,如果左孩子是空,将右孩子替代节点
if (current.leftNode == null) {
transfer(current, current.rightNode);
return true;
} else if (current.rightNode == null) {
transfer(current, current.leftNode);
return true;
}
// 当前节点有两个子节点(有左孩子和右孩子),此处一定会进入这个分支,if其实可以不写,不过为减少上下文依赖,写这个if
if (current.leftNode != null && current.rightNode != null) {
MyNode successor = minimum(current.rightNode);
// 如果右子树的最小值(就是current的后继节点)不是current的右孩子,
// 需要将后继节点successor的右孩子交换到后继节点位置,然后将后继节点替代删除节点current
if (successor.parent != current) {
//让后继节点的右孩子替代后继节点
transfer(successor, successor.rightNode);
//删除节点current的右孩子给后继节点的右孩子
successor.rightNode = current.rightNode;
successor.rightNode.parent = successor;
}
//将后继节点successor替代删除节点current
transfer(current, successor);
successor.leftNode = current.leftNode;
successor.leftNode.parent = successor;
return true;
}
return false;
}
// 将new节点替换old节点
public void transfer(MyNode oldNode, MyNode newNode) {
// 如果被替代的节点是根节点(父节点是空),将root改为新的节点
if (oldNode.parent == null) {
root = newNode;
} else if (oldNode == oldNode.parent.leftNode) {
// 如果被替换的节点,是父节点的左孩子,将父节点的左孩子改为新节点
oldNode.parent.leftNode = newNode;
} else {
// 如果被替换的节点是父节点的右孩子,将父节点的右孩子换为新节点
oldNode.parent.rightNode = newNode;
}
//将老节点的父节点,赋给新节点的父节点
newNode.parent = oldNode.parent;
}
// 找到一个节点树的最小左孩子
public MyNode minimum(MyNode myNode) {
while (myNode.leftNode != null) {
myNode = myNode.leftNode;
}
return myNode;
}
@Override
public void inorderTreeWalk(MyNode myNode) {
if (myNode != null) {
inorderTreeWalk(myNode.leftNode);
System.out.println(myNode.data);
inorderTreeWalk(myNode.rightNode);
}
}
@Override
public int count() {
return super.count();
}
}
跑个demo测一下,构造二叉树,查询,遍历,插入,删除都测了下
package algorithm.BST;
import java.util.ArrayList;
import java.util.Arrays;
public class BSTDemo {
public static void main(String[] args) {
BSTAbstract myBST = new MyBST();
ArrayList<Integer> list = new ArrayList<Integer>(Arrays.asList(
(int) (Math.random() * 100), (int) (Math.random() * 100),
(int) (Math.random() * 100), (int) (Math.random() * 100),
(int) (Math.random() * 100), (int) (Math.random() * 100),
(int) (Math.random() * 100), (int) (Math.random() * 100),
(int) (Math.random() * 100), (int) (Math.random() * 100),
(int) (Math.random() * 100), (int) (Math.random() * 100),
(int) (Math.random() * 100), (int) (Math.random() * 100),
(int) (Math.random() * 100), (int) (Math.random() * 100)));
System.out.println(list);
for (int e : list) {
myBST.insert(new MyNode(e));
}
System.out.println("==============遍历二叉树");
myBST.inorderTreeWalk(myBST.root);
System.out.println("==============根节点:" + myBST.root);
System.out.println("==============找到最后一个元素:" + list.get(list.size() - 1));
myBST.find(list.get(list.size() - 1));
System.out.println("==============删除倒数第四个元素:" + list.get(list.size() - 4));
myBST.delete(myBST.find(list.get(list.size() - 4)));
System.out.println("==============删除后的结果");
myBST.inorderTreeWalk(myBST.root);
}
}
结果:
[7, 72, 67, 29, 83, 3, 79, 84, 68, 39, 81, 83, 37, 31, 90, 76]
==============遍历二叉树
3
7
29
31
37
39
67
68
72
76
79
81
83
83
84
90
==============根节点:MyNode{data=7}
==============找到最后一个元素:76
find函数路径为
途径节点:MyNode{data=7}
途径节点:MyNode{data=72}
途径节点:MyNode{data=83}
途径节点:MyNode{data=79}
途径节点:MyNode{data=76}
找到节点:MyNode{data=76}
==============删除倒数第四个元素:37
find函数路径为
途径节点:MyNode{data=7}
途径节点:MyNode{data=72}
途径节点:MyNode{data=67}
途径节点:MyNode{data=29}
途径节点:MyNode{data=39}
途径节点:MyNode{data=37}
找到节点:MyNode{data=37}
==============删除后的结果
3
7
29
31
39
67
68
72
76
79
81
83
83
84
90Process finished with exit code 0
参考文章:
书:算法
书:算法导论