您的位置:首页 > 其它

从零开始学算法---二叉查找树

2021-10-10 20:47 155 查看 https://www.cnblogs.com/haigs/

二叉查找树也叫二叉排序树,二叉搜索树, 它具备以下特性:

1)可以是一颗空树

2)如果不是空树,那么每个节点左子树的值都比该节点小;右子树的值都比该节点大

3)左右子树都为二叉树

4)原则上没有重复值(实际应用中如需要有重复值可忽略)

接下来我们来试着定义一棵二叉查找树

首先定义节点内部类:

/**
* 定义节点内部类
*/
static class TreeNode{
//数据域, 这里的数据域可以是实现了equals,compareTo的任意类型
//这里为了方便,定义为Int
private int data;

//左孩子
private TreeNode leftChild;

//右孩子
private TreeNode rightChild;

//为了插入、删除方便, 我们采用孩子双亲表示法来描述节点
private TreeNode parent;

public TreeNode(int data){
this.data = data;
this.leftChild = null;
this.rightChild = null;
this.parent = null;
}
}

增加节点:

//根节点
private TreeNode root;

/**
* 增加节点
*/
public void put(int data){
TreeNode newNode = new TreeNode(data);
//如果当前是一颗空树,那么新加的节点就是根节点
if (root == null){
root = newNode;
}else {
//否则就从根节点开始遍历
TreeNode node = root;
//跟踪记录parent
TreeNode parent = node.parent;
//结合下面代码, node根据data的大小向左或向右移动
//直到找到一个空位置
while(node!= null){
parent = node;
//比当前节点小往左走
if (data < node.data){
node = node.leftChild;

}else if (data > node.data) {
//比当前节点大往右走
node = node.rightChild;
}else {
//如果相等,说明有重复值
return;
}
}
//while循环结束, node定位在一个空位置
//我们把新节点放在这里, 根据parent来引用他
newNode.parent = parent;
//通过比较确定新节点是作为parent的leftchild还是rightchild
if(data < parent.data){
parent.leftChild = newNode;
}else {
parent.rightChild = newNode;
}
}
}

查询节点

/**
* 查找某个Int类型的data是否存在
* 存在则返回该数字
* 不存在返回-1
*/
public TreeNode search(int data){
//从根节点开始
TreeNode node = root;
while(node!=null){
if(data == node.data){
return node;
}else if(data < node.data){
//往左边走
node = node.leftChild;
}else {
node = node.rightChild;
}
}
return null;
}

删除节点

第一步,判断节点是否存在

/**
* 删除节点
*/
public void delete(int data){
//先找到节点
TreeNode targetNode = search(data);
if(targetNode == null){
return;
}
deleteNode(targetNode);
}

第二步,执行删除。这里比较复杂

需要分4种情况来处理

1,要删除的节点是叶子,没有左儿子或右儿子, 那么可以直接删除

/**
* 删除节点分为4种情况
* @param node
* @return
*/
private void deleteNode(TreeNode node){
//找到它的父节点,左儿子, 右儿子
TreeNode parent  = node.parent;
TreeNode nodeLeftChild = node.leftChild;
TreeNode nodeRightChild = node.rightChild;
int data = node.data;
//case1, 如果要删除的节点没有孩子,可以直接删除
if (nodeLeftChild == null && nodeRightChild == null){
if(parent == null){
//说明要删除的是根节点,整个树上只有这一个节点
root = null;
return;
}
//如果它是左孩子,就将父节点的左孩子置空
if(data < parent.data){
parent.leftChild = null;
}else{
//反之亦然
parent.rightChild = null;
}
}
}

2, 如果要删除的节点只有一个左儿子,则它被删除后,左儿子顶上去

else if (nodeLeftChild!= null && nodeRightChild==null){
//case2, 要删除的节点只有一个左孩子
//如果是根节点
if (parent == null){
root = nodeLeftChild;
root.parent = null;
return;
}
//如果它是左孩子,就将它的leftChild赋值给父节点的左孩子
if(data < parent.data){
parent.leftChild = nodeLeftChild;
nodeLeftChild.parent = parent;
}else{
//反之亦然
parent.rightChild = nodeLeftChild;
nodeLeftChild.parent = parent;
}
}

3, 如果要删除的节点只有一个右儿子,则它被删除后,右儿子顶上去

