1. 程式人生 > >二叉樹算法小結

二叉樹算法小結

ret 先序遍歷 ref south key 中序 eas 工具 deque

導航
  • 頂部
  • 概述
  • 準備工作
  • 先序遍歷法
  • 中序遍歷法
  • 後序遍歷法
  • 層次遍歷法
  • 測試
  • 總結
  • 頂部
  • 概述
  • 準備工作
  • 先序遍歷法
  • 中序遍歷法
  • 後序遍歷法
  • 層次遍歷法
  • 測試
  • 總結

概述

遍歷二叉樹有前序,中序,後序三大方法算。對於二叉樹的三種不同的遍歷方式,用遞歸的方法很容易寫出實現代碼,對於非遞歸的遍歷算法使用數據結構堆棧作為工具代替遞歸以節省時空開銷,在經過節點是先將其壓入棧,要到他第幾次從棧中彈出是才訪問它,對於前序來說,是一次,對於中序來說是兩次,對於後序則是三次。下面介紹下二叉樹的幾種遍歷算法,包括遞歸和非遞歸的實現。

關於二叉樹遍歷方式的規則,可前往這裏,本篇主要是對算法的陳列。

準備工作

首先,我們先建個二叉樹結構

我們建立如下圖所示的二叉樹

技術分享

然後,我們創建樹,和遍歷的結構,采用策略模式,讓客戶端調用


    //創建數的結構
    public class Tree
    {
        public string Data { get; set; }

        public Tree L_Tree { get; set; }

        public Tree R_Tree { get; set; }

        public Tree(string data)
        {
            this.Data = data;
            this.L_Tree = null;
            this.R_Tree = null;
        }
        public Tree(string data,Tree l,Tree r)
        {
            this.Data = data;
            this.L_Tree = l;
            this.R_Tree = r;
        }
    }
             

      //定義一個管理樹的類,
       public class TreeManager
       {
           public TreeManager()
           {
               this.BuildTree();
           }
            //樹的根節點
           private Tree root_Tree;
          
           private void BuildTree()
           {
               Tree g = new Tree("G");
               Tree d = new Tree("D", g, null);
               Tree h = new Tree("H");
               Tree e = new Tree("E", null, h);
               Tree b = new Tree("B", d, e);
               Tree f = new Tree("F");
               Tree c = new Tree("C", null, f);
               Tree a = new Tree("A", b, c);
               root_Tree = a;
           }

            //通過遞歸遍歷
           public void outputTree_Recursion(ITreeShow fs)
           {
               fs.show_Tree_Recursion(this.root_Tree);
           }

            //通過遞歸遍歷
           public void show_Tree_NonRecursion(ITreeShow fs)
           {
               fs.show_Tree_NonRecursion(this.root_Tree);
           }
       }
             

            //定義了一個接口類 (作為先序,中序,後序等方式遍歷的抽象)
     public interface ITreeShow
    {
        void show_Tree_NonRecursion(Tree t);

        void show_Tree_Recursion(Tree t);
        
    }
             

準備工作做好以後,現在開始各種遍歷算法的實現

先序遍歷法

先序是根節點,左子樹,右子數


     //先序遍歷
    public class First_Read_Show : ITreeShow
    {
        //非遞歸-先序
        public void show_Tree_NonRecursion(Tree t)
        {           
            Stack<Tree> tree_stack = new Stack<Tree>();
            while (t != null || tree_stack.Count > 0)
            {
                if (t != null)
                {
                    Console.Write(t.Data+",");
                    tree_stack.Push(t);
                    t = t.L_Tree;
                }
                else
                {
                    var item = tree_stack.Pop();
                    t = item.R_Tree;
                }
            }

        }

        //遞歸-先序
        public void show_Tree_Recursion(Tree t)
        {
            if (t == null)
            {
                return;
            }
            Console.Write(t.Data + ",");
            show_Tree_Recursion(t.L_Tree);
            show_Tree_Recursion(t.R_Tree);
        }
    }
             

中序遍歷法

中序是先左子樹,再根節點,再右子樹


     public class Middle_Read_Show : ITreeShow
    {
        public void show_Tree_NonRecursion(Tree t)
        {
            Stack<Tree> stack_tree = new Stack<Tree>();
            while (t != null || stack_tree.Count > 0)
            {
                if (t != null)
                {
                    stack_tree.Push(t);
                    t = t.L_Tree;
                }
                else
                {
                    var item = stack_tree.Pop();
                    Console.Write(item.Data+",");
                    t = item.R_Tree;
                }
            }
        }

        //遞歸-後序
        public void show_Tree_Recursion(Tree t)
        {
            if (t == null)
            {
                return;
            }
            show_Tree_Recursion(t.L_Tree);
            Console.Write(t.Data + ",");
            show_Tree_Recursion(t.R_Tree);
        }
    }
             

後序遍歷法

