Algoritma pencarian pertama kedalaman non-rekursif


173

Saya mencari algoritma pencarian kedalaman non-rekursif pertama untuk pohon non-biner. Bantuan apa pun sangat kami hargai.


1
@ Bart Kiers Pohon secara umum, dilihat dari tag.
biziclop

13
Kedalaman pencarian pertama adalah algoritma rekursif. Jawaban di bawah ini secara rekursif menjelajahi node, mereka hanya tidak menggunakan stack panggilan sistem untuk melakukan rekursi mereka, dan menggunakan stack eksplisit sebagai gantinya.
Null Set

8
@Null Set Tidak, itu hanya satu lingkaran. Menurut definisi Anda, setiap program komputer bersifat rekursif. (Yang, dalam arti tertentu, kata mereka.)
biziclop

1
@Null Set: Pohon juga merupakan struktur data rekursif.
Gumbo

2
@MuhammadUmer manfaat utama dari iteratif daripada pendekatan rekursif ketika iteratif dianggap kurang dapat dibaca adalah bahwa Anda dapat menghindari ukuran tumpukan maksimum / kendala kedalaman rekursi yang diterapkan oleh kebanyakan sistem / bahasa pemrograman untuk melindungi stack. Dengan tumpukan memori, tumpukan Anda hanya dibatasi oleh jumlah memori yang diizinkan untuk dikonsumsi oleh program Anda, yang biasanya memungkinkan tumpukan lebih besar dari ukuran tumpukan panggilan maksimum.
John B

Jawaban:


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
}

Simetri keduanya cukup keren.

Pembaruan: Seperti yang ditunjukkan, take_first()menghapus dan mengembalikan elemen pertama dalam daftar.


11
+1 untuk mencatat betapa mirip keduanya ketika dilakukan secara non-rekursif (seolah-olah mereka sangat berbeda ketika mereka rekursif, tapi masih ...)
corsiKa

3
Dan kemudian untuk menambahkan ke simetri, jika Anda menggunakan antrian prioritas min sebagai pinggiran, Anda memiliki pencari jalan terpendek satu sumber.
Mark Peters

10
BTW, .first()fungsinya juga menghilangkan elemen dari daftar. Seperti shift()dalam banyak bahasa. pop()juga berfungsi, dan mengembalikan node anak dalam urutan kanan-ke-kiri, bukan kiri-ke-kanan.
Ariel

5
IMO, DFS algo sedikit salah. Bayangkan 3 simpul semua terhubung satu sama lain. Kemajuan harus: gray(1st)->gray(2nd)->gray(3rd)->blacken(3rd)->blacken(2nd)->blacken(1st). Tapi kode Anda menghasilkan: gray(1st)->gray(2nd)->gray(3rd)->blacken(2nd)->blacken(3rd)->blacken(1st).
batman

3
@ LEARNER Saya mungkin salah paham contoh Anda tetapi jika mereka semua terhubung satu sama lain, itu tidak benar-benar pohon.
biziclop

40

Anda akan menggunakan tumpukan yang menyimpan simpul-simpul yang belum dikunjungi:

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

2
@ Gumbo Saya ingin tahu apakah itu grafik dengan cycyles. Bisakah ini bekerja? Saya pikir saya bisa saja menghindari menambahkan simpul titik ke tumpukan dan itu bisa bekerja. Apa yang akan saya lakukan adalah untuk menandai semua tetangga dari node yang muncul dan menambahkan if (nodes are not marked)untuk menilai apakah itu mungkin untuk didorong ke stack. Bisakah itu berhasil?
Alston

1
@Stallman Anda dapat mengingat node yang sudah Anda kunjungi. Jika kemudian Anda hanya mengunjungi node yang belum Anda kunjungi, Anda tidak akan melakukan siklus apa pun.
Gumbo

@ Gumbo Apa maksudmu doing cycles? Saya pikir saya hanya ingin urutan DFS. Benar atau tidak, terima kasih.
Alston

Hanya ingin menunjukkan bahwa menggunakan stack (LIFO) berarti kedalaman traversal pertama. Jika Anda ingin menggunakan breadth-first, gunakan antrian (FIFO).
Per Lundberg

