Skip to content
Open

Jason #1024

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!--title={Linked Lists}-->

<!--badges={Algorithms:15}-->

<!--concepts={The Linked List}-->

To put it in the simplest terms, one can think of a linked list like a string of pearls. Each pearl in the string leads to the next. The first pearl on the string can be called the "head" pearl. In a linked list, the "head" node, as can be assumed, is the very first node in the list. Each node contains two things: some data associated with the node, and a reference to the next node in the list.

In a singly linked list, each node (or pearl) has one single reference to the next node (or pearl) in the list. A doubly linked list instead has two references, one for the next node, and one for the previous node. The last node in all linked lists points to a "null" node, signifying the end of the list.

Many benefits come with using a linked list instead of any other similar data structure (like a static array). This includes dynamic memory allocation--if one doesn't know the amount of data they want to store during initialization, a linked list can help with quickly adjusting the size of the list.

However, there are also several disadvantages to using a linked list. More space is used when dynamically allocating memory (mostly for the reference to the next node), and if you want to access an item in the middle, you have to start at the "head" node and follow the references until reaching the item wanted.

**In practice, some insertions cost more. If the list initially allocates enough space for six nodes, inserting a seventh means the list has to double its space (up to 12).**

<img src="../images/1.png" style="zoom:200%;" />

### The Linked List

A simple implementation of a linked list includes the following methods:

- Node class: implementing the idea of a Node and its attributes
- Insert: inserts a new node into the list
- Size: returns size of list
- Search: searches list for a node containing the requested data and returns that node if found, otherwise raises an error
- Delete: searches list for a node containing the requested data and removes it from list if found, otherwise raises an error

Now, we can create the start of our linked list! Luckily, the linked list (as a class) itself is actually the "head" node of the list!

**Note: Upon initialization, a list has no nodes, so the "head" node is set to None. Because a linked list doesn't necessarily need a node to initialized, the "head" node will, by default, set itself to None.**

```python
class LinkedList(object):
def __init__(self, head=None):
self.head = head
```
Now we can implement the most important attribute of a linked list: the node!
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@ The created graph is an undirected linearly connected graph connecting the integ

You can use `nx.draw(G,with_labels=True)` to show the labels on the nodes. Note that the direction of the graph doesn't matter.

