二叉排序树
1、 二叉排序树的定义
二叉排序树
,又称二叉搜索树是一颗空树,或者具有下列性质的二叉树:
- 每个结点都有一个作为搜索依据的关键码(key),所有结点的关键码互不相同。
- 左子树(如果存在)上所有结点的关键码都小于根节点的关键码。
- 右子树(如果存在)上所有结点的关键码都大于根结点的关键码。
- 左子树和右子树也是二叉排序树。
2、非递归查询
二叉排序树如上图所示,每一个结点有四个域,数据域,指向父节点的指针
,指向左孩子的指针,指向右孩子的指针。
结点定义如下:
BstNode
public class BstNode {
public int data;
public BstNode parent;
public BstNode leftChild;
public BstNode rightChild;
public BstNode(int date) {
this.data = date;
}
}
算法思想: 非递归遍历,首先需要一个指针ptr指向根结点,如果根结点的关键码等于要查询的val,则找到了,如果val大于根结点的关键码,则ptr指向其右孩子,如果val小于根结点的关键码,则ptr指向其左孩子,然后重复如上操作,依次循环比较,直到ptr为空或找到为止。
代码如下:
private BstNode findVal(BstNode ptr, int val) {
while (ptr != null && ptr.data != val) {
if (ptr.data > val) {
ptr = ptr.rightChild;
} else {
ptr = ptr.leftChild;
}
}
return ptr;
}
public BstNode findVal(int val) {
return findVal(root,val);
}
3、递归查询
算法思想: 在二叉排序树上进行搜索,从根结点开始,沿着某一个分支逐层向下进行比较判等的过程。
假设想要在二叉排序树中搜索关键码为val的元素,搜索过程从根结点开始,如果根结点为空,则搜索不成功,直接返回;否则用给定值val与根结点的关键码进行比较:
如果给定值等于根结点的关键码,则搜索成功;
如果给定值小于根结点的关键码,则继续递归搜索根结点的左子树;
否则,递归搜索根结点的右子树。
代码如下:
private BstNode findValue(BstNode ptr, int val) {
if (ptr == null || ptr.data == val) {
return ptr;
} else if (ptr.data > val) {
return findValue(ptr.leftChild, val);
} else {
return findValue(ptr.rightChild, val);
}
}
public BstNode findValue(int val) {
return findValue(root,val);
}
4、找二叉排序树的第一个结点
找二叉排序树的第一个结点,就是相当于找这颗树的最小的结点,也就是最左侧的那个结点。
代码如下:
private BstNode first(BstNode ptr) {
while (ptr != null && ptr.leftChild != null) {
ptr = ptr.leftChild;
}
return ptr;
}
5、找二叉排序树的最后一个结点
找二叉排序树的最后一个结点,就是相当于找这颗排序二叉树的最大的一个结点,也就是最右侧的那个结点。
private BstNode last(BstNode ptr) {
while (ptr != null && ptr.rightChild != null) {
ptr = ptr.rightChild;
}
return ptr;
}
6、非递归的中序遍历(不用栈)
非递归的中序遍历,我们可以先找到他的第一个结点进行打印,然后寻找它的下一个结点进行打印,直到全部打印结束。
代码如下:
/**
* 非递归中序遍历
*/
public void niceInOrder() {
for (BstNode p = first(root); p != null; p = next(p)) {
System.out.print(p.data + " ");
}
}
那么现在要解决的就是如何写next()方法了,也就是如何去寻找结点的后继,下一个要打印的结点。
首先找到第一个结点09,打印完09后,判断该结点的右子树是否为null,为null,那么它的后继就是它的父节点,即打印17,打印完17以后,判断其右子树是否为null,不为null,以它的右孩子为根节点,找到该子树的第一个结点23,打印完23后判断其右子树是否为null,为null,打印他的父节点45,打印完45后,判断其右子树也为null,那么此时他的后继为谁呢?显然不是它的父节点,而是53这个结点。所以编码如下:
next函数:
/**
* 寻找下一个结点,它的后继
*/
private BstNode next(BstNode ptr) {
if (ptr == null) {
return null;
}
if (ptr.rightChild != null) {
return first(ptr.rightChild);
} else {
BstNode pa = ptr.parent;
while (pa != null && pa.rightChild == ptr) {
ptr = pa;
pa = ptr.parent;
}
return pa;
}
}
7、构建排序二叉树
算法思想: 如果root为null,表明这是一颗空树,直接将结点插入,如果根结点不为null,那么我们需要把val值与根结点的data进行比较,如果大于就往右子树寻找位置,小于就往左子树寻找位置,但是最后我们要插入的p结点要指向它的父节点,所以我们需要用一个pa指针保存其父节点的位置。
代码如下:
/**
* 插入结点
*/
public boolean insert(int val) {
//如果根节点为null,直接插入
if (root == null) {
root = new BstNode(val);
return true;
}
BstNode p = root;
BstNode pa = null;
while (p != null && p.data != val) {
pa = p;
p = val > p.data ? p.rightChild : p.leftChild;
}
//如果该树中有相同结点,插入失败
if (p != null && p.data == val) {
return false;
}
//找到位置
p = new BstNode(val);
p.parent = pa;
if (pa.data > val) {
pa.leftChild = p;
}else {
pa.rightChild = p;
}
return true;
}
测试类
public class TestBstTree {
public static void main(String[] args) {
BinarySortTree bst = new BinarySortTree();
//构建二叉排序树
int[] arr = {53, 17, 78, 9, 45, 65, 23, 81, 94, 88, 100, 87};
for (int i = 0; i < arr.length; i++) {
bst.insert(arr[i]);
}
//中序遍历
bst.niceInOrder();
}
}
8、逆向遍历二叉排序树
逆向遍历二叉排序树,与非递归中序遍历二叉排序树相反,我们需要先找到他的最后一个结点进行打印,然后寻找它的前驱结点进行打印,直到全部打印结束。
public void reNiceInOrder() {
for (BstNode p = last(root); p != null; p = prev(p)) {
System.out.print(p.data + " ");
}
System.out.println();
}
prev函数
private BstNode prev(BstNode ptr) {
if (ptr == null) {
return null;
}
if (ptr.leftChild != null) {
return last(ptr.leftChild);
} else {
BstNode pa = ptr.parent;
while (pa != null && pa.leftChild == ptr) {
ptr = pa;
pa = ptr.parent;
}
return pa;
}
}
测试类
public class TestBstTree {
public static void main(String[] args) {
BinarySortTree bst = new BinarySortTree();
//构建二叉排序树
int[] arr = {53, 17, 78, 9, 45, 65, 23, 81, 94, 88, 100, 87};
for (int i = 0; i < arr.length; i++) {
bst.insert(arr[i]);
}
//中序遍历
// bst.niceInOrder();
//逆向遍历
bst.reNiceInOrder();
}
}
9、删除二叉排序树的结点
删除二叉排序树的一个结点,
如果该树是一颗空树,那么就不需要删除了,
如果树不为空,那么我们需要在该树中寻找该结点,
没有找到,不需要删除了
找到了又分为三种情况,
1、结点为叶子结点,
2、结点为单分支结点
3、结点为双分支结点
先考虑结点为叶子结点
结点为叶子结点的话,如果该结点是父节点的左孩子,那么将父节点的左指针置为空,反之,如果该结点是父节点的右孩子,那么将父节点的右指针置为空
BstNode p = findValue(root, val);
if (p == null) return false;//没有找到
//找到,且为叶子结点
BstNode pa = p.parent;
if (pa.leftChild == p) {
pa.leftChild = null;
} else {
pa.rightChild = child;
}
我们可以改进上述的删除叶子结点的代码,使代码既能删除叶子结点,又可以删除单分支节点
BstNode p = findValue(root, val);
if (p == null) return false;//没有找到
//找到了,且为叶子结点或单分支结点
BstNode pa = p.parent;
BstNode child = p.leftChild != null ? p.leftChild : p.rightChild;
if (pa.leftChild == p) {
pa.leftChild = child;
} else {
pa.rightChild = child;
}
接下来,如果该结点,既是单分支结点,又是根节点呢
如上图所示,如果删除的结点是53,53这个结点的父节点是个null,那么我们就只能把root直接指向其孩子了。再次改进代码
BstNode p = findValue(root, val);
if (p == null) return false;//没有找到
//为叶节点或单分支结点或单根分支结点
BstNode pa = p.parent;
BstNode child = p.leftChild != null ? p.leftChild : p.rightChild;
if (pa == null) {//判断是否是单根分支结点
root = child;//是单分支结点直root直接指向其孩子结点
} else {
if (pa.leftChild == p) {
pa.leftChild = child;
} else {
pa.rightChild = child;
}
}
最后我们要考虑删除双分支结点了
比如我们要删除的是78结点,删除了78结点后,我们需要维护这颗二叉排序树的,即在它的右孩子中找到最小的结点来接替他,换句话说就是找到它的后继来替换他的位置,所以我们可以用狸猫换太子来删除一个双分支结点,即把其后继赋值给他,然后删除后继就可以了,后继必然是一个叶子结点,那么就可以复用上面的代码啦!
public boolean remove(int val) {
if (root == null) return false; //如果是空树,不删
BstNode p = findValue(root, val);
if (p == null) return false;//没有找到,不删
//删除的结点是双分支结点
if (p.leftChild != null && p.rightChild != null) {
BstNode next = next(p);//找到其后继
p.data = next.data;//后继的值赋给他
p = next;//后继赋值给他,即后面把后继删除即可,此时后继必定是一个叶子结点
}
//结点为叶子结点或单分支结点或单根分支结点
//pa其父节点
BstNode pa = p.parent;
BstNode child = p.leftChild != null ? p.leftChild : p.rightChild;
if (pa == null) {
root = child;
} else {
if (pa.leftChild == p) {
pa.leftChild = child;
} else {
pa.rightChild = child;
}
}
return true;
}
完整代码
BstNode
public class BstNode {
public int data;
public BstNode parent;
public BstNode leftChild;
public BstNode rightChild;
public BstNode(int date) {
this.data = date;
}
}
BinarySortTree
public class BinarySortTree {
private BstNode root;
/**
* 非递归查询
*/
private BstNode findVal(BstNode ptr, int val) {
while (ptr != null && ptr.data != val) {
if (ptr.data > val) {
ptr = ptr.rightChild;
} else {
ptr = ptr.leftChild;
}
}
return ptr;
}
public BstNode findVal(int val) {
return findVal(root, val);
}
/**
* 二叉排序树的递归查询
*/
private BstNode findValue(BstNode ptr, int val) {
if (ptr == null || ptr.data == val) {
return ptr;
} else if (ptr.data > val) {
return findValue(ptr.leftChild, val);
} else {
return findValue(ptr.rightChild, val);
}
}
public BstNode findValue(int val) {
return findValue(root, val);
}
/**
* 寻找第一个结点
*/
private BstNode first(BstNode ptr) {
while (ptr != null && ptr.leftChild != null) {
ptr = ptr.leftChild;
}
return ptr;
}
/**
* 寻找最后一个结点
*/
private BstNode last(BstNode ptr) {
while (ptr != null && ptr.rightChild != null) {
ptr = ptr.rightChild;
}
return ptr;
}
/**
* 寻找下一个结点,它的后继
*/
private BstNode next(BstNode ptr) {
if (ptr == null) {
return null;
}
if (ptr.rightChild != null) {
return first(ptr.rightChild);
} else {
BstNode pa = ptr.parent;
while (pa != null && pa.rightChild == ptr) {
ptr = pa;
pa = ptr.parent;
}
return pa;
}
}
/**
* 非递归中序遍历
*/
public void niceInOrder() {
for (BstNode p = first(root); p != null; p = next(p)) {
System.out.print(p.data + " ");
}
}
/**
* 插入结点
*/
public boolean insert(int val) {
//如果根节点为null,直接插入
if (root == null) {
root = new BstNode(val);
return true;
}
BstNode p = root;
BstNode pa = null;
while (p != null && p.data != val) {
pa = p;
p = val > p.data ? p.rightChild : p.leftChild;
}
//如果该树中有相同结点,插入失败
if (p != null && p.data == val) {
return false;
}
//找到位置
p = new BstNode(val);
p.parent = pa;
if (pa.data > val) {
pa.leftChild = p;
} else {
pa.rightChild = p;
}
return true;
}
private BstNode prev(BstNode ptr) {
if (ptr == null) {
return null;
}
if (ptr.leftChild != null) {
return last(ptr.leftChild);
} else {
BstNode pa = ptr.parent;
while (pa != null && pa.leftChild == ptr) {
ptr = pa;
pa = ptr.parent;
}
return pa;
}
}
public void reNiceInOrder() {
for (BstNode p = last(root); p != null; p = prev(p)) {
System.out.print(p.data + " ");
}
System.out.println();
}
//删除结点
public boolean remove(int val) {
if (root == null) return false; //如果是空树,不删
BstNode p = findValue(root, val);
if (p == null) return false;//没有找到,不删
//删除的结点是双分支结点
if (p.leftChild != null && p.rightChild != null) {
BstNode next = next(p);//找到其后继
p.data = next.data;//后继的值赋给他
p = next;//后继赋值给他,即后面把后继删除即可,此时后继必定是一个叶子结点
}
//结点为叶子结点或单分支结点或单根分支结点
//pa其父节点
BstNode pa = p.parent;
BstNode child = p.leftChild != null ? p.leftChild : p.rightChild;
if (pa == null) {
root = child;
} else {
if (pa.leftChild == p) {
pa.leftChild = child;
} else {
pa.rightChild = child;
}
}
return true;
}
}
TestBstTree
public class TestBstTree {
public static void main(String[] args) {
BinarySortTree bst = new BinarySortTree();
//构建二叉排序树
int[] arr = {53, 17, 78, 9, 45, 65, 23, 81, 94, 88, 100, 87};
for (int i = 0; i < arr.length; i++) {
bst.insert(arr[i]);
}
bst.niceInOrder();
//测试删除结点
int val;
Scanner scanner = new Scanner(System.in);
val = scanner.nextInt();
while (val != -1) {
System.out.println(bst.remove(val));
bst.niceInOrder();
val = scanner.nextInt();
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/95477.html