dfs深度优先搜索
Depth First Search (DFS) is an algorithm that searches a graph/tree, in a depth-wise manner.
深度优先搜索 ( DFS )是一种以深度方式搜索图/树的算法。
There are many ways through which we can traverse through a graph.
我们可以通过多种方式遍历图形。
The two most common methods that we can use are:
我们可以使用的两种最常见的方法是:
- Breadth-First Search (BFS) 广度优先搜索(BFS)
- Depth-First Search (DFS) 深度优先搜索(DFS)
Both of these methods are recursive in nature.
这两种方法本质上都是递归的。
The only difference is that a BFS first searches the breadth of the graph/tree, while a DFS searches from top to bottom first, before branching out.
唯一的区别是, BFS首先搜索图/树的宽度,而DFS首先从顶部到底部搜索,然后再进行分支。
Also, a Depth First Search will tell us if two nodes are reachable or not. If the algorithm terminates and we still haven’t found our answer, that means that the two nodes are not connected!
同样,深度优先搜索将告诉我们两个节点是否可达。 如果算法终止,但我们仍未找到答案,则意味着两个节点未连接!
In this article, let us take a look at the DFS Algorithm in detail.
在本文中,让我们详细了解DFS算法。
I will also show you an implementation of this algorithm in C so that you can get a programmer’s perspective on how you can write this algorithm.
我还将向您展示该算法在C语言中的实现,以便您可以从程序员的角度了解如何编写此算法。
深度优先搜索算法 (The Depth First Search Algorithm)
As I mentioned earlier, the depth-first search algorithm is recursive in nature.
如前所述,深度优先搜索算法本质上是递归的。
So, if you want to look for an element in the graph, the DFS procedure will first go as deep as possible from the current node, until you cannot go any further.
因此,如果要在图中查找元素,则DFS过程将首先从当前节点开始尽可能深入,直到无法继续进行为止。
When you hit a dead end, you simply move back and try to find deeper routes from any of those nodes.
当您走到尽头时,您只需退后并尝试从这些节点中的任何一个中查找更深的路线。
You simply keep trying all these ‘deepest’ routes until you have exhausted all possibilities.
您只需继续尝试所有这些“最深”的路线,直到用尽所有可能性。
To apply this algorithm, we need to keep track of the path ‘history‘, that includes the current node visited, so that we can come back to that point.
要应用此算法,我们需要跟踪路径“ history ”,其中包括访问的当前节点,以便我们可以回到这一点。
Since this will try different paths from the path of visited nodes, it is very natural to use a Stack Data Structure.
由于这将尝试与访问节点的路径不同的路径,因此使用堆栈数据结构是很自然的。
To simplify things, I will show you how this algorithm works on a graph.
为简化起见,我将向您展示该算法如何在图形上工作。
算法 (The Algorithm)
Before we start our algorithm, let us have a stack of the nodes in the current path. Initially, it is empty.
在开始算法之前,让我们在当前路径中有一堆节点。 最初,它是空的。
- If a node to be visited is not there in the stack, we push it onto the stack and mark the node as visited. 如果要访问的节点不在堆栈中,则将其推入堆栈,然后将该节点标记为已访问。
- We then check if the current node matches our search criteria. 然后,我们检查当前节点是否符合搜索条件。
- If it does, we are done! 如果是这样,我们就完成了!
- Otherwise, we need to go to all the nodes adjacent to the current node. 否则,我们需要转到与当前节点相邻的所有节点。
- We will visit all such nodes, in any random order, and keep searching. 我们将以任何随机顺序访问所有此类节点,并继续搜索。
- If all adjacent nodes are already visited, it is a dead end. We must go to the previously visited node and pop the recent node from the stack. 如果所有相邻节点都已被访问,那将是一个死胡同。 我们必须转到先前访问的节点并从堆栈中弹出最近的节点。
- The algorithm will terminate if all the nodes have been searched (stack is empty), or if we get our answer. 如果已经搜索了所有节点(堆栈为空),或者得到了答案,该算法将终止。
So this algorithm will search the whole graph and is thus a good way to check if a path exists between two nodes.
因此,该算法将搜索整个图,因此是检查两个节点之间是否存在路径的好方法。
一个例子–搜索顶点 (An Example – Searching for a vertex)
Let’s search for the vertex 7 in the graph below, using DFS.
让我们使用DFS在下图中搜索顶点7 。
When we begin at the start node (6), we mark it as visited, and add it to the stack.
当我们从起始节点( 6 )开始时,我们将其标记为已访问,并将其添加到堆栈中。
![Dfs Step One](https://cdn.journaldev.com/wp-content/uploads/2020/02/dfs_step_one.png)
Since this is not the node we want, we will visit all its unvisited adjacent nodes, and keep going until we hit a dead end.
由于这不是我们想要的节点,因此我们将访问其所有未访问的相邻节点,并继续进行直到遇到死角。
Since the order doesn’t matter to us, let’s visit vertex 2 next. (We can also choose vertex 5)
由于顺序对我们而言无关紧要,因此接下来让我们访问顶点2 。 (我们也可以选择顶点5 )
![Dfs Step Two](https://cdn.journaldev.com/wp-content/uploads/2020/02/dfs_step_two.png)
Now, again, since it is not the destination vertex, let’s again visit all unvisited adjacent nodes of 2. Let’s choose 14.
现在,再次,因为它不是目标顶点,让我们再次访问的所有2 未访问的相邻节点。 让我们选择14 。
![Dfs Step Three](https://i-blog.csdnimg.cn/blog_migrate/175128da74074d2ff5da4bc4e62fee7c.png)
Again, this is not the destination node. But now, this is a dead-end, since it does not have any adjacent node.
同样,这不是目标节点。 但是现在,这是一个死胡同,因为它没有任何相邻节点。
So, we must go back a step, and pop 14 from the stack. We are now at vertex 2 again.
因此,我们必须退后一步,并从堆栈中弹出14 。 我们现在再次处于顶点2 。
![Dfs Step Four](https://i-blog.csdnimg.cn/blog_migrate/67cb1fa9d4da658a67c79a02751730f6.png)
Now, we move onto the node 7. Since this is the destination node, we are done!
现在,我们进入节点7 。 由于这是目标节点,因此我们完成了!
![Dfs Last Step](https://cdn.journaldev.com/wp-content/uploads/2020/02/dfs_last_step.png)
Hopefully, you can now understand how DFS works. Let’s now move onto a C implementation!
希望您现在可以了解DFS的工作原理。 现在让我们进入C实现!
深度优先搜索的实现 (Implementation of Depth First Search)
We will use the code from our previous article on Graph Theory as a template, and build on from that.
我们将使用上一篇有关图论的文章中的代码作为模板,并以此为模板。
I’ll be adding to this code. So let’s first quickly implement a stack using Linked Lists.
我将添加到此代码中。 因此,让我们首先使用链接列表快速实现堆栈。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
typedef struct Graph Graph;
typedef struct Node Node;
struct Node {
// To represent the linked list node.
// Contains the vertex index
int vertex;
// Vertex Key
int key;
// And a pointer to the next element in the linked list
Node* next;
};
struct Graph {
// Key List
int* key_list;
// Number of vertices
int v;
// Array of Adjacency Lists
Node** adj_lists;
};
// Define the Stack here
typedef struct StackNode StackNode;
struct StackNode{
// Stack of integers
int data;
StackNode* next;
};
int is_empty(StackNode* stack) {
// Check if stack is empty
if (!stack)
return 1;
return 0;
}
StackNode* push(StackNode* stack, int data) {
// Pushes the data into the stack
StackNode* node = (StackNode*) malloc (sizeof(StackNode));
StackNode* temp = stack;
node->data = data;
node->next = temp;
stack = node;
return stack;
}
StackNode* pop(StackNode* stack) {
// Pops the head of the stack
if (!stack)
return NULL;
StackNode* temp = stack;
stack = stack->next;
temp->next = NULL;
free(temp);
return stack;
}
int top(StackNode* stack) {
// Return the top of the stack
if (!stack)
return INT_MIN;
return stack->data;
}
StackNode* init_stack(int data) {
// Initializes the stack
StackNode* stack = (StackNode*) malloc (sizeof(StackNode));
stack->data = data;
stack->next = NULL;
return stack;
}
void free_stack(StackNode* stack) {
// Free the stack
if (!stack)
return;
StackNode* temp = stack;
stack = stack->next;
temp->next = NULL;
free(temp);
free_stack(stack);
}
void print_stack(StackNode* stack) {
if (!stack)
return;
StackNode* temp = stack;
printf("Stack: \n");
while(temp) {
printf("Data: %d -> ", temp->data);
temp = temp->next;
}
printf("\n");
}
We’ll now show the code for building the graph. Since we denote each vertex using an index as well as its key value, the Node structure looks like this.
现在,我们将显示用于构建图形的代码。 由于我们使用索引及其键值来表示每个顶点,因此Node结构看起来像这样。
struct Node {
// To represent the linked list node.
// Contains the vertex index
int vertex;
// Vertex Key
int key;
// And a pointer to the next element in the linked list
Node* next;
};
The rest of the code is almost the same as that of the previous article, except that we’ll also be adding in the key for every vertex.
其余代码与上一篇文章几乎相同,除了我们还将为每个顶点添加键。
// Adjacency Lists for the Graph
Node** init_adjacency_lists(Graph g) {
// Initializes an adjacency matrix for the graph
if (g.adj_lists)
return g.adj_lists;
// Allocates memory for the lists
// There is a list for every vertex in the graph
// which means there are g.v adjacent lists
Node** adj_lists = (Node**) calloc (g.v, sizeof(Node*));
// Set them to NULL initially
for (int i = 0; i < g.v; i++)
adj_lists[i] = NULL;
printf("Initialized Adjacency Lists!\n");
return adj_lists;
}
void free_list(Node* list) {
// Frees all nodes in the list, headed by 'list'
Node* temp = list;
while(temp) {
Node* rm_node = temp;
temp = rm_node->next;
rm_node->next = NULL;
free(rm_node);
}
}
void free_adj_lists(Graph g) {
// Free the adjacency matrix
if (!g.adj_lists)
return;
for (int i=0; i<g.v; i++)
free_list(g.adj_lists[i]);
free(g.adj_lists);
}
void print_list(Node* list) {
// Prints the linked list
Node* temp = list;
while(temp) {
printf("Node: %d, Key: %d -> ", temp->vertex, temp->key);
temp = temp->next;
}
printf("\n");
}
Node* create_node(int vertex, int key) {
// Creates a LinkedList node to hold the vertex
Node* node = (Node*) calloc (1, sizeof(Node));
node->next = NULL;
node->vertex = vertex;
node->key = key;
return node;
}
void add_edge(Graph g, int i, int j) {
// Adds an edge connecting two vertices i and j
if (!g.adj_lists) {
fprintf(stderr, "Adjacency Lists not initialized!\n");
exit(1);
}
else if (i > g.v || j > g.v) {
fprintf(stderr, "Invalid Vertex Number\n");
exit(1);
}
// Create the new node in the souce vertex
// adjacency list and add the destination
// vertex to it
// Create a node containing the dst vertex index
Node* node = create_node(j, g.key_list[j-1]);
// Insert at the source list
// Let's insert at the top, since it doesn't
// matter whether we insert at the head or not
node->next = g.adj_lists[i-1];
// Make the new node as the head
g.adj_lists[i-1] = node;
}
void remove_edge(Graph g, int i, int j) {
// Sets the edge from i to j as zero
if (!g.adj_lists) {
fprintf(stderr, "Adjacency Lists not initialized!\n");
exit(1);
}
// Search for vertex j in i's adjacency list
Node* temp = g.adj_lists[i-1];
if (!temp) {
return;
}
if (!(temp->next)) {
if (temp->vertex == j) {
free(temp);
g.adj_lists[i-1] = NULL;
}
return;
}
while (temp->next) {
if (temp->vertex == j) {
// We have found an edge! Remove this element.
Node* rm_node = temp;
temp->next = rm_node->next;
rm_node->next = NULL;
free(rm_node);
return;
}
temp = temp->next;
}
// No element found :(
return;
}
int check_if_exists(Graph g, int i, int j) {
// Checks if there is an edge from vertex i to j
if (!g.adj_lists) {
fprintf(stderr, "Adjacency Lists not initialized!\n");
exit(1);
}
else if (i > g.v || j > g.v) {
fprintf(stderr, "Invalid Vertex Number\n");
return 0;
}
// Search for vertex j in i's adjacency list
Node* temp = g.adj_lists[i-1];
if (!temp) {
return 0;
}
if (!(temp->next)) {
if (temp->vertex == j) {
return 1;
}
return 0;
}
while (temp->next) {
if (temp->vertex == j) {
// We have found an edge! Remove this element.
return 1;
}
temp = temp->next;
}
// No element found :(
return 0;
}
Let’s now move onto the main part: the DFS() function.
现在让我们进入主要部分: DFS()函数。
DFS()函数 (The DFS() Functions)
Now, it is often common practice to write a recursive function within another wrapper function. So our core DFS algorithm will be wrapped around a function called DFS(), which calls it under the hood.
现在,通常的惯例是在另一个包装函数中编写递归函数。 因此,我们的核心DFS算法将被一个名为DFS()的函数包装,该函数在后台进行调用。
This will check whether a path exists from the start_vertex
to our destination vertex, specified by it’s key
.
这将检查从start_vertex
到我们的目标顶点的路径( start_vertex
key
指定)是否存在。
So it’s signature is :
所以它的签名是:
int DFS(Graph g, int start_vertex, int key);
We’ll keep track of the visited nodes using an array of integers int* visited_list
. Initially, this is set to Zero.
我们将使用整数数组int* visited_list
跟踪访问的节点。 最初,将其设置为零。
// g.v -> Number of Vertices
// This is initialized to zero using calloc()
int* visited_list = (int*) calloc (g.v, sizeof(int));
We also need to set up the stack for the DFS Algorithm.
我们还需要为DFS算法设置堆栈。
// Initialize a stack with the key of the starting vertex
StackNode* stack = init_stack(g.key_list[start_vertex-1]);
Now, we are ready to call the main algorithm function DFS_recursive()
.
现在,我们准备调用主要算法函数DFS_recursive()
。
int ret_val = DFS_recursive(g, start_vertex, key, &stack, visited_list, 0);
This takes all the required parameters above. We also have one more parameter called start
, which will push to the stack only if it is called recursively.
这将接受上面所有必需的参数。 我们还有一个名为start
参数,仅当递归调用该参数start
,该参数才会压入堆栈。
NOTE: We pass the stack
address to DFS_recursive()
, since otherwise, it will get overwritten, since it is simply a variable. Passing it as a pointer will preserve the stack
during such recursive calls.
注意 :我们将stack
地址传递给DFS_recursive()
,否则将被覆盖,因为它只是一个变量。 将其作为指针传递将在此类递归调用期间保留stack
。
int DFS(Graph g, int start_vertex, int key) {
// Performs a DFS on the Graph from start_vertex
// and returns 1 if the destination key is found
printf("Start Vertex: %d, Key: %d\n", start_vertex, g.key_list[start_vertex - 1]);
if (g.key_list[start_vertex-1] == key) {
return 1;
}
// Keep a visited list of nodes
int* visited_list = (int*) calloc (g.v, sizeof(int));
// Perform the DFS
StackNode* stack = init_stack(g.key_list[start_vertex-1]);
int ret_val = DFS_recursive(g, start_vertex, key, &stack, visited_list, 0);
// Free Stuff and exit
free_stack(stack);
free(visited_list);
return ret_val;
}
Now, let’s go to the DFS_recursive()
function.
现在,让我们转到DFS_recursive()
函数。
This pushes the current node to the stack and marks it as visited.
这会将当前节点压入堆栈并将其标记为已访问。
if (start)
*stack = push(*stack, g.key_list[start_vertex-1]);
printf("Current Node: key: %d\n", g.key_list[start_vertex - 1]);
// Mark the current node as visited
visited_list[start_vertex - 1] = 1;
Now, we use the exact same algorithm that we mentioned above, in the example graph.
现在,我们在示例图中使用与上面提到的算法完全相同的算法。
int DFS_recursive(Graph g, int start_vertex, int key, StackNode** stack, int* visited_list, int start) {
// Recursive DFS function that is used to perform DFS()
if (start)
*stack = push(*stack, g.key_list[start_vertex-1]);
printf("Current Node: key: %d\n", g.key_list[start_vertex - 1]);
// Mark the current node as visited
visited_list[start_vertex - 1] = 1;
// While the stack is not empty
while (!is_empty(*stack)) {
if (g.key_list[start_vertex - 1] == key) {
return 1;
}
else {
// Not found. Go to the next node
Node* node = g.adj_lists[start_vertex - 1];
if (!node) {
// Dead End. Go back after popping current node
*stack = pop(*stack);
}
while (node) {
// If any of it's neighbours are not visited
if(visited_list[node->vertex - 1] == 0) {
// Go to that node and do a DFS from that node
if (DFS_recursive(g, node->vertex, key, stack, visited_list, 1) == 1)
return 1;
else {
// Pop the recently visited neighbour from the stack
*stack = pop(*stack);
}
}
node = node->next;
}
}
}
return 0;
}
Now, that completes our DFS functions. Let’s now test it for our example graph.
现在,这完成了我们的DFS功能。 现在,为示例图进行测试。
![Dfs Step 1 1](https://i-blog.csdnimg.cn/blog_migrate/06f1d4b23c87421f2bb6d82da16c5961.png)
void dfs_print(Graph g, int src, int dst) {
// Find the vertex for the src key
int start = -1;
for (int i=0; i<g.v; i++) {
if (g.key_list[i] == src)
start = i + 1;
}
if (start == -1) {
fprintf(stderr, "Error: Key %d not found\n", src);
return;
}
printf("Performing DFS on the Graph...\n");
int ret_val = DFS(g, start, dst);
if (ret_val == 1)
printf("Key: %d is found!\n", dst);
else
printf("Key: %d is not found.\n", dst);
}
int main() {
// Graph with 6 vertices
int vertex_list[] = {6, 2, 5, 14, 7, 1};
Graph g = {vertex_list, 6, NULL};
printf("Created a Graph Structure with %d vertices\n", g.v);
g.adj_lists = init_adjacency_lists(g);
// Let's connect the 6 vertices using edges
add_edge(g, 1, 2);
add_edge(g, 1, 3);
add_edge(g, 2, 4);
add_edge(g, 2, 5);
add_edge(g, 3, 6);
add_edge(g, 6, 5);
// Print the Adjacency Lists
for (int i=0; i<g.v; i++) {
printf("Vertex: %d , Key: %d => ", i+1, g.key_list[i]);
print_list(g.adj_lists[i]);
}
// Print a Depth First Search from 6 to 14
printf("\n");
dfs_print(g, 6, 7);
printf("\n");
dfs_print(g, 6, 8);
printf("\n");
dfs_print(g, 6, 14);
printf("\n");
free_adj_lists(g);
return 0;
}
The full code can be downloaded from the below section. When you compile the complete code, you’ll get the following output.
完整的代码可以从下面的部分下载。 编译完整的代码时,将得到以下输出。
Output
输出量
Created a Graph Structure with 6 vertices
Initialized Adjacency Lists!
Vertex: 1 , Key: 6 => Node: 3, Key: 5 -> Node: 2, Key: 2 ->
Vertex: 2 , Key: 2 => Node: 5, Key: 7 -> Node: 4, Key: 14 ->
Vertex: 3 , Key: 5 => Node: 6, Key: 1 ->
Vertex: 4 , Key: 14 =>
Vertex: 5 , Key: 7 =>
Vertex: 6 , Key: 1 => Node: 5, Key: 7 ->
Performing DFS on the Graph...
Start Vertex: 1, Key: 6
Current Node: key: 6
Current Node: key: 5
Current Node: key: 1
Current Node: key: 7
Key: 7 is found!
Performing DFS on the Graph...
Start Vertex: 1, Key: 6
Current Node: key: 6
Current Node: key: 5
Current Node: key: 1
Current Node: key: 7
Current Node: key: 2
Current Node: key: 14
Key: 8 is not found.
Performing DFS on the Graph...
Start Vertex: 1, Key: 6
Current Node: key: 6
Current Node: key: 5
Current Node: key: 1
Current Node: key: 7
Current Node: key: 2
Current Node: key: 14
Key: 14 is found!
This shows that our code indeed works properly! It also prints the path of the search, so this will give you more insight into what it is doing.
这表明我们的代码确实可以正常工作! 它还会打印搜索的路径,因此这将使您对搜索的操作有更深入的了解。
下载代码 (Download the Code)
The Code is available as a Github Gist. I have tried my best to avoid errors and make the code as clear as possible, but if you spot any, please do mention them to me!
该规范可作为Github Gist使用 。 我已尽力避免出现错误,并使代码尽可能清晰,但如果发现任何错误,请向我提及!
结论 (Conclusion)
Hopefully, you’ve understood what the DFS Algorithm does, using the example and the implementation that we showed you. If you have any doubts or suggestions, please do mention them in the comment section below. Until next time!
希望您已经使用示例和我们向您展示的实现了解了DFS算法的功能。 如果您有任何疑问或建议,请在下面的评论部分中提及它们。 直到下一次!
参考资料 (References)
- Wikipedia Article on Depth First Search 维基百科有关深度优先搜索的文章
dfs深度优先搜索