Özyinelemesiz derinlik ilk arama algoritması


173

Ben ikili olmayan bir ağaç için özyinelemeli olmayan derinlik ilk arama algoritması arıyorum. Herhangi bir yardım çok takdir edilmektedir.


1
@Bart Kiers Genel olarak, etikete göre değerlendirilen bir ağaç.
biziclop

13
Derinlik ilk arama özyinelemeli bir algoritmadır. Aşağıdaki yanıtlar düğümleri özyinelemeli olarak araştırıyor, özyinelemelerini yapmak için sistemin çağrı yığınını kullanmıyor ve bunun yerine açık bir yığın kullanıyorlar.
Null Set

8
@Null Set No, sadece bir döngü. Tanımınıza göre, her bilgisayar programı özyinelemelidir. (Hangi, kelimenin belirli bir anlamda olduklarını.)
biziclop

1
@Null Set: Ağaç aynı zamanda özyinelemeli bir veri yapısıdır.
Gumbo

2
Yinelemenin daha az okunabilir olduğu düşünüldüğünde, yinelemeli yaklaşımlara göre yinelemenin temel yararı, çoğu sistem / programlama dilinin yığını korumak için uyguladığı maksimum yığın boyutu / yineleme derinliği kısıtlamalarından kaçınabilmenizdir. Bir bellek içi yığını ile, yığınınız yalnızca programınızın tüketmesine izin verilen bellek miktarı ile sınırlıdır, bu da genellikle maksimum çağrı yığını boyutundan çok daha büyük bir yığına izin verir.
John B

Yanıtlar:


313

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
}

İkisinin simetrisi oldukça havalı.

Güncelleme: Belirtildiği gibi take_first(), listedeki ilk öğeyi kaldırır ve döndürür.


11
Yinelemesiz olarak yapıldıklarında ikisinin ne kadar benzer olduklarını belirtmek için +1 (özyinelemede oldukları zaman radikal olarak farklılar, ama yine de ...)
corsiKa

3
Ve sonra simetri eklemek için, saçak olarak bir min öncelik sırası kullanırsanız, tek kaynaklı en kısa yol bulucunuz vardır.
Mark Peters

10
BTW, .first()işlev ayrıca öğeyi listeden kaldırır. İster shift()birçok dilde. pop()ayrıca çalışır ve alt düğümleri soldan sağa yerine sağdan sola doğru döndürür.
Ariel

5
IMO, DFS algo biraz yanlış. Hepsi birbirine bağlı 3 köşe düşünün. İlerleme olmalıdır: gray(1st)->gray(2nd)->gray(3rd)->blacken(3rd)->blacken(2nd)->blacken(1st). Ancak kodunuz üretir: gray(1st)->gray(2nd)->gray(3rd)->blacken(2nd)->blacken(3rd)->blacken(1st).
batman

3
@learner Örneğinizi yanlış anlıyor olabilirim, ancak hepsi birbirine bağlıysa, bu gerçekten bir ağaç değildir.
biziclop

40

Henüz ziyaret edilmemiş düğümleri tutan bir yığın kullanırsınız :

stack.push(root)
while !stack.isEmpty() do
    node = stack.pop()
    for each node.childNodes do
        stack.push(stack)
    endfor
    // …
endwhile

2
@ Gumum merak ediyorum, eğer döngüleri olan bir grafikse. Bu işe yarayabilir mi? Ben sadece yığına dulplicated düğüm eklemek için önleyebilirsiniz düşünüyorum ve işe yarayabilir. Yapacağım düğümün tüm komşularını işaretlemek if (nodes are not marked)ve yığına itilmeye uygun olup olmadığına karar vermek için a eklemek . Bu işe yarayabilir mi?
Alston

1
@Stallman Daha önce ziyaret ettiğiniz düğümleri hatırlayabilirsiniz. Daha sonra yalnızca henüz ziyaret etmediğiniz düğümleri ziyaret ederseniz, herhangi bir döngü yapmazsınız.
Gumbo

@Gumbo Ne demek istiyorsun doing cycles? Bence sadece DFS sırasını istiyorum. Bu doğru mu değil mi, teşekkür ederim.
Alston

Sadece bir yığın (LIFO) kullanmanın önce derinlik geçişi anlamına geldiğini belirtmek istedim. Önce genişlik kullanmak istiyorsanız, bunun yerine bir kuyruk (FIFO) kullanın.
Per Lundberg

