原创作品,转载请标明来源。
一、 什么是二叉树?
二叉树每个节点最多有两个孩子
二叉树每个节点最多有一个父亲
性质
- 二叉树具有天然的递归结构
- 每个节点的左子树也是二叉树
- 每个节点的柚子树也是二叉树
- 二叉树不一定是“满”的
二、什么是二分搜索树?
- 存储的元素必须有可比较性(比如节点存储的是学生,我们可以以年龄比较或学号比较等)
注意:本文的二分搜索树不包含重复元素
如果想包含重复元素的话,只需要定义:左子树小于等于节点;或者柚子树大于节点即可
二分搜索树的增删茶
- 新增节点
二分搜索树添加元素的非递归写法,和链表很像
本文在二分搜索树方面的实现,关注递归实现
public class BST<E extends Comparable<E>> {
private class Node{
public E e;
public Noe left, right;
public Node(E e) {
this.e = e;
left = null;
right = null;
}
// 向二分搜索树中添加新的元素e -- 优化前
public void add1(E e) {
if (root == null) {
root = new Node(e);
size++;
} else {
add(root,e);
}
}
// 向以node为根的二分搜索树中插入元素E,递归算法---优化前的插入元素部分
private void add1(Node node, E e) {
// 递归终止条件
if (e.equals(node.e)) {
return;
} else if (e.compareTo(node.e) < 0 && node.left == null) {
node.left = new Node(e);
size++;
return;
} else if (e.compareTo(node.e) > 0 && node.right == null) {
node.right = new Node(e);
size++;
return;
}
if (e.compareTo(node.e) < 0 && node.left != null) {
add(node.left,e);
} else{ // (e.compareTo(node.e) > 0 && node.right != null)
add(node.right,e);
}
}
// 向二分搜索树中添加新的元素e -- 优化后
public void add(E e) {
root = add(root,e);
}
// 向以node为根的二分搜索树中插入元素E,递归算法---优化后的插入元素部分
// null 也是一个二叉树来考虑
private Node add(Node node, E e) {
// 递归终止条件
if (node == null) {
size++;
return new Node(e);
}
if (e.compareTo(node.e) < 0) {
node.left = add(node.left,e);
} else if (e.compareTo(node.e) > 0) {
node.right = add(node.right,e);
}
return node;
}
}
- 二分搜索树的查询操作
// 递归算法
private boolean contains(Node node, E e) {
if (node == null)
return false;
if (e.compareTo(node.e) == 0)
return true;
else if (e.compareTo(node.e) < 0)
return contains(node.left,e);
else //e.compareTo(node.e) > 0
return contains(node.right,e);
}
-
遍历(前序、中序、后序-也叫深度优先遍历)
前序遍历
public void preOrder() {
System.out.println();
preOrder(root);
}
private void preOrder(Node node) {
if (node == null)
return;
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
// 非递归的前序遍历
public void preOrderNR (){
Stack<Node> stack = new Stack();
stack.push(root);
while (!stack.isEmpty()) {
Node cur = stack.pop();
System.out.println(cur.e);
if (cur.right != null) stack.push(cur.right);
if (cur.left != null) stack.push(cur.left);
}
}
中序遍历
/**
* 中序遍历
*/
public void inOrder(){
System.out.println();
inOrder(root);
}
private void inOrder(Node node) {
if (node == null)
return;
inOrder(node.left);
System.out.print(node.e+ " ");
inOrder(node.right);
}
/**
* 后序遍历
*/
public void postOrder() {
System.out.println();
postOrder(root);
}
private void postOrder(Node node) {
if (node == null) return;
postOrder(node.left);
postOrder(node.right);
System.out.print(node.e+" ");
}
-
层序遍历(广度优先遍历)--更快的找到问题的解(常用于算法设计中-最短路径)
public void levelOrder (){
LinkedListQueue <Node> queue = new LinkedListQueue();
queue.enqueue(root);
while (!queue.isEmpty()) {
Node cur = queue.dequeue();
System.out.println(cur.e);
if (cur.left != null) queue.enqueue(cur.left);
if (cur.right != null) queue.enqueue(cur.right);
}
}
- 删除操作
删除二分搜索树的某一个节点可能不是很好实现,我们可以先实现删除最小节点和最大节点。可以发现最小节点就是二分搜索树最左边的节点,最大节点是二分搜索书最右边的节点;代码如下
// 得到最小节点值
public E miniMum(){
if (size == 0)
throw new IllegalArgumentException("BST is empty");
return miniMum(root).e;
}
private Node miniMum(Node node) {
if (node.left == null)
return node;
return miniMum(node.left);
}
// 得到醉倒节点值
public E maxMum(){
if (size == 0)
throw new IllegalArgumentException("BST is empty");
return maxMum(root).e;
}
private Node maxMum(Node node) {
if (node.right == null)
return node;
return maxMum(node.right);
}
public E removeMin(){
E ret = miniMum();
root = removeMin(root);
return ret;
}
// 删除掉以node为根的二分搜索树中的最小节点
// 返回删除节点后新的二分搜索树的根
private Node removeMin(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
size--;
return rightNode;
}
node.left = removeMin(node.left);
return node;
}
public E removeMax(){
E ret = maxMum();
root = removeMax(root);
return ret;
}
// 删除掉以node为根的二分搜索树中的最大节点
// 返回删除节点后新的二分搜索树的根
private Node removeMax(Node node) {
if (node.right == null) {
Node leftNode = node.left;
node.left = null;
size--;
return leftNode;
}
node.right = removeMax(node.right);
return node;
}
当我们试图删除某一个节点时,如果这个节点只有左节点,没有右节点,我们可以以删除最大节点的方式去处理(即使该节点未必是最大节点),如果只有右节点没有左节点时,我们同样可以将它以删除最小节点的方式去处理,问题是如果这个要删除的节点既有左节点又有右节点时,怎么处理呢?
我引入一句话,给出了方案
1962年,Hibgard提出- Hibgard Deletion
其实也很简单,就是找到待删除节点的后置最小节点替代待删节点;
比如下图中,d节点是待删节点,根据二分搜索树的性质,d节点的右子叶都比左子叶要打,那么我们只要找到右子叶的最小值替代当前待删节点就可以了。
另一种方案:也可以找到待删节点的前驱最大节点替代待删节点。如上图其实也可以用53所在的节点替换d节点;
写在后面
好了,现在二分搜索树的分析暂时可以告一段落了,