一步一图一代码:排序二叉树

作者:禅楼望月(http://www.cnblogs.com/yaoyinglong/

属性:

①若它的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。

②若它的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。

③它的左、右子树也都是排序二叉树。

添加操作:

当根节点为空时,添加进的节点作为根节点。然后每次添加节点时,都从根节点过滤,以根节点作为当前节点,如果新节点大于当前节点,则走当前节点的右子节点分支,如果新节点小于当前节点,则走当前节点的左子节点分支。然后再用右子节点或左子节点作为当前节点与新加节点比较,如果新加节点大,则当前节点的右子节点,如果新加节点小,则当前节点的左子节点。依次类推直到左子树或右子树为null时,将该新节点添加在这里。

图解:

代码解析:

public void add(T data){
    Node newNode=new Node(data);
    if(root==null){
        root=newNode;
    }else {
        add(newNode, root);
    }
}
public void add(Node addNode,Node parentNode) {
    T data=addNode.data;
    Node currentNode=parentNode;
    Node parent=null;
    int flag=0;
    while(currentNode!=null){
        parent=currentNode;
        flag=data.compareTo(currentNode.data);
        if(flag>0){
            currentNode=currentNode.rightChild;
        }else {
            currentNode=currentNode.leftChild;
        }
    }
    if(flag>0){
        parent.rightChild=addNode;
    }else {
        parent.leftChild=addNode;
    }
    addNode.parent=parent;
}

添加操作相对简单,麻烦的是删除操作。

删除操作

该操作有很多的情况:

第一:待删除的节点是叶子节点

第二:待删除的节点有左子树没有右子树

第三:待删除的节点没有左子树有右子树

第四:待删除的节点既没有左子树也没有右子树。

在第一大类中我们还可分:

1、该叶子节点就是根节点,即树中只有一个根节点。

这是最简单的情况,直接树的根root指向null即可。

2、否则便要判断该叶子节点是其父节点的左子节点呢还是其父节点的右子节点

下图为叶子节点是其父节点的左子节点,右子节点一样。

在第二大类中我们还可以细分:

1、待删除的节点就是根节点。

只需将根节点的左子节点(因为这是根节点只有左子节点)指向父节点的指针打断,并将该左子节点设为根节点即可。

2、如果待删除的节点不是根节点,那就必须讨论待删除的节点是其父节点的左子节点还是右子节点。

以是其父节点的左子节点为例(右子节点的情况一样),我们需要让待删除节点的父节点的指向其左孩子的指针重新指向待删除节点的左孩子,让待删除节点的左孩子的指向父节点的指针重新指向待删除节点的父节点。

这期间,我们并没有打断图中节点5指向其左孩子节点3的指针,也没有打断节点5指向其父节点6的指针。这是因为,没这个必要。我们不打断这些节点5永远也不会被访问到,而且会被垃圾回收。

第三大类和第二大类一样。

第四大类

总的思想就是:找到待删除节点左子树中的最大节点(leftMaxNode),然后用leftMaxNode替代待删除的节点即可。这句话看似简单,但是实现起来可不是那么的容易!!!

我们也可以细分:

1、待删除节点就是根节点

1.1 leftMaxNode就是待删除节点的左孩子。即待删除节点左子树中没有右节点。

1.2 如果待删除的节点不是根节点

对于这种情况,我们需要显示显示打断的“指针”只有两个:如下图中,节点3指向其右子节点4(leftMaxNode)的指针,即节点4(leftMaxNode)指向其父节点3的指针。然后用节点4(leftMaxNode)替换节点5(根节点)即可。

2、待删除的节点不是根节点

2.1 被删除节点的左子树中最大节点不是其左子节点

如下图所示:

在这种情况下使用节点节点4替换节点5的时候,对于节点4和节点8、节点9的之间的“指针”创建比较容易,但是节点3与节点4之间的“指针”却不好创建,因为节点4的左子树可能很复杂,我们不知道将节点3插入哪里合适,所以,解决办法为,将节点3利用排序二叉树添加新节点的功能以新节点的形式加入节点4 中。

2.2 被删除节点的左子树中最大节点就是是其左子节点

即,如下情况,

这种情况很简单了,就不详细说明了。

完整代码如下:

package com.yyl.tree;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;

public class SortedBinaryTree<T extends Comparable<T>> {
    public class Node{
        T data=null;
        Node parent=null;
        public Node getParent() {
            return parent;
        }
        public void setParent(Node parent) {
            this.parent = parent;
        }

        Node leftChild=null;
        Node rightChild=null;
        //region【访问器】
        public T getData() {
            return data;
        }
        public void setData(T data) {
            this.data = data;
        }
        public Node getLeftChild() {
            return leftChild;
        }
        public void setLeftChild(Node leftChild) {
            this.leftChild = leftChild;
        }
        public Node getRightChild() {
            return rightChild;
        }
        public void setRightChild(Node rightChild) {
            this.rightChild = rightChild;
        }
        //endregion

        // region 【构造器】
        public Node(){}
        public Node(T data){
            this.data=data;
        }
        public Node(T data, Node leftChild, Node rightChild){
            this.data=data;
            this.leftChild=leftChild;
            this.rightChild=rightChild;
        }
        //endregion
        @Override
        public boolean equals(Object obj) {
            if(this.data==obj){
                return true;
            }else if(this.getClass()==obj.getClass()){
                Node nodeObj=(Node)obj;
                 return this.data.compareTo(nodeObj.data) ==0 && this.leftChild.equals(nodeObj.leftChild)
                        && this.rightChild.equals(nodeObj);
            }
            return false;
        }

        public String toString(){
            return "[data:"+this.data+"]";
        }
    }

    Node root=null;
    public SortedBinaryTree() {}
    public SortedBinaryTree(Node root){
        this.root=root;
    }

    //添加节点
    public void add(T data){
        Node newNode=new Node(data);
        if(root==null){
            root=newNode;
        }else {
            add(newNode, root);
        }
    }
    public void add(Node addNode,Node parentNode) {
        T data=addNode.data;
        Node currentNode=parentNode;
        Node parent=null;
        int flag=0;
        while(currentNode!=null){
            parent=currentNode;
            flag=data.compareTo(currentNode.data);
            if(flag>0){
                currentNode=currentNode.rightChild;
            }else {
                currentNode=currentNode.leftChild;
            }
        }
        if(flag>0){
            parent.rightChild=addNode;
        }else {
            parent.leftChild=addNode;
        }
        addNode.parent=parent;
    }
    // 删除节点
    public void remove(T data){
        if(root==null){
            return;
        }else {
            Node removeNode=null;//待删除的节点
            removeNode = getNode(data);
            //endregion

            //如果没有找见抛出异常
            if(removeNode==null){
                throw new RuntimeException("未找见要删除的节点");
            }
            System.out.println("待删除的节点为:"+removeNode.toString());
            //如果待删除的节点既没有左孩子也没有右孩子
            if(removeNode.leftChild==null && removeNode.rightChild==null){
                if(removeNode==root){//如果待删除的节点就是根节点,即树中只有一个根节点
                    root=null;
                }else {//如果待删除的节点不是根节点,这就要考虑这个节点是其父节点的左孩子还是右孩子
                    if(isLeftNodeOfParent(removeNode)){//如果待删除是其父节点的左孩子,则断开其父节点指向左孩子的“指针”
                        removeNode.parent.leftChild=null;
                    }else {//如果待删除是其父节点的有孩子,则断开其父节点指向右孩子的“指针”
                        removeNode.parent.rightChild=null;
                    }
                    //【注】断开他的指向父节点的“指针”
                    //removeNode.parent=null;
                }
            }else if (removeNode.leftChild!=null && removeNode.rightChild==null) {//如果待删除的节点有左孩子但是没有右孩子
                if(removeNode==root){//如果待删除的节点就是根节点

                    //这条语句并不是必须的,因为对于数而言,不管怎么都是以root为起点开始所有操作,所以遍历等操作不会涉及
                    //到root.leftChild.parent.不将它设为null,同样不影响树的正常运行。但是root.leftChild.parent所引
                    //用的对象并不会被当成垃圾而被回收,因为从root.leftChild.parent依然可以访问到该Java对象
                    root.leftChild.parent=null;

                    root=root.leftChild;
                }else {//如果待删除的节点不是根节点,那就必须讨论待删除的节点是其父节点的左子节点还是右子节点
                    if(isLeftNodeOfParent(removeNode)){
                        removeNode.parent.leftChild=removeNode.leftChild;
                    }else {
                        removeNode.parent.rightChild=removeNode.leftChild;
                    }
                    //这条语句不要更精炼,加上会让程序更清晰
                    //removeNode.parent=null;        

                    removeNode.leftChild.parent=removeNode.parent;
                }
                //这条语句不要更精炼,加上会让程序更清晰
                //removeNode.leftChild=null;
            }
            else if (removeNode.leftChild==null && removeNode.rightChild!=null) {//如果待删除的节点没有左孩子但是有右孩子
                if(root==removeNode){//如果待删除的节点就是根节点
                    root=root.rightChild;
                    //释放内存,让垃圾回收removeNode
                    removeNode.rightChild.parent=null;
                }else {//如果待删除的节点不是根节点
                    if(isLeftNodeOfParent(removeNode)){
                        removeNode.parent.leftChild=removeNode.rightChild;
                    }else {
                        removeNode.parent.rightChild=removeNode.rightChild;
                    }
                    removeNode.rightChild.parent=removeNode.parent;

                    //这条语句不要更精炼,加上会让程序更清晰
                    //removeNode.parent=null;
                }
                //这条语句不要更精炼,加上会让程序更清晰
                //removeNode.rightChild=null;
            }else {//如果待删除的节点既有左孩子也有右孩子
                Node leftMaxNode=null;
                //region【寻找被删除节点左子树中最大节点】
                Node tmp=removeNode.leftChild;
                while(tmp.rightChild!=null){
                    tmp=tmp.rightChild;
                }
                leftMaxNode=tmp;
                //endregion

                System.out.println("leftmaxnode:"+leftMaxNode.toString());
                if(removeNode==root){//如果待删除的节点就是根节点
                    if(leftMaxNode==removeNode.leftChild){//如果被删除节点的左子树中最大节点就是其左子节点
                        leftMaxNode.rightChild=root.rightChild;
                        root.rightChild.parent=leftMaxNode;
                        root=leftMaxNode;
                        leftMaxNode.parent=null;

                        //这条语句不要更精炼,加上会让程序更清晰
                        //root.leftChild=null;
                    }else {
                        leftMaxNode.rightChild=root.rightChild;
                        leftMaxNode.leftChild=root.leftChild;

                        root.rightChild.parent=leftMaxNode;
                        root.leftChild.parent=leftMaxNode;

                        leftMaxNode.parent.rightChild=null;
                        leftMaxNode.parent=null;
                        root=leftMaxNode;
                    }
                }else {//如果待删除的节点不是根节点
                    if(leftMaxNode!=removeNode.leftChild){//如果被删除节点的左子树中最大节点不是其左子节点
                        Node addNode=removeNode.leftChild;
                        removeNode.leftChild=null;
                        addNode.parent=null;
                        add(addNode,leftMaxNode);
                        leftMaxNode.parent.rightChild=null;
                    }
                    leftMaxNode.rightChild=removeNode.rightChild;
                    removeNode.rightChild.parent=leftMaxNode;    

                    leftMaxNode.parent=removeNode.parent;    

                    if(isLeftNodeOfParent(removeNode)){
                        removeNode.parent.leftChild=leftMaxNode;
                    }else {
                        removeNode.parent.rightChild=leftMaxNode;
                    }
                }
            }
        }
    }

    public Node getNode(T data) {
        Node removeNode=null;
        Node current=root;//临时变量,用于辅助查找待删除的节点
        //region 【寻找要待删除的节点】
        while(current!=null){
            if(data.compareTo(current.data)>0){
                current=current.rightChild;
            }else if (data.compareTo(current.data)<0) {
                current=current.leftChild;
            }else {
                removeNode=current;
                break;
            }
        }
        return removeNode;
    }

    private boolean isLeftNodeOfParent(Node node) {
        return node.data.compareTo(node.parent.data)<=0;
    }
    //中序遍历
    public List<Node> midIterator(){
        return midIterator(root);
    }
    private List<Node> midIterator(Node node) {
        List<Node> list=new ArrayList<Node>();
        if(node==null){
            return null;
        }
        if(node.leftChild!=null){
            list.addAll(midIterator(node.leftChild));
        }
        list.add(node);
        if(node.rightChild!=null){
            list.addAll(midIterator(node.rightChild));
        }
        return list;
    }

    //先序遍历
    public List<Node> preIterator(){
        return preIterator(root);
    }
    private List<Node> preIterator(Node root) {
        List<Node> list=new ArrayList<Node>();
        if(root==null)
            return null;
        list.add(root);
        if(root.leftChild!=null){
            list.addAll(preIterator(root.leftChild));
        }
        if(root.rightChild!=null){
            list.addAll(preIterator(root.rightChild));
        }
        return list;
    }

    //广度优先遍历
    public List<Node> breadthFirst(){
        return breadthFirst(root);
    }
    private List<Node> breadthFirst(Node root) {
        Queue<Node> queue=new ArrayDeque<Node>();
        List<Node> list=new ArrayList<Node>();
        if(root==null)
            return null;
        queue.offer(root);
        while(!queue.isEmpty()){
            Node node=queue.poll();
            list.add(node);
            if(node.leftChild!=null){
                queue.offer(node.leftChild);
            }
            if(node.rightChild!=null){
                queue.offer(node.rightChild);
            }
        }
        return list;
    }
}

可能有些地方理解的还是不到位,并且代码的性能不是很高,请大家批评指正。

时间: 2024-10-18 12:28:29

一步一图一代码:排序二叉树的相关文章

一步一步写算法(之排序二叉树)

原文:一步一步写算法(之排序二叉树) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前面我们讲过双向链表的数据结构.每一个循环节点有两个指针,一个指向前面一个节点,一个指向后继节点,这样所有的节点像一颗颗珍珠一样被一根线穿在了一起.然而今天我们讨论的数据结构却有一点不同,它有三个节点.它是这样定义的: typedef struct _TREE_NODE { int data; struct _TREE_NODE* parent; str

一步一步写算法(之排序二叉树线索化)

原文:一步一步写算法(之排序二叉树线索化) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前面我们谈到了排序二叉树,还没有熟悉的同学可以看一下这个,二叉树基本操作.二叉树插入.二叉树删除1.删除2.删除3.但是排序二叉树也不是没有缺点,比如说,如果我们想在排序二叉树中删除一段数据的节点怎么办呢?按照现在的结构,我们只能一个一个数据查找验证,首先看看在不在排序二叉树中,如果在那么删除:如果没有这个数据,那么继续查找.那么有没有方法,可以保

一步一步写算法(之排序二叉树删除-2)

原文:一步一步写算法(之排序二叉树删除-2) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 2.4 删除节点的左右子树都存在,此时又会分成两种情形 1)左节点是当前左子树的最大节点,此时只需要用左节点代替根节点即可 /* * * 10 ======> 6 * / \ / * 6 15 5 15 * / * 5 */ 代码该怎么编写呢? STATUS delete_node_from_tree(TREE_NODE** ppTreeNode

一步一步写算法(之排序二叉树删除-3)

原文:一步一步写算法(之排序二叉树删除-3) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 3 普通节点的删除 3.1 删除的节点没有左子树,也没有右子树 测试用例1: 删除节点6 /* * * 10 ======> 10 * / \ * 6 15 15 * */ static void test8() { TREE_NODE* pTreeNode = NULL; assert(TRUE == insert_node_into_tree

一步一步写算法(之排序二叉树插入)

原文:一步一步写算法(之排序二叉树插入) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 二叉树的节点插入比较简单.一般来说,二叉树的插入主要分为以下两个步骤: 1) 对当前的参数进行判断,因为需要考虑到头结点,所以我们使用了指针的指针作为函数的输入参数 2) 分情况讨论: 如果原来二叉树连根节点都没有,那么这个新插入的数据就是根节点: 如果原来的二叉树有根节点,那我们判断这个数据是否存在过,如果存在,那么返回:如果不存在,那么继续插入数

一步一步写算法(之排序二叉树删除-1)

原文:一步一步写算法(之排序二叉树删除-1) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 相比较节点的添加,平衡二叉树的删除要复杂一些.因为在删除的过程中,你要考虑到不同的情况,针对每一种不同的情况,你要有针对性的反应和调整.所以在代码编写的过程中,我们可以一边写代码,一边写测试用例.编写测试用例不光可以验证我们编写的代码是否正确,还能不断提高我们开发代码的自信心.这样,即使我们在开发过程对代码进行修改或者优化也不会担心害怕.然而看起

一步一步写算法(之排序二叉树的保存和加载)

原文:一步一步写算法(之排序二叉树的保存和加载) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 排序二叉树是我们开发中经常使用到的一种数据结构,它具有较好的插入.删除.查找特性.但是由于二叉树的指针较多,所以相比较其他的数据结构而言,二叉树来得比较麻烦些.但是也不是没有办法,下面介绍一下我个人常用的方法. 我们知道,如果一个二叉树是一个满树的话,那么二叉树的节点应该是按照1.2.3.4依次排开的.但是现实情况是这样的,由于排序二叉树自身

一步一图一代码,一定要让你真正彻底明白红黑树【转】

转自:http://blog.csdn.net/chenhuajie123/article/details/11951777 一步一图一代码,一定要让你真正彻底明白红黑树 作者:July   二零一一年一月九日 ----------------------------- 本文参考:I.  The Art of Computer Programming Volume III. Introduction to Algorithms, Second EditionIII.The Annotated S

一步一图一代码之排序二叉树

作者:禅楼望月(http://www.cnblogs.com/yaoyinglong/) 属性: ①若它的左子树不为空,则左子树上所有节点的值均小于它的根节点的值. ②若它的右子树不为空,则右子树上所有节点的值均大于它的根节点的值. ③它的左.右子树也都是排序二叉树. 添加操作: 当根节点为空时,添加进的节点作为根节点.然后每次添加节点时,都从根节点过滤,以根节点作为当前节点,如果新节点大于当前节点,则走当前节点的右子节点分支,如果新节点小于当前节点,则走当前节点的左子节点分支.然后再用右子节点