binary search tree python_我理解的数据结构(五)—— 二分搜索树(Binary Search Tree)...

我理解的数据结构(五)—— 二分搜索树(Binary Search Tree)

一、二叉树

和链表一样,动态数据结构

具有唯一根节点

每个节点最多有两个子节点

每个节点最多有一个父节点

具有天然的递归结构

每个节点的左子树也是二叉树

每个节点的右子树也是二叉树

一个节点或者空也是二叉树

二、二分搜索树

是二叉树

每个节点的值

大于其左子树的所有节点的值

小于其右子树的所有节点的值

每一颗子树也是二分搜索树

存储的元素必须有可比较性

三、二分搜索树基础代码实现

1. 基础代码

因为二分搜索树的元素必须具有可比较行,所以E继承了Comparable,这是一个注意点

public class BST> {

// 节点

private class Node {

public E e;

public Node left;

public Node right;

public Node(E e) {

this.e = e;

left = null;

right = null;

}

}

private Node root;

private int size;

public BST() {

root = null;

size = 0;

}

public int getSize() {

return size;

}

public boolean isEmpty() {

return size == 0;

}

}

2. 添加元素代码

public void add(E e) {

if (root == null) {

root = new Node(e);

size++;

}

add(root, e);

}

// 在以node为根节点的二分搜索树添加元素e,递归调用

private void add(Node node, E e) {

if (node.e.compareTo(e)) { // 不考虑重复元素

return;

} else if (node.e.compareTo(e) > 0 && node.left == null) {

node.left = new Node(e);

size++;

return;

} else if (node.e.compareTo(e) < 0 && node.right == null) {

node.right = new Node(e);

size++;

return;

}

if (node.e.compareTo(e) > 0) {

add(node.left, e);

} else {

add(node.right, e);

}

}

3. 添加元素代码(优化)

public void add(E e) {

root = add(root, e);

}

// 返回插入二分搜索树的根

private Node add(Node node, E e) {

if (node == null) {

size++;

return new Node(e);

}

if (node.e.compareTo(e) > 0) {

node.left = add(node.left, e);

} else if (node.e.compareTo(e) < 0) {

node.right = add(node.right, e);

}

return node;

}

4. 查询元素代码

// 是否包含元素e

public boolean contains(E e) {

return contains(root, e);

}

private boolean contains(Node node, E e) {

if (node == null) {

return false;

}

if (node.e.compareTo(e) > 0) {

return contains(node.left, e);

} else if (node.e.compareTo(e) < 0) {

return contains(node.right, e);

} else {

return true;

}

}

四、二分搜索树的前、中、后序遍历

二叉树的前中后序遍历取决于在什么位置去访问元素,每个遍历都有不同的业务场景。

就拿下面这个二叉树举例:

//

// 5 //

// / \ //

// 3 6 //

// / \ \ //

// 2 4 8 //

//

1. 前序遍历(深度优先遍历)

最常用的遍历方式

// 前序遍历

public void preOrder() {

preOrder(root);

}

private void preOrder(Node node) {

if (node == null) {

return;

}

// 遍历前访问元素:前序遍历

System.out.println(node.e);

preOrder(node.left);

preOrder(node.right);

}

前序遍历的结果:5 3 2 4 6 8

2. 前序遍历(非递归写法)

public void preOrderNR() {

// import java.util.Stack;

Stack 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);

}

}

}

3. 中序遍历

二分搜索树的中序遍历结果是顺序的

// 中序遍历

public void inOrder() {

inOrder(root);

}

private void inOrder(Node node) {

if (node == null) {

return;

}

inOrder(node.left);

// 遍历的中间访问元素:中序遍历

System.out.println(node.e);

inOrder(node.right);

}

中序遍历的结果:2 3 4 5 6 8

4. 后序遍历

应用场景:释放内存

// 后序遍历

public void postOrder() {

postOrder(root);

}