3
Perlu dicatat bahwa untuk memiliki kode setara sebagai jawaban @biziclop paling populer, Anda perlu mendorong catatan anak dalam urutan terbalik ( for each node.childNodes.reverse() do stack.push(stack) endfor). Ini juga mungkin yang Anda inginkan. Penjelasan yang bagus mengapa seperti itu ada di video ini: youtube.com/watch?v=cZPXfl_tUkA endfor
Mariusz Pawelski

32

Jika Anda memiliki pointer ke node induk, Anda dapat melakukannya tanpa memori tambahan.

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

Perhatikan bahwa jika child node disimpan sebagai array alih-alih melalui sibling pointer, saudara berikutnya dapat ditemukan sebagai:

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

Ini adalah solusi yang baik karena tidak menggunakan memori tambahan atau manipulasi daftar atau tumpukan (beberapa alasan bagus untuk menghindari rekursi). Namun itu hanya mungkin jika simpul pohon memiliki tautan ke orang tua mereka.
joeytwiddle

Terima kasih. Algoritma ini sangat bagus. Tetapi dalam versi ini Anda tidak dapat menghapus memori simpul dalam fungsi kunjungan. Algoritma ini dapat mengonversi susunan pohon ke daftar yang ditautkan dengan menggunakan pointer "first_child". Daripada Anda bisa berjalan melaluinya dan membebaskan memori simpul tanpa rekursi.
puchu

6
"Jika Anda memiliki pointer ke node induk, Anda dapat melakukannya tanpa memori tambahan": menyimpan pointer ke node induk menggunakan beberapa "memori tambahan" ...
rptr

1
@ rptr87 jika tidak jelas, tanpa memori tambahan selain dari pointer tersebut.
Abhinav Gauniyal

Ini akan gagal untuk pohon parsial di mana simpul bukan akar mutlak, tetapi dapat dengan mudah diperbaiki while not node.next_sibling or node is root:.
Basel Shishani

5

Gunakan tumpukan untuk melacak node Anda

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
@Dapat O. Tidak, karena Anda mendorong kembali anak-anak dari simpul yang dikunjungi di depan semua yang sudah ada.
biziclop

Saya pasti salah mengartikan semantik push_back saat itu.
Dave O.

@ Sudahkah Anda memiliki poin yang sangat bagus. Saya berpikir itu harus "mendorong sisa antrian kembali" bukan "mendorong ke belakang." Saya akan mengedit dengan tepat.
corsiKa

Jika Anda mendorong ke depan itu harus tumpukan.
penerbangan

@ Timmy ya saya tidak yakin apa yang saya pikirkan di sana. @ quasiverse Kami biasanya menganggap antrian sebagai antrian FIFO. Tumpukan didefinisikan sebagai antrian LIFO.
corsiKa

4

Meskipun "menggunakan tumpukan" mungkin berfungsi sebagai jawaban untuk pertanyaan wawancara yang dibuat-buat, pada kenyataannya, itu hanya melakukan secara eksplisit apa yang dilakukan oleh program rekursif di belakang layar.

Rekursi menggunakan stack bawaan program. Ketika Anda memanggil fungsi, itu mendorong argumen ke fungsi ke stack dan ketika fungsi mengembalikannya melakukannya dengan membuka tumpukan program.


7
Dengan perbedaan penting bahwa tumpukan ulir sangat terbatas, dan algoritma non-rekursif akan menggunakan tumpukan jauh lebih scalable.
Yam Marcovic

1
Ini bukan hanya situasi yang dibuat-buat. Saya telah menggunakan teknik seperti ini pada beberapa kesempatan dalam C # dan JavaScript untuk mendapatkan keuntungan kinerja yang signifikan atas equivelant panggilan rekursif yang ada. Sering terjadi bahwa mengelola rekursi dengan tumpukan alih-alih menggunakan panggilan tumpukan jauh lebih cepat dan kurang intensif sumber daya. Ada banyak overhead yang terlibat dalam menempatkan konteks panggilan ke tumpukan vs programmer mampu membuat keputusan praktis tentang apa yang akan ditempatkan pada tumpukan kustom.
Jason Jackson

4

Implementasi ES6 berdasarkan jawaban hebat 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);
  }
}


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

                }
            }

