El octavo día de revisión del algoritmo: búsqueda primero en amplitud/búsqueda primero en profundidad--2

Tabla de contenido

1. Fusionar árboles binarios 

1. Búsqueda en profundidad

Análisis de complejidad

2. Búsqueda en amplitud

Análisis de complejidad

En segundo lugar, complete el siguiente puntero del nodo derecho de cada nodo

 1. Recorrido de nivel:

Análisis de complejidad

2. Utilice el siguiente puntero establecido

Ideas

algoritmo

Análisis de complejidad

1. Fusionar árboles binarios 

617. Fusionar árboles binarios - LeetCode https://leetcode.cn/problems/merge-two-binary-trees/?plan=algorithms&plan_progress=gzwnnxs

1. Búsqueda en profundidad


Se pueden fusionar dos árboles binarios mediante la búsqueda en profundidad. Atraviese dos árboles binarios simultáneamente comenzando desde el nodo raíz y combine los nodos correspondientes.

Los nodos correspondientes de dos árboles binarios pueden existir en las tres situaciones siguientes y se utilizan diferentes métodos de fusión para cada situación.

Si los nodos correspondientes de los dos árboles binarios están vacíos, los nodos correspondientes del árbol binario fusionado también estarán vacíos;

Si sólo uno de los nodos correspondientes de los dos árboles binarios está vacío, el nodo correspondiente del árbol binario fusionado será el nodo no vacío;

Si los nodos correspondientes de los dos árboles binarios no están vacíos, el valor del nodo correspondiente del árbol binario fusionado es la suma de los valores de los nodos correspondientes de los dos árboles binarios. En este caso, los dos nodos necesitan fusionarse explícitamente.

Después de fusionar un nodo, los subárboles izquierdo y derecho del nodo deben fusionarse respectivamente. Este es un proceso recursivo.

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == nullptr) {
            return t2;
        }
        if (t2 == nullptr) {
            return t1;
        }
        auto merged = new TreeNode(t1->val + t2->val);
        merged->left = mergeTrees(t1->left, t2->left);
        merged->right = mergeTrees(t1->right, t2->right);
        return merged;
    }
};

Análisis de complejidad

Complejidad del tiempo: O (min (m, n)), donde myn son el número de nodos de los dos árboles binarios respectivamente. La búsqueda en profundidad se realiza en dos árboles binarios al mismo tiempo. Sólo cuando los nodos correspondientes en los dos árboles binarios no estén vacíos, el nodo se fusionará explícitamente. Por lo tanto, el número de nodos visitados no excederá el número de árbol binario más pequeño Número de nodos.

Complejidad espacial: O (min (m, n)), donde myn son el número de nodos de los dos árboles binarios respectivamente. La complejidad del espacio depende del número de niveles de llamadas recursivas. El número de niveles de llamadas recursivas no excederá la altura máxima del árbol binario más pequeño. En el peor de los casos, la altura del árbol binario es igual al número de nodos. .

2. Búsqueda en amplitud


También puede utilizar la búsqueda en amplitud para fusionar dos árboles binarios. Primero, determine si los dos árboles binarios están vacíos. Si ambos árboles binarios están vacíos, el árbol binario fusionado también estará vacío. Si solo un árbol binario está vacío, el árbol binario fusionado será otro árbol binario no vacío.

Si ambos árboles binarios no están vacíos, primero calcule el valor del nodo raíz fusionado, luego inicie una búsqueda en amplitud desde los nodos raíz del árbol binario fusionado y los dos árboles binarios originales, y recorra cada árbol binario simultáneamente comenzando desde el nodo raíz y fusionar los nodos correspondientes.

Se utilizan tres colas para almacenar los nodos del árbol binario fusionado y los nodos de los dos árboles binarios originales, respectivamente. Inicialmente, el nodo raíz de cada árbol binario se agrega a la cola correspondiente. Cada vez que se saca un nodo de cada cola, se juzga si los nodos secundarios izquierdo y derecho de los dos nodos del árbol binario original están vacíos. Si el nodo secundario izquierdo de al menos un nodo en los nodos actuales de los dos árboles binarios originales no está vacío, entonces el nodo secundario izquierdo del nodo correspondiente del árbol binario fusionado tampoco está vacío. Lo mismo ocurre con el nodo secundario derecho.

Si el nodo secundario izquierdo del árbol binario fusionado no está vacío, el nodo secundario izquierdo del árbol binario fusionado y todo el subárbol izquierdo deben calcularse en función de los nodos secundarios izquierdos de los dos árboles binarios originales. Considere los siguientes dos escenarios:

Si los nodos secundarios izquierdos de ambos árboles binarios originales no están vacíos, el valor del nodo secundario izquierdo del árbol binario fusionado es la suma de los valores de los nodos secundarios izquierdos de los dos árboles binarios originales, después del nodo secundario izquierdo Se crea el nodo del árbol binario fusionado, agregue el nodo secundario izquierdo en cada árbol binario a la cola correspondiente;

