【圖解數據結構】 二叉樹遍歷


扯一扯

mark

昨天在看《極客時間》嚴嘉偉老師的《如何做出好的職業選擇——認識你的職業錨》專題直播時,嚴老師講到了關於選擇的一些問題,我認為其中的一些點講的非常好,總結一下分享給大家。

人為什么難做選擇?

選擇意味着放棄

你選擇一方,也就意味着放棄了另一方。擺在你面前的選擇項越接近,你的選擇就會越困難,因為放棄其中任何一個選擇項都不容易。如果擺在你面前的選擇項對比明顯,那么選擇起來就會輕松許多,大家幾乎都會毫不猶豫的選擇“好”的選擇項,放棄掉“差”的選擇項。

選擇永遠都不是完美的

選擇永遠都不可能十全十美,只可能滿足盡量多的側重點。選擇的時候想滿足越多的側重點,可能就會越難做出選擇。所以在選擇上不要過於追求完美。

警惕逃避性選擇——不知道自己要去哪兒,還要選擇離開。

有一種選擇是對現狀不滿,想逃離這種現狀,但是卻不知道去哪里。舉個例子,可能目前的公司有各種問題,比如開發流程不規范等,如果因為這些問題離開,可能就會從一個坑跳到另外一個更大的坑。當決定離開的時候,一定是自己有明確的目標,很清楚自己想要什么。

二叉樹遍歷原理

二叉樹的遍歷是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次。

為什么研究二叉樹的遍歷?

因為計算機只會處理線性序列,而我們研究遍歷,就是把樹中的結點變成某種意義的線性序列,這給程序的實現帶來了好處。

二叉樹的創建

遍歷二叉樹之前,首先我們要有一個二叉樹。要創建一個如下圖的二叉樹,就要先進行二叉樹的擴展,也就是將二叉樹每個結點的空指針引出一個虛結點,其值為一個特定值,比如'#'。處理后的二叉樹稱為原二叉樹的擴展二叉樹。擴展二叉樹的每個遍歷序列可以確定一個一顆二叉樹,我們采用前序遍歷創建二叉樹。前序遍歷序列:124##5##36##7##。

mark

mark

定義二叉鏈表結點:

/// <summary>
/// 二叉鏈表結點類
/// </summary>
/// <typeparam name="T"></typeparam>
public class TreeNode<T>
{
    /// <summary>
    /// 數據域
    /// </summary>
    public T Data { get; set; }
    /// <summary>
    /// 左孩子   
    /// </summary>
    public TreeNode<T> LChild { get; set; } 
    /// <summary>
    /// 右孩子
    /// </summary>
    public TreeNode<T> RChild { get; set; } 

    public TreeNode(T val, TreeNode<T> lp, TreeNode<T> rp)
    {
        Data = val;
        LChild = lp;
        RChild = rp;
    }

    public TreeNode(TreeNode<T> lp, TreeNode<T> rp)
    {
        Data = default(T);
        LChild = lp;
        RChild = rp;
    }

    public TreeNode(T val)
    {
        Data = val;
        LChild = null;
        RChild = null;
    }

    public TreeNode()
    {
        Data = default(T);
        LChild = null;
        RChild = null;
    }
}

先序遞歸創建二叉樹:

/// <summary>
/// 先序創建二叉樹
/// </summary>
/// <param name="node"></param>
public static void CreateTree(TreeNode<char> node)
{
    node.Data = Console.ReadKey().KeyChar;

    if (node.Data == '#')
    {
        return;
    }

    node.LChild = new TreeNode<char>();

    CreateTree(node.LChild);

    if (node.LChild.Data == '#')
    {
        node.LChild = null;
    }

    node.RChild = new TreeNode<char>();

    CreateTree(node.RChild);

    if (node.RChild.Data == '#')
    {
        node.RChild = null;
    }
}

二叉樹遍歷方法

mark

mark

前序遍歷

mark

遞歸方式實現前序遍歷

具體過程:

  1. 先訪問根節點
  2. 再序遍歷左子樹
  3. 最后序遍歷右子樹

