扯一扯
昨天在看《極客時間》嚴嘉偉老師的《如何做出好的職業選擇——認識你的職業錨》專題直播時,嚴老師講到了關於選擇的一些問題,我認為其中的一些點講的非常好,總結一下分享給大家。
人為什么難做選擇?
選擇意味着放棄
你選擇一方,也就意味着放棄了另一方。擺在你面前的選擇項越接近,你的選擇就會越困難,因為放棄其中任何一個選擇項都不容易。如果擺在你面前的選擇項對比明顯,那么選擇起來就會輕松許多,大家幾乎都會毫不猶豫的選擇“好”的選擇項,放棄掉“差”的選擇項。
選擇永遠都不是完美的
選擇永遠都不可能十全十美,只可能滿足盡量多的側重點。選擇的時候想滿足越多的側重點,可能就會越難做出選擇。所以在選擇上不要過於追求完美。
警惕逃避性選擇——不知道自己要去哪兒,還要選擇離開。
有一種選擇是對現狀不滿,想逃離這種現狀,但是卻不知道去哪里。舉個例子,可能目前的公司有各種問題,比如開發流程不規范等,如果因為這些問題離開,可能就會從一個坑跳到另外一個更大的坑。當決定離開的時候,一定是自己有明確的目標,很清楚自己想要什么。
二叉樹遍歷原理
二叉樹的遍歷是指從根結點出發,按照某種次序依次訪問二叉樹中所有結點,使得每個結點被訪問一次且僅被訪問一次。
為什么研究二叉樹的遍歷?
因為計算機只會處理線性序列,而我們研究遍歷,就是把樹中的結點變成某種意義的線性序列,這給程序的實現帶來了好處。
二叉樹的創建
遍歷二叉樹之前,首先我們要有一個二叉樹。要創建一個如下圖的二叉樹,就要先進行二叉樹的擴展,也就是將二叉樹每個結點的空指針引出一個虛結點,其值為一個特定值,比如'#'。處理后的二叉樹稱為原二叉樹的擴展二叉樹。擴展二叉樹的每個遍歷序列可以確定一個一顆二叉樹,我們采用前序遍歷創建二叉樹。前序遍歷序列:124##5##36##7##。
定義二叉鏈表結點:
/// <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;
}
}
二叉樹遍歷方法
前序遍歷
遞歸方式實現前序遍歷
具體過程:
- 先訪問根節點
- 再序遍歷左子樹
- 最后序遍歷右子樹
代碼實現:
public static void PreOrderRecur(TreeNode<char> treeNode)
{
if (treeNode == null)
{
return;
}
Console.Write(treeNode.Data);
PreOrderRecur(treeNode.LChild);
PreOrderRecur(treeNode.RChild);
}
非遞歸方式實現前序遍歷
具體過程:
- 首先申請一個新的棧,記為stack;
- 將頭結點head壓入stack中;
- 每次從stack中彈出棧頂節點,記為cur,然后打印cur值,如果cur右孩子不為空,則將右孩子壓入棧中;如果cur的左孩子不為空,將其壓入stack中;
- 重復步驟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);
}
}
}
過程模擬:
執行結果:
中序遍歷
遞歸方式實現中序遍歷
具體過程:
- 先中序遍歷左子樹
- 再訪問根節點
- 最后中序遍歷右子樹
代碼實現:
public static void InOrderRecur(TreeNode<char> treeNode)
{
if (treeNode == null)
{
return;
}
InOrderRecur(treeNode.LChild);
Console.Write(treeNode.Data);
InOrderRecur(treeNode.RChild);
}
非遞歸方式實現中序遍歷
具體過程:
- 申請一個新棧,記為stack,申請一個變量cur,初始時令cur為頭節點;
- 先把cur節點壓入棧中,對以cur節點為頭的整棵子樹來說,依次把整棵樹的左子樹壓入棧中,即不斷令cur=cur.left,然后重復步驟2;
- 不斷重復步驟2,直到發現cur為空,此時從stack中彈出一個節點記為node,打印node的值,並讓cur = node.right,然后繼續重復步驟2;
- 當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;
}
}
過程模擬:
執行結果:
后序遍歷
遞歸方式實現后序遍歷
- 先后序遍歷左子樹
- 再后序遍歷右子樹
- 最后訪問根節點
代碼實現:
public static void PosOrderRecur(TreeNode<char> treeNode)
{
if (treeNode == null)
{
return;
}
PosOrderRecur(treeNode.LChild);
PosOrderRecur(treeNode.RChild);
Console.Write(treeNode.Data);
}
非遞歸方式實現后序遍歷一
具體過程:
使用兩個棧實現
- 申請兩個棧stack1,stack2,然后將頭結點壓入stack1中;
- 從stack1中彈出的節點記為cur,然后先把cur的左孩子壓入stack1中,再把cur的右孩子壓入stack1中;
- 在整個過程中,每一個從stack1中彈出的節點都放在第二個棧stack2中;
- 不斷重復步驟2和步驟3,直到stack1為空,過程停止;
- 從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); ;
}
}
過程模擬:
執行結果:
非遞歸方式實現后序遍歷二
具體過程:
使用一個棧實現
申請一個棧stack,將頭節點壓入stack,同時設置兩個變量 h 和 c,在整個流程中,h代表最近一次彈出並打印的節點,c代表當前stack的棧頂節點,初始時令h為頭節點,,c為null;
每次令c等於當前stack的棧頂節點,但是不從stack中彈出節點,此時分一下三種情況:
(1)如果c的左孩子不為空,並且h不等於c的左孩子,也不等於c的右孩子,則吧c的左孩子壓入stack中
(2)如果情況1不成立,並且c的右孩子不為空,並且h不等於c的右孩子,則把c的右孩子壓入stack中;
(3)如果情況1和2不成立,則從stack中彈出c並打印,然后令h等於c;
- 一直重復步驟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;
}
}
}
過程模擬:
執行結果:
層序遍歷
具體過程:
- 首先申請一個新的隊列,記為queue;
- 將頭結點head壓入queue中;
- 每次從queue中出隊,記為node,然后打印node值,如果node左孩子不為空,則將左孩子入隊;如果node的右孩子不為空,則將右孩子入隊;
- 重復步驟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);
}
}
}
執行結果:
參考:《大話數據結構》