Pencarian Pertama Kedalaman di Java

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan meneroka carian Kedalaman-pertama di Java.

Pencarian Depth-first (DFS) adalah algoritma traversal yang digunakan untuk struktur data Tree dan Graph. Pencarian pertama yang mendalam dilakukan di setiap cabang sebelum bergerak untuk meneroka cabang lain .

Pada bahagian seterusnya, pertama-tama kita akan melihat pelaksanaan untuk Pohon dan kemudian Grafik.

Untuk melihat bagaimana menerapkan struktur ini di Jawa, lihat tutorial sebelumnya mengenai Binary Tree and Graph.

2. Pencarian Kedalaman Pokok Pertama

Terdapat tiga pesanan berbeza untuk melintasi pokok menggunakan DFS:

  1. Melintasi Preorder
  2. Melintasi Inorder
  3. Melintasi Posorder

2.1. Melintasi Preorder

Dalam traversal preorder, kita melintasi akar terlebih dahulu, kemudian subtrees kiri dan kanan.

Kita hanya boleh melaksanakan traversal preorder menggunakan rekursi :

  • Lawati nod semasa
  • Traverse meninggalkan anak pohon
  • Traverse kanan anak pohon
public void traversePreOrder(Node node) { if (node != null) { visit(node.value); traversePreOrder(node.left); traversePreOrder(node.right); } }

Kami juga dapat melaksanakan traversal preorder tanpa berulang.

Untuk melaksanakan traversal preorder berulang, kita memerlukan Stack , dan kita akan melalui langkah-langkah berikut:

  • Menolak akar dalam s kami tack
  • Sementara timbunan tidak kosong
    • Pop nod semasa
    • Lawati nod semasa
    • Tolak anak kanan , kemudian anak kiri untuk menumpuk
public void traversePreOrderWithoutRecursion() { Stack stack = new Stack(); Node current = root; stack.push(root); while(!stack.isEmpty()) { current = stack.pop(); visit(current.value); if(current.right != null) { stack.push(current.right); } if(current.left != null) { stack.push(current.left); } } }

2.2. Melintasi Inorder

Untuk traversal inorder, kita melintasi subtree kiri terlebih dahulu, kemudian root, dan akhirnya subtree kanan .

Inorder traversal untuk pohon carian binari bermaksud melintasi nod dengan menaikkan urutan nilainya.

Kita hanya boleh melaksanakan traversal inorder menggunakan rekursi:

public void traverseInOrder(Node node) { if (node != null) { traverseInOrder(node.left); visit(node.value); traverseInOrder(node.right); } }

Kami juga dapat melaksanakan traversal inorder tanpa berulang , juga:

  • Push akar nod ke s tack
  • Walaupun taktik kosong
    • Terus tolak anak kiri ke tumpukan, hingga kita mencapai anak paling kiri simpul semasa
    • Lawati nod semasa
    • Tolak anak kanan ke timbunan
public void traverseInOrderWithoutRecursion() { Stack stack = new Stack(); Node current = root; stack.push(root); while(! stack.isEmpty()) { while(current.left != null) { current = current.left; stack.push(current); } current = stack.pop(); visit(current.value); if(current.right != null) { current = current.right; stack.push(current); } } }

2.3. Melintasi Posorder

Akhirnya, semasa melintasi postorder, kita melintasi subtree kiri dan kanan sebelum kita melintasi akarnya .

Kami boleh mengikuti penyelesaian rekursif kami sebelumnya :

public void traversePostOrder(Node node) { if (node != null) { traversePostOrder(node.left); traversePostOrder(node.right); visit(node.value); } }

Atau, kita juga boleh melaksanakan traversal pasca pesanan tanpa berulang :

  • Push akar nod dalam s tack
  • Walaupun taktik kosong
    • Periksa sama ada kita telah melintasi subtree kiri dan kanan
    • Sekiranya tidak, tolak anak kanan dan anak kiri ke timbunan