![Image of a linearly connected graph](https://tva1.sinaimg.cn/large/006tNbRwgy1gbk20rnl76j30hs0dct8q.jpg)
![Image of a linearly connected graph](../images/1.jpg)
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

Undirected graphs have edges that do not have a direction. The edges indicate a *two-way* relationship, in that each edge can be traversed in both directions. This figure below shows a simple undirected graph. The absence of an arrow tells us that the graph is undirected.

![](https://tva1.sinaimg.cn/large/0082zybpgy1gbpjlvkwlkj319k05smxb.jpg)
![](../images/2a.jpg)

> The graph can be traversed from node **A** to **B** and vice versa.



![img](https://tva1.sinaimg.cn/large/0082zybpgy1gbpjg9zybej31be0je0u0.jpg)
![img](../images/2b.jpg)

> Undirected Graph

Expand All @@ -41,4 +41,4 @@ plt.show() # display the graph

### Output:

![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbk2if36gpj30zk0qoaan.jpg)
![](../images/2c.jpg)
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ Directed graphs have edges with direction. The edges indicate a *one-way* relati



![](https://tva1.sinaimg.cn/large/0082zybpgy1gbpjjiug6cj318e090weq.jpg)
![](../images/5a.jpg)



> The graph can be traversed from vertex **A** to **B**, but not in the opposite direction.

![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbl9364zthj31am0im75n.jpg)
![](../images/5b.jpg)



Expand Down Expand Up @@ -78,5 +78,5 @@ plt.show()

### Output:

![](https://tva1.sinaimg.cn/large/006y8mN6gy1g843xb1tywj30rw0kwmyb.jpg)
![](../images/5c.jpg)

Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ Dijkstra's algorithm will assign some initial distance values and improve them s

**unvisited set = {"a", "b", "c", "d"}**

![](https://tva1.sinaimg.cn/large/006y8mN6gy1g8ek2ol4kqj313o0nk0up.jpg)
![](../images/6a.jpg)

2. In order to keep track of the total distance from the start node to its destination, we use `distance` to store the current total weight of the smallest path.

Assign to every node a tentative distance value, set the distance equal to zero for our initial node (since it's 0 away from the initial node) and set the distance to infinity for all other nodes.

Also, set the initial node as current because we will process the initial node first. In this case, we start with node a.

![](https://tva1.sinaimg.cn/large/006y8mN6gy1g8ek31s7uuj313k0nqac3.jpg)
![](../images/6b.jpg)

###### **current_node = a**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@



![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbkdvh64tlj313k0nqab4.jpg)
![](../images/7a.jpg)



Expand All @@ -23,7 +23,7 @@

3. For the current node, consider all of its unvisited neighbors and calculate their *tentative* distances through the current node. If the distance is smaller than what the node currently has, we update the distance of the node.

![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbke5oqga7j313s0ngjsg.jpg)
![](../images/7b.jpg)

###### current_node = a

Expand All @@ -40,7 +40,7 @@

Now, let's process node `c`:

![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbke76w69xj31400ncdgw.jpg)
![](../images/7c.jpg)

###### current_node = a

Expand Down Expand Up @@ -69,7 +69,7 @@
5. If the destination node has been marked visited (when planning a route between two specific nodes) or if the smallest tentative distance among the nodes in the *unvisited set* is infinity (when planning a complete traversal, this occurs when there is no connection between the initial node and remaining unvisited nodes), then stop. The algorithm has finished.
6. Otherwise, select the unvisited node marked with the smallest tentative distance and set it as the new "current node." Then, go back to step 3.

![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbkeqgg0p7j31360n0jsf.jpg)
![](../images/7d.jpg)

###### current_node = c

Expand All @@ -84,7 +84,7 @@ Since `c` has a smaller distance than b, we process c first. We update the dist

Then, we process the node `b`, as the distance of `b` is smaller than the distance of `d`.

![](https://tva1.sinaimg.cn/large/006tNbRwgy1gbkevudfmkj31360o8dgv.jpg)
![](../images/7e.jpg)



Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!--title={Dijkstra's Algorithm in Python}-->

<!--badges={Algorithms:15,Python:5}-->

<!--concepts={useOfGraphs, realLifeApplication, Dijkstra'sAlgorithm, Dijkstra'sdirected,}-->

### Travel in Europe: A Real-Life Application of Dijkstra's Algorithm

<<<<<<< HEAD:Data-Structures-and-Algos-Topic/Module3-Search-and-Sorting-Algos/Activity1_Graphs/Cards/8.md
Let's say we need to find out the shortest path from point r to point e in Europe, we can use Dijkstra's algorithm.
Suppose that any geographical map as a graph. Now locations in the map are our **nodes** in algorithm.
And road between locations are **edges.** **Weights** of the edges are the distance between those two locations here.
=======
Let's say we need to find out the shortest path from point r to point e in Europe. We can use Dijkstra's algorithm to do this.
Suppose that any geographical map has a graph. Locations on the map are our **nodes** in the algorithm. Roads between locations are **edges.** **Weights** of the edges are the distance between locations.
>>>>>>> 61fbca8d6845aed8b70285df94eadff8f1956bf4:Data-Structures-and-Algos-Topic/Module3-Search-and-Sorting-Algos/Activity1_Graphs/8.md


![](../images/8.jpg)

We’ll start by constructing this graph in Python:

**Step 1:** Create a class called Graph. Use the `__init__()` function to assign values for edges and weights.

```python
from collections import defaultdict

class Graph():
def __init__(self):
"""
self.edges is a dict of all possible next nodes
e.g. {'r': ['i', 'a', 'b', 'd'], ...}
self.weights has all the weights between two nodes,
with the two nodes as a tuple as the key
e.g. {('r', 'i'): 7, ('r', 'a'): 2, ...}
"""
self.edges = defaultdict(list)
self.weights = {}
```

**Step 2:** Create a function inside class Graph, which will add directed weighted edges to the graph.

```python
def add_edge(self, from_node, to_node, weight):
# Note: assumes edges are bi-directional
self.edges[from_node].append(to_node)
self.edges[to_node].append(from_node)
self.weights[(from_node, to_node)] = weight
self.weights[(to_node, from_node)] = weight
```
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ Students will learn about graphs and different ways computer scientists use them
Easy

# Image
![Path](https://images.pexels.com/photos/808465/pexels-photo-808465.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940)
![Path](./images/readme.jpeg)
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!--title={Graphs}-->

<!--badges={Python:10}-->

<!--concepts={introToGraphs, pythonCode}-->

## Graphs

A graph is a collection of *nodes* and *edges* that represents relationships:

- **Nodes** are vertices that correspond to objects.

- **Edges** are the connections between objects.

- The graph's edges sometimes have **weights**, which indicate the strength (or some other attribute) of each connection between the nodes.

Graphs provide us with a very useful data structure. They can help us to find structure within our data. With the advent of machine learning and big data, managing and manipulating our data has never been more important. Learning a little bit of graph theory will help us with that.

Note that trees are a kind of graph. They are undirected, connected graphs without cycles.

### Python Code:

> In order for this to work, you need to install the modules `networkx` and `matplotlib`.
>
> Open terminal and run the statements `pip3 install networkx` and `pip3 install matplotlib`.

NetworkX is a Python library for graphs and networks. Similarly, Matplotlib is a plotting library in Python. With Matplotlib, you can generate plots, bar charts, or histograms with just a few lines of code. We can use Matplotlib to draw graphs.

We can create a Path Graph with linearly connected nodes with the method `path_graph()`. The Python code uses `matplotlib` and `pyplot` to plot the graph. We will give detailed information on `matplotlib` at a later stage in this activity. For now, follow this code to print out nodes and edges, and save your graph to a local image:

```python
import networkx as nx #set shortcut for networkx
import matplotlib.pyplot as plt #set shortcut for matplotlib.pyplot

G = nx.path_graph(4) #Return a path graph G of 4 nodes linearly connected by 4-1 edges

print("Nodes of graph: ")
print(G.nodes()) #return a list of all the nodes in the graph
print("Edges of graph: ") #nx.draw(G,with_labels=True)
print(G.edges()) #return a list of all the edges
nx.draw(G) #Draw the graph G with Matplotlib
plt.savefig("path_graph1.png") #save the current figure as "path_graph1.png"
plt.show() # display the graph
```

The created graph is an undirected linearly connected graph connecting the integer numbers 0 to 3 in their natural order:

You can use `nx.draw(G,with_labels=True)` to show the labels on the nodes. Note that the direction of the graph doesn't matter.

<img src="../images/1.jpg" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!--title={Dijkstra's Algorithm in Python}-->

<!--badges={Algorithms:15,Python:5}-->

<!--concepts={useOfGraphs, realLifeApplication, Dijkstra'sAlgorithm, Dijkstra'sdirected,}-->

**Step 5**: Now, we need to implement our algorithm.

```python
def dijsktra(graph, initial, end):
# shortest paths is a dict of nodes
# whose value is a tuple of (previous node, weight)
shortest_paths = {initial: (None, 0)}
current_node = initial
visited = set()
```

> Here, we initialized the Dijkstra's algorithm function (Step 1 and 2 from the previous slides.) Instead of an unvisited set, we use a visited set this time.
>
> For example:
>
> current_node = a
>
> ###### visited set = {}
>
> | | a | b | c | d |
> | -------- | --------- | --------- | --------- | --------- |
> | visited? | unvisited | unvisited | unvisited | unvisited |
> | distance | 0 | ∞ | ∞ | ∞ |



```python
while current_node != end: # do it recursively, until the destination node is reached
visited.add(current_node) # set current_node as visited
destinations = graph.edges[current_node]
weight_to_current_node = shortest_paths[current_node][1]

for next_node in destinations:
weight = graph.weights[(current_node, next_node)] + weight_to_current_node
if next_node not in shortest_paths:
shortest_paths[next_node] = (current_node, weight)
else:
current_shortest_weight = shortest_paths[next_node][1]
#If the distance is smaller than what the node already has, update the distance of the node.
if current_shortest_weight > weight:
shortest_paths[next_node] = (current_node, weight)
```

> Recall Step 3 from the previous card: For the current node, consider all of its unvisited neighbors and calculate their *tentative* distances through the current node. Compare the newly calculated *tentative* distance to the current assigned value and assign the smaller one.
>
> Recall Step 6 from the previous card: Select the unvisited node marked with the smallest tentative distance, set it as the new "current node", and go back to step 3.
>
> Here, we made a code that calculates the distance between nodes and compares it with its neighbors. We then append the shortest distance path to a list. By doing so, we are repeating the process of both Step 3 and Step 6 from the previous card.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!--title={Dijkstra's Algorithm in Python}-->

<!--badges={Algorithms:15,Python:5}-->

<!--concepts={useOfGraphs, realLifeApplication, Dijkstra'sAlgorithm, Dijkstra'sdirected,}-->

```python
next_destinations = {node: shortest_paths[node] for node in shortest_paths if node not in visited}
if not next_destinations:
return "Route Not Possible"
# next node is the destination with the lowest weight
current_node = min(next_destinations, key=lambda k: next_destinations[k][1])
```

> Recall Step 4 from the previous card: When we are done considering all unvisited neighbors of the current node, mark it as "visited" and remove it from the *unvisited set*. A visited node will never be checked again.
>
> Here the code checks for if the node has been visited or not.

```python
# Work back through destinations in shortest path
path = []
while current_node is not None:
path.append(current_node)
next_node = shortest_paths[current_node][0]
current_node = next_node
# Reverse path
path = path[::-1]
return path
```

> Recall Step 5 from the previous card: If the destination node has been marked visited (when planning a route between two specific nodes) or if the smallest tentative distance among the nodes in the *unvisited set* is infinity (when planning a complete traversal, this occurs when there is no connection between the initial node and remaining unvisited nodes), then stop. The algorithm has finished.
>
> Here, we check the node by using a while-loop and see if it is in None or not. If it is not in None, we append it to the list called "path."

**Step 6**

```python
print(dijsktra(graph, 'r', 'e'))
```

> We use the comment print to show the graph.

Output:

``['r', 'a', 'f', 'n', 'e']``

Now, we have confirmation that the shortest path from X to Y is:

**r -> a -> f -> n -> e**

Loading