C#算法系列(3)——二叉排序树

一、什么是二叉排序树

       二叉排序树,又称为二叉查找树。它或者是一棵空树,或者具有下列性质的二叉树:
       (1)若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
       (2)若它的右子树不空,则右子树上所有结点的值均大于它的根节点的值;
       (3)它的左右子树分别为二叉排序树。
       构造一棵二叉排序树的目的,其实并不是为了排序,而是为了提高查找,和插入删除关键字的速度。二叉排序树这种非线性结构也有利于插入和删除的实现。

二、二叉排序树一些常用操作

       在这里,为了方便起见,默认输入不相等的int类型的实验数据。
       (1)二叉排序树结点定义。
       定义类如下:

 class BSNode
    {
        public BSNode LeftChild { get; set; }
        public BSNode RightChild { get; set; }
        public BSNode Parent { get; set; }
        public int Data { get; set; }

        public BSNode()
        {

        }

        public BSNode(int item)
        {
            this.Data = item;
        }
    }

       (2)给二叉排序树添加实验数据。过程如下:首先判断,树的根节点是否为空;若为空,直接给树添加新的结点;若不为空,则循环遍历待插入的结点与当前访问的结点进行比较。如果待插入结点比当前结点要小且当前结点左子树为空,则进行父子相认操作;如果左子树不为空,则直接进入下一次循环。同理,比当前访问结点的要大且右子树为空,则进行父子相认操作;如果右子树不为空,则直接进入下一次循环。具体实现代码可以参考最后的实现类BSTree.cs中的AddItem(int item)方法。
       (3)查找是否存在指定元素。有两种查找方式:递归方式查找和循环方式查找。不论哪种查找方式,最好依照二叉排序树的创建方式来进行查找。即判断待查元素与当前访问的结点元素的大小关系,如果二者相等,则相等直接返回该节点;如果比当前访问的结点元素小,则进行左子树查找操作;如过比当前访问的结点元素大,则进行右子树查找操作。具体实现代码可以参考最后的实现类BSTree.cs中的FindItem(int item,BSNode node)方法。
       (4)删除指定元素。删除操作,要结合查找思想来实现的。要删除二叉排序树上指定元素,首先需要确定待删除元素的位置,然后再考虑待删除位置的左右子树情形,这时有三种情形,具体如下:

  1. 叶子结点:如果为根结点,则直接设置为null即可。如果为某个父节点的左孩子或右孩子,则断开父子关系即可。
  2. 仅有左/右子树的结点:以左子树为例,直接将左子树的结点数据赋给删除的结点,然后再断开与左子树的关系。同理,右子树也是如此。
  3. 左右子树都有结点:思想为找到要删除的结点的直接后继(或直接前驱)。通过循环遍历,找到待删除结点的右子树最左边的结点,即右子树的最小值。

       具体实现代码可以参考最后的实现类BSTree.cs中的DeleteBSTree(int item,BSNode node)方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 二叉排序树_链式存储
{
    class BSTree
    {
         private BSNode root = null; //创建二叉排序树的根节点

        //添加数据
        public void AddItem(int item)
        {
            BSNode newNode = new BSNode(item);
            if (root == null)
            {
                root = newNode;
            }
            else
            {
                BSNode temp = root;
                while (true)
                {
                    //放在temp的左边
                    if (item <= temp.Data)
                    {
                        if (temp.LeftChild == null)
                        {
                            //父子相认
                            temp.LeftChild = newNode;
                            newNode.Parent = temp;
                            break;
                        }
                        else
                        {
                            temp = temp.LeftChild;
                        }
                    }
                    //放在temp的右边
                    else
                    {
                        if (temp.RightChild == null)
                        {
                            //父子相认
                            temp.RightChild = newNode;
                            newNode.Parent = temp;
                            break;
                        }
                        else
                        {
                            temp = temp.RightChild;
                        }
                    }
                }
            }
        }

        //中序遍历,使得二叉排序树,从小到大输出。
        public void MiddleTraversal()
        {
            MiddleTraversal(root);
        }
        private void MiddleTraversal(BSNode node)
        {
            if (node == null)
            {
                return;
            }
            MiddleTraversal(node.LeftChild);
            Console.Write(node.Data + " ");
            MiddleTraversal(node.RightChild);

        }

        //查找是否存在指定元素
        public bool FindItem(int item)
        {
            //递归方式查找
            //return FindItem(item,root);
            //循环方式查找
            return FindByWhile(item,root);
        }

        //递归方式查找
        private bool FindItem(int item, BSNode node)
        {
            if (node == null)
                return false;
            if (node.Data == item)
            {
                return true;
            }
            else if (item < node.Data)
            {
                return FindItem(item, node.LeftChild);
            }
            else
            {
                return FindItem(item, node.RightChild);
            }
        }
        //循环方式进行查找
        private bool FindByWhile(int item, BSNode node)
        {
            BSNode temp = root;
            //下列代码逻辑上有返回值
            while(true)
            {
                if (temp == null) return false;
                else if (temp.Data == item) return true;
                else if (item > temp.Data)
                    temp = temp.RightChild;
                else
                    temp = temp.LeftChild;
            }
        }

        public bool DeleteBSTree(int item)
        {
            BSNode temp = root;
            //下列代码逻辑上有返回值
            while (true)
            {
                if (temp == null) return false;
                else if (temp.Data == item) Delete(temp);
                else if (item > temp.Data)
                    temp = temp.RightChild;
                else
                    temp = temp.LeftChild;
            }
        }

        private void Delete(BSNode node)
        {
            //1.叶子结点删除情况
            if (node.LeftChild == null && node.RightChild == null)
            {
                //删除为根节点
                if (node.Parent == null)
                {
                    root = null;
                }
                else if (node.Parent.LeftChild == node)
                {
                    //断开父子关系
                    node.Parent.LeftChild = null;
                }
                else if (node.Parent.RightChild == node)
                {
                    //断开父子关系
                    node.Parent.RightChild = null;
                }
                return;
            }
            //2.仅有右子树的结点
            else if (node.LeftChild == null && node.RightChild != null)
            {
                node.Data = node.RightChild.Data;
                node.RightChild = null;
                return;
            }
            //3.仅有左子树的结点
            else if (node.RightChild == null && node.LeftChild != null)
            {
                node.Data = node.LeftChild.Data;
                node.LeftChild = null;
                return;
            }
            //左右子树都有的结点
            else
            {
                BSNode temp = node.RightChild;
                //找删除结点的右子树的最左边结点,即右子树的最小值
                while (true)
                {
                    if (temp.LeftChild != null)
                    {
                        temp = temp.LeftChild;
                    }
                    else
                    {
                        break;
                    }
                }
                //直接覆盖数据
                node.Data = temp.Data;
                //递归删除
                Delete(temp);
            }
        }
    }
}

       最后,主程序测试类Program.cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 二叉排序树_链式存储
{
    class Program
    {
        static void Main(string[] args)
        {
            BSTree tree = new BSTree();
            int[] data = { 62,58,88,47,73,99,35,51,93,37};
            //创建二叉排序树
            foreach (int item in data)
            {
                tree.AddItem(item);
            }
            //二叉树排序中序遍历
            tree.MiddleTraversal();
            Console.WriteLine();
            //查找操作
            Console.WriteLine(tree.FindItem(58));
            Console.WriteLine(tree.FindItem(100));
            //删除操作
            tree.DeleteBSTree(35);
            tree.MiddleTraversal();
            Console.WriteLine();
            Console.ReadKey();
        }
    }
}

       实验测试结果截图:


这里写图片描述

三、二叉排序树的总结

       优点:二叉排序树是以链接的方式存储,保持了链接存储结构在执行,插入或删除操作时不用移动元素的优点,只要找到合适的插入和删除位置后,仅需修改链接指针即可,插入删除的时间性能比较好。而对于二叉排序树的查找,走的就是从根节点到要查找的结点路径,其比较次数等于给定值的结点在二叉排序树的层数。极端情况,最小为1次,即根节点就是要找的结点,最多也不会超过树的深度。也就是说,二叉排序树的查找性能取决于二叉排序树的形状。可问题就在于,二叉排序树的形状不确定的。如果在构建二叉树一开始的序列是有序的,比如从小到大有序的序列就会形成了极端的右斜树。这时候进行查找的时间复杂度为O(n),这就等同于顺序查找。为了针对这一问题优化,后面便引入了平衡二叉树来优化二叉排序树的深度

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值