數據結構和算法系列15 線索二叉樹


上一篇總結了二叉樹,這一篇要總結的是線索二叉樹,我想從以下幾個方面進行總結。

1,什么是線索二叉樹?

2,為什么要建立線索二叉樹?

3,如何將二叉樹線索化?

4,線索二叉樹的常見操作及實現思路?

5,算法實現代碼?

一,什么是線索二叉樹

在有n個結點的二叉鏈表中必定存在n+1個空指針域,因此可以利用這些空指針域存放指向結點的某種遍歷次序下的前趨和后繼結點的指針,這種指向前趨和后繼結點的指針稱為“線索”,加上線索的二叉鏈表稱為線索鏈表,相應的二叉樹被稱為線索二叉樹。

二,為什么要建立線索二叉樹

有了二叉樹不就足夠了嗎?那為什么還要弄個線索二叉樹出來呢?

在原來的二叉鏈表中,查找結點的左,右孩子可以直接實現,可是如果要找該結點的前趨和后繼結點呢?這就變得非常困難,所以為了實現這個常見的需求,我們要在每個結點中增加兩個指針域來存放遍歷時得到的前趨和后繼結點,這樣就可以通過該指針直接或間接訪問其前趨和后繼結點。

三,如何將二叉樹線索化

按某種次序遍歷二叉樹,在遍歷過程中用線索取代空指針即可。

下面是線索二叉樹和線索二叉鏈表示意圖,它可以幫助我們更好地理解線索二叉樹。

ds47

四,線索二叉樹的常見操作及實現思路

1,二叉樹線索化

實現思路:按某種次序遍歷二叉樹,在遍歷過程中用線索取代空指針即可。

2,查找后繼節點

實現思路:分兩種情況,一,沒有右孩子,直接獲取。二,有右孩子,中序遍歷查找右子樹中序遍歷的第一個節點即為當前節點的后繼節點。

3,查找前驅節點

實現思路:同樣也分兩種情況,一,沒有左孩子,依據線索直接獲取。二,有左孩子,中序遍歷左子樹中往右鏈中第一個沒有右孩子的節點即為前驅節點

4,遍歷

實現思路:以中序遍歷為例,首先找到中序遍歷的開始節點,然后利用線索依次查找后繼節點即可。

五,算法實現代碼

C#版本:

namespace DS.BLL
{
    /// <summary>
    /// Description:線索二叉樹業務邏輯類
    /// Author:McgradyLu
    /// Time:9/10/2013 10:27:38 PM
    /// </summary>
    public class BinTreeThreadBLL
    {
        /// <summary>
        /// 創建線索二叉樹(利用中序)
        /// 思路:利用中序遍歷,用線索取代空指針
        /// </summary>
        /// <typeparam name="T">數據類型</typeparam>
        /// <param name="tree">待操作線索二叉樹</param>
        /// <param name="prevNode">當前節點前驅</param>
        public static void CreateByLDR<T>(ref BinTreeThread<T> tree, ref BinTreeThread<T> prevNode)
        {
            if (tree == null) return;

            //中序遍歷左子樹,找到起始點
            CreateByLDR(ref tree.left,ref prevNode);

            //如果左指針域為空,左標志位放線索
            tree.leftFlag = tree.left == null ? EnumNodeFlag.Thread : EnumNodeFlag.Child; //如果為空放線索,否則放孩子節點
            
            //如果右指針域為空,右標志位放線索
            tree.rightFlag = tree.right == null ? EnumNodeFlag.Thread : EnumNodeFlag.Child;

            if (prevNode != null)
            {
                //如果當前節點的左標志位為線索,則將前驅節點保存到當前節點的左指針域中
                if (tree.leftFlag == EnumNodeFlag.Thread) tree.left = prevNode;

                //如果前驅節點的右標志位為線索,則將當前節點保存到前驅節點的右指針域中
                if (prevNode.rightFlag == EnumNodeFlag.Thread) prevNode.right = tree;
            }

            //保存前驅節點
            prevNode = tree;

            //遍歷右子樹進行同樣的操作
            CreateByLDR(ref tree.right,ref prevNode);
        }

        /// <summary>
        /// 查找后繼節點
        /// </summary>
        /// <typeparam name="T">二叉線索樹類型</typeparam>
        /// <param name="tree">當前節點</param>
        /// <returns>后繼節點</returns>
        public static BinTreeThread<T> SearchNextNode<T>(BinTreeThread<T> tree)
        {
            if (tree == null) return null;

            //沒有右孩子,直接獲取
            if (tree.rightFlag == EnumNodeFlag.Thread) return tree.right;

            //有右孩子,中序遍歷查找右子樹中序遍歷的第一個節點(即為后繼節點)
            var rightNode = tree.right;
            while (rightNode.leftFlag == EnumNodeFlag.Child)
            {
                rightNode = rightNode.left;
            }
            return rightNode;
        }

