Sto cercando un algoritmo di ricerca di profondità non ricorsivo per un albero non binario. Qualsiasi aiuto è molto apprezzato.
Sto cercando un algoritmo di ricerca di profondità non ricorsivo per un albero non binario. Qualsiasi aiuto è molto apprezzato.
Risposte:
DFS:
list nodes_to_visit = {root};
while( nodes_to_visit isn't empty ) {
currentnode = nodes_to_visit.take_first();
nodes_to_visit.prepend( currentnode.children );
//do something
}
BFS:
list nodes_to_visit = {root};
while( nodes_to_visit isn't empty ) {
currentnode = nodes_to_visit.take_first();
nodes_to_visit.append( currentnode.children );
//do something
}
La simmetria dei due è piuttosto interessante.
Aggiornamento: come sottolineato, take_first()
rimuove e restituisce il primo elemento nell'elenco.
.first()
funzione rimuove anche l'elemento dall'elenco. Come shift()
in molte lingue. pop()
funziona anche e restituisce i nodi figlio nell'ordine da destra a sinistra anziché da sinistra a destra.
gray(1st)->gray(2nd)->gray(3rd)->blacken(3rd)->blacken(2nd)->blacken(1st)
. Ma il codice produce: gray(1st)->gray(2nd)->gray(3rd)->blacken(2nd)->blacken(3rd)->blacken(1st)
.
Dovresti usare uno stack che contiene i nodi che non sono stati ancora visitati:
stack.push(root)
while !stack.isEmpty() do
node = stack.pop()
for each node.childNodes do
stack.push(stack)
endfor
// …
endwhile
if (nodes are not marked)
per giudicare se è opportuno che sia spinto nello stack. Può funzionare?
doing cycles
? Penso di volere solo l'ordine di DFS. È giusto o no, grazie.
for each node.childNodes.reverse() do stack.push(stack) endfor
). Questo è probabilmente anche quello che vuoi. Bella spiegazione del perché è così in questo video: youtube.com/watch?v=cZPXfl_tUkA endfor
Se si dispone di puntatori a nodi padre, è possibile farlo senza memoria aggiuntiva.
def dfs(root):
node = root
while True:
visit(node)
if node.first_child:
node = node.first_child # walk down
else:
while not node.next_sibling:
if node is root:
return
node = node.parent # walk up ...
node = node.next_sibling # ... and right
Si noti che se i nodi figlio sono archiviati come array anziché tramite puntatori di pari livello, il prossimo fratello può essere trovato come:
def next_sibling(node):
try:
i = node.parent.child_nodes.index(node)
return node.parent.child_nodes[i+1]
except (IndexError, AttributeError):
return None
while not node.next_sibling or node is root:
.
Usa uno stack per tracciare i tuoi nodi
Stack<Node> s;
s.prepend(tree.head);
while(!s.empty) {
Node n = s.poll_front // gets first node
// do something with q?
for each child of n: s.prepend(child)
}
Mentre "usare uno stack" potrebbe funzionare come risposta alla domanda di intervista forzata, in realtà sta facendo esplicitamente ciò che un programma ricorsivo fa dietro le quinte.
La ricorsione utilizza lo stack integrato dei programmi. Quando si chiama una funzione, inserisce gli argomenti nella funzione nello stack e quando la funzione ritorna lo fa facendo scattare lo stack del programma.
Un'implementazione di ES6 basata sulla grande risposta di biziclops:
root = {
text: "root",
children: [{
text: "c1",
children: [{
text: "c11"
}, {
text: "c12"
}]
}, {
text: "c2",
children: [{
text: "c21"
}, {
text: "c22"
}]
}, ]
}
console.log("DFS:")
DFS(root, node => node.children, node => console.log(node.text));
console.log("BFS:")
BFS(root, node => node.children, node => console.log(node.text));
function BFS(root, getChildren, visit) {
let nodesToVisit = [root];
while (nodesToVisit.length > 0) {
const currentNode = nodesToVisit.shift();
nodesToVisit = [
...nodesToVisit,
...(getChildren(currentNode) || []),
];
visit(currentNode);
}
}
function DFS(root, getChildren, visit) {
let nodesToVisit = [root];
while (nodesToVisit.length > 0) {
const currentNode = nodesToVisit.shift();
nodesToVisit = [
...(getChildren(currentNode) || []),
...nodesToVisit,
];
visit(currentNode);
}
}
PreOrderTraversal is same as DFS in binary tree. You can do the same recursion
taking care of Stack as below.
public void IterativePreOrder(Tree root)
{
if (root == null)
return;
Stack s<Tree> = new Stack<Tree>();
s.Push(root);
while (s.Count != 0)
{
Tree b = s.Pop();
Console.Write(b.Data + " ");
if (b.Right != null)
s.Push(b.Right);
if (b.Left != null)
s.Push(b.Left);
}
}
La logica generale è, spingere un nodo (a partire dalla radice) nel valore Stack, Pop () e Print (). Quindi se ha figli (sinistro e destro), spingili nello stack - premi prima Destra in modo da visitare prima il figlio Sinistro (dopo aver visitato il nodo stesso). Quando stack è vuoto () avrai visitato tutti i nodi in Preordine.
DFS non ricorsivo con generatori ES6
class Node {
constructor(name, childNodes) {
this.name = name;
this.childNodes = childNodes;
this.visited = false;
}
}
function *dfs(s) {
let stack = [];
stack.push(s);
stackLoop: while (stack.length) {
let u = stack[stack.length - 1]; // peek
if (!u.visited) {
u.visited = true; // grey - visited
yield u;
}
for (let v of u.childNodes) {
if (!v.visited) {
stack.push(v);
continue stackLoop;
}
}
stack.pop(); // black - all reachable descendants were processed
}
}
Si discosta dal tipico DFS non ricorsivo per rilevare facilmente quando tutti i discendenti raggiungibili di un determinato nodo sono stati elaborati e per mantenere il percorso corrente nell'elenco / stack.
Supponiamo di voler eseguire una notifica quando ogni nodo in un grafico viene visitato. La semplice implementazione ricorsiva è:
void DFSRecursive(Node n, Set<Node> visited) {
visited.add(n);
for (Node x : neighbors_of(n)) { // iterate over all neighbors
if (!visited.contains(x)) {
DFSRecursive(x, visited);
}
}
OnVisit(n); // callback to say node is finally visited, after all its non-visited neighbors
}
Ok, ora vuoi un'implementazione basata su stack perché il tuo esempio non funziona. Grafici complessi potrebbero, ad esempio, far esplodere lo stack del programma ed è necessario implementare una versione non ricorsiva. Il problema più grande è sapere quando inviare una notifica.
Il seguente pseudo-codice funziona (mix di Java e C ++ per leggibilità):
void DFS(Node root) {
Set<Node> visited;
Set<Node> toNotify; // nodes we want to notify
Stack<Node> stack;
stack.add(root);
toNotify.add(root); // we won't pop nodes from this until DFS is done
while (!stack.empty()) {
Node current = stack.pop();
visited.add(current);
for (Node x : neighbors_of(current)) {
if (!visited.contains(x)) {
stack.add(x);
toNotify.add(x);
}
}
}
// Now issue notifications. toNotifyStack might contain duplicates (will never
// happen in a tree but easily happens in a graph)
Set<Node> notified;
while (!toNotify.empty()) {
Node n = toNotify.pop();
if (!toNotify.contains(n)) {
OnVisit(n); // issue callback
toNotify.add(n);
}
}
Sembra complicato ma la logica aggiuntiva necessaria per l'emissione delle notifiche esiste perché è necessario notificare in ordine inverso alla visita: DFS inizia da root ma lo notifica per ultimo, a differenza di BFS che è molto semplice da implementare.
Per i calci, prova il seguente grafico: i nodi sono s, t, v e w. i bordi diretti sono: s-> t, s-> v, t-> w, v-> w e v-> t. Esegui la tua implementazione di DFS e l'ordine in cui i nodi dovrebbero essere visitati deve essere: w, t, v, s Un'implementazione goffa di DFS potrebbe notificare t prima e ciò indica un errore. Un'implementazione ricorsiva di DFS avrebbe sempre raggiunto la fine.
Codice COMPLETO di esempio WORKING, senza stack:
import java.util.*;
class Graph {
private List<List<Integer>> adj;
Graph(int numOfVertices) {
this.adj = new ArrayList<>();
for (int i = 0; i < numOfVertices; ++i)
adj.add(i, new ArrayList<>());
}
void addEdge(int v, int w) {
adj.get(v).add(w); // Add w to v's list.
}
void DFS(int v) {
int nodesToVisitIndex = 0;
List<Integer> nodesToVisit = new ArrayList<>();
nodesToVisit.add(v);
while (nodesToVisitIndex < nodesToVisit.size()) {
Integer nextChild= nodesToVisit.get(nodesToVisitIndex++);// get the node and mark it as visited node by inc the index over the element.
for (Integer s : adj.get(nextChild)) {
if (!nodesToVisit.contains(s)) {
nodesToVisit.add(nodesToVisitIndex, s);// add the node to the HEAD of the unvisited nodes list.
}
}
System.out.println(nextChild);
}
}
void BFS(int v) {
int nodesToVisitIndex = 0;
List<Integer> nodesToVisit = new ArrayList<>();
nodesToVisit.add(v);
while (nodesToVisitIndex < nodesToVisit.size()) {
Integer nextChild= nodesToVisit.get(nodesToVisitIndex++);// get the node and mark it as visited node by inc the index over the element.
for (Integer s : adj.get(nextChild)) {
if (!nodesToVisit.contains(s)) {
nodesToVisit.add(s);// add the node to the END of the unvisited node list.
}
}
System.out.println(nextChild);
}
}
public static void main(String args[]) {
Graph g = new Graph(5);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(1, 2);
g.addEdge(2, 0);
g.addEdge(2, 3);
g.addEdge(3, 3);
g.addEdge(3, 1);
g.addEdge(3, 4);
System.out.println("Breadth First Traversal- starting from vertex 2:");
g.BFS(2);
System.out.println("Depth First Traversal- starting from vertex 2:");
g.DFS(2);
}}
output: Breadth First Traversal- a partire dal vertice 2: 2 0 3 1 4 Depth First Traversal- a partire dal vertice 2: 2 3 4 1 0
Puoi usare uno stack. Ho implementato grafici con la matrice di adiacenza:
void DFS(int current){
for(int i=1; i<N; i++) visit_table[i]=false;
myStack.push(current);
cout << current << " ";
while(!myStack.empty()){
current = myStack.top();
for(int i=0; i<N; i++){
if(AdjMatrix[current][i] == 1){
if(visit_table[i] == false){
myStack.push(i);
visit_table[i] = true;
cout << i << " ";
}
break;
}
else if(!myStack.empty())
myStack.pop();
}
}
}
DFS iterativo in Java:
//DFS: Iterative
private Boolean DFSIterative(Node root, int target) {
if (root == null)
return false;
Stack<Node> _stack = new Stack<Node>();
_stack.push(root);
while (_stack.size() > 0) {
Node temp = _stack.peek();
if (temp.data == target)
return true;
if (temp.left != null)
_stack.push(temp.left);
else if (temp.right != null)
_stack.push(temp.right);
else
_stack.pop();
}
return false;
}
http://www.youtube.com/watch?v=zLZhSSXAwxI
Ho appena visto questo video ed è uscito con l'implementazione. Mi sembra facile da capire. Per favore, critica questo.
visited_node={root}
stack.push(root)
while(!stack.empty){
unvisited_node = get_unvisited_adj_nodes(stack.top());
If (unvisited_node!=null){
stack.push(unvisited_node);
visited_node+=unvisited_node;
}
else
stack.pop()
}
Utilizzando Stack
, ecco i passaggi da seguire: Spingere il primo vertice nello stack quindi,
Ecco il programma Java seguendo i passaggi precedenti:
public void searchDepthFirst() {
// begin at vertex 0
vertexList[0].wasVisited = true;
displayVertex(0);
stack.push(0);
while (!stack.isEmpty()) {
int adjacentVertex = getAdjacentUnvisitedVertex(stack.peek());
// if no such vertex
if (adjacentVertex == -1) {
stack.pop();
} else {
vertexList[adjacentVertex].wasVisited = true;
// Do something
stack.push(adjacentVertex);
}
}
// stack is empty, so we're done, reset flags
for (int j = 0; j < nVerts; j++)
vertexList[j].wasVisited = false;
}
Stack<Node> stack = new Stack<>();
stack.add(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
System.out.print(node.getData() + " ");
Node right = node.getRight();
if (right != null) {
stack.push(right);
}
Node left = node.getLeft();
if (left != null) {
stack.push(left);
}
}
Pseudo-codice basato sulla risposta di @ biziclop:
getNode(id)
egetChildren(id)
N
NOTA: utilizzo l'indicizzazione di array da 1, non da 0.
Breadth-first
S = Array(N)
S[1] = 1; // root id
cur = 1;
last = 1
while cur <= last
id = S[cur]
node = getNode(id)
children = getChildren(id)
n = length(children)
for i = 1..n
S[ last+i ] = children[i]
end
last = last+n
cur = cur+1
visit(node)
end
In profondità
S = Array(N)
S[1] = 1; // root id
cur = 1;
while cur > 0
id = S[cur]
node = getNode(id)
children = getChildren(id)
n = length(children)
for i = 1..n
// assuming children are given left-to-right
S[ cur+i-1 ] = children[ n-i+1 ]
// otherwise
// S[ cur+i-1 ] = children[i]
end
cur = cur+n-1
visit(node)
end
Ecco un link a un programma java che mostra DFS seguendo metodi sia ricorsivi che non ricorsivi e anche calcolando il tempo di scoperta e di fine , ma nessun limite.
public void DFSIterative() {
Reset();
Stack<Vertex> s = new Stack<>();
for (Vertex v : vertices.values()) {
if (!v.visited) {
v.d = ++time;
v.visited = true;
s.push(v);
while (!s.isEmpty()) {
Vertex u = s.peek();
s.pop();
boolean bFinished = true;
for (Vertex w : u.adj) {
if (!w.visited) {
w.visited = true;
w.d = ++time;
w.p = u;
s.push(w);
bFinished = false;
break;
}
}
if (bFinished) {
u.f = ++time;
if (u.p != null)
s.push(u.p);
}
}
}
}
}
Fonte completa qui .
Volevo solo aggiungere la mia implementazione di Python alla lunga lista di soluzioni. Questo algoritmo non ricorsivo ha eventi di scoperta e terminati.
worklist = [root_node]
visited = set()
while worklist:
node = worklist[-1]
if node in visited:
# Node is finished
worklist.pop()
else:
# Node is discovered
visited.add(node)
for child in node.children:
worklist.append(child)