後序是先左子樹,再右子樹,再根節點


    //後序遍歷
    public class Last_Read_Show : ITreeShow
    {
       //非遞歸-後序
        public void show_Tree_NonRecursion(Tree t)
        {
            Stack<Tree> stack_tree = new Stack<Tree>();
            HashSet<Tree> visited = new HashSet<Tree>();
            while (t != null || stack_tree.Count > 0)
            {
                if (t != null)
                {
                    stack_tree.Push(t);
                    t = t.L_Tree;
                }
                else
                {
                    var item=stack_tree.Peek();
                    if (item.R_Tree != null && !visited.Contains(item.R_Tree))
                    {
                        t = item.R_Tree;
                    }
                    else {
                        Console.Write(item.Data+",");
                        visited.Add(item);
                        stack_tree.Pop();
                    }
                }
            }
        }

        //遞歸-後序
        public void show_Tree_Recursion(Tree t)
        {
            if (t == null)
            {
                return;
            }
            show_Tree_Recursion(t.L_Tree);
            show_Tree_Recursion(t.R_Tree);
            Console.Write(t.Data + ",");
        }    
             

層次排序法

層序遍歷就是按照層次由左向右輸出


        public class Layer_Read_Show : ITreeShow
    {
        public void show_Tree_NonRecursion(Tree t)
        {
            Queue<Tree> queue_tree = new Queue<Tree>();
            if (t != null)
            {
                queue_tree.Enqueue(t);
                while (queue_tree.Count > 0)
                {
                    var item = queue_tree.Dequeue();
                    Console.Write(item.Data + ",");
                    if (item.L_Tree != null)
                    {
                        queue_tree.Enqueue(item.L_Tree);
                    }
                    if (item.R_Tree != null)
                    {
                        queue_tree.Enqueue(item.R_Tree);
                    }
                }
            }
        }

        public void show_Tree_Recursion(Tree t)
        {           
        }
    }
             

測試


             static void Main(string[] args)
        {
            TreeManager tree = new TreeManager();
            //先序-遞歸            
            Console.WriteLine("先序-遞歸:");
            tree.outputTree_Recursion(new First_Read_Show());
            //先序-非遞歸
            Console.WriteLine();
            Console.WriteLine("先序-非遞歸:");
            tree.show_Tree_NonRecursion(new First_Read_Show());
            //中序-遞歸
            Console.WriteLine();
            Console.WriteLine("中序-遞歸:");
            tree.outputTree_Recursion(new Middle_Read_Show());
            //中序-非遞歸
            Console.WriteLine();
            Console.WriteLine("中序-非遞歸:");
            tree.show_Tree_NonRecursion(new Middle_Read_Show());
            //後序-遞歸
            Console.WriteLine();
            Console.WriteLine("後序-遞歸:");
            tree.outputTree_Recursion(new Last_Read_Show());
            //後序-非遞歸
            Console.WriteLine();
            Console.WriteLine("後序-非遞歸:");
            tree.show_Tree_NonRecursion(new Last_Read_Show());
            //層次遍歷
            Console.WriteLine();
            Console.WriteLine("層次遍歷:");
            tree.show_Tree_NonRecursion(new Layer_Read_Show());           

            Console.ReadKey();
        }
             
技術分享

總結

算法文字描述

二叉樹的遍歷算法包括遞歸和非遞歸兩種,遞歸比較簡單,先敘述下非遞歸算法的實現

為了便於理解,這裏以下圖的二叉樹為例,分析二叉樹的三種遍歷方式的實現過程。

技術分享
  • 先序遍歷的非遞歸實現

    根據先序遍歷的順序,先訪問根節點,再訪問左子樹,後訪問右子樹,而對於每個子樹來說,又按照同樣的訪問順序進行遍歷,上圖的先序遍歷順序為:ABDECF。非遞歸的實現思路如下:

    對於任一節點P,
    • 輸出節點P,然後將其入棧,再看P的左孩子是否為空;
    • 若P的左孩子不為空,則置P的左孩子為當前節點,重復1)的操作;
    • 若P的左孩子為空,則將棧頂節點出棧,但不輸出,並將出棧節點的右孩子置為當前節點,看其是否為空;
    • 若不為空,則循環至1)操作;
    • 如果為空,則繼續出棧,但不輸出,同時將出棧節點的右孩子置為當前節點,看其是否為空,重復4)和5)操作;
    • 直到當前節點P為NULL並且棧空,遍歷結束。
  • 中序遍歷的非遞歸實現

    根據中序遍歷的順序,先訪問左子樹,再訪問根節點,後訪問右子樹,而對於每個子樹來說,又按照同樣的訪問順序進行遍歷,上圖的中序遍歷順序為:DBEAFC。非遞歸的實現思路如下:

    對於任一節點P,
    • 若P的左孩子不為空,則將P入棧並將P的左孩子置為當前節點,然後再對當前節點進行相同的處理;
    • 若P的左孩子為空,則輸出P節點,而後將P的右孩子置為當前節點,看其是否為空;
    • 若不為空,則重復1)和2)的操作;
    • 若為空,則執行出棧操作,輸出棧頂節點,並將出棧的節點的右孩子置為當前節點,看起是否為空,重復3)和4)的操作;
    • 直到當前節點P為NULL並且棧為空,則遍歷結束。
  • 後序遍歷的非遞歸實現

    根據後序遍歷的順序,先訪問左子樹,再訪問右子樹,後訪問根節點,而對於每個子樹來說,又按照同樣的訪問順序進行遍歷,上圖的後序遍歷順序為:DEBFCA。後序遍歷的非遞歸的實現相對來說要難一些,要保證根節點在左子樹和右子樹被訪問後才能訪問,思路如下:

    對於任一節點P,
    • 先將節點P入棧;
    • 若P不存在左孩子和右孩子,或者P存在左孩子或右孩子,但左右孩子已經被輸出,則可以直接輸出節點P,並將其出棧,將出棧節點P標記為上一個輸出的節點,再將此時的棧頂結點設為當前節點;
    • 若不滿足2)中的條件,則將P的右孩子和左孩子依次入棧,當前節點重新置為棧頂結點,之後重復操作2);
    • 直到棧空,遍歷結束。

完整代碼下載地址:點這裏

二叉樹算法小結