Si uno de los nodos secundarios izquierdos de los dos árboles binarios originales está vacío, es decir, el subárbol izquierdo de un árbol binario original está vacío, entonces el subárbol izquierdo del árbol binario fusionado es el subárbol izquierdo del otro árbol binario original. En este momento, no es necesario continuar atravesando el subárbol izquierdo no vacío, por lo que no es necesario agregar el nodo secundario izquierdo a la cola.

Para el nodo secundario derecho y el subárbol derecho, el método de procesamiento es el mismo que para el nodo secundario izquierdo y el subárbol izquierdo.

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == nullptr) {
            return t2;
        }
        if (t2 == nullptr) {
            return t1;
        }
        auto merged = new TreeNode(t1->val + t2->val);
        auto q = queue<TreeNode*>();
        auto queue1 = queue<TreeNode*>();
        auto queue2 = queue<TreeNode*>();
        q.push(merged);
        queue1.push(t1);
        queue2.push(t2);
        while (!queue1.empty() && !queue2.empty()) {
            auto node = q.front(), node1 = queue1.front(), node2 = queue2.front();
            q.pop();
            queue1.pop();
            queue2.pop();
            auto left1 = node1->left, left2 = node2->left, right1 = node1->right, right2 = node2->right;
            if (left1 != nullptr || left2 != nullptr) {
                if (left1 != nullptr && left2 != nullptr) {
                    auto left = new TreeNode(left1->val + left2->val);
                    node->left = left;
                    q.push(left);
                    queue1.push(left1);
                    queue2.push(left2);
                } else if (left1 != nullptr) {
                    node->left = left1;
                } else if (left2 != nullptr) {
                    node->left = left2;
                }
            }
            if (right1 != nullptr || right2 != nullptr) {
                if (right1 != nullptr && right2 != nullptr) {
                    auto right = new TreeNode(right1->val + right2->val);
                    node->right = right;
                    q.push(right);
                    queue1.push(right1);
                    queue2.push(right2);
                } else if (right1 != nullptr) {
                    node->right = right1;
                } else {
                    node->right = right2;
                }
            }
        }
        return merged;
    }
};

Análisis de complejidad

Complejidad del tiempo: O (min (m, n)), donde myn son el número de nodos de los dos árboles binarios respectivamente. Se realiza una búsqueda en amplitud en dos árboles binarios al mismo tiempo. Se accederá al nodo solo cuando los nodos correspondientes en los dos árboles binarios no estén vacíos, por lo que el número de nodos visitados no excederá el número de nodos en el árbol binario más pequeño.

Complejidad espacial: O (min (m, n)), donde myn son el número de nodos de los dos árboles binarios respectivamente. La complejidad del espacio depende de la cantidad de elementos en la cola, que no excederá la cantidad de nodos en el árbol binario más pequeño.

En segundo lugar, complete el siguiente puntero del nodo derecho de cada nodo

116. Completar el siguiente puntero de nodo derecho de cada nodo: LeetCode https://leetcode.cn/problems/populated-next-right-pointers-in-each-node/?plan=algorithms&plan_progress=gzwnnxs

 1. Recorrido de nivel:

La pregunta en sí requiere que conectemos los nodos en cada nivel del árbol binario para formar una lista vinculada. Por lo tanto, la forma intuitiva es que podemos realizar un recorrido jerárquico del árbol binario. Durante el proceso de recorrido jerárquico, sacaremos los nodos en cada nivel del árbol binario, los atravesaremos y los conectaremos.

El recorrido jerárquico se basa en la búsqueda en amplitud. La diferencia entre esta y la búsqueda en amplitud es que la búsqueda en amplitud solo seleccionará un nodo para la expansión a la vez, mientras que el recorrido jerárquico eliminará todos los elementos en la cola para la expansión. cada vez Esto puede Se garantiza que los elementos sacados de la cola y atravesados ​​cada vez pertenezcan a la misma capa, por lo que podemos modificar el siguiente puntero de cada nodo durante el proceso transversal y expandir la nueva cola de la siguiente capa.

class Solution {
public:
    Node* connect(Node* root) {
        if (root == nullptr) {
            return root;
        }
        
        // 初始化队列同时将第一层节点加入队列中,即根节点
        queue<Node*> Q;
        Q.push(root);
        
        // 外层的 while 循环迭代的是层数
        while (!Q.empty()) {
            
            // 记录当前队列大小
            int size = Q.size();
            
            // 遍历这一层的所有节点
            for(int i = 0; i < size; i++) {
                
                // 从队首取出元素
                Node* node = Q.front();
                Q.pop();
                
                // 连接
                if (i < size - 1) {
                    node->next = Q.front();
                }
                
                // 拓展下一层节点
                if (node->left != nullptr) {
                    Q.push(node->left);
                }
                if (node->right != nullptr) {
                    Q.push(node->right);
                }
            }
        }
        
        // 返回根节点
        return root;
    }
};

Análisis de complejidad

Complejidad del tiempo: O (N). Cada nodo será visitado una vez y sólo una vez, es decir, será sacado de la cola y se establecerá el siguiente puntero.

Complejidad espacial: O (N). Este es un árbol binario perfecto cuyo último nivel contiene N/2 nodos. La complejidad del recorrido en amplitud depende del número máximo de elementos en un nivel. En este caso la complejidad espacial es O(N).

2. Utilice el siguiente puntero establecido


Ideas

1) En un árbol, hay dos tipos de punteros siguientes.

El primer caso consiste en conectar dos nodos secundarios del mismo nodo principal. Se puede acceder a ellos directamente a través del mismo nodo, así que realice las siguientes operaciones para completar la conexión.

node.left.next = node.right


2) El segundo caso establece una conexión entre nodos hijos de diferentes padres, en este caso la conexión directa no es posible.

Si cada nodo tiene un puntero a su nodo principal, el siguiente nodo se puede encontrar a través de este puntero. Si el puntero no existe, la conexión se establece según las siguientes ideas:

Después de que se establezcan los siguientes punteros entre los nodos de nivel N, se establecen los siguientes punteros de los nodos de nivel N+1. Se puede acceder a todos los nodos en la misma capa a través del siguiente puntero, por lo que el siguiente puntero de la enésima capa se puede usar para establecer un siguiente puntero para el nodo de la capa N+1.

algoritmo

1) Comenzando desde el nodo raíz, dado que solo hay un nodo en la capa 0, no es necesario conectarse, simplemente cree un siguiente puntero para el nodo en la capa 1 directamente. Una cosa a tener en cuenta en este algoritmo es que cuando establecemos el siguiente puntero para el nodo de la enésima capa, estamos en la capa N-1. Cuando se establezcan todos los siguientes punteros de los nodos de la capa N, pase a la capa N y establezca los siguientes punteros de los nodos de la capa N+1.

2) Al atravesar los nodos de una determinada capa, se ha establecido el siguiente puntero del nodo de esta capa. Por lo tanto, solo necesitamos conocer el nodo más a la izquierda de esta capa y podemos recorrerlo en una lista vinculada sin usar una cola.

3) El pseudocódigo de la idea anterior es el siguiente:

leftmost = root
while (leftmost.left != null) {
    head = leftmost
    while (head.next != null) {
        1) Establish Connection 1
        2) Establish Connection 2 using next pointers
        head = head.next
    }
    leftmost = leftmost.left
}

4) Dos tipos de punteros siguientes.

        1. En el primer caso, los dos nodos secundarios pertenecen al mismo nodo principal, por lo que los siguientes punteros de los dos nodos secundarios se pueden establecer directamente a través del nodo principal.

node.left.next = node.right

        2. El segundo caso es el caso de conectar nodos secundarios entre diferentes nodos principales. Más específicamente, lo que está conectado es el hijo derecho del primer nodo padre y el hijo izquierdo del segundo nodo padre. Dado que el siguiente puntero se ha establecido en el nivel del nodo principal, el segundo nodo principal se puede encontrar directamente a través del siguiente puntero del primer nodo principal y luego se establece una conexión entre sus hijos.

node.right.next = node.next.left

5) Después de completar la conexión de la capa actual, ingrese a la siguiente capa y repita la operación hasta que todos los nodos estén conectados. Después de ingresar a la siguiente capa, debe actualizar el nodo más a la izquierda y luego atravesar todos los nodos en la capa comenzando desde el nuevo nodo más a la izquierda. Debido a que es un árbol binario perfecto, el nodo más a la izquierda debe ser el hijo izquierdo del nodo más a la izquierda de la capa actual. Si el hijo izquierdo del nodo más a la izquierda actual no existe, significa que se alcanzó el último nivel del árbol y se completó la conexión de todos los nodos.

class Solution {
public:
    Node* connect(Node* root) {
        if (root == nullptr) {
            return root;
        }
        
        // 从根节点开始
        Node* leftmost = root;
        
        while (leftmost->left != nullptr) {
            
            // 遍历这一层节点组织成的链表,为下一层的节点更新 next 指针
            Node* head = leftmost;
            
            while (head != nullptr) {
                
                // CONNECTION 1
                head->left->next = head->right;
                
                // CONNECTION 2
                if (head->next != nullptr) {
                    head->right->next = head->next->left;
                }
                
                // 指针向后移动
                head = head->next;
            }
            
            // 去下一层的最左的节点
            leftmost = leftmost->left;
        }
        
        return root;
    }
};

Análisis de complejidad

  • Complejidad del tiempo: O (N), cada nodo solo se visita una vez.

  • Complejidad del espacio: O (1), no es necesario almacenar nodos adicionales.

Supongo que te gusta

Origin blog.csdn.net/m0_63309778/article/details/126753319
Recomendado
Clasificación