else if (nodeLeftChild == null && nodeRightChild!=null){
//case3, 如果要删除的节点只有一个右孩子,那么让右孩子顶上去
//如果是根节点
if (parent == null){
root = nodeRightChild;
root.parent = null;
return;
}
//如果它是左孩子,就将它的rightChild赋值给父节点的左孩子
if(data < parent.data){
parent.leftChild = nodeRightChild;
nodeRightChild.parent = parent;
}else{
//反之亦然
parent.rightChild = nodeRightChild;
nodeRightChild.parent = parent;
}

}

4, 如果要删除的节点既有左孩子也有右孩子

这就是最复杂的情况了, 比如我们要删除x节点, 那么首先要在x的右子树上找到一个最小值节点替换它

鉴于二叉排序树的特性, 我们知道这个节点就是x的右子树的左子树一直往左遍历,最后一个节点,我们把它标记为leftNode

接下来, 1)让原本x的左子树成为leftNode 的左子树

2) leftNode如果有右子树,让它成为leftNode的父节点的左子树

3)让原本x的右子树成为leftNode的右子树

4)用leftNode替换x(即leftNode.parent = x.parent)

 

 

 结合上图,看下代码实现

先从简单的入手,处理根节点的情况

if (parent == null){
//先处理要删除的是根节点的情况
//把minLeftNode这个节点独立出来
TreeNode targetLeftNodeParent = targetNode.parent;
targetLeftNodeParent.leftChild = null;
targetNode.parent = null;
//它的右儿子(如果有)就成为它的父节点的左儿子
if (minLeftNode.rightChild != null){
targetLeftNodeParent.leftChild = targetNode.rightChild;
}
//让这个节点成为根节点
root = targetNode;
//重新构建它和原node的子节点的关系
root.rightChild = nodeRightChild;
nodeRightChild.parent = root;
root.leftChild = nodeLeftChild;
nodeLeftChild.parent = root;
}

非根节点的情况

else {
//step 1
targetNode.leftChild = nodeLeftChild;
nodeLeftChild.parent = targetNode;
//step2
TreeNode targetLeftNodeParent = targetNode.parent;
//它的右儿子(如果有)就成为它的父节点的左儿子
if (minLeftNode.rightChild != null && minLeftNode.data < targetLeftNodeParent.data){
targetLeftNodeParent.leftChild = targetNode.rightChild;
}
//step 3
if (minLeftNode.rightChild != null && minLeftNode.data < targetLeftNodeParent.data){
targetNode.rightChild = nodeRightChild;
}
//step 4
if(targetNode.data < parent.data){
//那么它就是左儿子
parent.leftChild = targetNode;
}else {
parent.rightChild = targetNode;
}
targetNode.parent = parent;
}
/**
* 找到root节点左子树中的最小值
* @param root
* @return
*/
private TreeNode findMinLeftNode(TreeNode root){
TreeNode node = root;
while (node.leftChild != null){
node = node.leftChild;
}
return node;
}

二叉排序树的遍历

/**
* 测试需要, 定义一个方法来打印所有的节点
* 传入根节点
*/
public void testPrint(){
printAllNodes(root);
//        printAllNotesByPost(root);
}

private void printAllNodes(TreeNode node){
//这里用二叉树的中序遍历, 可以打印出有序的数组
//当然也可以用前序或者后序
if(node == null){
return;
}
printAllNodes(node.leftChild);
System.out.print(node.data+" ");
printAllNodes(node.rightChild);
}

/**
* 测试后序遍历
*/
private void printAllNotesByPost(TreeNode node){
if(node == null){
return;
}
printAllNotesByPost(node.leftChild);
printAllNotesByPost(node.rightChild);
System.out.print(node.data+" ");
}

测试方法: 

@Test
fun testTree(){
//        val array = intArrayOf(8,7,10,5,3,9,13,2,4,6,11,12)
val array = intArrayOf(5,2,7,3,4,1,6)
val binarySearchTree = BinarySearchTree()
for (i in array) {
binarySearchTree.put(i)
}
binarySearchTree.testPrint();
//测试查找
//        for (i in array) {
//            System.out.println(binarySearchTree.search(i))
//        }
//循环删除
for (i in array) {
System.out.println("------------------"+i)
binarySearchTree.delete(i)
binarySearchTree.testPrint()
}
}

测试结果:

 

 

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: