分析
如果是使用递归来实现二叉树的先,中,后序遍历只需要更改三行代码的位置,但若是使用栈来写那便会有趣得多
无论是根节点还是左右子树,在栈中的表示方式都是指针,所以我们可以将左右子树各当作是一个整体,这样问题就简化为具有3个结点的完全二叉树的输出了
左右子树有三种状态,空,遍历过,未遍历过
通用逻辑
- 栈中初始化只有一个指向根结点的指针
- 遍历往往以根节点作为基础参数
- 栈顶指针指向的结点总是当做根结点处理
- 模拟递归
- 只有栈顶指针指向的结点内容才会被输出
- 栈顶易于操作
- 结点内容输出后指向该结点的指针会立即出栈
- 避免结点重复输出
- 当一个指针出栈时,如果该指针指向的结点的左右子树有未被使用过(不为空并且未遍历过),那么指向其左右子树的指针将随后入栈,且右子树总是优先于左子树入栈(左子树总是优先于右子树输出)
- 入栈是避免信息的丢失,顺序是栈的特性(先进后出)
- 当栈为空时遍历结束
- 即遍历完所有结点(可以由2,3推导出来)
栈,逻辑,输出顺序分析
输出顺序
遍历类型 | 输出顺序 |
---|---|
先序遍历 | 根结点->左子树->右子树 |
中序遍历 | 左子树->根结点->右子树 |
后序遍历 | 左子树->右子树->根结点 |
简单空栈实现操作(栈先进后出)
遍历类型 | 实现操作 |
---|---|
先序遍历 | 右子树入栈->左子树入栈->根结点入栈->输出->输出->输出 |
中序遍历 | 右子树入栈->根结点入栈->左子树入栈->输出->输出->输出 |
后序遍历 | 根结点入栈->右子树入栈->左子树入栈->输出->输出->输出 |
更进一步(括号视作默认操作)
实际情况是栈总是当做根结点处理,这意味着栈中始终存在根结点,相当于根结点入栈是默认操作,在此前提下完成的操作如下
遍历类型 | 实现操作 |
---|---|
先序遍历 | (根结点入栈)->输出根结点->右子树入栈->左子树入栈->输出左子树->输出右子树 |
中序遍历 | (根结点入栈)->左子树入栈->输出左子树->输出根结点->右子树入栈->输出右子树 |
后序遍历 | (根结点入栈)->左子树入栈->输出左子树->右子树入栈->输出右子树->输出根结点 |
还有一点
只有栈顶的结点才能被输出,又因为栈顶总是被视作根结点,那么只有根结点可以输出
遍历类型 | 实现操作 |
---|---|
先序遍历 | (根结点入栈)->输出根结点->右子树入栈->左子树入栈 |
中序遍历 | (根结点入栈)->左子树入栈->输出根结点->右子树入栈 |
后序遍历 | (根结点入栈)->左子树入栈->右子树入栈->输出根结点 |
通用逻辑推导栈顶操作流程
大部分操作都在栈顶完成,所以只要分析栈顶变化就可以解决问题
栈顶只有两个选择,要么输出,要么选择相应子树入栈
输出后要备份,出栈,再考虑有没有丢失信息然后回到栈顶状态
graph TD 1(栈顶)==>2(可输出???) 2==>|否|33(栈顶相应子树入栈) 33==>1 2==>|是|3(输出栈顶) 3==>4(备份栈顶) 4==>5(栈顶出栈) 5==>12(备份右子树状态???) 12==>|未遍历|13(备份右子树入栈) 12==>|遍历过或为空|6 13==>6(备份左子树状态???) 6==>|未遍历|7(备份左子树入栈) 6==>|遍历过或为空|1 7==>1
算法
先序遍历
分析
- 先序遍历的实际操作:(根结点入栈)->输出根结点->右子树入栈->左子树入栈
- 栈顶指向的结点总是可以输出的
- 由于先序遍历的特性,根结点输出早于左右子树所以不存在左右子树遍历过的情况(所以左右子树只有空和非空两种状态)
结合分析修改栈顶操作流程图
graph TD 1(栈顶)==>3(输出栈顶) 3==>4(备份栈顶) 4==>5(栈顶出栈) 5==>12(备份右子树状态???) 12==>|不为空|13(备份右子树入栈) 12==>|为空|6 13==>6(备份左子树状态???) 6==>|不为空|7(备份左子树入栈) 6==>|为空|1 7==>1
流程
- 先输出栈顶指针指向的结点
- 栈顶指针出栈(栈顶指针备份,否则左右子树信息丢失)
- 备份指针指向结点的右子树入栈(如果右子树指针不为空)
- 备份指针指向结点的左子树入栈(如果左子树指针不为空)
- 回到步骤1
C++代码实现
void preorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
std::cout << s.top()->data << " ";//输出栈顶指针指向的结点
Node* temp = s.top();//备份栈顶指针
s.pop();//栈顶指针出栈
if (temp->R != NULL) s.push(temp->R);//备份指向结点右子树不为空则将其压入栈
if (temp->L != NULL) s.push(temp->L);//备份指向结点左子树不为空则将其压入栈
}
}
中序遍历
分析
- 中序遍历的实际操作:(根结点入栈)->左子树入栈->输出根结点->右子树入栈
- 左子树入栈在输出根结点之前,这意味着必须一直向左搜索没有左子树(左子树为空)的结点,然后输出
- 向左搜索时一直添加栈顶左子树,这样回退的时候就不用考虑左子树(必定遍历过或者为空),即当栈顶可输出时,左子树必定遍历过或为空
- 但是一旦添加了右子树就会打破规则(左子树必定遍历过或者为空),因为新栈顶没有向左搜索,需要重新执行向左搜索
- 向左搜索时一直添加栈顶左子树,这样回退的时候就不用考虑左子树(必定遍历过或者为空),即当栈顶可输出时,左子树必定遍历过或为空
结合分析修改栈顶操作流程图
graph TD 1(栈顶)==>2(可输出???) 2==>|否|33(栈顶左子树入栈) 33==>1 2==>|是|3(输出栈顶) 3==>4(备份栈顶) 4==>5(栈顶出栈) 5==>12(备份右子树状态???) 12==>|未遍历|13(备份右子树入栈) 12==>|遍历过或为空|3 13==>1
流程
- 重复压入栈顶结点左子树指针,直到栈顶结点左子树指针为空
- 重复输出栈顶指针指向的结点(输出之前备份,输出之后出栈),然后检查
- 如果备份结点右子树未遍历过,然后将其右子树入栈,回到1
C++代码实现
void inorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
{
s.push(s.top()->L);
}
while (!s.empty())//重复输出栈顶指针指向的结点
{
std::cout << s.top->data << " ";//输出栈顶指针指向的结点
Node* temp = s.top();//备份栈顶指针
s.pop();//栈顶指针出栈
if (temp->R != NULL) {//检查,如果栈顶指针指向的结点右子树不为空,则将其右子树入栈,退出检查
s.push(temp->R);
break;//回到1
}
}
}
}
后序遍历
分析
- 中序遍历的实际操作:(根结点入栈)->左子树入栈->右子树入栈->输出根结点
- 左子树入栈在输出根结点之前,这意味着必须一直向左搜索没有左子树(左子树为空)的结点
- 向左搜索时一直添加栈顶左子树,这样回退的时候就不用考虑左子树(必定遍历过或者为空),即当栈顶可输出时,左子树必定遍历过或为空
- 但是一旦添加了右子树就会打破规则(左子树必定遍历过或者为空),因为新栈顶没有向左搜索
- 向左搜索时一直添加栈顶左子树,这样回退的时候就不用考虑左子树(必定遍历过或者为空),即当栈顶可输出时,左子树必定遍历过或为空
- 右子树入栈在左子树入栈之后,这意味这找到左子树为空的结点,然后检查其右子树
- 当根结点右子树为空或者遍历过就输出根结点
- 当根结点右子树未遍历过就将其入栈打破规则需要重新向左搜索
结合分析修改栈顶操作流程图
graph TD 1(栈顶)==>2(可输出???) 2==>|否|33(栈顶左子树入栈) 33==>1 2==>|是|4(备份栈顶) 3(输出备份指向结点) 4==>5(栈顶出栈) 5==>12(备份右子树状态???) 12==>|未遍历|13(备份右子树入栈) 12==>|遍历过或为空|3 3==>2 13==>1
流程
- 重复压入栈顶结点左子树指针,直到栈顶结点左子树指针为空
- 重复将栈顶出栈(出栈之前备份,输出之后出栈)然后检查
- 如果备份指向结点右子树未遍历过,然后将其右子树入栈,回到1
- 如果备份指向结点右子树遍历过或为空,输出备份指向结点
C++代码实现
void postorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
{
s.push(s.top()->L);
}
Node* last = NULL;//上一次遍历过的指针
while (!s.empty())//重复检查
{
if (s.top()->R==NULL||last==s.top()->R) {//如果栈顶指针指向结点的右子树为空或者遍历过
std::cout << s.top()->data << " ";//输出栈顶指向的结点
last = s.top();//更新指针last
s.pop();//栈顶指针出栈
}
else if(s.top()->R!=NULL)//如果栈顶指针指向的结点的右子树未遍历过
{
s.push(s.top()->R);//将右子树入栈
break;//退出检查
}
}
}
}
整体代码
#include <iostream>
#include <stack>
template <typename T>
class BST {
public:
BST() :root(NULL) {};
~BST() {};
void insert(T data) {
Node* temp = new Node();
temp->L = NULL;
temp->R = NULL;
temp->data = data;
if (root == NULL) {
root = temp;
}
else {
Node* tracer = root;
while (true)
{
if (tracer->data >= data)
if (tracer->L == NULL) {
tracer->L = temp;
break;
}
else
tracer = tracer->L;
else
if (tracer->R == NULL) {
tracer->R = temp;
break;
}
else
tracer = tracer->R;
}
}
}
void preorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
std::cout << s.top()->data << " ";//输出栈顶指针指向的结点
Node* temp = s.top();//备份栈顶指针
s.pop();//栈顶指针出栈
if (temp->R != NULL) s.push(temp->R);//备份指向结点右子树不为空则将其压入栈
if (temp->L != NULL) s.push(temp->L);//备份指向结点左子树不为空则将其压入栈
}
}
void inorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
{
s.push(s.top()->L);
}
while (!s.empty())//重复输出栈顶指针指向的结点
{
std::cout << s.top->data << " ";//输出栈顶指针指向的结点
Node* temp = s.top();//备份栈顶指针
s.pop();//栈顶指针出栈
if (temp->R != NULL) {//检查,如果栈顶指针指向的结点右子树不为空,则将其右子树入栈,退出检查
s.push(temp->R);
break;//回到1
}
}
}
}
void postorder_travel() {
if (root == NULL)
return;
std::stack<Node*> s;
s.push(root);//根指针入栈
while (!s.empty())
{
while (s.top()->L != NULL)//重复将栈顶指针指向结点的左子树压栈,直到栈顶指针指向结点的左子树为空
{
s.push(s.top()->L);
}
Node* last = NULL;//上一次遍历过的指针
while (!s.empty())//重复检查
{
if (s.top()->R==NULL||last==s.top()->R) {//如果栈顶指针指向结点的右子树为空或者遍历过
std::cout << s.top()->data << " ";//输出栈顶指向的结点
last = s.top();//更新指针last
s.pop();//栈顶指针出栈
}
else if(s.top()->R!=NULL)//如果栈顶指针指向的结点的右子树未遍历过
{
s.push(s.top()->R);//将右子树入栈
break;//退出检查
}
}
}
}
private:
struct Node
{
Node* L;
Node* R;
T data;
};
Node* root;
};
int main(){
BST<int> b;
b.insert(2);
b.insert(4);
b.insert(1);
b.insert(5);
b.insert(3);
b.insert(0);
b.preorder_travel();
std::cout << std::endl;
b.inorder_travel();
std::cout << std::endl;
b.postorder_travel();
}