代碼實現:

public static void PreOrderRecur(TreeNode<char> treeNode)
 {
     if (treeNode == null)
     {
         return;
     }
     Console.Write(treeNode.Data); 
     PreOrderRecur(treeNode.LChild);
     PreOrderRecur(treeNode.RChild);
 }

非遞歸方式實現前序遍歷

具體過程:

  1. 首先申請一個新的棧,記為stack;
  2. 將頭結點head壓入stack中;
  3. 每次從stack中彈出棧頂節點,記為cur,然后打印cur值,如果cur右孩子不為空,則將右孩子壓入棧中;如果cur的左孩子不為空,將其壓入stack中;
  4. 重復步驟3,直到stack為空.

代碼實現:

 public static void PreOrder(TreeNode<char> head)
{
    if (head == null)
    {
        return;
    }
    Stack<TreeNode<char>> stack = new Stack<TreeNode<char>>();
    stack.Push(head);
    while (!(stack.Count == 0))
    {
        TreeNode<char> cur = stack.Pop();
        Console.Write(cur.Data);

        if (cur.RChild != null)
        {
            stack.Push(cur.RChild);
        }
        if (cur.LChild != null)
        {
            stack.Push(cur.LChild);
        }
    }
}

過程模擬:

執行結果:mark

中序遍歷

mark

遞歸方式實現中序遍歷

具體過程:

  1. 先中序遍歷左子樹
  2. 再訪問根節點
  3. 最后中序遍歷右子樹

代碼實現:

public static void InOrderRecur(TreeNode<char> treeNode)
{
    if (treeNode == null)
    {
        return;
    }  
    InOrderRecur(treeNode.LChild);
    Console.Write(treeNode.Data); 
    InOrderRecur(treeNode.RChild);
}

非遞歸方式實現中序遍歷

具體過程:

  1. 申請一個新棧,記為stack,申請一個變量cur,初始時令cur為頭節點;
  2. 先把cur節點壓入棧中,對以cur節點為頭的整棵子樹來說,依次把整棵樹的左子樹壓入棧中,即不斷令cur=cur.left,然后重復步驟2;
  3. 不斷重復步驟2,直到發現cur為空,此時從stack中彈出一個節點記為node,打印node的值,並讓cur = node.right,然后繼續重復步驟2;
  4. 當stack為空並且cur為空時結束。

代碼實現:

public static void InOrder(TreeNode<char> treeNode)
{
    if (treeNode == null)
    {
        return;
    }
    Stack<TreeNode<char>> stack = new Stack<TreeNode<char>>();

    TreeNode<char> cur = treeNode;

    while (!(stack.Count == 0) || cur != null)
    {
        while (cur != null)
        {
            stack.Push(cur);
            cur = cur.LChild;
        }
        TreeNode<char> node = stack.Pop();
        Console.WriteLine(node.Data);
        cur = node.RChild;
    }
}

過程模擬:

執行結果:mark

后序遍歷

mark

遞歸方式實現后序遍歷

  1. 先后序遍歷左子樹
  2. 再后序遍歷右子樹
  3. 最后訪問根節點

代碼實現:

public static void PosOrderRecur(TreeNode<char> treeNode)
{
    if (treeNode == null)
    {
        return;
    }
    PosOrderRecur(treeNode.LChild);
    PosOrderRecur(treeNode.RChild);
    Console.Write(treeNode.Data); 
}

非遞歸方式實現后序遍歷一

具體過程:

使用兩個棧實現

  1. 申請兩個棧stack1,stack2,然后將頭結點壓入stack1中;
  2. 從stack1中彈出的節點記為cur,然后先把cur的左孩子壓入stack1中,再把cur的右孩子壓入stack1中;
  3. 在整個過程中,每一個從stack1中彈出的節點都放在第二個棧stack2中;
  4. 不斷重復步驟2和步驟3,直到stack1為空,過程停止;
  5. 從stack2中依次彈出節點並打印,打印的順序就是后序遍歷的順序;