public void traversePostOrderWithoutRecursion() { Stack stack = new Stack(); Node prev = root; Node current = root; stack.push(root); while (!stack.isEmpty()) { current = stack.peek(); boolean hasChild = (current.left != null || current.right != null); boolean isPrevLastChild = (prev == current.right || (prev == current.left && current.right == null)); if (!hasChild || isPrevLastChild) { current = stack.pop(); visit(current.value); prev = current; } else { if (current.right != null) { stack.push(current.right); } if (current.left != null) { stack.push(current.left); } } } }

3. Carian Kedalaman Grafik pertama

Perbezaan utama antara grafik dan pokok ialah grafik mungkin mengandungi kitaran .

Oleh itu, untuk mengelakkan carian dalam kitaran, kami akan menandakan setiap simpul semasa kami mengunjunginya.

Kami akan melihat dua pelaksanaan untuk grafik DFS, dengan pengulangan, dan tanpa pengulangan.

3.1. Grafik DFS dengan Pengulangan

Pertama, mari kita mulakan dengan cara berulang:

  • Kami akan bermula dari simpul tertentu
  • Tandakan nod semasa sebagai dilawati
  • Lawati nod semasa
  • Melintasi bucu bersebelahan yang tidak dilawati
public void dfs(int start) { boolean[] isVisited = new boolean[adjVertices.size()]; dfsRecursive(start, isVisited); } private void dfsRecursive(int current, boolean[] isVisited) { isVisited[current] = true; visit(current); for (int dest : adjVertices.get(current)) { if (!isVisited[dest]) dfsRecursive(dest, isVisited); } }

3.2. Grafik DFS Tanpa Pengulangan

Kita juga dapat melaksanakan grafik DFS tanpa pengulangan. Kami hanya akan menggunakan Stack :

  • Kami akan bermula dari simpul tertentu
  • Tolak nod permulaan ke dalam timbunan
  • Sementara Stack tidak kosong
    • Tandakan nod semasa sebagai dilawati
    • Lawati nod semasa
    • Tolak bucu bersebelahan yang tidak dilawati
public void dfsWithoutRecursion(int start) { Stack stack = new Stack(); boolean[] isVisited = new boolean[adjVertices.size()]; stack.push(start); while (!stack.isEmpty()) { int current = stack.pop(); isVisited[current] = true; visit(current); for (int dest : adjVertices.get(current)) { if (!isVisited[dest]) stack.push(dest); } } }

3.4. Urutan Topologi

Terdapat banyak aplikasi untuk carian kedalaman grafik pertama. Salah satu aplikasi terkenal untuk DFS adalah Topologi Urut.

Urutan Topologi untuk graf yang diarahkan adalah susunan garis lurus dari bucunya sehingga bagi setiap tepi simpul sumber datang sebelum tujuan.

Untuk mendapatkan urutan topologi, kami memerlukan penambahan ringkas pada DFS yang baru kami laksanakan:

  • Kita perlu menyimpan bucu yang dikunjungi dalam susunan kerana jenis topologi adalah bucu yang dikunjungi dalam urutan terbalik
  • Kami mendorong simpul yang dikunjungi ke tumpukan hanya setelah melintasi semua jirannya
public List topologicalSort(int start) { LinkedList result = new LinkedList(); boolean[] isVisited = new boolean[adjVertices.size()]; topologicalSortRecursive(start, isVisited, result); return result; } private void topologicalSortRecursive(int current, boolean[] isVisited, LinkedList result) { isVisited[current] = true; for (int dest : adjVertices.get(current)) { if (!isVisited[dest]) topologicalSortRecursive(dest, isVisited, result); } result.addFirst(current); }

4. Kesimpulan

Dalam artikel ini, kami membincangkan carian pertama untuk struktur data Pohon dan Grafik.

Kod sumber penuh terdapat di GitHub.