您的位置:首页 > 其它

二叉查找树

2016-03-16 17:52 288 查看
使二叉树成为尔叉查找树的性质是:

对于树中的每个节点X,它的左子树中所有项的值小于X中的项,而它的右子树中所有的项的值大于X中的项。

由于树的递归定义,通常是递归地编写有关树的操作例程。因为二叉查找树的平均深度是O(log N),所以一般不必担心栈空间被用尽。

1 contains方法

//contains的包装方法
public boolean contains(int val) {
return contains(val, root);
}

private boolean contains(int val, TreeNode r) {
if(r == null){
return false;
}

if(val - r.val < 0){
return contains(val, r.left);
}
else if (val - r.val > 0) {
return contains(val, r.right);
}
else {
return true;
}
}


2 findMin 和 findMax 方法

//findMin包装方法
public TreeNode findMin() {
return findMin(root);
}

private TreeNode findMin(TreeNode t) {
if(t == null){
return null;
}
else if (t.left == null) {
return t;
}
else {
return findMin(t.left);
}
}


//findMax的包装方法
public TreeNode findMax() {
return findMax(root);
}

private TreeNode findMax(TreeNode t) {
if(t == null){
return null;
}
else if (t.right == null) {
return t;
}
else {
return findMax(t.right);
}
}


3 insert操作

跟contains类似,就是通过比较,二分查找到要插入的位置;如果发现已经存在,则什么也不做。

// insert的包装方法
public void insert(int val) {
root = insert(root, val);
}

private TreeNode insert(TreeNode t, int val) {
// 由于是递归编写,所以每次找准了要插入的位置才新建节点;不然没调用该方法一次就新建一个节点是一种浪费
if (t == null) {
TreeNode node = new TreeNode(val);
return node;
}

// 小于0,则插在在左子树
if (val - t.val < 0) {
// 如果左子树为空,则新建节点,插入为左节点即可
if (t.left == null) {
TreeNode node = new TreeNode(val);
t.left = node;
}
// 如果左子树不为空,则接着判断一次
else {
insert(t.left, val);
}
}
// 如果大于0,则插在右子树
else if (val - t.val > 0) {
if (t.right == null) {
TreeNode node = new TreeNode(val);
t.right = node;
} else {
insert(t.right, val);
}
}
// 发现二叉搜索树中已经存在这个值了
else {
// do nothing
}

return t;

}


4 remove操作

remove操作比较复杂。

(1)如果要删除的节点是一个叶子,那么直接去掉就可以了;

(2)如果要删除的节点A有一个儿子,那么调整一下A的父节点的指针,使它直接指向A的儿子即可;

(3)如果要删除的节点A有两个儿子,那么可以用A的右子树中的最小值,代替A的值,然后再去删除那么最小节点即可。这样的好处是,最小节点是一定没有左子树的,所以,删除它,就是用情况2中的步骤。

// remove的包装方法
public void remove(int val) {
root = remove(root, val);
}

private TreeNode remove(TreeNode t, int val) {
if (t == null) {
return null;
}

// 第一步是,找到要删除的节点
// 小于0,说明该节点在左子树中
if (val - t.val < 0) {
t.left = remove(t.left, val);

4000
}
// 大于0,说明该节点在右子树中
else if (val - t.val > 0) {
t.right = remove(t.right, val);
}
// 等于0,说明找到了
else {
// 如果这是一个叶子,则直接删除这个节点
if (t.left == null && t.right == null) {
t = null;
}
// 如果该节点有一个儿子
else if (t.left == null ^ t.right == null) {
return t.left != null ? t.left : t.right;
}
// 如果有两个儿子
else if (t.left != null && t.right != null) {
TreeNode minNode = findMin(t.right);
t.val = minNode.val;
t.right = remove(t.right, minNode.val);
}
}

return t;
}


5 完整代码即测试结果

BinarySearchTree.java

package Tree;

import java.util.LinkedList;

