二叉搜索树定义#
二叉搜索树,是指一棵空树或者具有下列性质的二叉树:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 任意节点的左,右子树也分别为二叉搜索树;
- 没有键值相等的节点。
源博客:https://www.cnblogs.com/yahuian/p/10813614.html
二叉搜索树,是指一棵空树或者具有下列性质的二叉树:
public class BinarySearchTree { // 二叉搜索树类 private class Node { // 节点类 int data; // 数据域 Node right; // 右子树 Node left; // 左子树 }</span><span style="color: #0000ff;">private</span> Node root; <span style="color: #008000;">//</span><span style="color: #008000;"> 树根节点</span>
}
首先,需要一个节点对象的类。这个对象包含数据域和指向节点的两个子节点的引用。
其次,需要一个树对象的类。这个对象包含一个根节点root。
public void insert(int key) { Node p=new Node(); //待插入的节点 p.data=key;</span><span style="color: #0000ff;">if</span>(root==<span style="color: #0000ff;">null</span><span style="color: #000000;">) { root</span>=<span style="color: #000000;">p; } </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> { Node parent</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> Node(); Node current</span>=<span style="color: #000000;">root; </span><span style="color: #0000ff;">while</span>(<span style="color: #0000ff;">true</span><span style="color: #000000;">) { parent</span>=<span style="color: #000000;">current; </span><span style="color: #0000ff;">if</span>(key><span style="color: #000000;">current.data) { current</span>=current.right; <span style="color: #008000;">//</span><span style="color: #008000;"> 右子树</span> <span style="color: #0000ff;">if</span>(current==<span style="color: #0000ff;">null</span><span style="color: #000000;">) { parent.right</span>=<span style="color: #000000;">p; </span><span style="color: #0000ff;">return</span><span style="color: #000000;">; } } </span><span style="color: #0000ff;">else</span> <span style="color: #008000;">//</span><span style="color: #008000;">本程序没有做key出现相等情况的处理,暂且假设用户插入的节点值都不同</span>
{
current=current.left; // 左子树
if(current==null)
{
parent.left=p;
return;
}
}
}
}
}
创建树的时候,主要用到了parent,current来记录要插入节点的位置。哪么怎么检验自己是否正确地创建了一颗二叉搜索树呢,我们通过遍历来输出各个节点的值
遍历指的是按照某种特定的次序来访问二叉搜索树中的每个节点,主要有三种遍历的方法:
上面的口诀“中左右”表示的含义是,先访问根节点,再访问左子,最后访问右子。举个例子:
你会发现,按照中序遍历的规则将一个二叉搜索树输入,结果为按照正序排列。
public void preOrder(Node root) { // 前序遍历,"中左右" if (root != null) { System.out.print(root.data + " "); preOrder(root.left); preOrder(root.right); } }</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> inOrder(Node root) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 中序遍历,"左中右"</span> <span style="color: #0000ff;">if</span> (root != <span style="color: #0000ff;">null</span><span style="color: #000000;">) { inOrder(root.left); System.out.print(root.data </span>+ " "<span style="color: #000000;">); inOrder(root.right); } } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> postOrder(Node root) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 后序遍历,"左右中"</span> <span style="color: #0000ff;">if</span> (root != <span style="color: #0000ff;">null</span><span style="color: #000000;">) { postOrder(root.left); postOrder(root.right); System.out.print(root.data </span>+ " "<span style="color: #000000;">); } } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> traverse(<span style="color: #0000ff;">int</span><span style="color: #000000;"> traverseType) { </span><span style="color: #008000;">//</span><span style="color: #008000;"> 选择以何种方式遍历</span> <span style="color: #0000ff;">switch</span><span style="color: #000000;"> (traverseType) { </span><span style="color: #0000ff;">case</span> 1<span style="color: #000000;">: System.out.print(</span>"preOrder traversal "<span style="color: #000000;">); preOrder(root); System.out.println(); </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #0000ff;">case</span> 2<span style="color: #000000;">: System.out.print(</span>"inOrder traversal "<span style="color: #000000;">); inOrder(root); System.out.println(); </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; </span><span style="color: #0000ff;">case</span> 3<span style="color: #000000;">: System.out.print(</span>"postOrder traversal "<span style="color: #000000;">); postOrder(root); System.out.println(); </span><span style="color: #0000ff;">break</span><span style="color: #000000;">; } }</span></pre>
以上的代码采用递归的方式实现三种遍历,为了方便我们使用,又写了一个traverse函数来实现选择哪种方式进行树的遍历。
这会儿就可以写单元测试了,我们首先创建一个二叉搜索树,然后分别使用“前序”,“中序”,“后序”来遍历输出树的所有节点。
public static void main(String[] args) //unit test { BinarySearchTree tree=new BinarySearchTree();tree.insert(</span>39<span style="color: #000000;">); tree.insert(</span>24<span style="color: #000000;">); tree.insert(</span>64<span style="color: #000000;">); tree.insert(</span>23<span style="color: #000000;">); tree.insert(</span>30<span style="color: #000000;">); tree.insert(</span>53<span style="color: #000000;">); tree.insert(</span>60<span style="color: #000000;">); tree.traverse(</span>1<span style="color: #000000;">); tree.traverse(</span>2<span style="color: #000000;">); tree.traverse(</span>3<span style="color: #000000;">); }</span></pre>
运行该单元测试,可以看到如下的结果:
public Node find(int key) { // 从树中按照关键值查找元素 Node current = root; while (current.data != key) { if (key > current.data) current = current.right; else current = current.left; if (current == null) return null; } return current; }</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> show(Node node) { </span><span style="color: #008000;">//</span><span style="color: #008000;">输出节点的数据域</span> <span style="color: #0000ff;">if</span>(node!=<span style="color: #0000ff;">null</span><span style="color: #000000;">) System.out.println(node.data); </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> System.out.println(</span>"null"<span style="color: #000000;">); }</span></pre>
查找节点比较简单,如果找到节点则返回该节点,否则返回null。为了方便在控制台输出,我们有添加了一个show函数,用来输出节点的数据域。
删除节点是二叉搜索树中,最复杂的一种操作,但是也不是特别难,我们分类讨论:
如图所示,只需要将parent.left(或者是parent.right)设置为null,然后Java垃圾自动回收机制会自动删除current节点。
如图所示,只需要将parent.left(或者是parent.right)设置为curren.right(或者是current.left)即可。
这种情况比较复杂,首先我们引入后继节点的概念,如果将一棵二叉树按照中序周游的方式输出,则任一节点的下一个节点就是该节点的后继节点。例如:上图中24的后继节点为25,64的后继节点为70.找到后继节点以后,问题就变得简单了,分为两种情况:
1.后继节点为待删除节点的右子,只需要将curren用successor替换即可,注意处理好current.left和successor.right.
注意:这种情况下,successor一定没有左孩子,一但它有左孩子,哪它必然不是current的后继节点。
2.后继节点为待删除结点的右孩子的左子树,这种情况稍微复杂点,请看动态图片演示。
算法的步骤是:
弄懂原理后,我们来看具体的代码实现:
private Node getSuccessor(Node delNode) //寻找要删除节点的中序后继结点 { Node successorParent=delNode; Node successor=delNode; Node current=delNode.right;</span><span style="color: #008000;">//</span><span style="color: #008000;">用来寻找后继结点</span> <span style="color: #0000ff;">while</span>(current!=<span style="color: #0000ff;">null</span><span style="color: #000000;">) { successorParent</span>=<span style="color: #000000;">successor; successor</span>=<span style="color: #000000;">current; current</span>=<span style="color: #000000;">current.left; } </span><span style="color: #008000;">//</span><span style="color: #008000;">如果后继结点为要删除结点的右子树的左子,需要预先调整一下要删除结点的右子树</span> <span style="color: #0000ff;">if</span>(successor!=<span style="color: #000000;">delNode.right) { successorParent.left</span>=<span style="color: #000000;">successor.right; successor.right</span>=<span style="color: #000000;">delNode.right; } </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> successor; } </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">boolean</span> delete(<span style="color: #0000ff;">int</span> key) <span style="color: #008000;">//</span><span style="color: #008000;"> 删除结点</span>
{
Node current = root;
Node parent = new Node();
boolean isRightChild = true;
while (current.data != key)
{
parent = current;
if (key > current.data)
{
current = current.right;
isRightChild = true;
}
else
{
current = current.left;
isRightChild = false;
}
if (current == null) return false; // 没有找到要删除的结点
}
// 此时current就是要删除的结点,parent为其父结点
// 要删除结点为叶子结点
if (current.right == null && current.left == null)
{
if (current root)
{
root = null; // 整棵树清空
}
else
{
if (isRightChild)
parent.right = null;
else
parent.left = null;
}
return true;
}
//要删除结点有一个子结点
else if(current.leftnull)
{
if(current==root)
root=current.right;
else if(isRightChild)
parent.right=current.right;
else
parent.left=current.right;
return true;
}
else if(current.right==null)
{
if(current==root)
root=current.left;
else if(isRightChild)
parent.right=current.left;
else
parent.left=current.left;
return true;
}
//要删除结点有两个子结点
else
{
Node successor=getSuccessor(current); //找到要删除结点的后继结点
<span style="color: #0000ff;">if</span>(current==<span style="color: #000000;">root)
root</span>=<span style="color: #000000;">successor;
</span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span><span style="color: #000000;">(isRightChild)
parent.right</span>=<span style="color: #000000;">successor;
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">
parent.left</span>=<span style="color: #000000;">successor;
successor.left</span>=<span style="color: #000000;">current.left;
</span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">true</span><span style="color: #000000;">;
}
}</span></pre>
二叉搜索树删除操作
大家注意哪个私有函数getSuccessor的功能,它不仅仅是用来找后继结点的。
二叉搜索树其实不是特别难,理解以后,多练习几次,应该可以掌握。以下是全部的代码:
package org.yahuian;
public class BinarySearchTree
{ // 二叉搜索树类
private class Node
{ // 节点类
int data; // 数据域
Node right; // 右子树
Node left; // 左子树
}
</span><span style="color: #0000ff;">private</span> Node root; <span style="color: #008000;">//</span><span style="color: #008000;"> 树根节点</span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> insert(<span style="color: #0000ff;">int</span><span style="color: #000000;"> key)
{
Node p </span>= <span style="color: #0000ff;">new</span> Node(); <span style="color: #008000;">//</span><span style="color: #008000;"> 待插入的节点</span>
p.data =<span style="color: #000000;"> key;
</span><span style="color: #0000ff;">if</span> (root == <span style="color: #0000ff;">null</span><span style="color: #000000;">)
{
root </span>=<span style="color: #000000;"> p;
}
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">
{
Node parent </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Node();
Node current </span>=<span style="color: #000000;"> root;
</span><span style="color: #0000ff;">while</span> (<span style="color: #0000ff;">true</span><span style="color: #000000;">)
{
parent </span>=<span style="color: #000000;"> current;
</span><span style="color: #0000ff;">if</span> (key ><span style="color: #000000;"> current.data)
{
current </span>= current.right; <span style="color: #008000;">//</span><span style="color: #008000;"> 右子树</span>
<span style="color: #0000ff;">if</span> (current == <span style="color: #0000ff;">null</span><span style="color: #000000;">)
{
parent.right </span>=<span style="color: #000000;"> p;
</span><span style="color: #0000ff;">return</span><span style="color: #000000;">;
}
}
</span><span style="color: #0000ff;">else</span> <span style="color: #008000;">//</span><span style="color: #008000;"> 本程序没有做key出现相等情况的处理,暂且假设用户插入的节点值都不同</span>
{
current = current.left; // 左子树
if (current == null)
{
parent.left = p;
return;
}
}
}
}
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> preOrder(Node root)
{ </span><span style="color: #008000;">//</span><span style="color: #008000;"> 前序遍历,"中左右"</span>
<span style="color: #0000ff;">if</span> (root != <span style="color: #0000ff;">null</span><span style="color: #000000;">)
{
System.out.print(root.data </span>+ " "<span style="color: #000000;">);
preOrder(root.left);
preOrder(root.right);
}
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> inOrder(Node root)
{ </span><span style="color: #008000;">//</span><span style="color: #008000;"> 中序遍历,"左中右"</span>
<span style="color: #0000ff;">if</span> (root != <span style="color: #0000ff;">null</span><span style="color: #000000;">)
{
inOrder(root.left);
System.out.print(root.data </span>+ " "<span style="color: #000000;">);
inOrder(root.right);
}
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> postOrder(Node root)
{ </span><span style="color: #008000;">//</span><span style="color: #008000;"> 后序遍历,"左右中"</span>
<span style="color: #0000ff;">if</span> (root != <span style="color: #0000ff;">null</span><span style="color: #000000;">)
{
postOrder(root.left);
postOrder(root.right);
System.out.print(root.data </span>+ " "<span style="color: #000000;">);
}
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> traverse(<span style="color: #0000ff;">int</span><span style="color: #000000;"> traverseType)
{ </span><span style="color: #008000;">//</span><span style="color: #008000;"> 选择以何种方式遍历</span>
<span style="color: #0000ff;">switch</span><span style="color: #000000;"> (traverseType)
{
</span><span style="color: #0000ff;">case</span> 1<span style="color: #000000;">:
System.out.print(</span>"preOrder traversal "<span style="color: #000000;">);
preOrder(root);
System.out.println();
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">case</span> 2<span style="color: #000000;">:
System.out.print(</span>"inOrder traversal "<span style="color: #000000;">);
inOrder(root);
System.out.println();
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">case</span> 3<span style="color: #000000;">:
System.out.print(</span>"postOrder traversal "<span style="color: #000000;">);
postOrder(root);
System.out.println();
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
}
}
</span><span style="color: #0000ff;">public</span> Node find(<span style="color: #0000ff;">int</span><span style="color: #000000;"> key)
{ </span><span style="color: #008000;">//</span><span style="color: #008000;"> 从树中按照关键值查找元素</span>
Node current =<span style="color: #000000;"> root;
</span><span style="color: #0000ff;">while</span> (current.data !=<span style="color: #000000;"> key)
{
</span><span style="color: #0000ff;">if</span> (key ><span style="color: #000000;"> current.data)
current </span>=<span style="color: #000000;"> current.right;
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">
current </span>=<span style="color: #000000;"> current.left;
</span><span style="color: #0000ff;">if</span> (current == <span style="color: #0000ff;">null</span>) <span style="color: #0000ff;">return</span> <span style="color: #0000ff;">null</span><span style="color: #000000;">;
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> current;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> show(Node node)
{ </span><span style="color: #008000;">//</span><span style="color: #008000;">输出节点的数据域</span>
<span style="color: #0000ff;">if</span>(node!=<span style="color: #0000ff;">null</span><span style="color: #000000;">)
System.out.println(node.data);
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">
System.out.println(</span>"null"<span style="color: #000000;">);
}
</span><span style="color: #0000ff;">private</span> Node getSuccessor(Node delNode) <span style="color: #008000;">//</span><span style="color: #008000;">寻找要删除节点的中序后继结点</span>
{
Node successorParent=delNode;
Node successor=delNode;
Node current=delNode.right;
</span><span style="color: #008000;">//</span><span style="color: #008000;">用来寻找后继结点</span>
<span style="color: #0000ff;">while</span>(current!=<span style="color: #0000ff;">null</span><span style="color: #000000;">)
{
successorParent</span>=<span style="color: #000000;">successor;
successor</span>=<span style="color: #000000;">current;
current</span>=<span style="color: #000000;">current.left;
}
</span><span style="color: #008000;">//</span><span style="color: #008000;">如果后继结点为要删除结点的右子树的左子,需要预先调整一下要删除结点的右子树</span>
<span style="color: #0000ff;">if</span>(successor!=<span style="color: #000000;">delNode.right)
{
successorParent.left</span>=<span style="color: #000000;">successor.right;
successor.right</span>=<span style="color: #000000;">delNode.right;
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> successor;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">boolean</span> delete(<span style="color: #0000ff;">int</span> key) <span style="color: #008000;">//</span><span style="color: #008000;"> 删除结点</span>
{
Node current = root;
Node parent = new Node();
boolean isRightChild = true;
while (current.data != key)
{
parent = current;
if (key > current.data)
{
current = current.right;
isRightChild = true;
}
else
{
current = current.left;
isRightChild = false;
}
if (current == null) return false; // 没有找到要删除的结点
}
// 此时current就是要删除的结点,parent为其父结点
// 要删除结点为叶子结点
if (current.right == null && current.left == null)
{
if (current root)
{
root = null; // 整棵树清空
}
else
{
if (isRightChild)
parent.right = null;
else
parent.left = null;
}
return true;
}
//要删除结点有一个子结点
else if(current.leftnull)
{
if(current==root)
root=current.right;
else if(isRightChild)
parent.right=current.right;
else
parent.left=current.right;
return true;
}
else if(current.right==null)
{
if(current==root)
root=current.left;
else if(isRightChild)
parent.right=current.left;
else
parent.left=current.left;
return true;
}
//要删除结点有两个子结点
else
{
Node successor=getSuccessor(current); //找到要删除结点的后继结点
<span style="color: #0000ff;">if</span>(current==<span style="color: #000000;">root)
root</span>=<span style="color: #000000;">successor;
</span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span><span style="color: #000000;">(isRightChild)
parent.right</span>=<span style="color: #000000;">successor;
</span><span style="color: #0000ff;">else</span><span style="color: #000000;">
parent.left</span>=<span style="color: #000000;">successor;
successor.left</span>=<span style="color: #000000;">current.left;
</span><span style="color: #0000ff;">return</span> <span style="color: #0000ff;">true</span><span style="color: #000000;">;
}
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> main(String[] args) <span style="color: #008000;">//</span><span style="color: #008000;"> unit test</span>
{
BinarySearchTree tree = new BinarySearchTree();
tree.insert(</span>39<span style="color: #000000;">);
tree.insert(</span>24<span style="color: #000000;">);
tree.insert(</span>64<span style="color: #000000;">);
tree.insert(</span>23<span style="color: #000000;">);
tree.insert(</span>30<span style="color: #000000;">);
tree.insert(</span>53<span style="color: #000000;">);
tree.insert(</span>60<span style="color: #000000;">);
tree.traverse(</span>1<span style="color: #000000;">);
tree.traverse(</span>2<span style="color: #000000;">);
tree.traverse(</span>3<span style="color: #000000;">);
tree.show(tree.find(</span>23<span style="color: #000000;">));
tree.show(tree.find(</span>60<span style="color: #000000;">));
tree.show(tree.find(</span>64<span style="color: #000000;">));
tree.delete(</span>23<span style="color: #000000;">);
tree.delete(</span>60<span style="color: #000000;">);
tree.delete(</span>64<span style="color: #000000;">);
tree.show(tree.find(</span>23<span style="color: #000000;">));
tree.show(tree.find(</span>60<span style="color: #000000;">));
tree.show(tree.find(</span>64<span style="color: #000000;">));
}
}
二叉搜索树详解