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

扯一扯

mark

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

人爲何難作選擇?數據結構

選擇意味着放棄學習

你選擇一方,也就意味着放棄了另外一方。擺在你面前的選擇項越接近,你的選擇就會越困難,由於放棄其中任何一個選擇項都不容易。若是擺在你面前的選擇項對比明顯,那麼選擇起來就會輕鬆許多,你們幾乎都會堅決果斷的選擇「好」的選擇項,放棄掉「差」的選擇項。spa

選擇永遠都不是完美的3d

選擇永遠都不可能十全十美,只可能知足儘可能多的側重點。選擇的時候想知足越多的側重點,可能就會越難作出選擇。因此在選擇上不要過於追求完美。指針

警戒逃避性選擇——不知道本身要去哪兒,還要選擇離開。code

有一種選擇是對現狀不滿,想逃離這種現狀,可是殊不知道去哪裏。舉個例子,可能目前的公司有各類問題,好比開發流程不規範等,若是由於這些問題離開,可能就會從一個坑跳到另一個更大的坑。當決定離開的時候,必定是本身有明確的目標,很清楚本身想要什麼。blog

二叉樹遍歷原理

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

爲何研究二叉樹的遍歷?

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

二叉樹的建立

遍歷二叉樹以前,首先咱們要有一個二叉樹。要建立一個以下圖的二叉樹,就要先進行二叉樹的擴展,也就是將二叉樹每一個結點的空指針引出一個虛結點,其值爲一個特定值,好比'#'。處理後的二叉樹稱爲原二叉樹的擴展二叉樹。擴展二叉樹的每一個遍歷序列能夠肯定一個一顆二叉樹,咱們採用前序遍歷建立二叉樹。前序遍歷序列: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

參考:《大話數據結構》





聲明:本文爲博主學習感悟總結,水平有限,若是不當,歡迎指正。若是您認爲還不錯,不妨點擊一下下方的 推薦按鈕,謝謝支持。轉載與引用請註明出處。
相關文章
相關標籤/搜索