栈和队列的简单应用(在二叉树中)
以前写过栈和队列使用C实现的帖子,不过以后的使用我打算使用C++,虽然实现起来C++和C区别还不算十分的大,但是C++在表达逻辑的角度要比C使用的工具更加广泛。
我们先介绍一种问题,函数的调用问题。我们知道函数可以实现递归调用,而且在思路上,递归调用非常的香。但是递归调用有一个很大的问题,就是调用函数会花费额外的时间开销。如果一直使用递归解决问题,会在函数调用上花费大量的时间。所以,我们剖析本质,看看递归到底是什么过程,能不能换成非递归的办法。
我们知道其实程序运行时就是使用栈来存储静态数据,我们调用函数的过程也是不断压栈弹栈的过程,所以我们想到使用一个栈来维护数据。进而模拟递归实现。
以二叉树为例
struct TreeNode {
int data;
TreeNode* left;
TreeNode* right;
TreeNode(int data, TreeNode* left = nullptr, TreeNode* right = nullptr):
data(data), left(left), right(right) {}
}*root;
假设我们已经得到了一棵酱婶儿的二叉树,根节点指针为root,我们如何实现先序遍历呢,之前写过,直接在树中定义
void print() {
cout << this->data << " ";
if (left) {
left->print();
}
if (right) {
right->print();
}
}
然后直接调用root->print();就OK;
如果我们使用递推实现呢?
我们发现,实际上,每次我都对当前操作的指针指向的节点进行一次cout操作,然后这个节点就再也没有出现过了,如果它有孩子,那么下一个出现的一定是它的孩子。
基于这种想法,我们想到,如果我维护一个栈,那么我们大可以使用访问栈顶元素来模拟每一次的函数调用,然后弹栈,转之将刚弹出的栈顶的左右孩子分别压栈。这样,我们就模拟出了先序遍历。
代码像这样。
void print() {
Node* arr[MAX];//维护一个栈
int top = 0;
arr[top++] = root;//根节点先入栈
while (top != 0) {//栈非空则取栈顶元素。
Node* ptr = arr[--top];
cout << ptr->data << " ";
if (ptr->right)//左右孩子入栈
arr[top++] = ptr->right;
if (ptr->left)
arr[top++] = ptr->left;
}
}
后序遍历和先序遍历有一些区别,但是思路都是一样的,都是使用栈来维护。
层次遍历就比较有意思了,如果学过二叉树的应该知道这实际就是如果将二叉树存入线性表的话,从左向右跑一趟就解决问题了,但是如果是二叉链表就不太好弄。不过思路想一想也很简单。
如果我现在在遍历第一层,那么我第二层要遍历的其实就是第一层的所有孩子,也就是说,我在遍历完全第一层的所有节点之后我应该正好可以开始遍历他们的孩子。其实这就是广搜的思路。
广搜和深搜相对应,广搜要维护一个队列。第一层的在队首,然后将第一层的所有节点的孩子入到队尾中去。当队首的人都出列之后,正好队尾的孩子们也到了队首,随即开始第二层的遍历。
代码如下:
void print() {
Node* arr[MAX];//维护一个队列。
int back = 0;
int front = 0;
arr[(back++) % MAX] = root;//根节点入队。
while (back != front) {//队非空,则取队首元素。
Node* ptr = arr[(front++) % MAX];
cout << ptr->data << " ";
if (ptr->left)
arr[(back++) % MAX] = ptr->left;
if (ptr->right)
arr[(back++) % MAX] = ptr->right;
}
}
代码大概就是酱婶儿的。
栈的作用一般是记录走过的路径,队列的作用一般是起到缓冲的作用。
根据不同情况灵活变化吧,只是个模板,面对不同的问题,还要根据情况向其中添加各种东西完成你想要的功能。
当然栈和队列能做的远不止这些,我们还可以进一步的讨论图的深搜和广搜,迷宫的问题,以及深搜和广搜的意义,乃至更高的使用方法。