3
En popüler @biziclop cevabı olarak eşdeğer bir koda sahip olmak, alt notaları ters sırada ( for each node.childNodes.reverse() do stack.push(stack) endfor) itmeniz gerektiğini belirtmek gerekir . Muhtemelen istediğiniz şey de budur. Neden bu videoda olduğu gibi güzel bir açıklama: youtube.com/watch?v=cZPXfl_tUkA endfor
Mariusz Pawelski

32

Üst düğümlere işaretçileriniz varsa, bunu ek bellek olmadan yapabilirsiniz.

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

Alt düğümlerin kardeş işaretçiler yerine bir dizi olarak depolanması durumunda, bir sonraki kardeşin şu şekilde bulunabileceğini unutmayın:

def next_sibling(node):
    try:
        i =    node.parent.child_nodes.index(node)
        return node.parent.child_nodes[i+1]
    except (IndexError, AttributeError):
        return None

Bu, iyi bir çözümdür, çünkü bir liste veya yığının ek belleğini veya manipülasyonunu kullanmaz (özyinelemeyi önlemek için bazı iyi nedenler). Bununla birlikte, sadece ağaç düğümlerinin ebeveynlerine bağlantıları varsa mümkündür.
joeytwiddle

Teşekkür ederim. Bu algoritma harika. Ancak bu sürümde, ziyaret işlevinde düğümün belleğini silemezsiniz. Bu algoritma "first_child" işaretçisini kullanarak ağacı tek bağlantılı listeye dönüştürebilir. Daha sonra üzerinden geçebilir ve özyineleme olmadan düğümün belleğini boşaltabilirsiniz.
puchu

6
"Üst düğümlere işaretçiler varsa, ek bellek olmadan yapabilirsiniz": işaretçiyi üst düğümlere depolamak bazı ek bellek kullanır ...
rptr

1
@ rptr87 bu işaretçiler dışında ek bellek olmadan net değilse.
Abhinav Gauniyal

Bu, düğümün mutlak kök olmadığı, ancak kolayca sabitlenebildiği kısmi ağaçlar için başarısız olur while not node.next_sibling or node is root:.
Basel Shishani

5

Düğümlerinizi izlemek için bir yığın kullanın

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)

}

1
@Dave O. Hayır, çünkü ziyaret edilen düğümün çocuklarını zaten orada olan her şeyin önüne geri itersiniz.
04'te biziclop

Ben sonra push_back anlambilim yanlış yorum gerekir .
Dave O.

@ Çok iyi bir noktanız var mı? "Kuyruğun geri kalanını geri itmek" değil, "arkaya itmek" olduğunu düşünüyordum. Uygun şekilde düzenleyeceğim.
corsiKa

Öne doğru itiyorsanız, bir yığın olmalıdır.
uçuş

@Timmy evet orada ne düşündüğümden emin değilim. @quasiverse Normalde bir kuyruğu FIFO kuyruğu olarak düşünürüz. Bir yığın bir LIFO kuyruğu olarak tanımlanır.
corsiKa

4

"Bir yığın kullanmak" iken olabilir gerçekte, yapmacık mülakat sorusu için çalışmak, sadece bir özyinelemeli programı perde arkasında ne açıkça yapıyor.

Özyineleme yerleşik programları kullanır. Bir işlevi çağırdığınızda, bağımsız değişkenleri işleve yığının üzerine iter ve işlev döndüğünde program yığınını açarak bunu yapar.


7
Önemli bir fark ile iplik yığını ciddi şekilde sınırlıdır ve özyinelemesiz algoritma çok daha ölçeklenebilir yığın kullanır.
Yam Marcovic

1
Bu sadece tartışmalı bir durum değil. Mevcut yinelemeli çağrı eşdeğerlerine göre önemli performans kazançları elde etmek için C # ve JavaScript'te birkaç kez böyle teknikler kullandım. Çoğu zaman özyinelemeyi, çağrı yığınını kullanmak yerine bir yığınla yönetmenin çok daha hızlı ve daha az kaynak yoğun olması söz konusudur. Programcının özel bir yığına ne yerleştirileceği konusunda pratik kararlar verebilmesi için bir yığının üzerine çağrı bağlamının yerleştirilmesinde çok fazla masraf vardır.
Jason Jackson

4

Biziclops büyük cevaba dayanan bir ES6 uygulaması:

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);
  }
}


3
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);

                }
            }

