二叉樹學習(上)


一:樹

  我們思維中的”樹“就是一種枝繁葉茂的形象,那么數據結構中的”樹“該是怎么樣呢?對的,他是一種現實中倒立的樹。

 

1:術語

     其實樹中有很多術語的,這個是我們學習樹形結構必須掌握的。

     <1>  父節點,子節點,兄弟節點

                  這個就比較簡單了,B和C的父節點就是A,反過來說就是B和C是A的子節點。B和C就是兄弟節點。

     <2>  結點的度

                 其實”度“就是”分支數“,比如A的分支數有兩個“B和C",那么A的度為2。

     <3> 樹的度

                看似比較莫名其妙吧,他和”結點的度“的區別就是,樹的度講究大局觀,乃樹中最大的結點度,其實也就是2。

     <4> 葉結點,分支結點

                葉結點就是既沒有左孩子也沒有右孩子結點,也就是結點度為0。分支節點也就是if的else的條件咯。

    <5> 結點的層數

               這個很簡單,也就是樹有幾層。

   <6> 有序樹,無序樹

               有序樹我們先前也用過,比如“堆”和“二叉排序樹”,說明這種樹是按照一定的規則進行排序的,else條件就是無序樹。

   <7>  森林

               現實中,很多的樹形成了森林,那在數據結構中,我們把上圖的“A”節點砍掉,那么B,C子樹合一起就是森林咯。

2: 樹的表示

     樹這個結構的表示其實有很多種,常用的也就是“括號”表示法。

     比如上面的樹就可以表示為:(A(B(D),(E)),(C(F),(G)))

二: 二叉樹

         在我們項目開發中,很多地方都會用到樹,但是多叉樹的處理還是比較糾結的,所以俺們本着“大事化小,小事化了“的原則

      把”多叉樹“轉化為”二叉樹“,那么問題就簡化了很多。

 

1: ”二叉樹“和”樹“有什么差異呢?

         第一點:  樹的度沒有限制,而“二叉樹”最多只能有兩個,不然也就不叫二叉樹了,哈哈。

         第二點:樹中的子樹沒有左右划分,很簡單啊,找不到參照點,二叉樹就有參照物咯。

 

2: 二叉樹的類型

       二叉樹中有兩種比較完美的類型,“完全二叉樹”和“滿二叉樹”。

          <1>  滿二叉樹    

                       除葉子節點外,所有節點的度都為2,文章開頭處的樹就是這里的“滿二叉樹”。

          <2>  完全二叉樹

                      必須要滿足兩個條件就即可:  干掉最后一層,二叉樹變為“滿二叉樹”。

                                                              最后一層的葉節點必須是“從左到右”依次排開。

                     我們干掉文章開頭處的節點“F和”G",此時還是“完全二叉樹”,但已經不是“滿二叉樹”了,你懂的。

3: 二叉樹的性質

         二叉樹中有5點性質非常重要,也是俺們必須要記住的。

     <1>  二叉樹中,第i層的節點最多有2(i-1)個。

     <2>  深度為k的二叉樹最多有2k-1個節點。

     <3>  二叉樹中,葉子節點樹為N1個,度為2的節點有N2個,那么N1=N2+1。

     <4>  具有N個結點的二叉樹深度為(Log2 N)+1層。

     <5>  N個結點的完全二叉樹如何用順序存儲,對於其中的一個結點i,存在以下關系,

              2*i是結點i的父結點。

              i/2是結點i的左孩子。

              (i/2)+1是結點i的右孩子。

4: 二叉樹的順序存儲

      同樣的存儲方式也有兩種,“順序存儲”和“鏈式存儲”。

       <1> 順序存儲

                 說實話,樹的存儲用順序結構比較少,因為從性質定理中我們都可以看出只限定為“完全二叉樹”,那么如果二叉樹不是

              “完全二叉樹”,那我們就麻煩了,必須將其轉化為“完全二叉樹”,將空的節點可以用“#”代替,圖中也可看出,為了維護

              性質定理5的要求,我們犧牲了兩個”資源“的空間。

 

 <2> 鏈式存儲

               上面也說了,順序存儲會造成資源的浪費,所以嘛,我們開發中用的比較多的還是“鏈式存儲”,同樣“鏈式存儲”

            也非常的形象,非常的合理。

               一個結點存放着一個“左指針”和一個“右指針”,這就是二叉鏈表。

               如何方便的查找到該結點的父結點,可以采用三叉鏈表。