        /// <summary>
        /// 查找前驅節點
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tree">當前節點</param>
        /// <returns>前驅節點</returns>
        public static BinTreeThread<T> SearchPreNode<T>(BinTreeThread<T> tree)
        {
            if (tree == null) return null;

            //沒有左孩子,直接獲取
            if (tree.leftFlag == EnumNodeFlag.Thread) return tree.left;

            //有左孩子,中序遍歷左子樹中往右鏈中第一個沒有右孩子的節點即為前驅節點
            var leftNode = tree.left;
            while (leftNode.rightFlag == EnumNodeFlag.Child)
            {
                leftNode = leftNode.right;
            }
            return leftNode;
        }

        /// <summary>
        /// 遍歷線索二叉樹(中序)
        /// </summary>
        /// <typeparam name="T">線索二叉樹數據類型</typeparam>
        /// <param name="tree">待操作線索二叉樹</param>
        public static void TraversalByLDR<T>(BinTreeThread<T> tree)
        {
            if (tree == null) return;

            //查找起始結點(中序遍歷的開始節點)
            while (tree.leftFlag == EnumNodeFlag.Child)
            {
                tree = tree.left;
            }

            do
            {
                Console.Write(tree.data+"\t");
                tree = SearchNextNode(tree); //利用線索獲取后繼節點
            } while (tree != null);
        }
    }

    /// <summary>
    /// 線索二叉樹存儲結構
    /// </summary>
    /// <typeparam name="T">數據類型</typeparam>
    public class BinTreeThread<T>
    {
        public T data; //數據

        public BinTreeThread<T> left; //左指針域

        public BinTreeThread<T> right; //右指針域

        public EnumNodeFlag leftFlag; //左線索標志位

        public EnumNodeFlag rightFlag; //右線索標志位
    }

    /// <summary>
    /// 結點指針域指向的是孩子的指針還是線索的枚舉
    /// </summary>
    public enum EnumNodeFlag
    {
        Child = 0, //指向孩子
        Thread = 1 //指向前趨或后繼的線索
    }
}

namespace BinTreeThread.CSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            //創建二叉樹
            BinTreeThread<string> tree = CreateRoot();
            Insert(tree);

            //創建線索二叉樹
            BinTreeThread<string> prevNode = null;
            BinTreeThreadBLL.CreateByLDR(ref tree,ref prevNode);

            //遍歷線索二叉樹
            Console.WriteLine("\n線索二叉樹的遍歷結果為:\n");
            BinTreeThreadBLL.TraversalByLDR(tree);

            Console.ReadKey();
        }

        /// <summary>
        /// 生成根節點
        /// </summary>
        /// <returns></returns>
        public static BinTreeThread<string> CreateRoot()
        {
            BinTreeThread<string> root = new BinTreeThread<string>();
            Console.WriteLine("請輸入根節點,以便生成樹");
            root.data = Console.ReadLine();
            Console.WriteLine("根節點生成成功");
            return root;
        }

        /// <summary>
        /// 插入節點
        /// </summary>
        /// <param name="tree">待操作的線索二叉樹</param>
        /// <returns></returns>
        public static BinTreeThread<string> Insert(BinTreeThread<string> tree)
        {
            while (true)
            {
                //創建要插入的節點
                BinTreeThread<string> node = new BinTreeThread<string>();
                Console.WriteLine("請輸入待插入節點的數據");
                node.data = Console.ReadLine();

                //獲取父節點數據
                Console.WriteLine("請輸入待查找的父節點數據");
                var parentNodeData = Console.ReadLine();

                //確定插入方向
                Console.WriteLine("請確定要插入到父節點的:1 左側, 2 右側");
                Direction direction = (Direction)Enum.Parse(typeof(Direction), Console.ReadLine());

                //插入節點
                tree = InsertNode(tree, node, parentNodeData, direction);

                //todo:沒有找到父節點沒有提示???
                if (tree == null)
                {
                    Console.WriteLine("未找到父節點,請重新輸入!");
                    continue;
                }
                Console.WriteLine("插入成功,是否繼續? 1 繼續,2 退出");

                if (int.Parse(Console.ReadLine()) == 1) continue;
                else break; //退出循環
            }
            return tree;
        }

        public static BinTreeThread<T> InsertNode<T>(BinTreeThread<T> tree, BinTreeThread<T> node, T parentNodeData, Direction direction)
        {
            if (tree == null) return null;

            //找到父節點
            if (tree.data.Equals(parentNodeData))
            {
                switch (direction)
                {
                    case Direction.Left:
                        if (tree.left != null) throw new Exception("左節點已存在,不能插入!");
                        else tree.left = node;
                        break;
                    case Direction.Right:
                        if (tree.right != null) throw new Exception("右節點已存在,不能插入!");
                        else tree.right = node;
                        break;
                }
            }

            //向左子樹查找父節點(遞歸)
            InsertNode(tree.left, node, parentNodeData, direction);

            //向右子樹查找父節點(遞歸)
            InsertNode(tree.right, node, parentNodeData, direction);

            return tree;
        }
    }
}

程序輸出結果如圖

ds46


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM