二叉樹是常見的數據結構,常見的有三種遍歷方式:前序遍歷(根→左→右)、中序遍歷(左→根→右)和後序遍歷(左→右→根)。使用遞歸的方式實現遍歷比較簡單,書上看到一個不使用遞歸的方法比較複雜,記錄一下。
二叉樹節點定義
typedef struct node {
int value;
struct node *left;
struct node *right;
}Node;
前序遍歷(遞歸)
遞歸方式實現前序遍歷,先打印根節點,然後依次把左節點和右節點當做根節點來打印。
void Tree::preOrderRecur(Node *tree) {
if (!tree) return;
std::cout << tree->value << " ";
preOrderRecur(tree->left);
preOrderRecur(tree->right);
}
前序遍歷(非遞歸)
非遞歸遍歷使用棧結構(先進先出)來暫存節點,先打印根節點,同時依次把右節點和左節點壓到棧中。接着取棧頂節點作爲根節點進行打印,然後再依次把右節點和左節點壓到棧中。。。這樣可以始終保持先打印的是根節點,且左節點比右節點先打印。
void Tree::preOrderUnRecur(Node *tree) {
if (!tree) return;
std::stack<Node*> nodeStack;
nodeStack.push(tree);
Node *root = nullptr;
while (!nodeStack.empty()) {
root = nodeStack.top();
nodeStack.pop();
std::cout << root->value << " ";
if (root->right)
nodeStack.push(root->right);
if (root->left)
nodeStack.push(root->left);
}
}
中序遍歷(遞歸)
遞歸方式依次打印左、根、右節點。
void Tree::inOrderRecur(Node *tree) {
if (!tree) return;
inOrderRecur(tree->left);
std::cout << tree->value << " ";
inOrderRecur(tree->right);
}
中序遍歷(非遞歸)
先依次把最左邊的節點壓到棧中,(這樣彈出節點打印的時候可以保證先打印左節點再打印根節點)。一旦檢測到左節點爲空,就彈出棧頂的節點並打印該節點(此時可以理解爲根節點)。接着把其右節點作爲根節點循環處理。
void Tree::inOrderUnRecur(Node *tree) {
if (!tree) return;
std::stack<Node*> nodeStack;
Node *root = tree;
while (!nodeStack.empty() || root) {
if (root) {
nodeStack.push(root);
root = root->left;
} else {
root = nodeStack.top();
std::cout << root->value << " ";
nodeStack.pop();
root = root->right;
}
}
}
後序遍歷(遞歸)
依次處理左右節點再打印根節點。
void Tree::posOrderRecur(Node *tree) {
if (!tree) return;
posOrderRecur(tree->left);
posOrderRecur(tree->right);
std::cout << tree->value << " ";
}
後序遍歷(非遞歸)
把根節點壓入棧中,記錄棧頂元素爲current當前節點。如果左節點不爲空則壓入棧中,否則把右節點壓入棧中(不爲空時)。如果當前節點的左右節點都爲空則打印當前節點,(可以保證根節點最後打印)。
壓入右節點的時候要保證上一次沒有打印過右節點,避免根節點和右節點打印後再壓棧變成死循環。壓入左節點的時候也要保證上次打印不是做節點,且右節點不存在或者已經處理過了,(可以保證左節點始終在右節點的前面被打印)。
void Tree::posOrderUnRecur(Node *tree) {
if (!tree) return;
std::stack<Node*> nodeStack;
nodeStack.push(tree);
Node *last = nullptr;
Node *current = nullptr;
while (!nodeStack.empty()) {
current = nodeStack.top();
if ((current->left) && (last != current->left)
&& ((last != current->right) || !current->right)) {
nodeStack.push(current->left);
} else if(current->right && (last != current->right)) {
nodeStack.push(current->right);
} else {
std::cout << current->value << " ";
last = current;
nodeStack.pop();
}
}
}
二叉樹的創建和銷燬
這裏我使用一個類似二叉樹層序遍歷的數組的形式創建二叉樹(避免歧義使用-1代表空節點),銷燬二叉樹要釋放所有申請空間的節點。
// 創建
Node *Tree::createBiTree(const int *arr, int size, int index) {
if (index >= size || arr[index] == -1){
return nullptr;
}
Node *node = new Node;
if (index != size) {
node->value = arr[index];
node->left = createBiTree(arr, size, 2*(index+1)-1);
node->right = createBiTree(arr, size, 2*(index+1));
} else {
node->left = NULL;
node->right = NULL;
}
return node;
}
// 銷燬
void Tree::destoryBiTree(Node *tree) {
if (tree == NULL)
return;
destoryBiTree(tree->left);
if (tree->left)
tree->left = NULL;
destoryBiTree(tree->right);
if (tree->right)
tree->right = NULL;
delete tree;
}
測試
int main()
{
/*
* 使用如下二叉樹
* 1
* 2 3
* 4 5 6 7
* 8 x x x x x 9 10
* /
int array[15] = {1,2,3,4,5,6,7,8,-1,-1,-1,-1,-1,9,10};
Tree *ins = new Tree;
Node *tree = ins->createBiTree(array, 15, 0);
std::cout << "\n前序遍歷(遞歸):" << std::endl;
ins->preOrderRecur(tree);
std::cout << "\n前序遍歷(非遞歸):" << std::endl;
ins->preOrderUnRecur(tree);
std::cout << "\n中序遍歷(遞歸):" << std::endl;
ins->inOrderRecur(tree);
std::cout << "\n中序遍歷(非遞歸):" << std::endl;
ins->inOrderUnRecur(tree);
std::cout << "\n後序遍歷(遞歸):" << std::endl;
ins->posOrderRecur(tree);
std::cout << "\n後序遍歷(非遞歸):" << std::endl;
ins->posOrderUnRecur(tree);
ins->destoryBiTree(tree);
}
程序輸出:
前序遍歷(遞歸):
1 2 4 8 5 3 6 7 9 10
前序遍歷(非遞歸):
1 2 4 8 5 3 6 7 9 10
中序遍歷(遞歸):
8 4 2 5 1 6 3 9 7 10
中序遍歷(非遞歸):
8 4 2 5 1 6 3 9 7 10
後序遍歷(遞歸):
8 4 5 2 6 9 10 7 3 1
後序遍歷(非遞歸):
8 4 5 2 6 9 10 7 3 1