二叉樹的遍歷是學習二叉樹最基本卻極爲重要的一環。當你熟練掌握二叉樹的遍歷之後,你會發現很多題目都是建立在遍歷的基礎上來解決的。本文章就是爲了盤點一下各種遍歷算法的原理和實現。
前序遍歷
前序遍歷也叫先序遍歷(preorder),整個操作過程比較簡單,先訪問根結點,在訪問左子樹,左子樹訪問完之後訪問右子樹。
- 若二叉樹爲空,則什麼也不做
- 否則:
2.1 訪問根結點
2.2. 先序遍歷左子樹
2.3. 先序遍歷右子樹
遞歸實現
void preOrder(BinTree* BT){
if(BT){
printf("%d ", BT->data);
preOrder(BT->left);
preOrder(BT->right);
}
}
非遞歸實現
void preOrder(TreeNode* root){
if(root == NULL) return;
stack<TreeNode*> S; // 初始化棧
TreeNode* node = root; // 聲明遍歷指針
while(node || !S.empty()){
if(node != NULL)}{ // 不斷訪問樹的根節點,並且存儲左子樹(若存在的話)
cout << node->val << " ";
S.push(node);
node = node->left;
}
else{ // 遇到空指針,訪問棧頂指針指向的右子樹結點
node = S.top()->right;
S.pop();
}
}
}
中序遍歷
中序遍歷(inorder),整個操作過程也比較簡單。
- 若二叉樹爲空,則什麼也不做
- 否則:
2.1 中序遍歷左子樹
2.2. 訪問根結點
2.3. 中序遍歷右子樹
遞歸實現
void preOrder(TreeNode* root){
if(root != NULL){
preOrder(root->left);
cout << root->val << " ";
preOrder(root->right);
}
}
非遞歸實現
void preOrder(TreeNode* root){
if(root == NULL) return;
stack<TreeNode*> S;
TreeNode* node = root;
while(node!=NULL || !S.empty()){
if(node != NULL)}{
// cout << node->val << " "; 先序遍歷:在push時訪問結點值
S.push(node);
node = node->left;
}
else{
cout << node->val << " "; // 中序遍歷:在彈出棧頂指針時訪問
node = S.top()->right;
S.pop();
}
}
}
後序遍歷
後序遍歷(postorder),整個操作過程也比較簡單。
- 若二叉樹爲空,則什麼也不做
- 否則:
2.1 後序遍歷左子樹
2.2. 後序遍歷右子樹
2.3 訪問根結點
後序遍歷的遞歸實現比較容易,而非遞歸實現則比較難。
遞歸實現
void preOrder(TreeNode* root){
if(root != NULL){
preOrder(root->left);
preOrder(root->right);
cout << root->val << " ";
}
}
非遞歸實現
非遞歸實現比較難,同樣的可以使用棧來存儲臨時值,但是我們需要區分返回根結點時,是從左子樹返回還是右子樹返回的。
// 先訪問左子樹,然後訪問右子樹,再訪問根節點
// 使用堆棧來存儲結點指針,需要分清返回根結點時是左子樹或者右子樹
// 我們使用一個輔助指針r來指向最近訪問的結點,藉助這個指針來區分根節點是從左子樹還是右子樹返回的。
void postOrder(BiTree* root){
// 初始化一個棧 S
initStack(S);
// 聲明一個遍歷指針 p
BiTree* p = root;
// 聲明一個空的輔助指針
BiTree* r = NULL;
while(p ||! S.empty()){
if(p){ // 非空結點,壓棧
push(S, p); // 壓棧,走左子樹
p = p->left;
}
else{ // 空結點
getTop(S, p); // 取出棧頂結點賦值給p
if(p->right && p->right != r){ // 存在右子樹,並且未訪問過
p = p->right; // 取右子樹根結點,入棧
push(S, p);
p = p->left;
}
else{ // 不存在右子樹或者 已經訪問完右子樹
pop(S, p); // 彈出棧頂指針並賦值給p
visit(p->val); // 訪問結點值
r = p; // 記錄最近訪問的結點
p = NULL; // 將p置爲空
}
}
}
}
層序遍歷
層序遍歷的思路也比較簡單易懂,屬於廣度優先的一種方法,在訪問結點之後,我們使用隊列來存儲一些左右結點指針。
void levelOrder(TreeNode* root){
if(root == NULL) return;
queue<TreeNode*> Q;
Q.push(root);
while(!Q.empty()){
TreeNpde* node = Q.front();
Q.pop();
cout << node->val << " ";
if(node->left != NULL)
Q.push(node->left);
if(node->right != NULL)
Q.push(node->right);
}
}
深度優先遍歷
深度優先的思路也不難,思路類似層序遍歷,但不同的是使用了棧來存儲臨時指針。
void depthOrder(TreeNode* root){
if(root == NULL) return;
stack<int> S;
S.push(root);
while(!S.empty()){
TreeNode* node = S.pop();
cout << node->val << " ";
if(node->left != NULL)
S.push(node->left);
if(node->right != NULL)
S.push(node->right);
}
}