二叉樹簡介
維基百科對二叉樹的定義:二叉樹(英語:Binary tree)是每個節點最多隻有兩個分支(不存在分支度大於2的節點)的樹結構。通常分支被稱作“左子樹”和“右子樹”。二叉樹的分支具有左右次序,不能顛倒。
二叉樹的遍歷有4種方式,先序遍歷,中序遍歷,後序遍歷,層次遍歷,前三者均屬於深度優先遍歷,先序、中序、後序指的是遍歷時根節點相對於左右孩子的次序,先序遍歷的順序爲根節點->左子樹->右子樹,中序遍歷的順序爲左子樹->根節點->右子樹,後序遍歷的順序爲左子樹->右子樹->根節點,遍歷時對每個子樹採用同樣的順序遍歷。層次遍歷屬於廣度優先遍歷,順序爲第一層->第二層->第三層->…,每層都按照從左到右的順序遍歷。其中三種深度優先遍歷均有遞歸和非遞歸兩種實現方式。
上圖四種遍歷方式的序列分別爲:
先序遍歷:2, 7, 2, 6, 5, 11, 5, 9, 4
中序遍歷:2, 7, 5, 6, 11, 2, 5, 4, 9
後序遍歷:2, 5, 11, 6, 7, 4, 9, 5, 2
層次遍歷:2, 7, 5, 2, 6, 9, 5, 11, 4
1. 先序遍歷
遍歷順序爲:根節點->左子樹->右子樹,分爲遞歸和非遞歸兩種方式
遞歸算法
思路
先讀取根節點的值,然後遞歸調用遍歷函數分別遍歷左子樹和右子樹。
僞代碼
PRE_ORDER_RECURSIVE_TRAVERSE(root)
if(root == null)
return
visit(root) //訪問根節點
PREORDER_RECURSIVE_TRAVERSE(root.left)
PREORDER_RECURSIVE_TRAVERSE(root.right)
C++實現
void preOrderRecursiveTraversal(TreeNode *root, vector<int> &sequence)
{
if(root == null)
{
return;
}
sequence.push_back(root->val);
preOrderRecursiveTraversal(root->left, sequence);
preOrderRecursiveTraversal(root->right, sequence);
}
非遞歸算法
思路
使用棧來存儲節點,每次打印節點後,右孩子先入棧、左孩子後入棧,這樣左孩子就會先出棧,並且打印左孩子時,左孩子的孩子會繼續入棧,這樣就會先把子樹訪問完畢,纔會出棧右孩子。
僞代碼
PRE_ORDER_NONRECURSIVE_TRAVERSE(root)
if(root == null)
return
stack.push(root)
while(stack not empty)
node = stack.pop()
visit(node)
stack.push(node.right) //先入棧右孩子,右孩子會後出棧
stack.push(node.left)
C++實現
vector<int> preOrderTraversal(TreeNode *root)
{
vector<int> sequence;
if(root == nullptr)
{
return sequence;
}
stack<TreeNode *> stackIn;
stackIn.push(root);
while(!stackIn.empty())
{
TreeNode *curNode = stackIn.top();
stackIn.pop();
sequence.push_back(curNode->val);
if(curNode->right)
{
stackIn.push(curNode->right);
}
if(curNode->left)
{
stackIn.push(curNode->left);
}
}
return sequence;
}
2. 中序遍歷
遍歷順序爲左子樹->根節點->右子樹,分爲遞歸和非遞歸兩種方式
遞歸算法
思路
每次遞歸:先遞歸遍歷左子樹,再打印根節點,最後遞歸遍歷右子樹
僞代碼
IN_ORDER_NONRECURSIVE_TRAVERSE(root)
if(root == null)
return
PREORDER_NONRECURSIVE_TRAVERSE(root.left)
visit(root)
PREORDER_NONRECURSIVE_TRAVERSE(root->right)
C++實現
void inOrderRecursiveTraversal(TreeNode *root, vector<int> &sequence)
{
if(root == nullptr)
{
return;
}
inOrderRecursiveTraversal(root->left, sequence);
sequence.push_back(root->val);
inOrderRecursiveTraversal(root->right, sequence);
}
非遞歸算法
思路
遍歷時,根先入棧,然後一直迭代地訪問左孩子,併入棧,直到左孩子爲空。此時出棧,打印該節點,該節點的左孩子一定爲空(否則前面步驟匯會繼續往下走),如果有右孩子,右孩子入棧,進入下次遍歷操作。(如果右孩子無左子樹,即右孩子的左孩子爲空,下次就會打印這個節點,反之會入棧其左子樹)
僞代碼
IN_ORDER_NONRECURSIVE_TRAVERSE(root)
curNode = root
while(curNode != null && stack is not empty)
while(curNode != null) //先把左孩子都入棧
stack.push(curNode)
curNode = curNode.next
curNode = stack.pop()
visit(curNode)
curNode = curNode->right //左孩子訪問完了,訪問右孩子
C++實現
vector<int> inOrderTraversal(TreeNode *root)
{
vector<int> sequence;
stack<TreeNode *> stackIn;
TreeNode *curNode = root;
while(curNode || !stackIn.empty())
{
while(curNode)
{
stackIn.push(curNode);
curNode = curNode->left;
}
curNode = stackIn.top();
stackIn.pop();
sequence.push_back(curNode->val);
curNode = curNode->right;
}
return sequence;
}
3. 後序遍歷
遍歷順序爲左子樹->右子樹->根節點,分爲遞歸和非遞歸兩種方式
遞歸算法
思路
先分別遞歸遍歷左子樹和右子樹,然後打印根節點
僞代碼
POST_ORDER_NONRECURSIVE_TRAVERSE(root)
if(root == null)
return
PREORDER_NONRECURSIVE_TRAVERSE(root.left)
PREORDER_NONRECURSIVE_TRAVERSE(root->right)
visit(root)
C++實現
void postOrderRecursiveTraversal(TreeNode *root, vector<int> &sequence)
{
if(root == nullptr)
{
return;
}
postOrderRecursiveTraversal(root->left, sequence);
postOrderRecursiveTraversal(root->right, sequence);
sequence.push_back(root->val);
}
非遞歸算法
思路
使用棧來存儲節點,對每個節點,如果沒有左右孩子了,那麼打印該節點,如果有孩子,孩子入棧(先右孩子後左孩子,這樣左孩子會先出棧)。這種方式面臨一個問題,一個節點的左右子樹都遍歷結束後,會再次訪問該節點,這個時候是應該入棧該節點的孩子呢,還是出棧打印該節點呢。無法判斷,所以這個時候需要使用一個數據結構(unordered_set)來記錄該節點的左右孩子是否已經被訪問過了,即入棧了。
僞代碼
POST_ORDER_NONRECURSIVE_TRAVERSE(root)
if(root == null)
return
stack.push(root)
while(stack is not empty)
curNode = stack.top()
if(curNode has no child or curNode's child has visited)
visit(curNode)
stack.pop()
else
if(curNode.right != null)
stack.push(curNode.right)
if(curNode.left != null)
stack.push(curNode.left)
visitedNode.add(curNode) //將curNode標記爲已訪問
C++實現
vector<int> postOrderTraversal(TreeNode *root)
{
vector<int> sequence;
if(!root)
{
return sequence;
}
stack<TreeNode *> stackIn;
stackIn.push(root);
set<TreeNode *> traversed;
while(!stackIn.empty())
{
TreeNode *curNode = stackIn.top();
if(traversed.find(curNode) != traversed.end() || !curNode->left && !curNode->right)
{
sequence.push_back(curNode->val);
stackIn.pop();
continue;
}
if(curNode->right)
{
stackIn.push(curNode->right);
}
if(curNode->left)
{
stackIn.push(curNode->left);
}
traversed.insert(curNode);
}
return sequence;
}
4. 層次遍歷
思路
使用隊列來按次序保存節點,每次出隊列,打印該節點,然後將其左、右孩子分別入棧。
僞代碼
LAYER_TRAVERSAL(TreeNode* root)
if(root == null)
return
queue.push(root)
while(queue is not empty)
curNode = queue.pop()
visite(curNode)
if(curNode->left != null)
queue.push(curNode->left)
if(curNode->right != null)
quque.push(curNode->right)
C++實現
vector<int> layerTraversal(TreeNode *root)
{
vector<int> sequence;
if(root == nullptr)
{
return sequence;
}
deque<TreeNode *> que;
que.push_back(root);
while(!que.empty())
{
TreeNode *curNode = que.front();
que.pop_front();
sequence.push_back(curNode->val);
if(curNode->left != nullptr)
{
que.push_back(curNode->left);
}
if(curNode->right != nullptr)
{
que.push_back(curNode->right);
}
}
return sequence;
}