mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-03-01 08:05:25 -05:00
feat(curriculum): add graphs and trees lecture (#62055)
Co-authored-by: Jessica Wilkins <67210629+jdwilkin4@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -4643,7 +4643,7 @@
|
||||
"lecture-understanding-graphs-and-trees": {
|
||||
"title": "Understanding Graphs and Trees",
|
||||
"intro": [
|
||||
"Learn about Understanding Graphs and Trees in these lectures."
|
||||
"In this lecture, you will learn about fundamental data structures like graphs, trees, and their practical applications in computer science."
|
||||
]
|
||||
},
|
||||
"workshop-shortest-path-algorithm": {
|
||||
|
||||
@@ -6,4 +6,4 @@ superBlock: full-stack-developer
|
||||
|
||||
## Introduction to Understanding Graphs and Trees
|
||||
|
||||
Learn about Understanding Graphs and Trees in these lectures.
|
||||
In this lecture, you will learn about fundamental data structures like graphs, trees, and their practical applications in computer science.
|
||||
|
||||
@@ -1,135 +1,243 @@
|
||||
---
|
||||
id: 68420c5ac17cf26ab2a4ca3c
|
||||
# title needs to be updated to correct title when lectures are finalized
|
||||
title: Understanding Graphs and Trees
|
||||
title: What Are Graphs in Computer Science?
|
||||
challengeType: 19
|
||||
# dashedName needs to be updated to correct title when lectures are finalized
|
||||
dashedName: lecture-understanding-graphs-and-trees
|
||||
dashedName: what-are-graphs-in-computer-science
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Watch the video or read the transcript and answer the questions below.
|
||||
Graphs are data structures used to represent the connections or relationships between objects or entities. They're often used to model networks.
|
||||
|
||||
The types of networks that you can model with a graph include social networks, transportation networks, communications networks, and even recommendation systems.
|
||||
|
||||
For example, graphs can represent connections between users on a social media platform, or connections between cities on a road network.
|
||||
|
||||
They're very versatile.
|
||||
|
||||
A graph is often represented as a collection of points or circles connected by lines.
|
||||
|
||||
These circles and lines represent the two main components of a graph: nodes and edges.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-graphs-in-computer-science-1.png" alt="A simple graph showing five nodes labeled A, B, C, D, and E connected by edges. Node A connects to B, B connects to A, C, and D, C connects to B and D, D connects to B, C, and E, and E connects to D.">
|
||||
|
||||
**Nodes**, also known as vertices, represent the objects or entities that are part of the network modeled by the graph. They could be users, products, stations, cities, or any other entities in the model.
|
||||
|
||||
In this example, nodes are represented as circles and labeled with the letters A, B, C, D, and E to distinguish them visually.
|
||||
|
||||
**Edges** are the connections between the nodes. If two nodes are connected by an edge, that means that they're somehow connected in the network.
|
||||
|
||||
In this example, there are five edges connecting the different pairs of nodes. Node A is connected to node B. node B is connected to nodes A, C, and D and so on.
|
||||
|
||||
The specific meaning of the connection will depend on the context. It may be physical, like a real road that connects two cities, or it could be more abstract, like the connection between two users on a social media platform.
|
||||
|
||||
If two nodes are directly connected by an edge, like nodes A and B in this example, they're known as **adjacent nodes**.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-graphs-in-computer-science-2.png" alt="The same graph with nodes A and B highlighted to illustrate adjacent nodes - two nodes that are directly connected by an edge.">
|
||||
|
||||
## Types of Graphs
|
||||
|
||||
There are different types of graphs with different characteristics and applications. Let's go over some of them.
|
||||
|
||||
### Undirected Graphs
|
||||
|
||||
**Undirected graphs** are graphs where the edges don't have a specific direction. This type of edge is usually represented with a straight line between the nodes.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-graphs-in-computer-science-1.png" alt="An example of an undirected graph where all edges are represented as straight lines without arrows, demonstrating that connections work in both directions between nodes.">
|
||||
|
||||
This means that, if there's an edge connecting nodes A and B, the connection works in both directions: from node A to node B and from node B to node A.
|
||||
|
||||
Depending on the network that is being modeled by the graph, this connection can have different meanings.
|
||||
|
||||
For example, if you're modeling connections between users of a social media platform, this means that user A is connected to user B and user B is connected to user A. The connection is bidirectional.
|
||||
|
||||
### Directed Graphs
|
||||
|
||||
In contrast, **directed graphs** are graphs where the edges do have a specific direction.
|
||||
|
||||
If there is a connection from node A to node B, that doesn't necessarily imply that there is a connection from node B to node A.
|
||||
|
||||
The edges of a directed graph are often represented as straight lines that end with an arrow to indicate the direction.
|
||||
|
||||
Here's an example. In this graph, you can go from node A to node B but not from node B to node A because of the direction of the edge.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-graphs-in-computer-science-4.png" alt="A directed graph example showing the same nodes as before but with arrows indicating specific one-way connections: from A to B, B to C, C to D, D to B, and D to E, demonstrating unidirectional edges.">
|
||||
|
||||
For example, if you are modeling a road network, this would be helpful to model one-way streets or roads. You can go from city A to city B through that road, but not from city B to city A. You'll need to take a different route.
|
||||
|
||||
If there is a two-way connection between the nodes of a directed graph, you can represent this with two directed edges between them.
|
||||
|
||||
Here you can see an example. You can go from node B to node D and from node D to node B because there are two edges connecting them, but each edge has a direction.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-graphs-in-computer-science-5.png" alt="A directed graph showing bidirectional connection between nodes B and D with two arrows, one pointing from B to D and another from D to B.">
|
||||
|
||||
## Vertex Labeled Graphs
|
||||
|
||||
A **vertex labeled graph** is a graph in which each node is associated with a label or identifier in addition to its data. These labels are used to identify the nodes, represent them visually, and store additional information about them.
|
||||
|
||||
For example, in a transportation network graph, nodes could be cities and their labels could be their names, coordinates, or any other characteristic that would be helpful for the purposes of the model.
|
||||
|
||||
## Cyclic Graphs
|
||||
|
||||
**Cyclic graphs** are directed graphs with at least one cycle.
|
||||
|
||||
A **cycle** is a path that you can follow through the edges of graph that will take you back to the initial node where you started.
|
||||
|
||||
In this example, we have a directed graph. If you look more closely, you'll notice that it has a cycle. If we start at node B, go to node C, and then to node D, we can go back to node B again through the directed edges.
|
||||
|
||||
This is a cycle, so this is a cyclic graph.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-graphs-in-computer-science-6.png" alt="A directed graph showing a cycle where you can start at node B, go to node C, then to node D, and back to node B, forming a complete cycle.">
|
||||
|
||||
## Edge Labeled Graphs
|
||||
|
||||
In **edge labeled graphs**, edges are associated with labels. These labels are usually drawn next to their corresponding edges.
|
||||
|
||||
## Weighted Graphs
|
||||
|
||||
**Weighted graphs** are a specific type of edge labeled graph in which the labels on the edges represent values that can be compared and used to perform arithmetic operations.
|
||||
|
||||
Some edges have a higher weight, while others have a lower weight. These weights represent the "cost" of the edge.
|
||||
|
||||
For example, they may represent the distance between two cities or the time it takes to get from one city to the next.
|
||||
|
||||
This is an example of a weighted graph. We write each weight next to its corresponding edge. The "cost" of going from node B to node D is 3, and since this is an undirected graph, that's the same cost of going from node D back to node B.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-graphs-in-computer-science-7.png" alt="A weighted graph showing nodes connected by edges with numerical weights labeled next to each edge, such as weight 3 between nodes B and D.">
|
||||
|
||||
## Directed Acyclic Graph
|
||||
|
||||
Another very common type of graph in computer science is the **directed acyclic graph**, which is a directed graph with no cycles.
|
||||
|
||||
Here's an example. It's a directed graph because each edge has a direction.
|
||||
|
||||
It's acyclic because it doesn't have any cycles. Why? Notice that, if you start at a specific node, you cannot go back to it because of the direction of the edges.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-graphs-in-computer-science-8.png" alt="A directed acyclic graph (DAG) with connections from A to B, B to C, B to D, C to D, and D to E, demonstrating how the directional flow prevents any cycles from forming.">
|
||||
|
||||
## Disconnected Graph
|
||||
|
||||
And the last type of graph that we'll cover in this lecture is the **disconnected graph**.
|
||||
|
||||
A disconnected graph is a graph with two or more groups of nodes that are not connected by any edges.
|
||||
|
||||
A real-world example would be a social media network, where you have two or more groups of people who don't know each other and who have no friends in common.
|
||||
|
||||
This is an example. The first component has the nodes A, B, and C. The second component has the nodes D and E. These components don't have any edges between them, so this is a disconnected graph.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-graphs-in-computer-science-9.png" alt="A disconnected graph showing two separate components: the first component has nodes A, B, and C connected in sequence (A connects to B, B connects to C), and the second component has nodes D and E connected to each other, with no connections between the two components.">
|
||||
|
||||
You can implement graphs in a variety of ways, including sets, functions, and arrays. You'll learn more about this in the coming lectures.
|
||||
|
||||
Understanding graphs and the characteristics of the different types of graphs is essential for solving a wide range of problems in computer science and other fields.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Question 1
|
||||
What is a graph in computer science?
|
||||
|
||||
## --answers--
|
||||
|
||||
Answer 1.1
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 1
|
||||
A data structure used to represent relationships between objects.
|
||||
|
||||
---
|
||||
|
||||
Answer 1.2
|
||||
A mathematical equation.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 1
|
||||
Think about how graphs are used to model connections and relationships.
|
||||
|
||||
---
|
||||
|
||||
Answer 1.3
|
||||
A visual representation of data.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 1
|
||||
Think about how graphs are used to model connections and relationships.
|
||||
|
||||
---
|
||||
|
||||
Answer 1.4
|
||||
A programming language.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 1
|
||||
Think about how graphs are used to model connections and relationships.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
5
|
||||
1
|
||||
|
||||
## --text--
|
||||
|
||||
Question 2
|
||||
What are the two main components of a graph?
|
||||
|
||||
## --answers--
|
||||
|
||||
Answer 2.1
|
||||
Points and lines
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 2
|
||||
Think about the basic building blocks of a graph.
|
||||
|
||||
---
|
||||
|
||||
Answer 2.2
|
||||
Vertices and nodes
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 2
|
||||
Think about the basic building blocks of a graph.
|
||||
|
||||
---
|
||||
|
||||
Answer 2.3
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 2
|
||||
Nodes and edges
|
||||
|
||||
---
|
||||
|
||||
Answer 2.4
|
||||
Elements and vertices
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 2
|
||||
Think about the basic building blocks of a graph.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
5
|
||||
3
|
||||
|
||||
## --text--
|
||||
|
||||
Question 3
|
||||
What is a directed graph?
|
||||
|
||||
## --answers--
|
||||
|
||||
Answer 3.1
|
||||
A graph where edges do not have a direction.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 3
|
||||
Think about whether the connections between nodes have a specific direction.
|
||||
|
||||
---
|
||||
|
||||
Answer 3.2
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 3
|
||||
A graph where edges have a direction.
|
||||
|
||||
---
|
||||
|
||||
Answer 3.3
|
||||
A graph with more nodes than edges.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 3
|
||||
Think about whether the connections between nodes have a specific direction.
|
||||
|
||||
---
|
||||
|
||||
Answer 3.4
|
||||
A graph with more edges than nodes.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Feedback 3
|
||||
Think about whether the connections between nodes have a specific direction.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
5
|
||||
2
|
||||
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
---
|
||||
id: 68baa5e4f0e07f079245ca08
|
||||
title: How Do Depth First and Breadth First Search Work?
|
||||
challengeType: 19
|
||||
dashedName: how-do-depth-first-and-breadth-first-search-work
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
As you start working with data structures and algorithms, you'll soon realize that one of the common operations that you'll need to perform is visiting each node.
|
||||
|
||||
This process is known as "traversing" the data structure.
|
||||
|
||||
Traversals are used to do something with every single node in the data structure, like printing their values, finding a specific value, or performing certain operations on the nodes.
|
||||
|
||||
By systematically visiting each node, you make sure that the process won't skip any nodes.
|
||||
|
||||
But how do you determine the order in which you should traverse the data structure? Where should the process start, and how should the next node be selected?
|
||||
|
||||
Without a clear way to traverse the data structure, going through it would be like walking through a maze without a specific path to follow.
|
||||
|
||||
That's where algorithms like breadth-first search (BFS) and depth-first search (DFS) become really important. They are commonly used to traverse graphs and for finding a path between two nodes.
|
||||
|
||||
When they are used to traverse a data structure, they define the order in which the nodes should be visited to make sure that none of them is skipped.
|
||||
|
||||
Let's start with breadth-first search (BFS).
|
||||
|
||||
## Breadth-First Search (BFS)
|
||||
|
||||
**Breadth-first search (BFS)** is an algorithm that visits all neighboring nodes before moving to the next level in the graph.
|
||||
|
||||
It can be used to find the shortest path between two nodes in an unweighted graph because it analyzes all the nodes at each level, so it finds the path with fewest edges first.
|
||||
|
||||
This algorithm is commonly implemented using a queue data structure to keep track of the nodes that have been visited. Queues follow the FIFO (first in, first out) method, where the first node that was added to the queue is the first one to be removed.
|
||||
|
||||
The algorithm works like this:
|
||||
|
||||
* You start at a specific node.
|
||||
|
||||
* That node is marked as visited and added to the queue.
|
||||
|
||||
* While the queue is not empty, the current node is removed from the queue (dequeued). Then, for each one of its neighbors, if the neighbor has not been visited, it is marked as visited and added to the queue.
|
||||
|
||||
|
||||
One important consideration is that, since breadth-first search (BFS) requires storing a queue in memory, and this queue may have a large number of nodes, the space requirements of this algorithm can be considerable. This is especially true for graphs with a large number of nodes on the same level.
|
||||
|
||||
Let's see an example of BFS applied to a specific type of graph called a tree.
|
||||
|
||||
You will learn more about trees in an upcoming lecture, but they are essentially graphs with no cycles, where nodes are organized in a hierarchy. Cycles are paths that start and end at the same node.
|
||||
|
||||
Let's apply the breadth-first search (BFS) algorithm to this tree:
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/how-do-depth-first-and-breadth-first-search-work-1.png" alt="A tree diagram showing nodes A through G arranged in a hierarchy. Node A is at the root, with children B and C. Node B has children D and E, and node C has children F and G.">
|
||||
|
||||
**Step 1:**
|
||||
|
||||
We start at the root of the tree, node A. We add A to the queue and immediately mark it as visited.
|
||||
|
||||
* **Queue:** `[A]`
|
||||
|
||||
* **Visited:** `{A}`
|
||||
|
||||
|
||||
**Step 2:**
|
||||
|
||||
We dequeue node A. We add its unvisited children (node B and then node C), to the queue and mark them as visited.
|
||||
|
||||
* **Queue:** `[B, C]`
|
||||
|
||||
* **Visited:** `{A, B, C}`
|
||||
|
||||
|
||||
The order in which nodes at the same level are added to the queue is defined by the implementation of the data structure and the order in which the edges (connections) are stored in the graph representation.
|
||||
|
||||
If the implementation is consistent, the specific order in which the nodes at the same level are traversed will not affect the correctness of the algorithm. It will still visit every node level by level.
|
||||
|
||||
**Step 3:**
|
||||
|
||||
We dequeue node B. We add its unvisited children, (node D and then node E), to the queue and mark them as visited.
|
||||
|
||||
* **Queue:** `[C, D, E]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E}`
|
||||
|
||||
|
||||
**Step 4:**
|
||||
|
||||
We dequeue node C. We add its unvisited children, (node F and then node G), to the queue and mark them as visited.
|
||||
|
||||
* **Queue:** `[D, E, F, G]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E, F, G}`
|
||||
|
||||
|
||||
**Step 5:**
|
||||
|
||||
We dequeue node D. This node does not have any unvisited children, so nothing changes in the visited set.
|
||||
|
||||
* **Queue:** `[E, F, G]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E, F, G}`
|
||||
|
||||
|
||||
**Step 6:**
|
||||
|
||||
We dequeue node E. This node does not have any unvisited children, so nothing changes in the visited set.
|
||||
|
||||
* **Queue:** `[F, G]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E, F, G}`
|
||||
|
||||
|
||||
**Step 7:**
|
||||
|
||||
We dequeue F. This node does not have any unvisited children, so nothing changes in the visited set.
|
||||
|
||||
* **Queue:** `[G]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E, F, G}`
|
||||
|
||||
|
||||
**Step 8:**
|
||||
|
||||
We dequeue G. This node does not have any unvisited children, so nothing changes in the visited set.
|
||||
|
||||
* **Queue:** `[]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E, F, G}`
|
||||
|
||||
|
||||
When the queue is empty, the traversal is complete.
|
||||
|
||||
The nodes were traversed in this order:
|
||||
|
||||
**A → B → C → D → E → F → G**
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/how-do-depth-first-and-breadth-first-search-work-2.png" alt="The same tree diagram with arrows showing the BFS traversal order: A to B to C to D to E to F to G, demonstrating level-by-level visitation.">
|
||||
|
||||
Notice how the algorithm visits the nodes per level.
|
||||
|
||||
We start at node A, then move to the next level to visit nodes B and C, then to the next level to nodes D, E, F, and G. That is the core principle of breadth-first search (BFS).
|
||||
|
||||
## Depth-First Search (DFS)
|
||||
|
||||
While breadth-first search (BFS) first visits all the neighboring nodes at the same level, **depth-first search (DFS)** follows each branch as deep as possible before it backtracks.
|
||||
|
||||
You can imagine this algorithm as exploring a maze by choosing a specific path and following it until you reach a dead end or the exit. If you reach a dead end, you go back and choose a different path.
|
||||
|
||||
Depth-first search (DFS) is commonly used to solve puzzles with a single solution, detecting cycles in a graph, and finding connected graph components.
|
||||
|
||||
This algorithm can be implemented using recursion or a stack data structure to keep track of the visited nodes.
|
||||
|
||||
Stacks follow the LIFO (last in, first out) method, where the last node that was added to the stack is the first one to the removed from the stack.
|
||||
|
||||
The algorithm works like this:
|
||||
|
||||
* Start at a specific node.
|
||||
|
||||
* That node is marked as visited and added to the stack.
|
||||
|
||||
* While the stack is not empty, the current node is popped (removed). This is when we "visit" or process it (for example, by printing its value). Then, all of its unvisited neighbors are marked as visited and added to the stack.
|
||||
|
||||
|
||||
One of the limitations of this algorithm is that it's not always guaranteed to find the shortest path between two nodes in an unweighted graph.
|
||||
|
||||
Let's see an example of Depth-First Search (DFS) applied to our tree example.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/how-do-depth-first-and-breadth-first-search-work-3.png" alt="The same tree diagram as before, showing nodes A through G in their hierarchical structure, ready for DFS demonstration.">
|
||||
|
||||
**Step 1:**
|
||||
|
||||
We start at the root node A. We mark it as visited and add it to the stack.
|
||||
|
||||
* **Stack:** `[A]`
|
||||
|
||||
* **Visited:** `{A}`
|
||||
|
||||
|
||||
**Step 2:**
|
||||
|
||||
We pop node A from the stack.
|
||||
|
||||
Then, we add its unvisited children, node B and node C, to the stack. We'll add them in reverse order, `C` then `B`, so that `B` is on top (LIFO) and will be processed next. We also mark them as visited.
|
||||
|
||||
* **Stack:** `[C, B]`
|
||||
|
||||
* **Visited:** `{A, B, C}`
|
||||
|
||||
|
||||
**Step 3:**
|
||||
|
||||
We pop node B from the stack.
|
||||
|
||||
Then, we add its unvisited children, node D and node E, to the stack in reverse order (`E` then `D`). We also mark them as visited.
|
||||
|
||||
* **Stack:** `[C, E, D]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E}`
|
||||
|
||||
|
||||
**Step 4:**
|
||||
|
||||
We pop node D from the stack. This node has no children to add to the stack.
|
||||
|
||||
* **Stack:** `[C, E]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E}`
|
||||
|
||||
|
||||
**Step 5:**
|
||||
|
||||
We pop node E from the stack. This node has no children to add to the stack.
|
||||
|
||||
* **Stack:** `[C]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E}`
|
||||
|
||||
|
||||
**Step 6:**
|
||||
|
||||
We pop node C.
|
||||
|
||||
Then, we add its children, node F and node G, to the stack in reverse order (node G then node F) and we mark them as visited.
|
||||
|
||||
* **Stack:** `[G, F]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E, F, G}`
|
||||
|
||||
|
||||
**Step 7:**
|
||||
|
||||
We pop node F from the stack. This node has no children to add to the stack.
|
||||
|
||||
* **Stack:** `[G]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E, F, G}`
|
||||
|
||||
|
||||
**Step 8:**
|
||||
|
||||
We pop node G. This node has no children to add to the stack.
|
||||
|
||||
* **Stack:** `[]`
|
||||
|
||||
* **Visited:** `{A, B, C, D, E, F, G}`
|
||||
|
||||
|
||||
When the stack is empty, the traversal is completed and all nodes have been visited.
|
||||
|
||||
The algorithm visited the nodes in this order:
|
||||
|
||||
**A → B → D → E → C → F → G**
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/how-do-depth-first-and-breadth-first-search-work-4.png" alt="The tree diagram with numbers showing the DFS traversal order: A(1), B(2), D(3), E(4), C(5), F(6), G(7), demonstrating depth-first exploration of branches.">
|
||||
|
||||
Notice how we start at node A, and then move all the way down the tree to node B, and nodes D and E, before we move up again to node C and then nodes F and G. This is the core principle of depth-first search (DFS), traversing full paths before backtracking and finding other paths.
|
||||
|
||||
In this case, we solved this example using a stack. Alternatively, depth-first search (DFS) can be implemented using recursion, where the function processes the current node and then call itself for each of its unvisited neighbors. The function call stack implicitly manages the LIFO (last-in, first-out) order.
|
||||
|
||||
Both breadth-first search (BFS) and depth-first search (DFS) are essential algorithms for traversing graphs and trees. Breadth-first search (BFS) explores nodes level by level, which is perfect for finding the shortest path in an unweighted graph. On the other hand, depth-first search (DFS) follows one branch as deep as possible before backtracking, which is perfect for solving mazes and detecting cycles. Understanding their pros and cons is helpful for choosing the right one for a particular problem.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following data structures is commonly used to implement a standard breadth-first search (BFS) algorithm?
|
||||
|
||||
## --answers--
|
||||
|
||||
Stack
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how a queue processes data and how that relates to visiting nodes level by level.
|
||||
|
||||
---
|
||||
|
||||
Queue
|
||||
|
||||
---
|
||||
|
||||
Linked List
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how a queue processes data and how that relates to visiting nodes level by level.
|
||||
|
||||
---
|
||||
|
||||
Tree
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how a queue processes data and how that relates to visiting nodes level by level.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following statements about depth-first search (DFS) is true?
|
||||
|
||||
## --answers--
|
||||
|
||||
Depth-first search is guaranteed to find the shortest path between two nodes in an unweighted graph.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the strategy that depth-first search (DFS) uses to traverse the data structure.
|
||||
|
||||
---
|
||||
|
||||
Depth-first search visits all neighbors at the current level before moving to the next level.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the strategy that depth-first search (DFS) uses to traverse the data structure.
|
||||
|
||||
---
|
||||
|
||||
Depth-first search is always more space-efficient than BFS.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the strategy that depth-first search (DFS) uses to traverse the data structure.
|
||||
|
||||
---
|
||||
|
||||
Depth-first search is typically implemented using recursion or a stack.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
|
||||
## --text--
|
||||
|
||||
If you wanted to find the shortest path from a starting node to a target node in an unweighted graph, which algorithm would be the most suitable choice?
|
||||
|
||||
## --answers--
|
||||
|
||||
Breadth-first search (BFS)
|
||||
|
||||
---
|
||||
|
||||
Depth-first search (DFS)
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the core traversal strategy of breadth-first search and depth-first search and which one guarantees finding the shortest path between two nodes.
|
||||
|
||||
---
|
||||
|
||||
Binary search
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the core traversal strategy of breadth-first search and depth-first search and which one guarantees finding the shortest path between two nodes.
|
||||
|
||||
---
|
||||
|
||||
Merge sort
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the core traversal strategy of breadth-first search and depth-first search and which one guarantees finding the shortest path between two nodes.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
@@ -0,0 +1,252 @@
|
||||
---
|
||||
id: 68baa5e4f0e07f079245ca09
|
||||
title: How Do Matrices and Adjacency Lists Work?
|
||||
challengeType: 19
|
||||
dashedName: how-do-matrices-and-adjacency-lists-work
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Graphs are very powerful data structures made by a set of nodes, also known as vertices, and edges that connect them.
|
||||
|
||||
There are two common ways to implement graphs in your code:
|
||||
|
||||
* Adjacency matrices
|
||||
|
||||
* Adjacency lists
|
||||
|
||||
|
||||
Let's look more into these, and go over their advantages and limitations.
|
||||
|
||||
## Adjacency Matrices
|
||||
|
||||
We'll start with adjacency matrices.
|
||||
|
||||
An adjacency matrix is a two-dimensional list in which the rows and columns represent the graph's vertices.
|
||||
|
||||
The values in the matrix represent the edges or connections between the nodes.
|
||||
|
||||
For example, if you have a matrix stored in a variable named `matrix`, the value stored at `matrix[i][j]`, where `i` is the row and `j` is the column, represents the edge or connection between nodes `i` and node `j`.
|
||||
|
||||
The values may have different meanings depending on whether the graph is weighted or unweighted:
|
||||
|
||||
* If the graph is unweighted, a value of `1` means that there is an edge connecting these nodes, while a value of `0` means there is no edge between them.
|
||||
|
||||
* If the graph is weighted, the value would represent the weight of the edge that connects the nodes.
|
||||
|
||||
|
||||
One of the great advantages of using an adjacency matrix is that checking if there is an edge between two nodes takes constant time `O(1)`. This is because the program only needs to find that particular value in the 2D list.
|
||||
|
||||
However, this efficiency in finding the edges comes with a tradeoff. Adjacency matrices have a large quadratic space requirement `O(V²)`, where `V` is the number of nodes in the graph.
|
||||
|
||||
This is inefficient for sparse graphs, which are graphs that only have a few edges. Why? Because if the graph is sparse, you will be storing many 0s in the matrix to represent the lack of edges between the nodes, and these 0s will still take space in memory.
|
||||
|
||||
Adjacency matrices are also inefficient for finding a node's neighbors because the program has to iterate over the entire row or column to find the 0s and 1s that represent the edges. In the worst case, this process can take O(V) time, where V is the number of nodes in the graph.
|
||||
|
||||
Let's see an example of an adjacency matrix for this particular graph:
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/how-do-matrices-and-adjacency-lists-work-1.png" alt="A graph with four nodes labeled A, B, C, and D. Node A connects to B, C, and D. Node B connects to A and D. Node C connects to A. Node D connects to A and B.">
|
||||
|
||||
In the adjacency matrix:
|
||||
|
||||
* Each row represents a node. The first row represents node A, the second row represents node B, and so on.
|
||||
|
||||
* Each column represents a node too.
|
||||
|
||||
* Each value in the matrix represents whether there is an edge between each pair of nodes. A value of 0 means there isn't an edge connecting these nodes, while a value of 1 means there is an edge.
|
||||
|
||||
|
||||
The values in the diagonal represent whether or not each node has a self-loop, an edge that connects the node to itself. In our example, they are all 0s because the graph doesn't have any self-loops.
|
||||
|
||||
This is a visual representation of the adjacency matrix to show you how the rows and columns represent the corresponding nodes.
|
||||
|
||||
For example, the first row is `[0, 1, 1, 1]` because node A has edges connecting it to nodes B, C, and D:
|
||||
|
||||
```markdown
|
||||
# A B C D
|
||||
# A [0, 1, 1, 1],
|
||||
# B [1, 0, 0, 1],
|
||||
# C [1, 0, 0, 0],
|
||||
# D [1, 1, 0, 0]
|
||||
```
|
||||
|
||||
And this is the same adjacency matrix, but implemented in Python code:
|
||||
|
||||
```python
|
||||
adjacency_matrix = [
|
||||
[0, 1, 1, 1], # The neighbors of A are B, C, and D
|
||||
[1, 0, 0, 1], # The neighbors of B are A and D
|
||||
[1, 0, 0, 0], # The only neighbor of C is A
|
||||
[1, 1, 0, 0] # The neighbors of D are A and B
|
||||
]
|
||||
```
|
||||
|
||||
## Adjacency Lists
|
||||
|
||||
Another common way to represent graphs is by using adjacency lists.
|
||||
|
||||
An adjacency list is an array or dictionary that stores all the neighbors of each node.
|
||||
|
||||
There are two ways to implement adjacency lists:
|
||||
|
||||
* As an array, where each index represents a node and the list stored at that index contains its neighbors.
|
||||
|
||||
* As a dictionary, where each key represents a node and the value associated to that key (a list) contains its neighbors.
|
||||
|
||||
|
||||
Adjacency lists are more efficient than adjacency matrices in terms of space requirements. They have a `O(V + E)` space complexity, where `V` is the number of vertices (nodes) and `E` is the number of edges.
|
||||
|
||||
It's also efficient for finding all neighbor nodes, since this operation only requires accessing the list associated to the node.
|
||||
|
||||
However, there is also a tradeoff.
|
||||
|
||||
Adjacency lists are less efficient than adjacency matrices for determining if there is an edge between two nodes.
|
||||
|
||||
The search process can take `O(V)` time in the worst-case, since it may have to search through a very long list of neighbors if the node is connected to all the other nodes in the graph.
|
||||
|
||||
Here is an example of an adjacency list for this graph:
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/how-do-matrices-and-adjacency-lists-work-1.png" alt="A graph with four nodes labeled A, B, C, and D. Node A connects to B, C, and D. Node B connects to A and D. Node C connects to A. Node D connects to A and B. It's the same image as before.">
|
||||
|
||||
This adjacency list is implemented as a dictionary. Every key in the dictionary represents a node, and the value associated to that key is a list with all the neighbors of the corresponding node:
|
||||
|
||||
```python
|
||||
adjacency_list = {
|
||||
'A': ['B', 'C', 'D'],
|
||||
'B': ['A', 'D'],
|
||||
'C': ['A'],
|
||||
'D': ['A', 'B']
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, we could implement it as a 2D list, where each index represents a node. For example, index 0 represents node A, index 1 represents node B, and so on:
|
||||
|
||||
```python
|
||||
adjacency_list = [
|
||||
['B', 'C', 'D'], # Neighbors of A (index 0)
|
||||
['A', 'D'], # Neighbors of B (index 1)
|
||||
['A'], # Neighbors of C (index 2)
|
||||
['A', 'B'] # Neighbors of D (index 3)
|
||||
]
|
||||
```
|
||||
|
||||
Notice that even if this 2D list may look similar to the adjacency matrix, they are quite different.
|
||||
|
||||
* The adjacency matrix stores 0s, 1s, or other values that represent the edges or weights of the edges in the graph.
|
||||
|
||||
* The adjacency list stores the actual list of all the neighbors of each node.
|
||||
|
||||
|
||||
This is a very important difference that you should be familiar with.
|
||||
|
||||
Both adjacency matrices and adjacency lists are very important for implementing graphs. Choosing between them depends on the graph's size and how you need to use the data. Adjacency matrices are helpful for dense graphs with many edges, while adjacency lists are usually the preferred choice for real-world scenarios, where sparse graphs are more common.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
For which of the following scenarios is an adjacency matrix the most efficient choice for representing a graph?
|
||||
|
||||
## --answers--
|
||||
|
||||
A social network with billions of people and very few connections per person.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how much memory a matrix uses and how that changes based on the number of connections.
|
||||
|
||||
---
|
||||
|
||||
A computer network with only five connections.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how much memory a matrix uses and how that changes based on the number of connections.
|
||||
|
||||
---
|
||||
|
||||
A dense graph where every node is connected to most other nodes.
|
||||
|
||||
---
|
||||
|
||||
A graph where the main operation is to find all neighbors of a specific node.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how much memory a matrix uses and how that changes based on the number of connections.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
## --text--
|
||||
|
||||
When would it be more efficient to use an adjacency list over an adjacency matrix?
|
||||
|
||||
## --answers--
|
||||
|
||||
When the graph is dense and has many edges.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how an adjacency list saves memory when a graph is not very connected.
|
||||
|
||||
---
|
||||
|
||||
When you need to check if an edge exists between two nodes very quickly.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how an adjacency list saves memory when a graph is not very connected.
|
||||
|
||||
---
|
||||
|
||||
When the graph has a high number of vertices and many connections.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how an adjacency list saves memory when a graph is not very connected.
|
||||
|
||||
---
|
||||
|
||||
When the graph is sparse, with a high number of vertices and a low number of edges.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following operations is faster in an adjacency matrix compared to an adjacency list?
|
||||
|
||||
## --answers--
|
||||
|
||||
Finding all neighbors of a single node.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how you would perform each of these operations in a grid and a list-based structure.
|
||||
|
||||
---
|
||||
|
||||
Checking if a direct edge exists between two specific nodes.
|
||||
|
||||
---
|
||||
|
||||
Iterating through all nodes in the graph.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how you would perform each of these operations in a grid and a list-based structure.
|
||||
|
||||
---
|
||||
|
||||
Adding a new node to the graph.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how you would perform each of these operations in a grid and a list-based structure.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
@@ -0,0 +1,216 @@
|
||||
---
|
||||
id: 68baa5e4f0e07f079245ca0a
|
||||
title: What Are Trees and Tries and How Do They Work?
|
||||
challengeType: 19
|
||||
dashedName: what-are-trees-and-tries-and-how-do-they-work
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
Trees are very important in the world of computer science.
|
||||
|
||||
A **tree** is a specific type of graph.
|
||||
|
||||
For a graph to be classified as a tree, it must:
|
||||
|
||||
* Have no loops or cycles (paths where the start and end nodes are the same).
|
||||
|
||||
* Be connected (every node can be reached from every other node).
|
||||
|
||||
|
||||
Trees are non-linear data structures that organize nodes in a hierarchy, where nodes may have children, siblings, and parent nodes.
|
||||
|
||||
The root node is the very top of a tree. It's the only node in the tree without a parent node. This is the node where you will start traversing the entire data structure, usually with algorithms like breadth-first search (BFS) or depth-first search (DFS).
|
||||
|
||||
This is a graphical example of a tree:
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-trees-and-tries-and-how-do-they-work-1.png" alt="A tree diagram showing a hierarchical structure with node A at the root, nodes B and C as children of A, and nodes D and E as children of C. Node B is a leaf node.">
|
||||
|
||||
Since nodes are organized in a hierarchy, they have relationships between them.
|
||||
|
||||
A **parent node** is a node that is immediately connected to other nodes below it. In the diagram, node A is the parent node of nodes B and C.
|
||||
|
||||
A **child node** is a node that is immediately connected to a node above it. In the diagram, node D and E are the child nodes of node C.
|
||||
|
||||
Nodes D and E are also classified as **leaves**. A leaf is a node that has no child nodes. You can think of them as the end of the "branches" of the tree.
|
||||
|
||||
Tree nodes also have important properties:
|
||||
|
||||
* **Depth**: the length of the path from the root to that node. For example, in the diagram, node D has depth 2 because if you start at the root, you have to go through two edges to reach it.
|
||||
|
||||
* **Height**: the length of the path from that node down to a leaf. For example, node C has a height of 1 because it's one level above the leaf nodes.
|
||||
|
||||
* **Degree:** the number of child nodes each node has. In the diagram, node B has degree 0 because it's a leaf node, so it has no child nodes. Node C has degree 2 because it has two child nodes.
|
||||
|
||||
|
||||
Trees also have a **height**. The height of a tree is the height of its root node.
|
||||
|
||||
There are many different types of trees, including Binary Trees, Binary Search Trees, AVL trees, Red-Black Trees, and B-Trees.
|
||||
|
||||
## Binary Trees and Binary Search Trees
|
||||
|
||||
These are two of the most commonly used types of trees.
|
||||
|
||||
A **binary tree** is a type of tree in which each node can have at most two child nodes, a left child node and a right child node. Yes, this means that the example that you have seen so far is a binary tree!
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-trees-and-tries-and-how-do-they-work-1.png" alt="The same binary tree example highlighting that it's a binary tree, with each node having at most two children.">
|
||||
|
||||
A **binary search tree** is a more specific version of a binary tree, with a very particular ordering property.
|
||||
|
||||
To understand it, first you need to understand subtrees. A **subtree** is a section of a tree that is a tree itself.
|
||||
|
||||
In our tree example, nodes C, D, and E form a tree by themselves, so they are considered a subtree.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-trees-and-tries-and-how-do-they-work-3.png" alt="A diagram highlighting a subtree within the main tree, showing nodes C, D, and E forming their own tree structure.">
|
||||
|
||||
The ordering property of binary search trees (BST) establishes that for every node, all values in its left subtree are less than the node's value, and all values in its right subtree are greater than the node's value.
|
||||
|
||||
The left and right subtrees must also be binary search trees themselves.
|
||||
|
||||
This ordering makes search, insertion, and deletion operations very efficient if the tree is balanced.
|
||||
|
||||
A balanced tree is a tree in which the heights of the left and right subtrees of any node are very similar to make sure that operations remain efficient.
|
||||
|
||||
## Tries
|
||||
|
||||
Now that you know more about trees and binary search trees, let's dive into tries.
|
||||
|
||||
**Tries** are tree data structures used to store a set of strings.
|
||||
|
||||
Tries are also known as **prefix trees** because they are very efficient for operations that require finding strings based on their prefixes.
|
||||
|
||||
Each node in the trie represents a single character of a string.
|
||||
|
||||
The root node does not represent any particular character, so you can think of it as representing an empty string.
|
||||
|
||||
As you traverse the trie down from the root, the path to a node defines a specific prefix. To find a word, you follow that prefix until you reach the node with the word you are looking for.
|
||||
|
||||
Nodes that represent complete words are assigned end-of-word markers.
|
||||
|
||||
This is an example of a trie with the words "top", "tea" and "ten".
|
||||
|
||||
Notice how the words "tea" and "ten" share the same prefix "te", so the data structure follows the same path until the last character, which is marked as an end-of-word character. In this diagram, this is represented with a red border around the node:
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/what-are-trees-and-tries-and-how-do-they-work-4.png" alt="A trie data structure showing the words 'top', 'tea', and 'ten'. The root node branches to 't', which then branches to 'o' (leading to 'top') and 'e' (leading to 'tea' and 'ten'). End-of-word nodes are marked with red borders.">
|
||||
|
||||
The worst-case time complexity for the search operation is O(L), where L is the length of the string that you are looking for.
|
||||
|
||||
Insertion is also efficient. This operation only requires creating new nodes for the characters that don't exist in the trie yet.
|
||||
|
||||
The great advantage of this data structure is that when multiple strings share the same prefix, their paths overlap, so the prefix itself is only stored once.
|
||||
|
||||
This efficiency makes tries perfect for implementing features like autocomplete and spell checkers.
|
||||
|
||||
However, tries are not efficient for all sets of strings. They can be inefficient if the set of strings has many unique characters. This would require storing many unique characters as individual nodes. These nodes would have to be traversed to find the words, which would not be optimal.
|
||||
|
||||
Now that you are familiar with the different types of trees and what they are used for, you can start using them in real-world scenarios. Knowing how to choose the right one is a valuable skill to have when you need to tackle challenges in your daily work.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following statements about a Binary Search Tree (BST) is always true?
|
||||
|
||||
## --answers--
|
||||
|
||||
All values in the left subtree of a node are less than the node's value.
|
||||
|
||||
---
|
||||
|
||||
Every node can have up to three children.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the specific rule that governs how numbers are organized in a Binary Search Tree (BST) to allow for efficient searching.
|
||||
|
||||
---
|
||||
|
||||
The tree is guaranteed to be perfectly balanced after every insertion.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the specific rule that governs how numbers are organized in a Binary Search Tree (BST) to allow for efficient searching.
|
||||
|
||||
---
|
||||
|
||||
It is a specialized tree-like structure optimized for storing strings.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the specific rule that governs how numbers are organized in a Binary Search Tree (BST) to allow for efficient searching.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
|
||||
## --text--
|
||||
|
||||
For which of the following tasks is a trie, or prefix tree most effective?
|
||||
|
||||
## --answers--
|
||||
|
||||
Finding the smallest value in a large dataset.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the main advantage of a trie's structure and how it's built based on characters.
|
||||
|
||||
---
|
||||
|
||||
Managing a hierarchical file system on a computer.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the main advantage of a trie's structure and how it's built based on characters.
|
||||
|
||||
---
|
||||
|
||||
Implementing an autocomplete feature.
|
||||
|
||||
---
|
||||
|
||||
Storing an unordered list of unique numbers.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the main advantage of a trie's structure and how it's built based on characters.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
## --text--
|
||||
|
||||
What is the fundamental difference between a general tree and a Binary Tree?
|
||||
|
||||
## --answers--
|
||||
|
||||
A Binary Tree can only store numbers, while a general tree can store any data type.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the number of connections each node in a Binary Tree can have, compared to general trees.
|
||||
|
||||
---
|
||||
|
||||
A Binary Tree has a root node, but a general tree does not.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the number of connections each node in a Binary Tree can have, compared to general trees.
|
||||
|
||||
---
|
||||
|
||||
A Binary Tree has no parent-child relationships, unlike a general tree.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the number of connections each node in a Binary Tree can have, compared to general trees.
|
||||
|
||||
---
|
||||
|
||||
A Binary Tree restricts each node to a maximum of two children, while a general tree does not.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
4
|
||||
@@ -0,0 +1,239 @@
|
||||
---
|
||||
id: 68baa5e4f0e07f079245ca0b
|
||||
title: How Do Priority Queues and Heaps Work?
|
||||
challengeType: 19
|
||||
dashedName: how-do-priority-queues-and-heaps-work
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
A **priority queue** is an abstract data type (ADT) that works similarly to a queue or stack, but with one key difference.
|
||||
|
||||
As you may already know, standard queues follow a FIFO (First-in, First-out) approach, where the first element added to the queue is the first one to be removed from the queue.
|
||||
|
||||
Stacks follow a LIFO (Last-in, First-out) approach, where the last element added to the stack is the first one to be removed from the stack.
|
||||
|
||||
Queues and stacks only consider the order of insertion of the elements.
|
||||
|
||||
However, priority queues take the "priority" of the elements into account. The priority is used to determine which element should be removed next.
|
||||
|
||||
Usually, the element with the highest priority is removed first, but some implementations may also choose to remove the element with the lowest priority first. This will depend on the requirements of your program.
|
||||
|
||||
Priority queues are very helpful for practical applications like finding the shortest path between two locations, scheduling tasks in operating systems, simulating traffic, compressing data, and managing networks.
|
||||
|
||||
In practice, priority queues are commonly implemented using a heap data structure.
|
||||
|
||||
A **heap** is a tree data structure with a very specific property called the **heap property**. This property determines the relationship between each node and its children, based on the type of heap.
|
||||
|
||||
There are two primary types of heaps:
|
||||
|
||||
* Max-heap
|
||||
|
||||
* Min-heap
|
||||
|
||||
|
||||
In a **max-heap**, the value of each node is greater than or equal to the value of its children.
|
||||
|
||||
In this example, you can see a tree structure with the nodes 8, 7, 5, 2, and 1. Note that node 7 is greater than node 2 and node 1, following the heap property. This is true for all the other nodes as well.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/how-do-priority-queues-and-heaps-work-1.png" alt="A max-heap tree structure showing nodes with values 8 at the root, 7 and 5 as children of 8, and 2 and 1 as children of 7, demonstrating that each parent node is greater than its children.">
|
||||
|
||||
In contrast, in a **min-heap**, the value of each node is less than or equal to the value of its children.
|
||||
|
||||
In this example, we have nodes with values 4, 7, 9, 12, and 15. For example, node 7 is less than node 12 and node 15, following the heap property. This is also true for all the other nodes.
|
||||
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/lecture-transcripts/how-do-priority-queues-and-heaps-work-2.png" alt="A min-heap tree structure showing nodes with values 4 at the root, 7 and 9 as children of 4, and 12 and 15 as children of 7, demonstrating that each parent node is less than its children.">
|
||||
|
||||
The heap property is key because it ensures that the maximum (or minimum) element (depending on the type of heap) always stays at the top, which makes it very easy to remove.
|
||||
|
||||
In practice, heaps are typically implemented as arrays to access parent and child nodes efficiently.
|
||||
|
||||
Using arrays simplifies the logic for accessing these values or "nodes" because behind the scenes, if the heap maintains the structure of a complete binary tree, the array implementation only requires simple mathematical operations based on their indices to find where the elements are located in memory.
|
||||
|
||||
Python has a `heapq` built-in module that you can use to work with an implementation of a min-heap.
|
||||
|
||||
It works by operating directly on Python lists, following specific steps that work with the elements as if the list was a heap, preserving the heap property.
|
||||
|
||||
To use this module, you just need to import it:
|
||||
|
||||
```python
|
||||
import heapq
|
||||
```
|
||||
|
||||
Then, you need to define an empty list. This will be the underlying data structure for the heap:
|
||||
|
||||
```python
|
||||
my_heap = []
|
||||
```
|
||||
|
||||
To add elements to the heap, you would call `heappush()`, passing the name of the heap and the element that you want to add as arguments. This will automatically add the element to the list where it should be, to preserve the heap property:
|
||||
|
||||
```python
|
||||
heapq.heappush(my_heap, 9)
|
||||
```
|
||||
|
||||
To get the element with the lowest priority (in this case, the smallest value), you would call `heappop()`:
|
||||
|
||||
```python
|
||||
heapq.heappop(my_heap)
|
||||
```
|
||||
|
||||
`heappushpop()` combines both operations into one call.
|
||||
|
||||
This is more efficient than calling them in a sequence separately, especially when the heap is large, since it only performs one "heapify" operation to reorder the list as a heap:
|
||||
|
||||
```python
|
||||
heapq.heappushpop(my_heap, 15)
|
||||
```
|
||||
|
||||
If you already have a list and you want to transform it into a heap, you could call `heapify()`, passing the heap as argument:
|
||||
|
||||
```python
|
||||
heapq.heapify(my_heap)
|
||||
```
|
||||
|
||||
But currently, we are sorting the elements by their values, right?
|
||||
|
||||
What if we want to sort them by their "priority" instead?
|
||||
|
||||
You could do this by storing tuples with this structure: `(priority, element)`.
|
||||
|
||||
Since tuples are compared element by element from left to right, the priorities will be compared first, and the decisions will be made based on them.
|
||||
|
||||
Please note that, in this case, lower values will represent higher priorities. This means that a tuple with priority of 1 will have a **higher** priority than a tuple with priority of 3:
|
||||
|
||||
```python
|
||||
my_heap = []
|
||||
|
||||
heapq.heappush(my_heap, (3, "A"))
|
||||
heapq.heappush(my_heap, (2, "B"))
|
||||
heapq.heappush(my_heap, (1, "C"))
|
||||
```
|
||||
|
||||
If you need elements with the same priority to be removed in the order that they were inserted, you could consider including a unique counter as the second element of your tuple to break the tie, like this `(priority, counter, element)`.
|
||||
|
||||
Now let's talk about the efficiency of heaps.
|
||||
|
||||
The average and worst case time complexities for inserting and extracting the minimum or maximum value from a heap (depending on the type of heap) are logarithmic, `O(log n)`, because the number of swaps required is usually proportional to the height of the heap, which is log(n).
|
||||
|
||||
The average and worst case time complexity for the "peek" operation is constant time, O(1). Peeking involves getting the minimum or maximum value (depending on the type of heap) without removing it.
|
||||
|
||||
The "heapify" operation, where the heap is built from an unsorted list, has linear time complexity, O(n), in the average and worst cases.
|
||||
|
||||
Similarly, both searching for and deleting an arbitrary element have linear average and worst case time complexities of O(n), since they potentially require traversing the entire heap.
|
||||
|
||||
And how much space do they require?
|
||||
|
||||
The space complexity of the heap is linear, O(n), where `n` is the number of elements it contains. It only needs to store the elements and a small additional overhead for the list object itself.
|
||||
|
||||
Priority queues and heaps are very important in computer science. They let you quickly find and use the most important elements from a list. This efficiency is crucial for many computer programs that perform critical real-world tasks, such as finding the fastest route on a map.
|
||||
|
||||
# --questions--
|
||||
|
||||
## --text--
|
||||
|
||||
What is the primary characteristic that distinguishes a priority queue from a standard queue or stack?
|
||||
|
||||
## --answers--
|
||||
|
||||
It allows elements to be accessed by their index.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the main factor that determines which element is removed next.
|
||||
|
||||
---
|
||||
|
||||
It always processes elements in the order they were inserted.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the main factor that determines which element is removed next.
|
||||
|
||||
---
|
||||
|
||||
It retrieves elements based on an assigned priority.
|
||||
|
||||
---
|
||||
|
||||
It only stores elements of the same data type.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about the main factor that determines which element is removed next.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
3
|
||||
|
||||
## --text--
|
||||
|
||||
Which of the following is a common real-world application where a priority queue would be helpful?
|
||||
|
||||
## --answers--
|
||||
|
||||
Scheduling tasks in an operating system where some tasks are more urgent.
|
||||
|
||||
---
|
||||
|
||||
Managing a playlist where songs play in a fixed order.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about scenarios where some items are more important and need to be handled first.
|
||||
|
||||
---
|
||||
|
||||
Storing a list of grocery items.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about scenarios where some items are more important and need to be handled first.
|
||||
|
||||
---
|
||||
|
||||
Keeping track of customer names in alphabetical order.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about scenarios where some items are more important and need to be handled first.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
1
|
||||
|
||||
## --text--
|
||||
|
||||
What is the main reason why heaps are typically implemented as arrays in practice, despite being conceptualized as trees?
|
||||
|
||||
## --answers--
|
||||
|
||||
Arrays are always faster than any other data structure.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how the tree-like structure of a heap can be efficiently mapped to a linear data structure.
|
||||
|
||||
---
|
||||
|
||||
Arrays simplify the logic for accessing parent and child nodes using mathematical formulas.
|
||||
|
||||
---
|
||||
|
||||
Arrays allow for direct random access to any element, which is a core heap operation.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how the tree-like structure of a heap can be efficiently mapped to a linear data structure.
|
||||
|
||||
---
|
||||
|
||||
Arrays are the only data structure that can guarantee the heap property.
|
||||
|
||||
### --feedback--
|
||||
|
||||
Think about how the tree-like structure of a heap can be efficiently mapped to a linear data structure.
|
||||
|
||||
## --video-solution--
|
||||
|
||||
2
|
||||
@@ -2,8 +2,29 @@
|
||||
"name": "Understanding Graphs and Trees",
|
||||
"isUpcomingChange": true,
|
||||
"dashedName": "lecture-understanding-graphs-and-trees",
|
||||
"challengeOrder": [
|
||||
{
|
||||
"id": "68420c5ac17cf26ab2a4ca3c",
|
||||
"title": "What Are Graphs in Computer Science?"
|
||||
},
|
||||
{
|
||||
"id": "68baa5e4f0e07f079245ca08",
|
||||
"title": "How Do Depth First and Breadth First Search Work?"
|
||||
},
|
||||
{
|
||||
"id": "68baa5e4f0e07f079245ca09",
|
||||
"title": "How Do Matrices and Adjacency Lists Work?"
|
||||
},
|
||||
{
|
||||
"id": "68baa5e4f0e07f079245ca0a",
|
||||
"title": "What Are Trees and Tries and How Do They Work?"
|
||||
},
|
||||
{
|
||||
"id": "68baa5e4f0e07f079245ca0b",
|
||||
"title": "How Do Priority Queues and Heaps Work?"
|
||||
}
|
||||
],
|
||||
"helpCategory": "Python",
|
||||
"blockType": "lecture",
|
||||
"blockLayout": "challenge-list",
|
||||
"challengeOrder": [{ "id": "68420c5ac17cf26ab2a4ca3c", "title": "Step 1" }],
|
||||
"helpCategory": "Python"
|
||||
"blockLayout": "challenge-list"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user