public class BinarySearchTree {

private TreeNode root;

public BinarySearchTree() {
root = null;
}

public void clear() {
root = null;
}

public boolean isEmpty() {
return root == null;
}

// remove的包装方法
public void remove(int val) {
root = remove(root, val);
}

private TreeNode remove(TreeNode t, int val) {
if (t == null) {
return null;
}

// 第一步是,找到要删除的节点
// 小于0,说明该节点在左子树中
if (val - t.val < 0) {
t.left = remove(t.left, val);
}
// 大于0,说明该节点在右子树中
else if (val - t.val > 0) {
t.right = remove(t.right, val);
}
// 等于0,说明找到了
else {
// 如果这是一个叶子,则直接删除这个节点
if (t.left == null && t.right == null) {
t = null;
}
// 如果该节点有一个儿子
else if (t.left == null ^ t.right == null) {
return t.left != null ? t.left : t.right;
}
// 如果有两个儿子
else if (t.left != null && t.right != null) {
TreeNode minNode = findMin(t.right);
t.val = minNode.val;
t.right = remove(t.right, minNode.val);
}
}

return t;
}

// insert的包装方法
public void insert(int val) {
root = insert(root, val);
}

private TreeNode insert(TreeNode t, int val) {
// 由于是递归编写,所以每次找准了要插入的位置才新建节点;不然没调用该方法一次就新建一个节点是一种浪费
if (t == null) {
TreeNode node = new TreeNode(val);
return node;
}

// 小于0,则插在在左子树
if (val - t.val < 0) {
// 如果左子树为空,则新建节点,插入为左节点即可
if (t.left == null) {
TreeNode node = new TreeNode(val);
t.left = node;
}
// 如果左子树不为空,则接着判断一次
else {
insert(t.left, val);
}
}
// 如果大于0,则插在右子树
else if (val - t.val > 0) {
if (t.right == null) {
TreeNode node = new TreeNode(val);
t.right = node;
} else {
insert(t.right, val);
}
}
// 发现二叉搜索树中已经存在这个值了
else {
// do nothing
}

return t;

}

// contains的包装方法
public boolean contains(int val) {
return contains(val, root);
}

private boolean contains(int val, TreeNode r) {
if (r == null) {
return false;
}

if (val - r.val < 0) {
return contains(val, r.left);
} else if (val - r.val > 0) {
return contains(val, r.right);
} else {
return true;
}
}

// findMin包装方法
public TreeNode findMin() {
return findMin(root);
}

private TreeNode findMin(TreeNode t) {
if (t == null) {
return null;
} else if (t.left == null) {
return t;
} else {
return findMin(t.left);
}
}

// findMax的包装方法
public TreeNode findMax() {
return findMax(root);
}

private TreeNode findMax(TreeNode t) {
if (t == null) {
return null;
} else if (t.right == null) {
return t;
} else {
return findMax(t.right);
}
}

public void buildTree() {
/**
*         6
*        / \
*       2   8
*      / \
*     1   4
*        /
*       3
* **/

root = new TreeNode(6);
TreeNode l = new TreeNode(2);
TreeNode r = new TreeNode(8);
TreeNode ll = new TreeNode(1);
TreeNode lr = new TreeNode(4);
TreeNode lrl = new TreeNode(3);

root.left = l;
root.right = r;
l.left = ll;
l.right = lr;
lr.left = lrl;
}

public void display() {
if (root == null) {
return;
}
System.out.println("\n*******************************");
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
TreeNode p;
queue.addLast(root);
int parentCount = 1;
int childrenCount = 0;
while (!queue.isEmpty()) {
p = queue.removeFirst();
System.out.print(p.val + " ");
parentCount--;

if (p.left != null) {
queue.addLast(p.left);
childrenCount++;
}
if (p.right != null) {
queue.addLast(p.right);
childrenCount++;
}

if (parentCount == 0) {
System.out.println();
parentCount = childrenCount;
childrenCount = 0;
}

}
System.out.println("*******************************\n");

}

}


Main.java

package Tree;

public class Main {

public static void main(String[] args) {
BinarySearchTree bTree = new BinarySearchTree();

/**新建一个用于测试的二叉搜索树
*         6
*        / \
*       2   8
*      / \
*     1   4
*        /
*       3
* **/
bTree.buildTree();
// 按层次打印这棵树
bTree.display();

// 是否包含 3 这个值?
System.out.println(bTree.contains(3) + "\n");

// 最小、最大值是多少?
System.out.println("Min is: " + bTree.findMin().val);
System.out.println("Max is: " + bTree.findMax().val);

// 插入 5
bTree.insert(5);
System.out.print("插入 5");
bTree.display();

// 插入12
bTree.insert(12);
System.out.print("插入12:");
bTree.display();

// 删除 8
// 目前 8 所在的节点有一个儿子 12
bTree.remove(8);
System.out.print("删除 8:");
bTree.display();

// 删除 2
// 目前2所在节点有两个儿子 1和 4
bTree.remove(2);
System.out.print("删除 2:");
bTree.display();

}

}


output:

**********
b6ee
*********************
6
2 8
1 4
3
*******************************

true

Min is: 1
Max is: 8

插入 5
*******************************
6
2 8
1 4
3 5
*******************************

插入12:
*******************************
6
2 8
1 4 12
3 5
*******************************

删除 8:
*******************************
6
2 12
1 4
3 5
*******************************

删除 2:
*******************************
6
3 12
1 4
5
*******************************


6 二叉搜索树的问题

执行大多数BST基本操作的时间显然依赖于树的形状。如果树是平衡的,即每个节点的左子树的节点数与右子树的节点数大致相等,则当查找指针下移一层的时候,需要查找的节点数降低为原来的一半。当BST变得不平衡的时候,比如极端的,一个BST退化为一个链表,使得查找的效率变成了O(N)。

所以问题有两个:

(1)上述的remove方法有助于使右子树的深度比左子树的浅,因为我们总是用右子树的最小节点来代替要删除的节点。

(2)如果是已经排序好的数据,那么可以想象一种极端情况是所有的节点都没有左儿子,或是,所有的节点都没有右儿子。

上面两个问题又可以总结为一类问题,那就是树的不平衡。为什么不平衡不好呢?因为这样的二叉搜索树,其查找等操作的平均时间将超过 O(log N)。比如,向一棵树输入预先排好的数据,那么这一连串的insert 操作将花费二次时间。为了解决这一类问题,有了平衡二叉树的概念。

平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。构造与调整方法 平衡二叉树的常用算法有红黑树、AVL、Treap等。系统的,将在下一篇博文里介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息