代碼實現:

public static void PosOrderOne(TreeNode<char> treeNode)
{
    if (treeNode == null)
    {
        return;
    }

    Stack<TreeNode<char>> stack1 = new Stack<TreeNode<char>>();
    Stack<TreeNode<char>> stack2 = new Stack<TreeNode<char>>();

    stack1.Push(treeNode);
    TreeNode<char> cur = treeNode;

    while (!(stack1.Count == 0))
    {
        cur = stack1.Pop();
        if (cur.LChild != null)
        {
            stack1.Push(cur.LChild);
        }
        if (cur.RChild != null)
        {
            stack1.Push(cur.RChild);
        }
        stack2.Push(cur);
    }

    while (!(stack2.Count == 0))
    {
        TreeNode<char> node = stack2.Pop();
        Console.WriteLine(node.Data); ;
    }
}

過程模擬:

執行結果:mark

非遞歸方式實現后序遍歷二

具體過程:

使用一個棧實現

  1. 申請一個棧stack,將頭節點壓入stack,同時設置兩個變量 h 和 c,在整個流程中,h代表最近一次彈出並打印的節點,c代表當前stack的棧頂節點,初始時令h為頭節點,,c為null;

  2. 每次令c等於當前stack的棧頂節點,但是不從stack中彈出節點,此時分一下三種情況:

(1)如果c的左孩子不為空,並且h不等於c的左孩子,也不等於c的右孩子,則吧c的左孩子壓入stack中

(2)如果情況1不成立,並且c的右孩子不為空,並且h不等於c的右孩子,則把c的右孩子壓入stack中;

(3)如果情況1和2不成立,則從stack中彈出c並打印,然后令h等於c;

  1. 一直重復步驟2,直到stack為空.

代碼實現:

public static void PosOrderTwo(TreeNode<char> treeNode)
{
    if (treeNode == null)
    {
        return;
    }

    Stack<TreeNode<char>> stack = new Stack<TreeNode<char>>();
    stack.Push(treeNode);

    TreeNode<char> h = treeNode;
    TreeNode<char> c = null;
    while (!(stack.Count == 0))
    {
        c = stack.Peek();
        //c結點有左孩子 並且 左孩子沒被遍歷(輸出)過 並且 右孩子沒被遍歷過
        if (c.LChild != null && h != c.LChild && h != c.RChild)
            stack.Push(c.LChild);
        //c結點有右孩子 並且 右孩子沒被遍歷(輸出)過
        else if (c.RChild != null && h != c.RChild)
            stack.Push(c.RChild);
        //c結點沒有孩子結點 或者孩子結點已經被遍歷(輸出)過
        else
        {
            TreeNode<char> node = stack.Pop();
            Console.WriteLine(node.Data);
            h = c;
        }
    }
}

過程模擬:

執行結果:mark

層序遍歷

mark

具體過程:

  1. 首先申請一個新的隊列,記為queue;
  2. 將頭結點head壓入queue中;
  3. 每次從queue中出隊,記為node,然后打印node值,如果node左孩子不為空,則將左孩子入隊;如果node的右孩子不為空,則將右孩子入隊;
  4. 重復步驟3,直到queue為空。

代碼實現:

public static void LevelOrder(TreeNode<char> treeNode)
{
    if(treeNode == null)
    {
         return;
    }
    Queue<TreeNode<char>> queue = new Queue<TreeNode<char>>();
    queue.Enqueue(treeNode);

    while (queue.Any())
    {
        TreeNode<char> node = queue.Dequeue();
        Console.Write(node.Data);

        if (node.Left != null)
        {
            queue.Enqueue(node.Left);
        }

        if (node.Right != null)
        {
            queue.Enqueue(node.Right);
        }
    }
}

執行結果:mark

參考:《大話數據結構》

聲明:本文為博主學習感悟總結,水平有限,如果不當,歡迎指正。如果您認為還不錯,不妨點擊一下下方的 推薦按鈕,謝謝支持。轉載與引用請注明出處。


免責聲明!

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



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