Logikanya umum adalah, dorong sebuah simpul (mulai dari root) ke nilai Stack, Pop () dan Print (). Kemudian jika memiliki anak (kiri dan kanan) dorong mereka ke tumpukan - dorong Kanan terlebih dahulu sehingga Anda akan mengunjungi Kiri anak terlebih dahulu (setelah mengunjungi simpul itu sendiri). Ketika tumpukan kosong () Anda akan mengunjungi semua simpul di Pra-Pemesanan.


2

DFS non-rekursif menggunakan generator 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 
  }    
}

Ini menyimpang dari DFS non-rekursif khas untuk dengan mudah mendeteksi ketika semua keturunan yang terjangkau dari node yang diberikan diproses dan untuk mempertahankan jalur saat ini dalam daftar / tumpukan.


1

Misalkan Anda ingin menjalankan notifikasi ketika setiap node dalam grafik dikunjungi. Implementasi rekursif sederhana adalah:

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
}

Oke, sekarang Anda menginginkan implementasi berbasis stack karena contoh Anda tidak berfungsi. Grafik kompleks mungkin misalnya menyebabkan ini untuk meniup tumpukan program Anda dan Anda perlu menerapkan versi non-rekursif. Masalah terbesar adalah mengetahui kapan harus mengeluarkan pemberitahuan.

Kode pseudo berikut berfungsi (campuran Java dan C ++ untuk keterbacaan):

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

Itu terlihat rumit tetapi logika tambahan yang diperlukan untuk mengeluarkan pemberitahuan ada karena Anda perlu memberi tahu dengan urutan kunjungan yang terbalik - DFS dimulai pada saat root tetapi memberi tahu yang terakhir, tidak seperti BFS yang sangat mudah diterapkan.

Untuk tendangan, coba grafik berikut: node adalah s, t, v dan w. tepi diarahkan adalah: s-> t, s-> v, t-> w, v-> w, dan v-> t. Jalankan implementasi DFS Anda sendiri dan urutan node yang harus dikunjungi harus: w, t, v, s Implementasi DFS yang canggung mungkin akan memberi tahu t terlebih dahulu dan itu menunjukkan bug. Implementasi DFS secara rekursif akan selalu mencapai yang terakhir.


1

Contoh lengkap kode KERJA, tanpa tumpukan:

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- mulai dari vertex 2: 2 0 3 1 4 Kedalaman First Traversal- mulai dari vertex 2: 2 3 4 1 0


0

Anda dapat menggunakan tumpukan. Saya menerapkan grafik dengan Adjacency Matrix:

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

Berulang DFS di Jawa:

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

Pertanyaan secara eksplisit meminta pohon non biner
user3743222

Anda memerlukan peta yang dikunjungi untuk menghindari infinite loop
spiralmoon

0

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

Baru saja menonton video ini dan keluar dengan implementasi. Tampaknya mudah bagi saya untuk mengerti. Tolong kritik ini.

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

Dengan menggunakan Stack, berikut adalah langkah-langkah yang harus diikuti: Dorong titik pertama pada tumpukan lalu,

  1. Jika memungkinkan, kunjungi verteks yang belum dikunjungi yang berdekatan, tandai, dan dorong pada tumpukan.
  2. Jika Anda tidak dapat mengikuti langkah 1, maka, jika mungkin, keluarkan sebuah simpul dari tumpukan.
  3. Jika Anda tidak dapat mengikuti langkah 1 atau langkah 2, Anda selesai.

Inilah program Java yang mengikuti langkah-langkah di atas:

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

Kode semu berdasarkan pada jawaban @ biziclop:

  • Hanya menggunakan konstruksi dasar: variabel, array, jika, sementara dan untuk
  • Fungsi getNode(id)dangetChildren(id)
  • Dengan asumsi jumlah node yang diketahui N

CATATAN: Saya menggunakan array-indexing dari 1, bukan 0.

Luasnya pertama

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

Kedalaman-pertama

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

Berikut ini adalah tautan ke program java yang menampilkan DFS mengikuti metode rekcursive dan non-reccursive dan juga menghitung waktu penemuan dan penyelesaian , tetapi tidak ada edge laleling.

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

Sumber lengkap di sini .


0

Hanya ingin menambahkan implementasi python saya ke daftar panjang solusi. Algoritma non-rekursif ini memiliki penemuan dan penyelesaian peristiwa.


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)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.