private void postOrder(Node node) {

if (node == null) {

return;

}

postOrder(node.left);

postOrder(node.right);

// 遍历的后面访问元素:后序遍历

System.out.println(node.e);

}

中序遍历的结果:2 4 3 8 6 5

五、二分搜索树的层序遍历(广度优先遍历)

和二分搜索树的前序遍历不一样,层序遍历是广度优先遍历。

还是这个例子:优先遍历根节点5,然后是3、6,最后是2、4、8

//

// 5 //

// / \ //

// 3 6 //

// / \ \ //

// 2 4 8 //

//

优点:

更快的找到问题的解

常用语设计算法中——最短路径

代码实现:

// 层序遍历

public void levelOrder() {

levelOrder(root);

}

private void levelOrder(Node node) {

// import java.util.Queue;

// import java.util.LinkedList;

Queue q = new LinkedList<>();

((LinkedList) q).add(node);

while (!q.isEmpty()) {

Node cur = q.remove();

System.out.println(cur.e);

if (cur.left != null) {

((LinkedList) q).add(cur.left);

}

if (cur.right != null) {

((LinkedList) q).add(cur.right);

}

}

}

六、删除二分搜索树最大值和最小值

1.找到最小值的节点

从根节点一直找左节点,直到找到node.left == null,此时的node就是最小值的节点

// 二分搜索树的最小值

public E minimum() {

if (size == 0) {

throw new IllegalArgumentException("BST is empty!");

}

return minimum(root).e;

}

// 返回以node为根的二分搜索树的最小值的节点

private Node minimum(Node node) {

if (node.left == null) {

return node;

}

return minimum(node.left);

}

2.找到最大值的节点

从根节点一直找右节点,直到找到node.right == null,此时的node就是最大值的节点

// 二分搜索树的最大值

public E maximum() {

if (size == 0) {

throw new IllegalArgumentException("BST is empty!");

}

return maximum(root).e;

}

// 返回以node为根的二分搜索树的最大值的节点

private Node maximum(Node node) {

if (node.right == null) {

return node;

}

return maximum(node.right);

}

3.删除最小值的节点

如果需要删除的节点是一个叶子节点,没有右子树,那么直接删除即可

如果需要删除的节点不是一个叶子节点,那么需要把右节点替换到当前的节点

// 删除最小值的节点

public E removeMin() {

E min = minimum();

root = removeMin(root);

return min;

}

// 删除二分搜索树以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;

}

4.删除最大值的节点

// 删除最大值的节点

public E removeMax() {

E max = maximum();

root = removeMax(root);

return max;

}

// 删除二分搜索树以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;

}

七、删除二分搜索树任意值

删除任意节点可以使用前驱(predecessor)和后继(successor)两种方法,下面使用的后继方法。

删除任意节点有三种情况:

删除只有左子树的节点

在逻辑上和删除最大值的节点是一样的

删除只有右子树的节点

在逻辑上和删除最小值的节点是一样的

删除既有左子树和右子树的节点

1962年,Hibbard提出Hibbard Deletion

原理图如下

代码实现:

// 删除元素为e的节点

public void remove(E e) {

root = remove(root, e);

}

private Node remove(Node node, E e) {

if (node == null) {

return null;

}

if (node.e.compareTo(e) > 0) {

node.left = remove(node.left, e);

return node;

} else if (node.e.compareTo(e) < 0) {

node.right = remove(node.right, e);

return node;

} else { // e == node.e

if (node.left == null) { // 左子树为空

Node rightNode = node.right;

node.right = null;

size--;

return rightNode;

}

if (node.right == null) { // 右子树为空

Node leftNode = node.left;

node.left = null;

size--;

return leftNode;

}

// node的后继

Node successor = minimum(node.right);

// 把删除node.right的后继后的二叉树赋值给后继的right

successor.right = removeMin(node.right);

// 把node.left赋值给后继的left

successor.left = node.left;

node.left = node.right = null;

return successor;

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值