1658608380
if you install it via pip install 'dash_echarts[play]'
, you can run the command echarts_play
to see the gallery demo!
step 1. run command
pip install 'dash_echarts[play]'
step 2. run command
echarts_line
step 3. access url
http://127.0.0.1:8050/
step 4. explore others
run other commands
echarts_play
echarts_bar
echarts_heat
echarts_map
echarts_scatter3d
echarts_histbar
echarts_regression
echarts_customprofit
echarts_line_race
echarts_bar_race
echarts_bar_style
contact the author for commercial dashboard support!
pip install dash_echarts
import dash_echarts
import dash, random
from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc
from dash.exceptions import PreventUpdate
def gen_randlist(num):
return random.sample(range(num), 7)
def main():
'''
dash_echarts examples
name: smooth line with echarts
author: dameng <pingf0@gmail.com>
'''
app = dash.Dash(__name__)
option = {
'xAxis': {
'type': 'category',
'data': ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
'yAxis': {
'type': 'value'
},
'series': [{
'data': gen_randlist(200),
'type': 'line',
'smooth': True
}, {
'data': gen_randlist(200),
'type': 'line',
'smooth': True
}]
}
app.layout = html.Div([
dash_echarts.DashECharts(
option = option,
id='echarts',
style={
"width": '100vw',
"height": '100vh',
}
),
dcc.Interval(id="interval", interval=1 * 1000, n_intervals=0),
])
@app.callback(
Output('echarts', 'option'),
[Input('interval', 'n_intervals')])
def update(n_intervals):
if n_intervals == 0:
raise PreventUpdate
else:
option['series'][0]['data'] = gen_randlist(200)
option['series'][1]['data'] = gen_randlist(200)
return option
app.run_server(debug=True)
if __name__ == '__main__':
main()
Author: mergeforward
Source code: https://github.com/mergeforward/dash-echarts
License: Apache-2.0 license
1658608380
if you install it via pip install 'dash_echarts[play]'
, you can run the command echarts_play
to see the gallery demo!
step 1. run command
pip install 'dash_echarts[play]'
step 2. run command
echarts_line
step 3. access url
http://127.0.0.1:8050/
step 4. explore others
run other commands
echarts_play
echarts_bar
echarts_heat
echarts_map
echarts_scatter3d
echarts_histbar
echarts_regression
echarts_customprofit
echarts_line_race
echarts_bar_race
echarts_bar_style
contact the author for commercial dashboard support!
pip install dash_echarts
import dash_echarts
import dash, random
from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc
from dash.exceptions import PreventUpdate
def gen_randlist(num):
return random.sample(range(num), 7)
def main():
'''
dash_echarts examples
name: smooth line with echarts
author: dameng <pingf0@gmail.com>
'''
app = dash.Dash(__name__)
option = {
'xAxis': {
'type': 'category',
'data': ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
'yAxis': {
'type': 'value'
},
'series': [{
'data': gen_randlist(200),
'type': 'line',
'smooth': True
}, {
'data': gen_randlist(200),
'type': 'line',
'smooth': True
}]
}
app.layout = html.Div([
dash_echarts.DashECharts(
option = option,
id='echarts',
style={
"width": '100vw',
"height": '100vh',
}
),
dcc.Interval(id="interval", interval=1 * 1000, n_intervals=0),
])
@app.callback(
Output('echarts', 'option'),
[Input('interval', 'n_intervals')])
def update(n_intervals):
if n_intervals == 0:
raise PreventUpdate
else:
option['series'][0]['data'] = gen_randlist(200)
option['series'][1]['data'] = gen_randlist(200)
return option
app.run_server(debug=True)
if __name__ == '__main__':
main()
Author: mergeforward
Source code: https://github.com/mergeforward/dash-echarts
License: Apache-2.0 license
1658615940
Notice
As of Dash 2, the development of dash-html-components has been moved to the main Dash repo
This package exists for backward compatibility
dash-html-components
Vanilla HTML components for Dash
Create a virtual env and activate.
$ virtualenv venv
$ venv/bin/activate
Note: venv\Scripts\activate for Windows
Install Python packages required to build components.
$ pip install -r dev-requirements.txt
Generate components and install npm packages
$ npm install
The components in src/components
, as well as the export index in src/index.js
are programmatically generated from element definitions in scripts/
. To regenerate:
$ npm run generate-components
The list of attributes is regenerated by scraping the MDN HTML attribute reference.
Note: This step will have already been done for you when you ran npm install
Watch for changes
$ npm run build:watch
Install module locally (after every change)
# Generate metadata, and build the JavaScript bundle
$ npm run install-local
# Now you're done. For subsequent changes, if you've got `npm run build:watch`
$ python setup.py install
Run the Dash layout you want to test
# Import dash_html_components to your layout, then run it:
$ python my_dash_layout.py
Before publishing to PyPi, you can test installing the module locally:
# Install in `site-packages` on your machine
$ npm run install-local
$ npm run uninstall-local
See the contributing guide for guidelines on contributing to this project.
Build your code:
$ npm run build
Create a Python tarball
$ python setup.py sdist
This distribution tarball will get generated in the dist/
folder
Test your tarball by copying it into a new environment and installing it locally:
$ pip install dash-html-components-<new-version>.tar.gz
If it works, then you can publish the component to NPM and PyPI:
$ twine upload dist/*
$ rm -rf dist
publish_on_npm
)$ npm publish
serve_locally
flags to True
(unless you choose False
on publish_on_npm
). We will eventually make serve_locally=True
the default, follow our progress in this issue.Author: plotly
Source code: https://github.com/plotly/dash-html-components
License: Unknown, MIT licenses found
1680101228
Depth First Search is a popular graph traversal algorithm. In this tutorial, We will understand how it works, along with examples; and how we can implement it in Python.
Introduction
Graphs and Trees are some of the most important data structures we use for various applications in Computer Science.
They represent data in the form of nodes, which are connected to other nodes through ‘edges’.
Like other data structures, traversing all the elements or searching for an element in a graph or a tree is one of the fundamental operations that is required to define such data structures. Depth First Search is one such graph traversal algorithm.
Depth First Search begins by looking at the root node (an arbitrary node) of a graph. If we are performing a traversal of the entire graph, it visits the first child of a root node, then, in turn, looks at the first child of this node and continues along this branch until it reaches a leaf node.
Next, it backtracks and explores the other children of the parent node in a similar manner. This continues until we visit all the nodes of the tree, and there is no parent node left to explore.
source: Wikipedia
However, if we are performing a search of a particular element, then at each step, a comparison operation will occur with the node we are currently at.
If the element is not present in a particular node, then the same process exploring each branch and backtracking takes place.
This continues until either all the nodes of the graph have been visited, or we have found the element we were looking for.
Before we try to implement the DFS algorithm in Python, it is necessary to first understand how to represent a graph in Python.
There are various versions of a graph. A graph may have directed edges (defining the source and destination) between two nodes, or undirected edges. The edges between nodes may or may not have weights. Depending on the application, we may use any of the various versions of a graph.
For the purpose of traversal through the entire graph, we will use graphs with directed edges (since we need to model parent-child relation between nodes), and the edges will have no weights since all we care about is the complete traversal of the graph.
Now there are various ways to represent a graph in Python; two of the most common ways are the following:
Adjacency Matrix is a square matrix of shape N x N (where N is the number of nodes in the graph).
Each row represents a node, and each of the columns represents a potential child of that node.
Each (row, column) pair represents a potential edge.
Whether or not the edge exists depends on the value of the corresponding position in the matrix.
A non-zero value at the position (i,j) indicates the existence of an edge between nodes i and j, while the value zero means there exists no edge between i and j.
The values in the adjacency matrix may either be a binary number or a real number.
We can use binary values in a non-weighted graph (1 means edge exists, and a 0 means it doesn’t).
For real values, we can use them for a weighted graph and represent the weight associated with the edge between the row and column representing the position.
E.g., a value 10 between at position (2,3) indicates there exists an edge bearing weight 10 between nodes 2 and 3.
In Python, we can represent the adjacency matrices using a 2-dimensional NumPy array.
Adjacency List is a collection of several lists. Each list represents a node in the graph, and stores all the neighbors/children of this node.
In Python, an adjacency list can be represented using a dictionary where the keys are the nodes of the graph, and their values are a list storing the neighbors of these nodes.
We will use this representation for our implementation of the DFS algorithm.
Let’s take an example graph and represent it using a dictionary in Python.
The given graph has the following four edges:
Let’s now create a dictionary in Python to represent this graph.
graph = {"A": ["B", "C"],
"B": ["C"],
"C": ["D"]}
Now that we know how to represent a graph in Python, we can move on to the implementation of the DFS algorithm.
We will consider the graph example shown in the animation in the first section.
Let’s define this graph as an adjacency list using the Python dictionary.
graph = {"A":["D","C","B"],
"B":["E"],
"C":["G","F"],
"D":["H"],
"E":["I"],
"F":["J"]}
One of the expected orders of traversal for this graph using DFS would be:
Let’s implement a method that accepts a graph and traverses through it using DFS. We can achieve this using both recursion technique as well as non-recursive, iterative approach.
In this section, we’ll look at the iterative method.
We will use a stack and a list to keep track of the visited nodes.
We’ll begin at the root node, append it to the path and mark it as visited. Then we will add all of its neighbors to the stack.
At each step, we will pop out an element from the stack and check if it has been visited.
If it has not been visited, we’ll add it to the path and add all of its neighbors to the stack.
def dfs_non_recursive(graph, source):
if source is None or source not in graph:
return "Invalid input"
path = []
stack = [source]
while(len(stack) != 0):
s = stack.pop()
if s not in path:
path.append(s)
if s not in graph:
#leaf node
continue
for neighbor in graph[s]:
stack.append(neighbor)
return " ".join(path)
Our user-defined method takes the dictionary representing the graph and a source node as input.
Note that the source node has to be one of the nodes in the dictionary, else the method will return an “Invalid input” error.
Let’s call this method on our defined graph, and verify that the order of traversal matches with that demonstrated in the figure above.
DFS_path = dfs_non_recursive(graph, "A")
print(DFS_path)
Output :
Thus the order of traversal of the graph is in the ‘Depth First’ manner.
We can implement the Depth First Search algorithm using a popular problem-solving approach called recursion.
Recursion is a technique in which the same problem is divided into smaller instances, and the same method is recursively called within its body.
We will define a base case inside our method, which is – ‘If the leaf node has been visited, we need to backtrack’.
Let’s implement the method:
def recursive_dfs(graph, source,path = []):
if source not in path:
path.append(source)
if source not in graph:
# leaf node, backtrack
return path
for neighbour in graph[source]:
path = recursive_dfs(graph, neighbour, path)
return path
Now we can create our graph (same as in the previous section), and call the recursive method.
graph = {"A":["B","C", "D"],
"B":["E"],
"C":["F","G"],
"D":["H"],
"E":["I"],
"F":["J"]}
path = recursive_dfs(graph, "A")
print(" ".join(path))
Output:
The order of traversal is again in the Depth-First manner.
A binary tree is a special kind of graph in which each node can have only two children or no child.
Another important property of a binary tree is that the value of the left child of the node will be less than or equal to the current node’s value.
Similarly, the value in the right child is greater than the current node’s value.
Thus every value in the left branch of the root node is smaller than the value at the root, and those in the right branch will have a value greater than that at the root.
Let’s understand how we can represent a binary tree using Python classes.
We can create a class to represent each node in a tree, along with its left and right children.
Using the root node object, we can parse the whole tree.
We will also define a method to insert new values into a binary tree.
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(self, value):
if value:
if value < self.value:
if self.left is None:
self.left = Node(value)
else:
self.left.insert(value)
elif value > self.value:
if self.right is None:
self.right = Node(value)
else:
self.right.insert(value)
else:
self.value = value
Let’s now create a root node object and insert values in it to construct a binary tree like the one shown in the figure in the previous section.
root = Node(7)
root.insert(2)
root.insert(25)
root.insert(9)
root.insert(80)
root.insert(0)
root.insert(5)
root.insert(15)
root.insert(8)
This will construct the binary tree shown in the figure above.
It will also ensure that the properties of binary trees i.e, ‘2 children per node’ and ‘left < root < right’ are satisfied no matter in what order we insert the values.
Let’s now define a recursive function that takes as input the root node and displays all the values in the tree in the ‘Depth First Search’ order.
def dfs_binary_tree(root):
if root is None:
return
else:
print(root.value,end=" ")
dfs_binary_tree(root.left)
dfs_binary_tree(root.right)
We can now call this method and pass the root node object we just created.
dfs_binary_tree(root)
Output:
This order is also called as the ‘preorder traversal’ of a binary tree.
So far, we have been writing our logic for representing graphs and traversing them.
But, like all other important applications, Python offers a library to handle graphs as well. It is called ‘networkx’.
‘networkx’ is a Python package to represent graphs using nodes and edges, and it offers a variety of methods to perform different operations on graphs, including the DFS traversal.
Let’s first look at how to construct a graph using networkx.
To construct a graph in networkx, we first create a graph object and then add all the nodes in the graph using the ‘add_node()’ method, followed by defining all the edges between the nodes, using the ‘add_edge()’ method.
Let’s construct the following graph using ‘networkx’.
import networkx as nx
G = nx.Graph() #create a graph
G.add_node(1) # add single node
G.add_node(2)
G.add_node(3)
G.add_node(4)
G.add_node(5)
G.add_nodes_from([6,7,8,9]) #add multiple nodes
Now that we have added all the nodes let’s define the edges between these nodes as shown in the figure.
# adding edges
G.add_edge(5,8)
G.add_edge(5,4)
G.add_edge(5,7)
G.add_edge(8,2)
G.add_edge(4,3)
G.add_edge(4,1)
G.add_edge(7,6)
G.add_edge(6,9)
Now, we constructed the graph by defining the nodes and edges let’s see how it looks the networkx’s ‘draw()’ method and verify if it is constructed the way we wanted it to be. We will use matplotlib to show the graph.
import matplotlib.pyplot as plt
nx.draw(G, with_labels=True, font_weight='bold')
plt.show()
Output:
The orientation may be a little different than our design, but it resembles the same graph, with the nodes and the same edges between them.
Let’s now perform DFS traversal on this graph.
The ‘networkx’ offers a range of methods for traversal of the graph in different ways. We will use the ‘dfs_preorder_nodes()’ method to parse the graph in the Depth First Search order.
The expected order from the figure should be:
5, 8, 2, 4, 3, 1, 7, 6, 9
Let’s call the method and see in what order it prints the nodes.
dfs_output = list(nx.dfs_preorder_nodes(G, source=5))
print(dfs_output)
Output:
Thus the order of traversal by networkx is along our expected lines.
Now that we have understood the depth-first search or DFS traversal well, let’s look at some of its applications.
Topological sorting is one of the important applications of graphs used to model many real-life problems where the beginning of a task is dependent on the completion of some other task.
For instance, we may represent a number of jobs or tasks using nodes of a graph.
Some of the tasks may be dependent on the completion of some other task. This dependency is modeled through directed edges between nodes.
A graph with directed edges is called a directed graph.
If we want to perform a scheduling operation from such a set of tasks, we have to ensure that the dependency relation is not violated i.e, any task that comes later in a chain of tasks is always performed only after all the tasks before it has finished.
We can achieve this kind of order through the topological sorting of the graph.
Note that for topological sorting to be possible, there has to be no directed cycle present in the graph, that is, the graph has to be a directed acyclic graph or DAG.
Let’s take an example of a DAG and perform topological sorting on it, using the Depth First Search approach.
Let’s say each node in the above graph represents a task in a factory to produce a product. The directed arrows between the nodes model are the dependencies of each task on the completion of the previous tasks.
Hence whatever ordering of tasks we chose to perform, to begin the task C, tasks A and E must have been completed.
Similarly, for performing the task I, the tasks A, E, C, and F must have been completed. Since there is no inward arrow on node H, the task H can be performed at any point without the dependency on completion of any other task.
We can construct such a directed graph using Python networkx’s ‘digraph’ module.
dag = nx.digraph.DiGraph()
dag.add_nodes_from(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'])
dag.add_edges_from([('A', 'B'), ('A', 'E'), ('B', 'D'), ('E', 'C'),
('D', 'G'),('C', 'G'),('C', 'I'), ('F', 'I')])
Note that we have used the methods ‘add_nodes_from()’ and ‘add_edges_from()’ to add all the nodes and edges of the directed graph at once.
We can now write a function to perform topological sorting using DFS.
We will begin at a node with no inward arrow, and keep exploring one of its branches until we hit a leaf node, and then we backtrack and explore other branches.
Once we explore all the branches of a node, we will mark the node as ‘visited’ and push it to a stack.
Once every node is visited, we can perform repeated pop operations on the stack to give us a topologically sorted ordering of the tasks.
Now let’s translate this idea into a Python function:
def dfs(dag, start, visited, stack):
if start in visited:
# node and all its branches have been visited
return stack, visited
if dag.out_degree(start) == 0:
# if leaf node, push and backtrack
stack.append(start)
visited.append(start)
return stack, visited
#traverse all the branches
for node in dag.neighbors(start):
if node in visited:
continue
stack, visited = dfs(dag, node, visited, stack)
#now, push the node if not already visited
if start not in visited:
print("pushing %s"%start)
stack.append(start)
visited.append(start)
return stack, visited
def topological_sort_using_dfs(dag):
visited = []
stack=[]
start_nodes = [i for i in dag.nodes if dag.in_degree(i)==0]
# print(start_nodes)
for s in start_nodes:
stack, visited = dfs(dag, s, visited, stack)
print("Topological sorted:")
while(len(stack)!=0):
print(stack.pop(), end=" ")
We have defined two functions – one for recursive traversal of a node, and the main topological sort function that first finds all nodes with no dependency and then traverses each of them using the Depth First Search approach.
Finally, it pops out values from the stack, which produces a topological sorting of the nodes.
Let’s now call the function ‘topological_sort_using_dfs()’
topological_sort_using_dfs(dag)
Output :
If we look closely at the output order, we’ll find that whenever each of the jobs starts, it has all its dependencies completed before it.
We can also compare this with the output of a topological sort method included in the ‘networkx’ module called ‘topological_sort()’.
topological_sorting = nx.topological_sort(dag)
for n in topological_sorting:
print(n, end=' ')
Output:
It looks like the ordering produced by the networkx’s sort method is the same as the one produced by our method.
A graph has another important property called the connected components. A connected component in an undirected graph refers to a set of nodes in which each vertex is connected to every other vertex through a path.
Let’s look at the following example:
In the graph shown above, there are three connected components; each of them has been marked in pink.
Let’s construct this graph in Python, and then chart out a way to find connected components in it.
graph = nx.Graph()
graph.add_nodes_from(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'])
graph.add_edges_from([('A', 'B'), ('B', 'E'), ('A', 'E')]) #component 1
graph.add_edges_from([('C', 'D'), ('D', 'H'), ('H', 'F'), ('F', 'C')]) #component 2
graph.add_edge('G','I') #component 3
Let’s also visualize it while we are at it.
import matplotlib.pyplot as plt
nx.draw(graph, with_labels=True, font_weight='bold')
plt.show()
Output:
To find connected components using DFS, we will maintain a common global array called ‘visited’, and every time we encounter a new variable that has not been visited, we will start finding which connected component it is a part of.
We will mark every node in that component as ‘visited’ so we will not be able to revisit it to find another connected component.
We will repeat this procedure for every node, and the number of times we called the DFS method to find connected components from a node, will be equal to the number of connected components in the graph.
Let’s write this logic in Python and run it on the graph we just constructed:
def find_connected_components(graph):
visited = []
connected_components = []
for node in graph.nodes:
if node not in visited:
cc = [] #connected component
visited, cc = dfs_traversal(graph, node, visited, cc)
connected_components.append(cc)
return connected_components
def dfs_traversal(graph, start, visited, path):
if start in visited:
return visited, path
visited.append(start)
path.append(start)
for node in graph.neighbors(start):
visited, path = dfs_traversal(graph, node, visited, path)
return visited, path
Let’s use our method on the graph we constructed in the previous step.
connected_components = find_connected_components(graph)
print("Total number of connected components =", len(connected_components))
for cc in connected_components:
print(cc)
Output:
In this blog, we understood the DFS algorithm and used it in different ways.
We began by understanding how a graph can be represented using common data structures and implemented each of them in Python.
We then implemented the Depth First Search traversal algorithm using both the recursive and non-recursive approach.
Next, we looked at a special form of a graph called the binary tree and implemented the DFS algorithm on the same.
Here we represented the entire tree using node objects constructed from the Python class we defined to represent a node.
Then we looked at Python’s offering for representing graphs and performing operations on them – the ‘networkx’ module.
We used it to construct a graph, visualize it, and run our DFS method on it. We compared the output with the module’s own DFS traversal method.
Finally, we looked at two important applications of the Depth First Search traversal namely, topological sort and finding connected components in a graph.
Original article source at: https://likegeeks.com/
1680116100
Поиск в глубину — популярный алгоритм обхода графа. В этом уроке мы поймем, как это работает, вместе с примерами; и как мы можем реализовать это на Python.
Введение
Графики и деревья являются одними из наиболее важных структур данных, которые мы используем для различных приложений в области компьютерных наук.
Они представляют данные в виде узлов, которые связаны с другими узлами через «ребра».
Как и в случае с другими структурами данных, обход всех элементов или поиск элемента в графе или дереве является одной из основных операций, необходимых для определения таких структур данных. Поиск в глубину — один из таких алгоритмов обхода графа.
Поиск в глубину начинается с просмотра корневого узла (произвольного узла) графа. Если мы выполняем обход всего графа, он посещает первого потомка корневого узла, затем, в свою очередь, смотрит на первого потомка этого узла и продолжает движение по этой ветви, пока не достигнет конечного узла.
Затем он возвращается и исследует другие дочерние узлы родительского узла аналогичным образом. Это продолжается до тех пор, пока мы не посетим все узлы дерева и не останется ни одного родительского узла для исследования.
источник: Википедия
Однако если мы выполняем поиск определенного элемента, то на каждом шаге будет происходить операция сравнения с узлом, в котором мы сейчас находимся.
Если элемент отсутствует в конкретном узле, то происходит тот же процесс изучения каждой ветви и возврата.
Так продолжается до тех пор, пока либо все узлы графа не будут посещены, либо мы не найдем искомый элемент.
Прежде чем мы попытаемся реализовать алгоритм DFS в Python, необходимо сначала понять, как представлять граф в Python.
Существуют различные версии графа. Граф может иметь направленные ребра (определяющие источник и место назначения) между двумя узлами или ненаправленные ребра. Ребра между узлами могут иметь или не иметь веса. В зависимости от приложения мы можем использовать любую из различных версий графа.
Для обхода всего графа мы будем использовать графы с направленными ребрами (поскольку нам нужно моделировать отношение родитель-потомок между узлами), а ребра не будут иметь весов, поскольку нас интересует только полный обход графа. .
Теперь есть различные способы представления графа в Python; два наиболее распространенных способа следующие:
Матрица смежности — это квадратная матрица формы N x N (где N — количество узлов в графе).
Каждая строка представляет собой узел, а каждый из столбцов представляет собой потенциального дочернего элемента этого узла.
Каждая пара (строка, столбец) представляет потенциальное ребро.
Наличие ребра зависит от значения соответствующей позиции в матрице.
Ненулевое значение в позиции (i,j) указывает на существование ребра между вершинами i и j, а нулевое значение означает, что ребра между i и j не существует.
Значения в матрице смежности могут быть либо двоичными числами, либо действительными числами.
Мы можем использовать двоичные значения в невзвешенном графе (1 означает, что ребро существует, а 0 означает, что его нет).
Для реальных значений мы можем использовать их для взвешенного графика и представлять вес, связанный с краем между строкой и столбцом, представляющим позицию.
Например, значение 10 между позициями (2,3) указывает, что между узлами 2 и 3 существует вес несущей кромки 10.
В Python мы можем представить матрицы смежности с помощью двумерного массива NumPy .
Список смежности представляет собой набор из нескольких списков. Каждый список представляет узел в графе и хранит всех соседей/потомков этого узла.
В Python список смежности может быть представлен с помощью словаря, где ключи — это узлы графа, а их значения — это список, хранящий соседей этих узлов.
Мы будем использовать это представление для нашей реализации алгоритма DFS.
Давайте возьмем пример графа и представим его с помощью словаря в Python.
Данный граф имеет следующие четыре ребра:
Давайте теперь создадим словарь в Python для представления этого графа.
graph = {"A": ["B", "C"],
"B": ["C"],
"C": ["D"]}
Теперь, когда мы знаем, как представить граф в Python, мы можем перейти к реализации алгоритма DFS.
Мы рассмотрим пример графика, показанный на анимации в первом разделе.
Давайте определим этот граф как список смежности, используя словарь Python.
graph = {"A":["D","C","B"],
"B":["E"],
"C":["G","F"],
"D":["H"],
"E":["I"],
"F":["J"]}
Один из ожидаемых порядков обхода этого графа с использованием DFS будет следующим:
Давайте реализуем метод, который принимает граф и проходит по нему с помощью DFS. Мы можем добиться этого, используя как метод рекурсии, так и нерекурсивный итеративный подход.
В этом разделе мы рассмотрим итеративный метод.
Мы будем использовать стек и список для отслеживания посещенных узлов.
Мы начнем с корневого узла, добавим его к пути и пометим как посещенный. Затем мы добавим в стек всех его соседей.
На каждом шаге мы будем извлекать элемент из стека и проверять, посещался ли он.
Если он не был посещен, мы добавим его в путь и добавим всех его соседей в стек.
def dfs_non_recursive(graph, source):
if source is None or source not in graph:
return "Invalid input"
path = []
stack = [source]
while(len(stack) != 0):
s = stack.pop()
if s not in path:
path.append(s)
if s not in graph:
#leaf node
continue
for neighbor in graph[s]:
stack.append(neighbor)
return " ".join(path)
Наш определяемый пользователем метод использует словарь, представляющий граф, и исходный узел в качестве входных данных.
Обратите внимание, что исходный узел должен быть одним из узлов в словаре, иначе метод вернет ошибку «Неверный ввод».
Давайте вызовем этот метод на нашем определенном графе и убедимся, что порядок обхода совпадает с порядком, показанным на рисунке выше.
DFS_path = dfs_non_recursive(graph, "A")
print(DFS_path)
Выход :
Таким образом, порядок обхода графа находится в порядке «сначала в глубину».
Мы можем реализовать алгоритм поиска в глубину, используя популярный подход к решению задач, называемый рекурсией.
Рекурсия — это метод, при котором одна и та же проблема разбивается на более мелкие экземпляры, и один и тот же метод рекурсивно вызывается внутри его тела.
Мы определим базовый случай внутри нашего метода, а именно: «Если конечный узел был посещен, нам нужно вернуться».
Реализуем метод:
def recursive_dfs(graph, source,path = []):
if source not in path:
path.append(source)
if source not in graph:
# leaf node, backtrack
return path
for neighbour in graph[source]:
path = recursive_dfs(graph, neighbour, path)
return path
Теперь мы можем создать наш график (так же, как в предыдущем разделе) и вызвать рекурсивный метод.
graph = {"A":["B","C", "D"],
"B":["E"],
"C":["F","G"],
"D":["H"],
"E":["I"],
"F":["J"]}
path = recursive_dfs(graph, "A")
print(" ".join(path))
Выход:
Порядок обхода снова в порядке поиска в глубину.
Бинарное дерево — это особый вид графа, в котором каждый узел может иметь только двух дочерних элементов или не иметь дочерних элементов.
Другое важное свойство бинарного дерева заключается в том, что значение левого потомка узла будет меньше или равно значению текущего узла.
Точно так же значение в правом дочернем элементе больше, чем значение текущего узла.
Таким образом, каждое значение в левой ветви корневого узла меньше, чем значение в корне, а значения в правой ветви будут иметь значение больше, чем в корне.
Давайте разберемся, как мы можем представить бинарное дерево с помощью классов Python.
Мы можем создать класс для представления каждого узла в дереве вместе с его левым и правым дочерними элементами.
Используя объект корневого узла, мы можем разобрать все дерево.
Мы также определим метод для вставки новых значений в двоичное дерево.
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(self, value):
if value:
if value < self.value:
if self.left is None:
self.left = Node(value)
else:
self.left.insert(value)
elif value > self.value:
if self.right is None:
self.right = Node(value)
else:
self.right.insert(value)
else:
self.value = value
Давайте теперь создадим объект корневого узла и вставим в него значения, чтобы построить двоичное дерево, подобное тому, которое показано на рисунке в предыдущем разделе.
root = Node(7)
root.insert(2)
root.insert(25)
root.insert(9)
root.insert(80)
root.insert(0)
root.insert(5)
root.insert(15)
root.insert(8)
Это построит бинарное дерево, показанное на рисунке выше.
Это также гарантирует, что свойства бинарных деревьев, т. е. «2 дочерних элемента на узел» и «левый < корень < правый», выполняются независимо от того, в каком порядке мы вставляем значения.
Давайте теперь определим рекурсивную функцию, которая принимает в качестве входных данных корневой узел и отображает все значения в дереве в порядке «Поиск в глубину».
def dfs_binary_tree(root):
if root is None:
return
else:
print(root.value,end=" ")
dfs_binary_tree(root.left)
dfs_binary_tree(root.right)
Теперь мы можем вызвать этот метод и передать объект корневого узла, который мы только что создали.
dfs_binary_tree(root)
Выход:
Этот порядок также называется «обходом в прямом порядке» бинарного дерева.
До сих пор мы писали нашу логику для представления графов и их обхода.
Но, как и все другие важные приложения, Python также предлагает библиотеку для обработки графиков. Он называется «networkx» .
«networkx» — это пакет Python для представления графов с использованием узлов и ребер, который предлагает множество методов для выполнения различных операций с графами, включая обход DFS.
Давайте сначала посмотрим, как построить график с помощью networkx.
Чтобы построить граф в networkx, мы сначала создаем объект графа, а затем добавляем все узлы в граф, используя метод add_node(), после чего определяем все ребра между узлами, используя метод add_edge().
Давайте построим следующий график, используя 'networkx'.
import networkx as nx
G = nx.Graph() #create a graph
G.add_node(1) # add single node
G.add_node(2)
G.add_node(3)
G.add_node(4)
G.add_node(5)
G.add_nodes_from([6,7,8,9]) #add multiple nodes
Теперь, когда мы добавили все узлы, давайте определим ребра между этими узлами, как показано на рисунке.
# adding edges
G.add_edge(5,8)
G.add_edge(5,4)
G.add_edge(5,7)
G.add_edge(8,2)
G.add_edge(4,3)
G.add_edge(4,1)
G.add_edge(7,6)
G.add_edge(6,9)
Теперь мы построили граф, определив узлы и ребра, давайте посмотрим, как он выглядит с помощью метода networkx 'draw()', и проверим, построен ли он так, как мы хотели. Мы будем использовать matplotlib , чтобы показать график.
import matplotlib.pyplot as plt
nx.draw(G, with_labels=True, font_weight='bold')
plt.show()
Выход:
The orientation may be a little different than our design, but it resembles the same graph, with the nodes and the same edges between them.
Давайте теперь выполним обход DFS на этом графе.
'networkx' предлагает ряд методов обхода графа различными способами. Мы будем использовать метод dfs_preorder_nodes() для анализа графа в порядке поиска в глубину.
Ожидаемый порядок на рисунке должен быть:
5, 8, 2, 4, 3, 1, 7, 6, 9.
Давайте вызовем метод и посмотрим, в каком порядке он печатает узлы.
dfs_output = list(nx.dfs_preorder_nodes(G, source=5))
print(dfs_output)
Выход:
Таким образом, порядок прохождения networkx совпадает с ожидаемым.
Теперь, когда мы хорошо разобрались с поиском в глубину или обходом в глубину, давайте рассмотрим некоторые из его приложений.
Топологическая сортировка — одно из важных приложений графов, используемых для моделирования многих реальных задач, когда начало задачи зависит от завершения какой-либо другой задачи.
Например, мы можем представить ряд работ или задач, используя узлы графа.
Некоторые из задач могут зависеть от завершения какой-либо другой задачи. Эта зависимость моделируется через направленные ребра между узлами.
Граф с ориентированными ребрами называется ориентированным графом.
Если мы хотим выполнить операцию планирования из такого набора задач, мы должны убедиться, что отношение зависимости не нарушается, т. е. любая задача, которая появляется позже в цепочке задач, всегда выполняется только после того, как все задачи до ее завершения .
Мы можем добиться такого порядка с помощью топологической сортировки графа.
Обратите внимание, что для того, чтобы топологическая сортировка была возможной, в графе не должно быть направленного цикла, то есть граф должен быть ориентированным ациклическим графом или DAG.
Давайте возьмем пример DAG и выполним на нем топологическую сортировку, используя подход поиска в глубину.
Допустим, каждый узел на приведенном выше графике представляет задачу на заводе по производству продукта. Направленные стрелки между узлами модели — это зависимости каждой задачи от завершения предыдущих задач.
Следовательно, какой бы порядок задач мы ни выбрали для выполнения, чтобы приступить к задаче C, задачи A и E должны быть завершены.
Точно так же для выполнения задачи I должны быть выполнены задачи A, E, C и F. Поскольку на узле H нет стрелки, направленной внутрь, задача H может быть выполнена в любой точке без зависимости от завершения какой-либо другой задачи.
Мы можем построить такой ориентированный граф, используя модуль 'digraph' Python networkx.
dag = nx.digraph.DiGraph()
dag.add_nodes_from(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'])
dag.add_edges_from([('A', 'B'), ('A', 'E'), ('B', 'D'), ('E', 'C'),
('D', 'G'),('C', 'G'),('C', 'I'), ('F', 'I')])
Обратите внимание, что мы использовали методы add_nodes_from() и add_edges_from() для одновременного добавления всех узлов и ребер ориентированного графа.
Теперь мы можем написать функцию для выполнения топологической сортировки с использованием DFS.
Мы начнем с узла без внутренней стрелки и продолжим исследовать одну из его ветвей, пока не наткнемся на конечный узел, а затем вернемся назад и исследуем другие ветви.
Как только мы изучим все ветви узла, мы пометим узел как «посещенный» и поместим его в стек.
После посещения каждого узла мы можем выполнять повторяющиеся операции извлечения в стеке, чтобы получить топологически отсортированный порядок задач.
Теперь давайте переведем эту идею в функцию Python:
def dfs(dag, start, visited, stack):
if start in visited:
# node and all its branches have been visited
return stack, visited
if dag.out_degree(start) == 0:
# if leaf node, push and backtrack
stack.append(start)
visited.append(start)
return stack, visited
#traverse all the branches
for node in dag.neighbors(start):
if node in visited:
continue
stack, visited = dfs(dag, node, visited, stack)
#now, push the node if not already visited
if start not in visited:
print("pushing %s"%start)
stack.append(start)
visited.append(start)
return stack, visited
def topological_sort_using_dfs(dag):
visited = []
stack=[]
start_nodes = [i for i in dag.nodes if dag.in_degree(i)==0]
# print(start_nodes)
for s in start_nodes:
stack, visited = dfs(dag, s, visited, stack)
print("Topological sorted:")
while(len(stack)!=0):
print(stack.pop(), end=" ")
Мы определили две функции — одну для рекурсивного обхода узла и основную функцию топологической сортировки, которая сначала находит все узлы без зависимости, а затем проходит каждый из них, используя подход поиска в глубину.
Наконец, он извлекает значения из стека, производя топологическую сортировку узлов.
Давайте теперь вызовем функцию 'topological_sort_using_dfs()'
topological_sort_using_dfs(dag)
Выход :
Если мы внимательно посмотрим на порядок вывода, мы обнаружим, что всякий раз, когда начинается каждое из заданий, все его зависимости завершаются до него.
Мы также можем сравнить это с выводом метода топологической сортировки, включенного в модуль «networkx», который называется «topological_sort()».
topological_sorting = nx.topological_sort(dag)
for n in topological_sorting:
print(n, end=' ')
Выход:
Похоже, порядок, создаваемый методом сортировки networkx, такой же, как и упорядочивание, создаваемое нашим методом.
У графа есть еще одно важное свойство, называемое компонентами связности. Компонент связности в неориентированном графе относится к набору узлов, в котором каждая вершина соединена с каждой другой вершиной посредством пути.
Давайте посмотрим на следующий пример:
В графе, показанном выше, есть три связных компонента; каждый из них отмечен розовым цветом.
Давайте построим этот граф на Python, а затем наметим, как найти в нем связанные компоненты.
graph = nx.Graph()
graph.add_nodes_from(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'])
graph.add_edges_from([('A', 'B'), ('B', 'E'), ('A', 'E')]) #component 1
graph.add_edges_from([('C', 'D'), ('D', 'H'), ('H', 'F'), ('F', 'C')]) #component 2
graph.add_edge('G','I') #component 3
Давайте также визуализируем это, пока мы это делаем.
import matplotlib.pyplot as plt
nx.draw(graph, with_labels=True, font_weight='bold')
plt.show()
Выход:
Чтобы найти подключенные компоненты с помощью DFS, мы будем поддерживать общий глобальный массив с именем «visited», и каждый раз, когда мы сталкиваемся с новой переменной, которая не была посещена, мы начинаем искать, частью какого подключенного компонента она является.
Мы будем отмечать каждый узел в этом компоненте как «посещенный», поэтому мы не сможем повторно посетить его, чтобы найти другой подключенный компонент.
Мы будем повторять эту процедуру для каждого узла, и количество раз, которое мы вызывали метод поиска в глубину для поиска связанных компонентов из узла, будет равно количеству связанных компонентов в графе.
Давайте напишем эту логику на Python и запустим ее на только что построенном графе:
def find_connected_components(graph):
visited = []
connected_components = []
for node in graph.nodes:
if node not in visited:
cc = [] #connected component
visited, cc = dfs_traversal(graph, node, visited, cc)
connected_components.append(cc)
return connected_components
def dfs_traversal(graph, start, visited, path):
if start in visited:
return visited, path
visited.append(start)
path.append(start)
for node in graph.neighbors(start):
visited, path = dfs_traversal(graph, node, visited, path)
return visited, path
Давайте используем наш метод на графике, который мы построили на предыдущем шаге.
connected_components = find_connected_components(graph)
print("Total number of connected components =", len(connected_components))
for cc in connected_components:
print(cc)
Выход:
В этом блоге мы разобрались с алгоритмом DFS и использовали его по-разному.
Мы начали с понимания того, как граф может быть представлен с помощью общих структур данных, и реализовали каждую из них на Python.
Затем мы реализовали алгоритм обхода поиска в глубину, используя как рекурсивный, так и нерекурсивный подход.
Затем мы рассмотрели специальную форму графа, называемую бинарным деревом, и реализовали на ней алгоритм поиска в глубину.
Здесь мы представили все дерево, используя объекты узла, созданные из класса Python, который мы определили для представления узла.
Затем мы рассмотрели предложение Python для представления графов и выполнения над ними операций — модуль networkx.
Мы использовали его для построения графика, его визуализации и запуска на нем нашего метода поиска в глубину. Мы сравнили результат с собственным методом обхода DFS модуля.
Наконец, мы рассмотрели два важных применения обхода поиска в глубину, а именно топологическую сортировку и поиск компонентов связности в графе.
Оригинальный источник статьи: https://likegeeks.com/
1680112320
深度优先搜索是一种流行的图遍历算法。在本教程中,我们将通过示例了解它的工作原理;以及我们如何在 Python 中实现它。
介绍
图和树是我们在计算机科学的各种应用中使用的一些最重要的数据结构。
它们以节点的形式表示数据,这些节点通过“边”连接到其他节点。
与其他数据结构一样,遍历所有元素或在图形或树中搜索元素是定义此类数据结构所需的基本操作之一。深度优先搜索就是这样一种图遍历算法。
深度优先搜索首先查看图形的根节点(任意节点)。如果我们正在执行整个图的遍历,它会访问根节点的第一个子节点,然后依次查看该节点的第一个子节点并沿着该分支继续,直到到达叶节点。
接下来,它以类似的方式回溯和探索父节点的其他子节点。这一直持续到我们访问了树的所有节点,并且没有父节点可供探索。
资料来源: 维基百科
但是,如果我们正在执行特定元素的搜索,那么在每一步中,都会对当前所在的节点进行比较操作。
如果该元素不存在于特定节点中,则将进行探索每个分支和回溯的相同过程。
这一直持续到图中的所有节点都被访问过,或者我们找到了我们正在寻找的元素。
在我们尝试用 Python 实现 DFS 算法之前,有必要先了解如何用 Python 表示图。
图表有多种版本。一个图可能在两个节点之间有有向边(定义源和目的地),或者无向边。节点之间的边可能有也可能没有权重。根据应用程序,我们可以使用图形的各种版本中的任何一种。
为了遍历整个图,我们将使用具有有向边的图(因为我们需要对节点之间的父子关系建模),并且边将没有权重,因为我们只关心图的完整遍历.
现在有多种方法可以在 Python 中表示图形;两种最常见的方法如下:
邻接矩阵是形状为 N x N 的方阵(其中 N 是图中的节点数)。
每行代表一个节点,每一列代表该节点的潜在子节点。
每个(行,列)对代表一个潜在的边缘。
边是否存在取决于矩阵中对应位置的值。
位置 (i,j) 的非零值表示节点 i 和 j 之间存在边,而值为零表示 i 和 j 之间不存在边。
邻接矩阵中的值可以是二进制数或实数。
我们可以在非加权图中使用二进制值(1 表示边存在,0 表示不存在)。
对于实数值,我们可以将它们用于加权图,并表示与表示位置的行和列之间的边相关联的权重。
例如,位置 (2,3) 之间的值 10 表示在节点 2 和 3 之间存在承载权重 10 的边。
在 Python 中,我们可以使用二维NumPy 数组表示邻接矩阵。
邻接列表是几个列表的集合。每个列表代表图中的一个节点,并存储该节点的所有邻居/子节点。
在 Python 中,邻接表可以使用字典表示,其中键是图的节点,它们的值是存储这些节点的邻居的列表。
我们将使用此表示来实现 DFS 算法。
让我们举一个示例图并使用 Python 中的字典表示它。
给定的图有以下四个边:
现在让我们在 Python 中创建一个字典来表示这个图。
graph = {"A": ["B", "C"],
"B": ["C"],
"C": ["D"]}
现在我们知道如何用 Python 表示图形,我们可以继续实现 DFS 算法。
我们将考虑第一部分动画中显示的图形示例。
让我们使用 Python 字典将此图定义为邻接表。
graph = {"A":["D","C","B"],
"B":["E"],
"C":["G","F"],
"D":["H"],
"E":["I"],
"F":["J"]}
使用 DFS 对该图进行遍历的预期顺序之一是:
让我们实现一个接受图形并使用 DFS 遍历它的方法。我们可以使用递归技术和非递归迭代方法来实现这一点。
在本节中,我们将研究迭代方法。
我们将使用堆栈和列表来跟踪访问过的节点。
我们将从根节点开始,将其附加到路径并将其标记为已访问。然后我们将其所有邻居添加到堆栈中。
在每一步,我们都会从堆栈中弹出一个元素并检查它是否被访问过。
如果它还没有被访问过,我们将把它添加到路径中并将它的所有邻居添加到堆栈中。
def dfs_non_recursive(graph, source):
if source is None or source not in graph:
return "Invalid input"
path = []
stack = [source]
while(len(stack) != 0):
s = stack.pop()
if s not in path:
path.append(s)
if s not in graph:
#leaf node
continue
for neighbor in graph[s]:
stack.append(neighbor)
return " ".join(path)
我们的用户定义方法将表示图形的字典和源节点作为输入。
请注意,源节点必须是字典中的节点之一,否则该方法将返回“无效输入”错误。
让我们在我们定义的图上调用这个方法,并验证遍历的顺序是否与上图中演示的相匹配。
DFS_path = dfs_non_recursive(graph, "A")
print(DFS_path)
输出 :
因此图的遍历顺序是“深度优先”的方式。
我们可以使用称为递归的流行问题解决方法来实现深度优先搜索算法。
递归是一种将同一个问题分成更小的实例,并在其主体内递归调用同一个方法的技术。
我们将在我们的方法中定义一个基本情况,即“如果叶节点已被访问,我们需要回溯”。
让我们来实现这个方法:
def recursive_dfs(graph, source,path = []):
if source not in path:
path.append(source)
if source not in graph:
# leaf node, backtrack
return path
for neighbour in graph[source]:
path = recursive_dfs(graph, neighbour, path)
return path
现在我们可以创建我们的图形(与上一节相同),并调用递归方法。
graph = {"A":["B","C", "D"],
"B":["E"],
"C":["F","G"],
"D":["H"],
"E":["I"],
"F":["J"]}
path = recursive_dfs(graph, "A")
print(" ".join(path))
输出:
遍历的顺序又是深度优先的方式。
二叉树是一种特殊的图,其中每个节点只能有两个孩子或没有孩子。
二叉树的另一个重要属性是节点的左子节点的值将小于或等于当前节点的值。
同样,右子节点中的值大于当前节点的值。
因此,根节点左分支中的每个值都小于根节点中的值,而右分支中的值将大于根节点中的值。
让我们了解如何使用 Python 类表示二叉树。
我们可以创建一个类来表示树中的每个节点,以及它的左右子节点。
使用根节点对象,我们可以解析整棵树。
我们还将定义一种将新值插入二叉树的方法。
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(self, value):
if value:
if value < self.value:
if self.left is None:
self.left = Node(value)
else:
self.left.insert(value)
elif value > self.value:
if self.right is None:
self.right = Node(value)
else:
self.right.insert(value)
else:
self.value = value
现在让我们创建一个根节点对象并在其中插入值以构建一棵二叉树,如上一节图中所示。
root = Node(7)
root.insert(2)
root.insert(25)
root.insert(9)
root.insert(80)
root.insert(0)
root.insert(5)
root.insert(15)
root.insert(8)
这将构建如上图所示的二叉树。
它还将确保无论我们以何种顺序插入值,都满足二叉树的属性,即“每个节点 2 个子节点”和“左 < 根 < 右”。
现在让我们定义一个递归函数,它将根节点作为输入并以“深度优先搜索”顺序显示树中的所有值。
def dfs_binary_tree(root):
if root is None:
return
else:
print(root.value,end=" ")
dfs_binary_tree(root.left)
dfs_binary_tree(root.right)
我们现在可以调用这个方法并传递我们刚刚创建的根节点对象。
dfs_binary_tree(root)
输出:
此顺序也称为二叉树的“前序遍历”。
到目前为止,我们一直在编写表示图和遍历图的逻辑。
但是,与所有其他重要应用程序一样,Python 也提供了一个库来处理图形。它被称为 'networkx'。
'networkx' 是一个使用节点和边表示图的 Python 包,它提供了多种方法来对图执行不同的操作,包括 DFS 遍历。
我们先看看如何使用networkx构造图。
要在 networkx 中构建图,我们首先创建一个图对象,然后使用“add_node()”方法添加图中的所有节点,然后使用“add_edge()”方法定义节点之间的所有边。
让我们使用“networkx”构建下图。
import networkx as nx
G = nx.Graph() #create a graph
G.add_node(1) # add single node
G.add_node(2)
G.add_node(3)
G.add_node(4)
G.add_node(5)
G.add_nodes_from([6,7,8,9]) #add multiple nodes
现在我们已经添加了所有节点,让我们定义这些节点之间的边,如图所示。
# adding edges
G.add_edge(5,8)
G.add_edge(5,4)
G.add_edge(5,7)
G.add_edge(8,2)
G.add_edge(4,3)
G.add_edge(4,1)
G.add_edge(7,6)
G.add_edge(6,9)
现在,我们通过定义节点和边来构建图形,让我们看看 networkx 的“draw()”方法看起来如何,并验证它是否按照我们想要的方式构建。我们将使用matplotlib来显示图表。
import matplotlib.pyplot as plt
nx.draw(G, with_labels=True, font_weight='bold')
plt.show()
输出:
方向可能与我们的设计略有不同,但它类似于同一张图,节点和它们之间的边相同。
现在让我们对该图执行 DFS 遍历。
'networkx' 提供了一系列以不同方式遍历图形的方法。我们将使用“dfs_preorder_nodes()”方法以深度优先搜索顺序解析图。
图中的预期顺序应该是:
5, 8, 2, 4, 3, 1, 7, 6, 9
让我们调用该方法,看看它以什么顺序打印节点。
dfs_output = list(nx.dfs_preorder_nodes(G, source=5))
print(dfs_output)
输出:
因此 networkx 的遍历顺序符合我们的预期。
现在我们已经很好地理解了深度优先搜索或 DFS 遍历,让我们看一下它的一些应用。
拓扑排序是图的重要应用之一,用于模拟许多现实生活中的问题,其中任务的开始取决于其他任务的完成。
例如,我们可以使用图的节点表示多个作业或任务。
一些任务可能依赖于其他一些任务的完成。这种依赖性是通过 节点之间的有向边 建模的。
具有有向边的图称为有向图。
如果我们想从这样的一组任务中执行调度操作,我们必须确保不违反依赖关系,即任务链中后面出现的任何任务总是只有在它完成之前的所有任务之后才执行.
我们可以通过图的拓扑排序来实现这种排序。
请注意,要使拓扑排序成为可能,图中必须不存在有向循环,也就是说,该图必须是有向 无环图 或 DAG。
让我们以 DAG 为例,并使用深度优先搜索方法对其执行拓扑排序。
假设上图中的每个节点代表工厂中生产产品的任务。节点模型之间的定向箭头是每个任务对先前任务完成的依赖性。
因此,无论我们选择执行的任务顺序如何,要开始任务 C,任务 A 和 E 必须已经完成。
同样,为了执行任务 I,必须完成任务 A、E、C 和 F。由于节点 H 上没有向内的箭头,因此任务 H 可以在任何时候执行,而不依赖于任何其他任务的完成。
我们可以使用 Python networkx 的“有向图”模块构建这样一个有向图。
dag = nx.digraph.DiGraph()
dag.add_nodes_from(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'])
dag.add_edges_from([('A', 'B'), ('A', 'E'), ('B', 'D'), ('E', 'C'),
('D', 'G'),('C', 'G'),('C', 'I'), ('F', 'I')])
请注意,我们使用了方法“add_nodes_from()”和“add_edges_from()”一次添加有向图的所有节点和边。
我们现在可以编写一个函数来使用 DFS 执行拓扑排序。
我们将从一个没有向内箭头的节点开始,继续探索它的一个分支,直到我们找到一个叶节点,然后我们回溯并探索其他分支。
一旦我们探索了一个节点的所有分支,我们就会将该节点标记为“已访问”并将其推送到堆栈中。
一旦访问了每个节点,我们就可以在堆栈上执行重复的弹出操作,从而为我们提供任务的拓扑排序顺序。
现在让我们将这个想法转化为 Python 函数:
def dfs(dag, start, visited, stack):
if start in visited:
# node and all its branches have been visited
return stack, visited
if dag.out_degree(start) == 0:
# if leaf node, push and backtrack
stack.append(start)
visited.append(start)
return stack, visited
#traverse all the branches
for node in dag.neighbors(start):
if node in visited:
continue
stack, visited = dfs(dag, node, visited, stack)
#now, push the node if not already visited
if start not in visited:
print("pushing %s"%start)
stack.append(start)
visited.append(start)
return stack, visited
def topological_sort_using_dfs(dag):
visited = []
stack=[]
start_nodes = [i for i in dag.nodes if dag.in_degree(i)==0]
# print(start_nodes)
for s in start_nodes:
stack, visited = dfs(dag, s, visited, stack)
print("Topological sorted:")
while(len(stack)!=0):
print(stack.pop(), end=" ")
我们定义了两个函数——一个用于节点的递归遍历,以及主要的拓扑排序函数,它首先找到所有没有依赖关系的节点,然后使用深度优先搜索方法遍历每个节点。
最后,它从堆栈中弹出值,生成节点的拓扑排序。
现在让我们调用函数“topological_sort_using_dfs()”
topological_sort_using_dfs(dag)
输出 :
如果我们仔细观察输出顺序,我们会发现每当每个作业开始时,它的所有依赖项都先于它完成。
我们还可以将其与名为“topological_sort()”的“networkx”模块中包含的拓扑排序方法的输出进行比较。
topological_sorting = nx.topological_sort(dag)
for n in topological_sorting:
print(n, end=' ')
输出:
看起来 networkx 的排序方法生成的排序与我们的方法生成的排序相同。
图还有另一个重要的属性,称为连通分量。无向图中的连通分量是指一组节点,其中每个顶点通过路径连接到每个其他顶点。
让我们看看下面的例子:
在上图中,有三个连通分量;他们每个人都被标记为粉红色。
让我们用 Python 构建这个图,然后绘制出一种在其中查找连通分量的方法。
graph = nx.Graph()
graph.add_nodes_from(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'])
graph.add_edges_from([('A', 'B'), ('B', 'E'), ('A', 'E')]) #component 1
graph.add_edges_from([('C', 'D'), ('D', 'H'), ('H', 'F'), ('F', 'C')]) #component 2
graph.add_edge('G','I') #component 3
让我们也想象一下它。
import matplotlib.pyplot as plt
nx.draw(graph, with_labels=True, font_weight='bold')
plt.show()
输出:
为了使用 DFS 查找连通分量,我们将维护一个名为“visited”的通用全局数组,每次遇到一个未被访问的新变量时,我们将开始查找它属于哪个连通分量。
我们将该组件中的每个节点标记为“已访问”,这样我们将无法重新访问它以找到另一个连接的组件。
我们将为每个节点重复此过程,我们调用 DFS 方法从节点中查找连通分量的次数将等于图中连通分量的数量。
让我们用 Python 编写这个逻辑并在我们刚刚构建的图上运行它:
def find_connected_components(graph):
visited = []
connected_components = []
for node in graph.nodes:
if node not in visited:
cc = [] #connected component
visited, cc = dfs_traversal(graph, node, visited, cc)
connected_components.append(cc)
return connected_components
def dfs_traversal(graph, start, visited, path):
if start in visited:
return visited, path
visited.append(start)
path.append(start)
for node in graph.neighbors(start):
visited, path = dfs_traversal(graph, node, visited, path)
return visited, path
让我们在上一步构建的图形上使用我们的方法。
connected_components = find_connected_components(graph)
print("Total number of connected components =", len(connected_components))
for cc in connected_components:
print(cc)
输出:
在这篇博客中,我们了解了 DFS 算法并以不同的方式使用它。
我们首先了解如何使用通用数据结构表示图形,并在 Python 中实现它们中的每一个。
然后,我们使用递归和非递归方法实现了深度优先搜索遍历算法。
接下来,我们研究了一种特殊形式的图,称为二叉树,并在其上实现了 DFS 算法。
在这里,我们使用从我们定义的表示节点的 Python 类构造的节点对象来表示整个树。
然后我们研究了 Python 提供的用于表示图形和对其执行操作的产品——“networkx”模块。
我们用它来构建一个图形,将其可视化,并在其上运行我们的 DFS 方法。我们将输出结果与模块自带的 DFS 遍历方法进行了比较。
最后,我们研究了深度优先搜索遍历的两个重要应用,即拓扑排序和查找图中的连通分量。
文章原文出处:https: //likegeeks.com/