Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
By definition, a factorial is the product of a positive integer and all the positive integers that are less than or equal to the given number. In other words, getting a factorial of a number means to multiply all whole numbers from that number, down to 1.
0! equals 1 as well, by convention.
A factorial is denoted by the integer and followed by an exclamation mark.
5! denotes a factorial of five.
And to calculate that factorial, we multiply the number with every whole number smaller than it, until we reach 1:
Keeping these rules in mind, in this tutorial, we will learn how to calculate the factorial of an integer with Python, using loops and recursion. Let's start with calculating the factorial using loops.
We can calculate factorials using both the while
loop and the for
loop. The general process is pretty similar for both. All we need is a parameter as input and a counter.
Let's start with the for
loop:
You may have noticed that we are counting starting from 1 to the n
, whilst the definition of factorial was from the given number down to 1. But mathematically:
1∗2∗3∗4...∗n=n∗(n−1)∗(n−2)∗(n−3)∗(n−4)...∗(n−(n−1))1∗2∗3∗4...∗n=n∗(n−1)∗(n−2)∗(n−3)∗(n−4)...∗(n−(n−1))
To simplify,
(n - (n-1))
will always be equal to 1.
That means that it doesn't matter in which direction we're counting. It can start from 1 and increase towards the n
, or it can start from n
and decrease towards 1. Now that's clarified, let's start breaking down the function we've just wrote.
Our function takes in a parameter n
which denotes the number we're calculating a factorial for. First, we define a variable named result
and assign 1
as a value to it.
Why assign 1 and not 0 you ask?
Because if we were to assign 0 to it then all the following multiplications with 0, naturally would result in a huge 0.
Then we start our for
loop in the range from 1
to n+1
. Remember, the Python range will stop before the second argument. To include the last number as well, we simply add an additional 1
.
Inside the for
loop, we multiply the current value of result
with the current value of our index i
.
Finally, we return the final value of the result
. Let's test our function print out the result:
If you'd like to read more about how to get user input, read our Getting User Input in Python.
It will prompt the user to give input. We'll try it with 4
:
You can use a calculator to verify the result:
4! is 4 * 3 * 2 * 1
, which results 24.
Now let's see how we can calculate factorial using the while
loop. Here's our modified function:
This is pretty similar to the for
loop. Except for this time we're moving from n
towards the 1, closer to the mathematical definition. Let's test our function:
We'll enter 4 as an input once more:
Although the calculation was 4 * 3 * 2 * 1
the final result is the same as before.
Calculating factorials using loops was easy. Now let's take a look at how to calculate the factorial using a recursive function.
A recursive function is a function that calls itself. It may sound a bit intimidating at first but bear with us and you'll see that recursive functions are easy to understand.
In general, every recursive function has two main components: a base case and a recursive step.
Base cases are the smallest instances of the problem. Also a break, a case that will return a value and will get out of the recursion. In terms of factorial functions, the base case is when we return the final element of the factorial, which is 1.
Without a base case or with an incorrect base case, your recursive function can run infinitely, causing an overflow.
Recursive steps - as the name implies- are the recursive part of the function, where the whole problem is transformed into something smaller. If the recursive step fails to shrink the problem, then again recursion can run infinitely.
Consider the recurring part of the factorials:
5! is 5 * 4 * 3 * 2 * 1
.
But we also know that:
4 * 3 * 2 * 1
is 4!.
In other words 5! is 5 * 4!
, and 4! is 4 * 3!
and so on.
So we can say that
n! = n * (n-1)!
. This will be the recursive step of our factorial!
A factorial recursion ends when it hits 1. This will be our base case. We will return 1
if n
is 1
or less, covering the zero input.
Let's take a look at our recursive factorial function:
As you see the if
block embodies our base case, while the else
block covers the recursive step.
Let's test our function:
We will enter 3 as input this time:
We get the same result. But this time, what goes under the hood is rather interesting:
You see, when we enter the input, the function will check with the if
block, and since 3 is greater than 1, it will skip to the else
block. In this block, we see the line return n * get_factorial_recursively(n-1)
.
We know the current value of
n
for the moment, it's3
, butget_factorial_recursively(n-1)
is still to be calculated.
Then the program calls the same function once more, but this time our function takes 2 as the parameter. It checks the if
block and skips to the else
block and again encounters with the last line. Now, the current value of the n
is 2
but the program still must calculate the get_factorial_recursively(n-1)
.
So it calls the function once again, but this time the if
block, or rather, the base class succeeds to return 1 and breaks out from the recursion.
Following the same pattern to upwards, it returns each function result, multiplying the current result with the previous n
and returning it for the previous function call. In other words, our program first gets to the bottom of the factorial (which is 1), then builds its way up, while multiplying on each step.
Also removing the function from the call stack one by one, up until the final result of the n * (n-1)
is returned.
This is generally how recursive functions work. Some more complicated problems may require deeper recursions with more than one base case or more than one recursive step. But for now, this simple recursion is good enough to solve our factorial problem!
Dijkstra's algorithm is an algorithm which finds the shortest paths between nodes in a graph. It was designed by a Dutch computer scientist Edsger Wybe Dijkstra in 1956, when he thought about how he might calculate the shortest route from Rotterdam to Groningen. It was published three years later.
In computer science (as well as in our everyday life), there are a lot of problems that require finding the shortest path between two points. We encounter problems such as:
Finding the shortest path from our house to our school or workplace
Finding the quickest way to get to a gas station while traveling
Finding a list of friends that a particular user on social network may know
These are just a few of the many "shortest path problems" we see daily. Dijkstra's algorithm is one of the most well known shortest path algorithms because of it's efficiency and simplicity.
Dijkstra's algorithm works on undirected, connected, weighted graphs. Before we get into it, let's explain what an undirected, connected, and weighed graph actually is.
A graph is a data structure represented by two things:
Vertices: Groups of certain objects or data (also known as a "node")
Edges: Links that connect those groups of objects or data
Let's explain this with an example: We want to represent a country with a graph. That country is made out of cities of different sizes and characteristics. Those cities would be represented by vertices. Between some of those cities, there are roads that connect them. Those roads would be represented by edges.
One more example of a graph can be a circle route of a train: the train stations are represented by vertices, and the rails between them are represented by the edges of a graph.
A graph can even be as abstract as a representation of human relationships: We can represent each person in a group of people as vertices in a graph, and if they know each other, we connect them with an edge.
Graphs can be divided based on the types of their edges or based on the number of components that represent them.
When it comes to their edges, graphs can be:
Directed
Undirected
A directed graph is a graph in which the edges have orientations. A second example mentioned above is an example of a directed graph: the train goes from one station to the other, but it can't go in reverse.
An undirected graph is a graph in which the edges do not have orientations (therefore, all edges in an undirected graph are considered to be bidirectional). An example of an undirected graph can be the third example mentioned above: It's impossible for person A to know person B without person B knowing the person A back.
Another division by the edges in a graph can be:
Weighted
Unweighted
A weighted graph is a graph whose edges have a certain numerical value joined to them: their weight. For example, if we knew the lengths of every road between each pair of cities in a country, we could assign them to the edges in a graph representation of a country and create a weighted graph. i.e. the weigth or "cost" of an edge is the distance between the cities.
An unweighted graph is a graph whose edges don't have weights, like the forementioned graph of relationships between people - we can't assign a numerical value to "knowing a person"!
Based on the number of components that represent them, graphs can be:
Connected
Disconnected
A connected graph is a graph that only consists of one component - every vertex inside of it has a path to every other vertex. If we were at a party, and every person there knew the host, that would be represented with a connected graph - everyone could meet anyone they want via the host.
A disconnected graph is a graph that consists of more than one component. Using the same example, if somehow an outsider who doesn't know anyone ended up at the party, the graph of all people at the party and their relationships, it would now be disconnected, because there is one vertex with no edges to connect it to the other vertices present.
The original Dijkstra's algorithm finds the shortest path between two nodes in a graph. Another, more common variation of this algorithm finds the shortest path between the first vertex and all of the other vertices in a graph. In this article, we will focus on this variation.
We will now go over this algorithm step by step:
First, we will create a set of visited vertices, to keep track of all of the vertices that have been assigned their correct shortest path.
We will also need to set "costs" of all vertices in the graph (lengths of the current shortest path that leads to it). All of the costs will be set to 'infinity' at the begining, to make sure that every other cost we may compare it to would be smaller than the starting one. The only exception is the cost of the first, starting vertex - this vertex will have a cost of 0, because it has no path to itself.
As long as there are vertices without the shortest path assigned to them, we do the following:
We pick a vertex with the shortest current cost, visit it, and add it to the visited vertices set
We update the costs of all its adjacent vertices that are not visited yet. We do this by comparing its current (old) cost, and the sum of the parent's cost and the edge between the parent (current node) and the adjacent node in question.
Let's explain this a bit better using an example:
Let's say the vertex 0 is our starting point. We are going to set the initial costs of vertices in this graph like this:
We pick the vertex with a minimum cost - that vertex is 0. We will mark it as visited and add it to our set of visited vertices. The visited nodes will be marked with green color in the images:
Then, we will update the cost of adjacent vertices (1 and 6).
Since 0 + 4 < infinity
and 0 + 7 < infinity
, the new costs will be the following (the numbers in bold will be visited vertices):
Now we visit the next smallest cost node, which is 1.
We mark it as visited, and then update the adjacent vertices: 2, 6, and 7:
Since 4 + 9 < infinity
, new cost of vertex 2 will be 13
Since 4 + 11 > 7
, the cost of vertex 6 will remain 7
Since 4 + 20 < infinity
, new cost of vertex 7 will be 24
These are our new costs:
The next vertex we're going to visit is vertex 6. We mark it as visited and update its adjacent vertices' costs:
Since 7 + 1 < 24
, the new cost for vertex 7 will be 8.
These are our new costs:
The next vertex we are going to visit is vertex 7:
Now we're going to update the adjacent vertices: vertices 4 and 8.
Since 8 + 1 < infinity
, the new cost of vertex 4 is 9
Since 8 + 3 < infinity
, the new cost of vertex 8 is 11
These are the new costs:
The next vertex we'll visit is vertex 4:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!Download the eBook
Since 9 + 2 < 13
, the new cost of vertex 2 is 11
Since 9 + 10 < infinity
, the new cost of vertex 3 is 19
Since 9 + 15 < infinity
, the new cost of vertex 5 is 24
Since 9 + 5 > 11
, the cost of vertex 8 remains 11
The new costs are:
The next vertex we are going to visit is vertex 2:
The only vertex we're going to update is vertex 3:
Since 11 + 6 > 19
, the cost of vertex 3 stays the same
Therefore, the current costs for all nodes will stay the same as before.
The next vertex we are going to visit is vertex 8:
We are updating the vertex 5:
Since 11 + 12 < 24
, the new cost of vertex 5 is going to be 23
The costs of vertices right now are:
The next vertex we are going to visit is vertex 3.
We are updating the remaining adjacent vertex - vertex 5.
Since 19 + 5 > 23
, the cost of vertex 5 remains the same
The final vertex we are going to visit is vertex 5:
There are no more unvisited vertices that may need an update.
Our final costs represents the shortest paths from node 0 to every other node in the graph:
Now that we've gone over an example, let's see how we can implement Dijkstra's algorithm in Python!
Before we start, we are first going to have to import a priority queue:
We will use a priority queue to easily sort the vertices we haven't visited yet, which will more easily allow us to choose the one of shortest current cost.
Now, we'll implement a constructor for a class called Graph
:
In this simple parametrized constructor, we provided the number of vertices in the graph as an argument, and we initialized three fields:
v
: Represents the number of vertices in the graph
edges
: Represents the list of edges in the form of a matrix. For nodes u
and v
, self.edges[u][v] = weight
of the edge
visited
: A set which will contain the visited vertices
Now, let's define a function which is going to add an edge to a graph:
Now, let's write the function for Dijkstra's algorithm:
In this code, we first created a list D
of the size v
. The entire list is initialized to infinity. This is going to be a list where we keep the shortest paths from start_vertex
to all of the other nodes.
We set the value of the start vertex to 0, since that is its distance from itself.
Then, we initialize a priority queue, which we will use to quickly sort the vertices from the least to most distant. We put the start vertex in the priority queue.
Now, for each vertex in the priority queue, we will first mark them as visited, and then we will iterate through their neighbors.
If the neighbor is not visited, we will compare its old cost and its new cost.
The old cost is the current value of the shortest path from the start vertex to the neighbor, while the new cost is the value of the sum of the shortest path from the start vertex to the current vertex and the distance between the current vertex and the neighbor.
If the new cost is lower than the old cost, we put the neighbor and its cost to the priority queue, and update the list where we keep the shortest paths accordingly.
Finally, after all of the vertices are visited and the priority queue is empty, we return the list D
. Our function is done!
Let's put the graph we used in the example above as the input of our implemented algorithm:
Originating from mathematics, graphs are now widely used data structures in Computer Science. One of the first problems we encounter when constructing any algorithm regarding Graph processing or traversal, is how we represent the graph and then, how to traverse that representation.
Graph traversal is not a trivial problem, and given the difficulty of the task - many algorithms have been devised for efficient (yet not perfect) graph traversal.
In this guide, we'll take a look at one of the two fundamental and simplest algorithms for Graph traversal - Depth-First Search (DFS). It's the most commonly used algorithm alongside the related Breadth-First Search (BFS) given their simplicity. After going over the main idea used for DFS, we'll implement it in Python on a Graph representation - an adjacency list.
Depth-First Search (DFS) is an algorithm used to traverse or locate a target node in a graph or tree data structure. It priorities depth and searches along one branch, as far as it can go - until the end of that branch. Once there, it backtracks to the first possible divergence from that branch, and searches until the end of that branch, repeating the process.
Given the nature of the algorithm, you can easily implement it recursively - and you can always implement a recursive algorithm iteratively as well:
The start node is the root node for tree data structures, while with more generic graphs - it can be any node.
DFS is widely-used as a part of many other algorithms that resolve graph-represented problems. From cycle searches, path finding, topological sorting, to finding articulation points and strongly connected components. The reason behind this widespread use of the DFS algorithm lays in its overall simplicity and easy recursive implementation.
The DFS algorithm is pretty simple and consists of the following steps:
Mark the current node as visited.
Traverse the neighbouring nodes that aren't visited and recursively call the DFS function for that node.
The algorithm stops either when the target node is found, or the whole graph has been traversed (all nodes are visited).
Since graphs can have cycles, we'll need a system to avoid them so we don't fall into infinity loops. That's why we "mark" every node we pass as visited by adding them to a
Set
containing only unique entries.
By marking nodes as "visited", if we ever encounter that node again - we're in a loop! Endless computational power and time has been wasted on loops, lost in the aether.
Pseudocode
Given these steps, we can summarize DFS in pseudocode:
Input and output processing are performed depending on the purpose of the graph search. Our input processing for DFS will be checking if the current node is equal to the target node.
With this view, you can really start to appreciate just how simple yet useful this algorithm is.
Depth-First Search implementation is usually recursive in code given how natural of a pair that is, but it can also be easily implemented non-recursively. We'll be using the recursive method as it's simpler and more fitting:
We added the start node to the beginning of our traversal path and marked it as visited by adding it to a set of visited
nodes. Then, we traversed the start node's neighbours that aren't already visited and called the function recursively for each of them. Recursive calls result in going as deep as we can along one "branch".
We saved the recursion result in a variable - in the case it returns None
that means that the target node was not found in this branch and that we should try another. If the recursive call, in fact, does not return None
, that means we have found our target node and we return the traversal path as result.
In the end, if we find ourselves outside of the for
loop, it means that all the neighbour branches of the current node have been visited and none of them lead to our target node. So, we remove the current node from the path, and return None
as result.
Let's illustrate how the code works through an example. We'll be using a Python dictionary to represent the graph as an adjacency list. Here's the graph we'll be using in the following example:
An adjacency list is a type of graph representation in code, it consists of keys which represent each node, and a list of values for each of them containing nodes that are connected to the key node with an edge.
Using a dictionary for this is the easiest way to quickly represent a graph in Python, though you could also define your own Node
classes and add them to a Graph
instance.
Here's what our example graph looks like:
We're searching for a path from node 0
to node 3
, if it exists, the path will be saved into a set of visited nodes, called traversal_path
so we can reconstruct it for printing:
The steps our algorithm will take are as follows:
Add node 0
to the traversal path and mark it as visited. Check if node 0
is equal to target node 3
, since it's not, continue and traverse it's neighbours (1
and 2
).
Is neighbour 1
visited? - No. Then, the algorithm calls the function recursively for that node.
Recursive call for node 1
: Add node 1
to the traversal path and mark it as visited, . Is 1
equal to our target node 3
? - No, continue and traverse it's neighbours (0
and 3
).
Is neighbour 0
visited? - Yes, move onto the next one.
Is neighbour 3
visited? - No, call the function recursively for this node.
Recursive call for node 3
: Add node 3
to the traversal path and mark it as visited. Is 3
equal to our target node 3
? - Yes, target node has been found, return the traversal path.
The algorithm stops and our program prints out the resulting traversal path from node 0
to node 3
:
After the search, the marked nodes on the graph represent the path we took to get to the target node:
In case there was no path between the start and target node, the traversal path would be empty.
Note: Graphs can also be disconnected, meaning that there are at least two nodes that cannot be connected by a path. In this case, DFS would ignore the nodes that it can't get to:
For example in this graph, if we were to start DFS from node 0
to node 4
, there would be no such path because it has no way of getting to the target node.
In this article we've explained the theory behind the Depth-First Search algorithm. We've depicted the widely-used recursive Python implementation, and went over the borderline cases for which the algorithm will not work properly.
Guess the author of the following quotes:
Guess the author of the following quotes:
Talk is cheap. Show me the code.
Software is like sex: it’s better when it’s free.
Microsoft isn’t evil, they just make really crappy operating systems.
Here’s some more:
Algorithms — Solved algorithms and data structures problems in many languages.
Algorithm Visualizer — Interactive online platform that visualizes algorithms from code.
Quadsort — Stable non-recursive merge sort named quadsort.
System design algorithms — Algorithms you should know before system design.
Wolfsort — Stable adaptive hybrid radix / merge sort.
Evolutionary Computation Bestiary — Bestiary of evolutionary, swarm and other metaphor-based algorithms.
Elements of Programming book — Decomposing programs into a system of algorithmic components. (Review) (HN) (Lobsters)
CPP/C — C/C++ algorithms/DS problems.
Elementary Algorithms — Introduces elementary algorithms and data structures. Includes side-by-side comparisons of purely functional realization and their imperative counterpart.
The Sound of Sorting — Visualization and “Audibilization” of Sorting Algorithms. (Web)
Open Data Structures — Provide a high-quality open content data structures textbook that is both mathematically rigorous and provides complete implementations. (Code)
Anna — Low-latency, cloud-native KVS.
The Daily Coding newsletter — Master JavaScript and Data Structures.
Michael Scott — Nonblocking data structures lectures (2020) — Nonblocking concurrent data structures are an increasingly valuable tool for shared-memory parallel programming.
Morphisms of Computational Constructs — Visual catalogue + story of morphisms displayed across computational structures.
cdb — Fast, reliable, simple package for creating and reading constant databases.
We will now update the adjacent vertices: 2, 3, 5, and 8.
vertex
cost to get to it from vertex 0
0
0
1
inf
2
inf
3
inf
4
inf
5
inf
6
inf
7
inf
8
inf
vertex
cost to get to it from vertex 0
0
0
1
4
2
inf
3
inf
4
inf
5
inf
6
7
7
inf
8
inf
vertex
cost to get to it from vertex 0
0
0
1
4
2
13
3
inf
4
inf
5
inf
6
7
7
24
8
inf
vertex
cost to get to it from vertex 0
0
0
1
4
2
13
3
inf
4
inf
5
inf
6
7
7
8
8
inf
vertex
cost to get to it from vertex 0
0
0
1
4
2
13
3
inf
4
9
5
inf
6
7
7
8
8
11
vertex
cost to get to it from vertex 0
0
0
1
4
2
11
3
19
4
9
5
24
6
7
7
8
8
11
vertex
cost to get to it from vertex 0
0
0
1
4
2
11
3
19
4
9
5
24
6
7
7
8
8
11
vertex
cost to get to it from vertex 0
0
0
1
4
2
11
3
19
4
9
5
24
6
7
7
8
8
11
Current Node
Path
Visited
0
[0
]
{0
}
1
[0
, 1
]
{0
, 1
}
3
[0
, 1
, 3
]
{0
, 1
, 3
}