二叉樹作為非線性資料結構,其遍歷操作是資料結構與算法的核心考點。本文系統梳理前序、中序、後序三種遍歷規則,並提供遞歸與非遞歸(棧模擬)雙重實現,幫助讀家深度理解樹的遍歷本質。
typedef struct _BinaryTreeNode {
int value;
struct _BinaryTreeNode* pLeft; // 左子節點指標
struct _BinaryTreeNode* pRight; // 右子節點指標
} BinaryTreeNode_t;
設 L=左子樹、R=右子樹、D=根節點,理論上有 6 種排列組合。若限定「先左後右」,則剩三種核心遍歷:
void PreOrder(BinaryTreeNode_t* pRoot) {
if (pRoot != NULL) {
printf("%d ", pRoot->value); // 1. 訪問根
PreOrder(pRoot->pLeft); // 2. 遞歸左子樹
PreOrder(pRoot->pRight); // 3. 遞歸右子樹
}
}
核心邏輯:沿左子樹下行時訪問節點並入棧,左子樹耗盡後出棧轉向右子樹。
void PreOrder(const BinaryTreeNode_t* pRoot) {
std::stack s;
BinaryTreeNode_t* p = pRoot;
while (p != NULL || !s.empty()) {
while (p != NULL) {
printf("%d ", p->value); // 訪問當前節點
s.push(p); // 入棧
p = p->pLeft; // 轉向左子樹
}
p = s.top(); s.pop(); // 出棧
p = p->pRight; // 轉向右子樹
}
}
void InOrder(BinaryTreeNode_t* pRoot) {
if (pRoot != NULL) {
InOrder(pRoot->pLeft); // 1. 遞歸左子樹
printf("%d ", pRoot->value); // 2. 訪問根
InOrder(pRoot->pRight); // 3. 遞歸右子樹
}
}
關鍵差異:入棧過程不訪問,出棧時才訪問(左子樹已處理完畢)。
void InOrder(const BinaryTreeNode_t* pRoot) {
std::stack s;
BinaryTreeNode_t* p = pRoot;
while (p != NULL || !s.empty()) {
while (p != NULL) {
s.push(p); // 僅入棧,不訪問
p = p->pLeft;
}
p = s.top(); s.pop(); // 出棧並訪問
printf("%d ", p->value);
p = p->pRight; // 轉向右子樹
}
}
void PostOrder(BinaryTreeNode_t* pRoot) {
if (pRoot != NULL) {
PostOrder(pRoot->pLeft); // 1. 左子樹
PostOrder(pRoot->pRight); // 2. 右子樹
printf("%d ", pRoot->value); // 3. 訪問根
}
}
難點分析:節點第二次出棧時才能訪問(需確認右子樹已處理)。引入 pre 指標記錄上一次訪問的節點。
void PostOrder(const BinaryTreeNode_t* pRoot) {
std::stack s;
BinaryTreeNode_t* p = pRoot;
BinaryTreeNode_t* pre = NULL; // 記錄最近訪問的節點
while (p != NULL || !s.empty()) {
while (p != NULL) {
s.push(p);
p = p->pLeft;
}
p = s.top(); // 查看棧頂,不彈出
// 右子樹為空 或 右子樹剛被訪問過
if (p->pRight == NULL || p->pRight == pre) {
printf("%d ", p->value);
s.pop();
pre = p; // 標記已訪問
p = NULL; // 避免重複進入左子樹
} else {
p = p->pRight; // 轉向右子樹
}
}
}
| 遍歷方式 | 時間複雜度 | 空間複雜度 | 適用場景 |
|---|---|---|---|
| 遞歸 | O(n) | O(h) 遞歸棧深度 | 代碼簡潔,樹高度較小時 |
| 非遞歸 | O(n) | O(h) 顯式棧 | 樹極深、避免棧溢出、需中斷遍歷時 |
掌握二叉樹三種遍歷的遞歸與非遞歸實現,是資料結構學習的必經之路。關鍵在於理解:
pre指標)判斷右子樹處理完成實際開發中,優先考慮遞歸版本以提高可讀性;當樹深度不可控或需精細控制遍歷流程時,採用非遞歸版本。