Genel mantık, bir düğümü (kökten başlayarak) Yığına, Pop () ve Print () değerine itmektir. Daha sonra çocukları varsa (sol ve sağ) onları yığına itin - önce Sol çocuğu ziyaret etmek için önce (düğümü ziyaret ettikten sonra) Sağa basın. Yığın boşken () Ön Siparişteki tüm düğümleri ziyaret etmiş olacaksınız.


2

ES6 jeneratörlerini kullanan özyinelemesiz DFS

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 
  }    
}

Belirli bir düğümün tüm ulaşılabilir torunları işlendiğinde kolayca algılamak ve liste / yığındaki geçerli yolu korumak için tipik özyinelemeli olmayan DFS'den sapar .


1

Bir grafikteki her düğüm ziyaret edildiğinde bir bildirim yürütmek istediğinizi varsayalım. Basit özyinelemeli uygulama:

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
}

Tamam, şimdi örneğiniz çalışmadığından yığın tabanlı bir uygulama istiyorsunuz. Karmaşık grafikler, bunun programınızın yığınını üflemesine neden olabilir ve özyinelemeli olmayan bir sürüm uygulamanız gerekir. En büyük sorun ne zaman bildirim yapılacağını bilmek.

Aşağıdaki sözde kod çalışır (okunabilirlik için Java ve C ++ karışımı):

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);
  }
}

Karmaşık görünüyor, ancak bildirimleri yayınlamak için gereken ekstra mantık var çünkü ziyaretin tersini bildirmeniz gerekiyor - DFS kökten başlıyor, ancak uygulanması çok basit olan BFS'nin aksine son bildiriyor.

Tekmeler için aşağıdaki grafiği deneyin: düğümler s, t, v ve w'dir. yönlendirilmiş kenarlar şunlardır: s-> t, s-> v, t-> w, v-> w ve v-> t. Kendi DFS uygulamanızı çalıştırın ve düğümlerin ziyaret edilme sırası şu olmalıdır: w, t, v, s DFS'nin beceriksiz bir uygulaması, önce t'yi bildirebilir ve bu bir hatayı gösterir. Yinelenen bir DFS uygulaması her zaman en son w'ye ulaşacaktır.


1

TAM örnek Yığın olmadan ÇALIŞMA kodu:

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);
}}

çıktı: Genişlik İlk Geçiş - köşe 2'den başlayarak: 2 0 3 1 4 Derinlik İlk Geçiş - köşe 2'den başlayarak: 2 3 4 1 0


0

Bir yığın kullanabilirsiniz. Bitişiklik Matrisi ile grafikler uyguladım:

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();
        }
    }
}

0

Java'da DFS yinelemesi:

//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;
}

Soru açıkça ikili olmayan bir ağaç
user3743222

Sonsuz döngüden kaçınmak için ziyaret edilmiş bir haritaya ihtiyacınız var
spiralmoon

0

http://www.youtube.com/watch?v=zLZhSSXAwxI

Sadece bu videoyu izledim ve uygulamaya koyulduk. Anlamak kolay görünüyor. Lütfen bunu eleştir.

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()
}

0

Kullanarak Stack, izlenmesi gereken adımlar şunlardır: Yığındaki ilk tepe noktasını itin,

  1. Mümkünse bitişik olmayan bir tepe noktasını ziyaret edin, işaretleyin ve yığının üzerine itin.
  2. 1. adımı izleyemiyorsanız, mümkünse yığından bir tepe noktası açın.
  3. 1. veya 2. adımı uygulayamıyorsanız, işleminiz tamamlanmıştır.

Yukarıdaki adımları izleyen Java programı şöyledir:

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;
}

0
        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);
            }
        }

0

@ Biziclop'un cevabına dayanan sahte kod:

  • Yalnızca temel yapıları kullanma: değişkenler, diziler, if, while ve for
  • Fonksiyonlar getNode(id)vegetChildren(id)
  • Bilinen düğüm sayısını varsayarsak N

Not: 0 değil, 1 dizini dizin oluşturma kullanın.

Genişlik-ilk

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

Derinlik öncelikli

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

0

Burada, hem yinelemeli hem de yinelemesiz yöntemleri izleyen ve aynı zamanda keşif ve bitiş zamanını hesaplayan , ancak kenar boşluğu olmayan DFS'yi gösteren bir java programı bağlantısı .

    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);
                }
            }
        }
    }
}

Tam kaynak burada .


0

Python uygulamamı uzun çözümler listesine eklemek istedim. Bu özyinelemeli olmayan algoritmanın keşif ve bitmiş olayları vardır.


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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.