Prefacio
El método recursivo es muy simple para resolver los tres métodos transversales, pero el método iterativo no es tan simple.Este artículo proporciona varios métodos y plantillas.
Tipo de nodo
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {
}
};
Recorrido de preorden
El recorrido de preorden es un orden secuencial de izquierda y derecha. De acuerdo con las características de primero en entrar, último en salir de la pila, el orden de apilamiento es opuesto al orden de recorrido, de modo que se puede obtener el orden deseado cuando la pila está publicado.
Pensamiento recursivo:先树根,然后左子树,然后右子树。每棵子树递归。
En el algoritmo iterativo, la idea evolucionó hasta convertirse en un nodo A, al que se debe acceder de inmediato.
Porque cada subárbol visita primero su nodo raíz. Para los subárboles izquierdo y derecho de un nodo, la raíz también debe visitarse primero. En los dos subárboles de A, después de atravesar el subárbol izquierdo, vuelva a atravesar el subárbol derecho. Por lo tanto, después de visitar el nodo raíz, antes de atravesar el subárbol izquierdo, inserte el subárbol derecho en la pila.
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root)
{
stack < TreeNode*> st;
vector< int > result;
st.push(root);
while (!st.empty())
{
TreeNode* node = st.top();
st.pop();
if (node != NULL)
result.push_back(node->val);
else
continue;
st.push(node->right);
st.push(node->left);
}
return result;
}
};
Recorrido posterior al pedido
Podemos completar el recorrido posterior al pedido de manera similar al recorrido posterior al pedido. El recorrido posterior al pedido es simétrico con el recorrido posterior al pedido.
Idea: 每到一个节点 A,就应该立即访问它。 然后将左子树压入栈,再次遍历右子树。
Debido a que el pedido anticipado es medio e izquierdo, después de completar lo anterior, es medio-derecho-izquierdo, y el resultado es izquierda-derecha y medio en orden inverso (recorrido posterior al pedido)
遍历完整棵树后,结果序列逆序即可。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root)
{
stack < TreeNode*> st;
vector< int > result;
st.push(root);
while (!st.empty())
{
TreeNode* node = st.top();
st.pop();
if (node != NULL)
result.push_back(node->val);
else
continue;
st.push(node->left);
st.push(node->right);
}
reverse(result.begin(), result.end());
return result;
}
};
Recorrido en orden
Ideas:每到一个节点 A,因为根的访问在中间,将 A 入栈。然后遍历左子树,接着访问 A,最后遍历右子树。
Después de acceder a A, A se puede sacar de la pila. Porque se han visitado A y su subárbol izquierdo.
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root)
{
stack < TreeNode*> st;
vector< int > result;
TreeNode* node = root;
while (node||st.size())
{
while (node)
{
st.push(node);
node = node->left;
}
if (st.size())
{
node = st.top(), st.pop();
result.push_back(node->val);
node = node->right;
//左子树遍历完成,开始遍历右子树
}
}
return result;
}
};
Solución de plantilla más inteligente
Idea: Necesitamos un signo para distinguir cada pila de llamadas recursivas, aquí usamos nullptr para representar el nodo a procesar.
En el proceso de iterar en este momento, en realidad tenemos dos operaciones, una está procesando: poner elementos en la matriz de resultados, y la otra está visitando: atravesar nodos.
Por qué el código de recorrido de la reserva que se acaba de escribir no se puede usar en común con el recorrido de orden medio, porque el orden de recorrido de la orden de anticipación es medio izquierdo y derecho, el elemento al que se accede primero es el nodo del medio y el elemento procesado también es el nodo intermedio, por lo que se puede escribir ahora Código relativamente conciso, porque el orden de los elementos a los que se accede y los elementos a procesar son los mismos, y ambos son nodos intermedios.
Pero el recorrido en orden no lo es, lo que hace que el orden de procesamiento y el orden de acceso sean inconsistentes. Entonces, en el método mejorado, use nullptr para marcar el nodo que se procesará
Recorrido de preorden
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res; //保存结果
stack<TreeNode*> call; //调用栈
if(root!=nullptr) call.push(root); //首先介入root节点
while(!call.empty()){
TreeNode *t = call.top();
call.pop(); //访问过的节点弹出
if(t!=nullptr){
if(t->right) call.push(t->right); //右节点先压栈,最后处理
if(t->left) call.push(t->left);
call.push(t); //当前节点重新压栈(留着以后处理),因为先序遍历所以最后压栈
call.push(nullptr); //在当前节点之前加入一个空节点表示已经访问过了
}else{
//空节点表示之前已经访问过了,现在需要处理除了递归之外的内容
res.push_back(call.top()->val); //call.top()是nullptr之前压栈的一个节点,也就是上面call.push(t)中的那个t
call.pop(); //处理完了,第二次弹出节点(彻底从栈中移除)
}
}
return res;
}
};
来源:力扣(LeetCode)
Recorrido posterior al pedido
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> call;
if(root!=nullptr) call.push(root);
while(!call.empty()){
TreeNode *t = call.top();
call.pop();
if(t!=nullptr){
call.push(t); //在右节点之前重新插入该节点,以便在最后处理(访问值)
call.push(nullptr); //nullptr跟随t插入,标识已经访问过,还没有被处理
if(t->right) call.push(t->right);
if(t->left) call.push(t->left);
}else{
res.push_back(call.top()->val);
call.pop();
}
}
return res;
}
};
来源:力扣(LeetCode)
Recorrido en orden
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> call;
if(root!=nullptr) call.push(root);
while(!call.empty()){
TreeNode *t = call.top();
call.pop();
if(t!=nullptr){
if(t->right) call.push(t->right);
call.push(t); //在左节点之前重新插入该节点,以便在左节点之后处理(访问值)
call.push(nullptr); //nullptr跟随t插入,标识已经访问过,还没有被处理
if(t->left) call.push(t->left);
}else{
res.push_back(call.top()->val);
call.pop();
}
}
return res;
}
};
来源:力扣(LeetCode)