5: 常用操作

      一般也就是“添加結點“,“查找節點”,“計算深度”,“遍歷結點”,“清空結點

 

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

namespace BinaryTree
{

    ///<summary>
    ///二叉鏈表存儲結構
    ///</summry>
    ///<typeparam name="T"></typeparam>

    public class ChinaTree<T>
    {
        //字段
        public T data;
        public ChinaTree<T> left;
        public ChinaTree<T> right;
        public int Length { get; set; }
        //構造函數
        public ChinaTree(T nodevalue)
        {
        this.data = nodevalue;
        this.left = null;
        this.right = null;
        }

     /// <summary> 
     /// 將指定節點插入到二叉樹中 
     /// </summary> 
     /// <typeparam name="T"></typeparam> 
     /// <param name="tree"></param> 
     /// <param name="node"></param> 
     /// <param name="direction">插入做左是右</param> 
     /// <returns></returns>
        public ChinaTree<T> BinTreeAddNode<T>(ChinaTree<T> tree, ChinaTree<T> node, T data, Direction direction)
        {
            //樹為空
            if (tree == null)
            {
                return null;
            }

            if(tree.data.Equals(data))
            {
               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;
               }
            }
            BinTreeAddNode(tree.left, node, data, direction);
            BinTreeAddNode(tree.right, node, data, direction);
            return tree;
        }

        //查找節點 在二叉樹中查找指定的key
        public ChinaTree<T> BinTreeFind<T>(ChinaTree<T> tree, T data)
        {
            if (tree == null)
                return null;
            if (tree.data.Equals(data))
                return tree;
            return BinTreeFind(tree, data);
        }

        //計算深度 獲取二叉樹的深度
        public int BinTreeLen<T>(ChinaTree<T> tree)
        {
            int leftLength;
            int rightLength;

            if (tree == null)
                return 0;
            //遞歸左子樹的深度
            leftLength = BinTreeLen(tree.left);
            //遞歸右子樹的深度
            rightLength = BinTreeLen(tree.right);
            if (leftLength > rightLength)
                return leftLength + 1;
            else
                return rightLength + 1;
        }
      //遍歷節點分為: 
            //先序:先訪問根,然后遞歸訪問左子樹,最后遞歸右子樹。(DLR模式)

            //中序:先遞歸訪問左子樹,在訪問根,最后遞歸右子樹。(LDR模式)

            //后序:先遞歸訪問左子樹,然后遞歸訪問右子樹,最后訪問根。(LRD模式)

            //按層:這個比較簡單,從上到下,從左到右的遍歷節點。

        //先序遍歷
        public void BinTree_DLR(ChinaTree<T> tree)
        { 
             if(tree==null)
            {
               return;
            }
            //先輸出根元素
            Console.WriteLine(tree.data+"\t");

            //然后遍歷左子樹
            BinTree_DLR(tree.left);
            //然后遍歷右子樹
            BinTree_DLR(tree.right);
        }

        //中序遍歷
        public void BinTree_LDR(ChinaTree<T> tree)
        {
            if (tree == null)
                return;
            //先遍歷左子樹
            BinTree_LDR(tree.left);
            //輸出節點值
            Console.WriteLine(tree.data + "\t");
            //遍歷右子樹
            BinTree_LDR(tree.right);
        }

        //后序遍歷
        public void BinTree_LRD<T>(ChinaTree<T> tree)
        {
            if (tree == null)
                return;
            //先遍歷左子樹
            BinTree_LRD(tree.left);
            //再遍歷右子樹
            BinTree_LRD(tree.right);
            //輸出節點值
            Console.WriteLine(tree.data + "\t");
        }

        //按層遍歷
        public void BinTree_Level<T>(ChinaTree<T> tree)
        {
            if (tree == null)
                return;
            //申請保存空間
            ChinaTree<T>[] treelist = new ChinaTree<T>[Length];

            int head = 0;
            int tail = 0;
            //存放數組
            treelist[tail] = tree;

            //循環鏈中計算tail的位置
            tail = (tail + 1) % Length;
            while (head != tail)
            {
                var tempNode = treelist[head];
                head = (head + 1) % Length;
                //輸出節點
                Console.WriteLine(tempNode.data + "\t");

                //如果左子樹不為空 則將左子樹存於數組的tail位置
                if (tempNode.left != null)
                {
                    treelist[tail] = tempNode.left;
                    tail = (tail + 1) % Length;
                }
                //如果右子樹不為空,則將右子樹存於數組的tail位置
                if (tempNode.right != null)
                {
                    treelist[tail] = tempNode.right;
                    tail = (tail + 1) % Length;
                }
            }

        }

        //清空二叉樹
        public void BinTreeClear<T>(ChinaTree<T> tree)
        {
            //遞的結束點,歸的起始點
            if (tree == null)
                return;
            BinTreeClear(tree.left);
            BinTreeClear(tree.right);
            //在歸的過程中,釋放當前節點的數據空間
            tree = null;
        
        }


    }

    //枚舉左節點和右節點
    public enum Direction {Left=1,Right=2}

}

 匯總代碼:

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

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            ChainTreeManager manager = new ChainTreeManager();
            //插入節點操作 
            ChinaTree<string> tree = CreateRoot();
            //插入節點數據 
            AddNode(tree);
            //先序遍歷 
            Console.WriteLine("\n先序結果為: \n");
            manager.BinTree_DLR(tree);
            //中序遍歷
            Console.WriteLine("\n中序結果為: \n");
            manager.BinTree_LDR(tree);
            //后序遍歷 
            Console.WriteLine("\n后序結果為: \n");
            manager.BinTree_LRD(tree);
            //層次遍歷 
            Console.WriteLine("\n層次結果為: \n");
            manager.Length = 100;
            manager.BinTree_Level(tree);
            Console.WriteLine("\n樹的深度為:" + manager.BinTreeLen(tree) + "\n");
            Console.ReadLine();
        }
        //生成根節點

        static ChinaTree<string> CreateRoot()
        {
            ChinaTree<string> tree = new ChinaTree<string>();
            Console.WriteLine("請輸入根節點,方便我們生成樹\n");
            tree.data = Console.ReadLine();
            Console.WriteLine("根節點已經生成\n");
            return tree;
        }

        //插入節點操作

        static ChinaTree<string> AddNode(ChinaTree<string> tree)
        {
            ChainTreeManager mananger = new ChainTreeManager();
            while (true)
            {
                ChinaTree<string> node = new ChinaTree<string>();
                Console.WriteLine("請輸入要插入節點的數據\n");
                node.data = Console.ReadLine();
                Console.WriteLine("請輸入要查找的父節點的數據\n");
                var parentData = Console.ReadLine();
                bool flag = mananger.BinTreeFind(tree, parentData);
                if (!flag)
                {
                    Console.WriteLine("未找到你輸入的父節點,請重新輸入");
                    continue;
                }
                Console.WriteLine("你確定要插到父節點的:1 左側,2右側");
                Direction direction = (Direction)Enum.Parse(typeof(Direction), Console.ReadLine());
                tree = mananger.BinTreeAddNode(tree, node, parentData, direction);
                Console.WriteLine("是否繼續?  1 繼續, 2 退出");
                if (int.Parse(Console.ReadLine()) == 1)
                    continue;
                else
                    break;
            }
            return tree;

        }

    }
    public enum Direction { Left = 1, Right = 2 }
    public class ChinaTree<T>
    {
        //字段
        public T data;
        public ChinaTree<T> left;
        public ChinaTree<T> right;
    }

    public class ChainTreeManager
    {
        //按層遍歷的Length的空間存儲
        public int Length { get; set; }

        /// <summary> 
        /// 將指定節點插入到二叉樹中 
        /// </summary> 
        /// <typeparam name="T"></typeparam> 
        /// <param name="tree"></param> 
        /// <param name="node"></param> 
        /// <param name="direction">插入做左是右</param> 
        /// <returns></returns>
        public ChinaTree<T> BinTreeAddNode<T>(ChinaTree<T> tree, ChinaTree<T> node, T data, Direction direction)
        {
            //樹為空
            if (tree == null)
            {
                return null;
            }
            try
            {
                if (tree.data.Equals(data))
                {
                    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;
                    }
                }
                BinTreeAddNode(tree.left, node, data, direction);
                BinTreeAddNode(tree.right, node, data, direction);
               
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return tree;
        }
        public ChinaTree<T> BinTreeChild<T>(ChinaTree<T> tree, Direction direction)
        {
            ChinaTree<T> childNode = null;
            if (tree == null)
                throw new Exception("二叉樹為空");
            switch (direction)
            {
                case Direction.Left:
                    childNode = tree.left;
                    break;
                case Direction.Right:
                    childNode = tree.right;
                    break;
            }
            return childNode;
        }


        //查找節點 在二叉樹中查找指定的key
        public bool BinTreeFind<T>(ChinaTree<T> tree, T data)
        {
            bool flag = false;
            if (tree == null)
                return false;
            if (tree.data.Equals(data))
                return true;
            if (tree.left != null)
            {
                return BinTreeFind(tree.left, data);
            }
            if (tree.right != null)
            {
                return BinTreeFind(tree.right, data);
            }
           
            return flag;


        }

        //計算深度 獲取二叉樹的深度
        public int BinTreeLen<T>(ChinaTree<T> tree)
        {
            int leftLength;
            int rightLength;

            if (tree == null)
                return 0;
            //遞歸左子樹的深度
            leftLength = BinTreeLen(tree.left);
            //遞歸右子樹的深度
            rightLength = BinTreeLen(tree.right);
            if (leftLength > rightLength)
                return leftLength + 1;
            else
                return rightLength + 1;
        }

        //判斷二叉樹是否為空
        public bool BinTreeisEmpty<T>(ChinaTree<T> tree)
        {
            return tree == null ? true : false;
        }
        //遍歷節點分為: 
        //先序:先訪問根,然后遞歸訪問左子樹,最后遞歸右子樹。(DLR模式)

        //中序:先遞歸訪問左子樹,在訪問根,最后遞歸右子樹。(LDR模式)

        //后序:先遞歸訪問左子樹,然后遞歸訪問右子樹,最后訪問根。(LRD模式)

        //按層:這個比較簡單,從上到下,從左到右的遍歷節點。

        //先序遍歷
        public void BinTree_DLR<T>(ChinaTree<T> tree)
        {
            if (tree == null)
            {
                return;
            }
            //先輸出根元素
            Console.Write(tree.data + "\t");

            //然后遍歷左子樹
            BinTree_DLR(tree.left);
            //然后遍歷右子樹
            BinTree_DLR(tree.right);
        }

        //中序遍歷
        public void BinTree_LDR<T>(ChinaTree<T> tree)
        {
            if (tree == null)
                return;
            //先遍歷左子樹
            BinTree_LDR(tree.left);
            //輸出節點值
            Console.Write(tree.data + "\t");
            //遍歷右子樹
            BinTree_LDR(tree.right);
        }

        //后序遍歷
        public void BinTree_LRD<T>(ChinaTree<T> tree)
        {
            if (tree == null)
                return;
            //先遍歷左子樹
            BinTree_LRD(tree.left);
            //再遍歷右子樹
            BinTree_LRD(tree.right);
            //輸出節點值
            Console.Write(tree.data + "\t");
        }

        //按層遍歷
        public void BinTree_Level<T>(ChinaTree<T> tree)
        {
            if (tree == null)
                return;
            //申請保存空間
            ChinaTree<T>[] treelist = new ChinaTree<T>[100];

            int head = 0;
            int tail = 0;
            //存放數組
            treelist[tail] = tree;

            //循環鏈中計算tail的位置
            tail = (tail + 1) % 100;
            while (head != tail)
            {
                var tempNode = treelist[head];
                head = (head + 1) % 100;
                //輸出節點
                Console.Write(tempNode.data + "\t");

                //如果左子樹不為空 則將左子樹存於數組的tail位置
                if (tempNode.left != null)
                {
                    treelist[tail] = tempNode.left;
                    tail = (tail + 1) % 100;
                }
                //如果右子樹不為空,則將右子樹存於數組的tail位置
                if (tempNode.right != null)
                {
                    treelist[tail] = tempNode.right;
                    tail = (tail + 1) % 100;
                }
            }

        }

        //清空二叉樹
        public void BinTreeClear<T>(ChinaTree<T> tree)
        {
            //遞的結束點,歸的起始點
            if (tree == null)
                return;
            BinTreeClear(tree.left);
            BinTreeClear(tree.right);
            //在歸的過程中,釋放當前節點的數據空間
            tree = null;

        }



    }
}

運行圖:

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

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



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