<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrei Visoiu</title>
    <description>The latest articles on DEV Community by Andrei Visoiu (@kruzzy).</description>
    <link>https://dev.to/kruzzy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F265486%2F0f8109c0-8afd-49b6-9584-a3165804115d.jpeg</url>
      <title>DEV Community: Andrei Visoiu</title>
      <link>https://dev.to/kruzzy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kruzzy"/>
    <language>en</language>
    <item>
      <title>LeetCode Explained: 50. Pow(x, n) - Logarithmic Exponentiation (medium)</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Mon, 26 Jul 2021 13:26:26 +0000</pubDate>
      <link>https://dev.to/kruzzy/leetcode-explained-50-pow-x-n-logarithmic-exponentiation-medium-3p1o</link>
      <guid>https://dev.to/kruzzy/leetcode-explained-50-pow-x-n-logarithmic-exponentiation-medium-3p1o</guid>
      <description>&lt;h2&gt;
  
  
  Problem Description
&lt;/h2&gt;

&lt;p&gt;The task of the problem at hand is quite clear: implement an exponentiation function, &lt;strong&gt;pow(x, n)&lt;/strong&gt;, that raises &lt;strong&gt;x&lt;/strong&gt; to the power of &lt;strong&gt;n&lt;/strong&gt; (&lt;strong&gt;n&lt;/strong&gt; is an integer). &lt;a href="https://leetcode.com/problems/powx-n/submissions/"&gt;Original link here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Constraints:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;-100.0 &amp;lt; x &amp;lt; 100.0&lt;/li&gt;
&lt;li&gt;-2^31 &amp;lt;= n &amp;lt;= 2^31-1&lt;/li&gt;
&lt;li&gt;-10^4 &amp;lt;= x^n &amp;lt;= 10^4&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example 1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input: x = 2.10000, n = 3&lt;/li&gt;
&lt;li&gt;output: 9.26100&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example 2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;input: x = 2.00000, n = -2&lt;/li&gt;
&lt;li&gt;output: 0.25000&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let's talk about solutions
&lt;/h2&gt;

&lt;p&gt;It is obvious that we can of course multiply the number &lt;strong&gt;x&lt;/strong&gt; by itself &lt;strong&gt;n&lt;/strong&gt; times (and in the case of &lt;strong&gt;n&lt;/strong&gt; being negative, multiply &lt;strong&gt;1/x&lt;/strong&gt; by itself the corresponding number of times). This approach takes O(n) time and is not the fastest one.&lt;/p&gt;

&lt;p&gt;The optimal approach is obtained by making a simple statement: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rwGKvZFh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5fb18xhbo4m3j9a5gj0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rwGKvZFh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5fb18xhbo4m3j9a5gj0y.png" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SRc-JECn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4wgs12el4aesvrn7shwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRc-JECn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4wgs12el4aesvrn7shwy.png" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can then calculate &lt;strong&gt;x&lt;/strong&gt; to the power of &lt;strong&gt;n&lt;/strong&gt; by calculating &lt;strong&gt;x^2&lt;/strong&gt;, then &lt;strong&gt;x^4&lt;/strong&gt; (by multiplying &lt;strong&gt;x^2&lt;/strong&gt; by itself) and so forth, until we get to &lt;strong&gt;x^n&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The algorithm looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if &lt;strong&gt;n&lt;/strong&gt; is negative, we make &lt;strong&gt;x&lt;/strong&gt; equal &lt;strong&gt;1/x&lt;/strong&gt; and &lt;strong&gt;n&lt;/strong&gt; equal &lt;strong&gt;-n&lt;/strong&gt; and take a dump variable, &lt;strong&gt;a&lt;/strong&gt; to equal 1; &lt;strong&gt;a&lt;/strong&gt; will help us later.&lt;/li&gt;
&lt;li&gt;as long as &lt;strong&gt;n&lt;/strong&gt; is greater than 0:

&lt;ol&gt;
&lt;li&gt;if we find an odd &lt;strong&gt;n&lt;/strong&gt;, we multiply &lt;strong&gt;a&lt;/strong&gt; by &lt;strong&gt;x&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;we multiply &lt;strong&gt;x&lt;/strong&gt; by itself and store the result in &lt;strong&gt;x&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;we divide &lt;strong&gt;n&lt;/strong&gt; by 2 and store the result in &lt;strong&gt;n&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;when &lt;strong&gt;n&lt;/strong&gt; becomes 0, the result will be in &lt;strong&gt;a&lt;/strong&gt;, because there will always be a case when &lt;strong&gt;n&lt;/strong&gt; will be odd; the motivation for this can come from the prime factorisation of an arbitrary number - 2 is the only even prime factor, and if we continue to divide by it we will get to a point at which there will be no 2s left in the prime factorisation of the number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this approach to factorisation, we have to perform &lt;strong&gt;log n&lt;/strong&gt; operations. If &lt;strong&gt;n&lt;/strong&gt; doubles, then the operations performed only grow by 1, as opposed to the first approach, in which the number of operations performed will double if &lt;strong&gt;n&lt;/strong&gt; doubles.&lt;/p&gt;

&lt;p&gt;This is a very basic implementation of the algorithm:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This approach is called logarithmic exponentiation and an examples where it proves useful is computing recurrence relations. I provided a more detailed description of the matter in &lt;a href="https://dev.to/kruzzy/the-magic-of-the-fibonacci-numbers-why-we-love-computing-them-part-3-4ce7"&gt;this article I wrote some time ago&lt;/a&gt;, which explains how to compute Fibonacci numbers using fast matrix exponentiation.&lt;/p&gt;

&lt;p&gt;That concludes today's solution. I'll be back with other LeetCode solutions (including harder ones!) for the &lt;strong&gt;LeetCode Explained&lt;/strong&gt; series, so don't forget to subscribe!&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>algorithms</category>
      <category>python</category>
      <category>mathematics</category>
    </item>
    <item>
      <title>LeetCode explained: July Challenge 2021, week 4 - Partition Array into Disjoint Sets (medium)</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Thu, 22 Jul 2021 12:37:11 +0000</pubDate>
      <link>https://dev.to/kruzzy/leetcode-explained-july-challenge-2021-week-4-partition-array-into-disjoint-sets-medium-54ic</link>
      <guid>https://dev.to/kruzzy/leetcode-explained-july-challenge-2021-week-4-partition-array-into-disjoint-sets-medium-54ic</guid>
      <description>&lt;h2&gt;
  
  
  Problem Description
&lt;/h2&gt;

&lt;p&gt;In the July LeetCoding Challenge 2021, week 4, we're tasked with the following (&lt;a href="https://leetcode.com/explore/challenge/card/july-leetcoding-challenge-2021/611/week-4-july-22nd-july-28th/3823/"&gt;original problem here&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;Given an array &lt;strong&gt;nums&lt;/strong&gt;, partition it into two (contiguous) subarrays &lt;strong&gt;left&lt;/strong&gt; and &lt;strong&gt;right&lt;/strong&gt; so that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every element in &lt;strong&gt;left&lt;/strong&gt; is less than or equal to every element in &lt;strong&gt;right&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;left&lt;/strong&gt; and &lt;strong&gt;right&lt;/strong&gt; are non-empty.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;left&lt;/strong&gt; has the smallest possible size.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have to return the length of &lt;strong&gt;left&lt;/strong&gt; after such a partitioning and the problem has the following notes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 &amp;lt;= nums.length &amp;lt;= 30000&lt;/li&gt;
&lt;li&gt;0 &amp;lt;= nums[i] &amp;lt;= 10^6&lt;/li&gt;
&lt;li&gt;it is guaranteed there is at least one way to partition nums as described.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example 1:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nums = [5,0,3,8,6] &lt;/li&gt;
&lt;li&gt;output: 3 &lt;/li&gt;
&lt;li&gt;explanation: left = [5, 0, 3] / right = [8, 6]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example 2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nums = [1,1,1,0,6,12]&lt;/li&gt;
&lt;li&gt;output: 4&lt;/li&gt;
&lt;li&gt;explanation: left = [1,1,1,0] /  right = [6,12]&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Finding a solution
&lt;/h2&gt;

&lt;p&gt;As we are being tasked with finding subarrays, which are &lt;strong&gt;contiguous&lt;/strong&gt;, it is only natural to search for an index in the array to make a "cut" - that is, the index after which array &lt;strong&gt;left&lt;/strong&gt; ends.&lt;/p&gt;

&lt;p&gt;We will make use of maximum values in subarrays of the array to devise a solution. &lt;/p&gt;

&lt;p&gt;It is a requirement that every element of &lt;strong&gt;left&lt;/strong&gt; has to be smaller than or equal to every element of &lt;strong&gt;right&lt;/strong&gt;, and &lt;strong&gt;left&lt;/strong&gt; has to be as small as possible. Let's think about this a little. &lt;/p&gt;

&lt;p&gt;For the first requirement, it would be sufficient to just find the maximum in the whole array and make a "cut" behind it. That is, if the maximum was at index &lt;strong&gt;i&lt;/strong&gt; (suppose we start from 0), then &lt;strong&gt;left&lt;/strong&gt; will contain indexes from 0 through &lt;strong&gt;i-1&lt;/strong&gt; of nums, and &lt;strong&gt;right&lt;/strong&gt; will contain indexes from &lt;strong&gt;i&lt;/strong&gt; through &lt;strong&gt;n-1&lt;/strong&gt;, if &lt;strong&gt;n&lt;/strong&gt; is the size of array &lt;strong&gt;nums&lt;/strong&gt;.&lt;br&gt;
That would, however, make &lt;strong&gt;left as big as possible&lt;/strong&gt;, not as small as possible as it is required.&lt;/p&gt;

&lt;p&gt;To keep the size of &lt;strong&gt;left&lt;/strong&gt; as small as possible, we can think of a scenario where there is an index &lt;strong&gt;i&lt;/strong&gt;, which is not necessarily the maximum of the array, but is the maximum of nums[0..i] and there is no other number in nums[i+1..n] that is bigger than what is left of index &lt;strong&gt;i&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The second example provided by the author presents this thing: while 6 is not the maximum value in the array as a whole, it is the maximum of the prefix from 0 through its index (which is 4 here), and has no number smaller than the maximum of nums[0..3] to its right.&lt;/p&gt;

&lt;p&gt;The problem then resumes to finding such the number. We can do this in O(n) time complexity and O(1) space complexity.&lt;/p&gt;

&lt;p&gt;We will traverse the array and keep two variables, let them be called &lt;strong&gt;left_max&lt;/strong&gt;, which represents the maximum value that will be put in the &lt;strong&gt;left&lt;/strong&gt; array, and &lt;strong&gt;last_max&lt;/strong&gt;, which represents the last maximum values we encountered up until that point.&lt;/p&gt;

&lt;p&gt;Initially, we will set both those values to the first element of the array. We will also keep a variable named &lt;strong&gt;cut&lt;/strong&gt; representing the index after we split the array in &lt;strong&gt;left&lt;/strong&gt; and &lt;strong&gt;right&lt;/strong&gt;. So, the array will be cut after the first element.&lt;/p&gt;

&lt;p&gt;While looping through the array, there are two types of numbers we can find at one index &lt;strong&gt;i&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a number smaller than &lt;strong&gt;left_max&lt;/strong&gt;: in such a case, we need to move the cut, because otherwise we will not obtain the arrays needed; we should keep in mind that before making a cut, nums[i] is to be placed in the array &lt;strong&gt;right&lt;/strong&gt;; so, if we weren't to make a cut, we will not respect the requirements of the problem.&lt;/li&gt;
&lt;li&gt;a number bigger than or equal to &lt;strong&gt;left_max&lt;/strong&gt;: in this case, we need to update the value of &lt;strong&gt;last_max&lt;/strong&gt; if needed, so that when we make a cut we will know the maximum in the &lt;strong&gt;left&lt;/strong&gt; array in O(1) time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's see a short Python solution using this approach:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;That would be all for this solution. I'll be back with other LeetCode solutions for my new &lt;strong&gt;LeetCode Explained&lt;/strong&gt; series. In the meantime, feel free to comment your solutions in other programming languages here. Or, if you don't feel like it, you may like to read through my other series - &lt;a href="https://dev.to/kruzzy/series/3724"&gt;The Magic of Computing&lt;/a&gt;, discussing other algorithmic topics!&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>computerscience</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>A look into Dynamic Programming - Matrix Chain Multiplication</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Sun, 18 Jul 2021 12:00:01 +0000</pubDate>
      <link>https://dev.to/kruzzy/a-look-into-dynamic-programming-matrix-chain-multiplication-34gb</link>
      <guid>https://dev.to/kruzzy/a-look-into-dynamic-programming-matrix-chain-multiplication-34gb</guid>
      <description>&lt;p&gt;In the beginning of the &lt;a href="https://dev.to/kruzzy/using-divide-and-conquer-closest-pair-of-points-5e2g"&gt;last article I wrote&lt;/a&gt;, I described two ways of solving a problem by splitting it into subproblems: on one hand, those problems can be solved independently from one another (a method called divide &amp;amp; conquer, which I described in the article); on the other hand, they can interact with each other, building up on the results. Problems on the latter category can be solved using a method called &lt;strong&gt;dynamic programming&lt;/strong&gt;, which will be the topic for today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formal Definition of Dynamic Programming
&lt;/h2&gt;

&lt;p&gt;In the field of Computer Science, Dynamic Programming is derived from a mathematical optimisation method. It refers to simplifying a problem by breaking it down into smaller subproblems. If the results of those smaller subproblems overlap so they can be fit inside the larger problems, then there is a relation between them and the results of the larger problem.&lt;/p&gt;

&lt;p&gt;For example, by modifying the &lt;a href="https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-3-bfs-bipartite-graphs-2860"&gt;BFS algorithm I presented in this article&lt;/a&gt; to find the shortest path in an unweighted graph we can obtain a dynamic programming solution to the problem. &lt;/p&gt;

&lt;p&gt;This is possible by making a simple statement: if &lt;strong&gt;i&lt;/strong&gt; and &lt;strong&gt;j&lt;/strong&gt; are two nodes in an unweighted graph, then the shortest path from &lt;strong&gt;i&lt;/strong&gt; to &lt;strong&gt;j&lt;/strong&gt; would be obtained by first obtaining the shortest path from &lt;strong&gt;i&lt;/strong&gt; to a neighbour of &lt;strong&gt;j&lt;/strong&gt;. Described in pseudocode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;min_dist[i][j] = infinity
for every neighbour k of j:
   min_dist[i][j] = min(min_dist[i][k]+1, min_dist[i][j])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The last line of the snippet is called a &lt;strong&gt;reccurence relation&lt;/strong&gt;. (such relations are widely used in mathematics; another example is the way the &lt;a href="https://dev.to/kruzzy/the-magic-of-the-fibonacci-numbers-why-we-love-computing-them-part-1-18gp"&gt;Fibonacci sequence&lt;/a&gt; is calculated.&lt;/p&gt;
&lt;h2&gt;
  
  
  Subproblems and Memoization
&lt;/h2&gt;

&lt;p&gt;Subproblems are basically smaller instances (or versions) of the original problem. By saying that a problem has "overlapping subproblems", we mean that finding its solution involves solving the same subproblem multiple times.&lt;/p&gt;

&lt;p&gt;An accessible example is calculating the n-th Fibonacci number, which I presented in &lt;a href="https://dev.to/kruzzy/the-magic-of-the-fibonacci-numbers-why-we-love-computing-them-part-1-18gp"&gt;an earlier article&lt;/a&gt;. Let's look again at the recursion tree of the problem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ryh3287x4xy4ox7piye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ryh3287x4xy4ox7piye.png" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is clear that, if we do not store the results in some way, some numbers will be calculated multiple times, resulting in a staggering time complexity of O(1.62^n) (see the &lt;a href="https://dev.to/kruzzy/the-magic-of-the-fibonacci-numbers-why-we-love-computing-them-part-1-18gp"&gt;article&lt;/a&gt; for information about how this was calculated). &lt;br&gt;
This technique is called "memoization" - we can store the value of a Fibonacci number in an array after we calculate it for later use. This would decrease the time complexity, in this case, to an ~O(n).&lt;/p&gt;

&lt;p&gt;Memoization is widely used in dynamic programming (which is, in essence, an optimisation technique). Let us see how we can create such a solution.&lt;/p&gt;
&lt;h2&gt;
  
  
  Matrix Chain Multiplication
&lt;/h2&gt;

&lt;p&gt;We know that matrix multiplication is &lt;strong&gt;not&lt;/strong&gt; a commutative operation, but it is associative. It also turns out that the order in which the multiplication is done affects the overall number of operations you do.&lt;/p&gt;

&lt;p&gt;Let's suppose we have three matrixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A&lt;/strong&gt;, of size 3 x 1 - a column matrix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B&lt;/strong&gt;, of size 1 x 3 - a line matrix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C&lt;/strong&gt;, of size 3 x 1 - a column matrix again&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can multiply them in two ways: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(AB)C - multiplying A and B would yield a 3 x 3 matrix, and would take 9 operations. Multiplying (AB) with C would take another 9 operations, for a total of 18 operations.&lt;/li&gt;
&lt;li&gt;A(BC) - multiply B and C only takes 3 operations and yield a 1 x 1 matrix. Multiplying A with (BC) would take another 3 operations, for a total of 6 operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping that in mind, we ask the question: what is the best order to do the multiplication? &lt;/p&gt;

&lt;p&gt;Let's suppose we have N matrixes (M_1 through M_N) whose sizes we store in an array S, such that S[i-1] and S[i] are the sizes for matrix &lt;strong&gt;i&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;We can solve the problem using dynamic programming by making the following observation: the first thing we need to determine is what multiplication should be done last. In other word, we search for a matrix &lt;strong&gt;i&lt;/strong&gt; such that our expression would look like (M_1 * M_2 * ... M_i ) * ( M_(i+1) * ... M_N), and both the products in parenthesis are also calculated optimally. &lt;/p&gt;

&lt;p&gt;We can construct an N x N 2D array, let's call it A, such that A[i][j] will hold the minimum cost (number of operations) to compute the product of matrixes from M_i through M_j. We will use this array to memoise the results.&lt;/p&gt;

&lt;p&gt;Let's see how we can calculate the cost for a "cut" in the product of matrixes from M_i through M_j. If we were to put the parenthesis such like (M_i * M_(i+1) * .... M_k) * (M_(k+1) * ... M_j), the cost would be the sum of the cost of the two parenthesis + the cost to multiply the matrix yield by those two, which will be S[i-1] * S[k] * S[j], as the first result would be of size S[i-1] x S[k], and the second would be of size S[k] * S[j].&lt;/p&gt;

&lt;p&gt;We now just have to find the best &lt;strong&gt;k&lt;/strong&gt; for our cut. We can make this in a recursive manner. Let us look at an implementation of the idea:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The sizes for the matrixes is the one in the example above, rows and column matrixes. The code outputs the result as 6, as we have concluded earlier.&lt;/p&gt;

&lt;p&gt;This was achieved recursively, by first calling matrix_chain_cost for positions 1 through N. We used &lt;strong&gt;memoisation&lt;/strong&gt; to avoid redundant calculations, and then applied the formula we found above.&lt;/p&gt;

&lt;p&gt;The time complexity of the code above is O(n^3), as we are basically generating the cost for all the "cuts" we can do in the expression.&lt;/p&gt;

&lt;p&gt;That was all for today. &lt;strong&gt;The Magic of Computing&lt;/strong&gt; will be back with yet another interesting algorithmic topic. But, until then, maybe you fancy some &lt;a href="https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-1-5ii"&gt;Graph Theory&lt;/a&gt;? Or are you more of a &lt;a href="https://dev.to/kruzzy/exploring-backtracking-25dp"&gt;Backtracking&lt;/a&gt; person?&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>algorithms</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Using divide and conquer: closest pair of points</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Sun, 09 May 2021 18:16:06 +0000</pubDate>
      <link>https://dev.to/kruzzy/using-divide-and-conquer-closest-pair-of-points-5e2g</link>
      <guid>https://dev.to/kruzzy/using-divide-and-conquer-closest-pair-of-points-5e2g</guid>
      <description>&lt;p&gt;What's a reasonable way to tackle a problem that looks hard to solve? Well, you would need to try to make the problem easier and there are some problems that can be solved by &lt;strong&gt;going smaller&lt;/strong&gt; - splitting the problem multiple times into sub-problems, looking for an answer.&lt;/p&gt;

&lt;p&gt;Those sub-problems can either interact with each other (i.e. requiring one of them to be solved before solving another one) or they could be entirely separate from one another, perfectly fitted for solving separately and then combining them. &lt;/p&gt;

&lt;p&gt;Both cases describe problems that can be solved using two programming paradigms widely considered to be siblings: &lt;strong&gt;dynamic programming&lt;/strong&gt;, for the first type of problems, and &lt;strong&gt;divide and conquer&lt;/strong&gt;,  for the second type. For now, we will look into the second type of problems, with the first one to come in its own, later, article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining Divide and Conquer Formally
&lt;/h2&gt;

&lt;p&gt;Divide and conquer is an algorithm design paradigm which works by recursively breaking down a problem into a number of sub-problems until they become easy enough to be solved directly and then combining their solutions.&lt;/p&gt;

&lt;p&gt;In general, three steps can be observed in the algorithms designed using this paradigm:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Divide&lt;/strong&gt; the problem into smaller sub-problems. (i.e. smaller instances of the problem, like sorting an array of size &lt;strong&gt;N&lt;/strong&gt;/2 instead one of size &lt;strong&gt;N&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conquer&lt;/strong&gt; the sub-problems, solving them recursively. If a sub-problem has become small enough, solve it directly. (i.e. sort an array of two numbers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine&lt;/strong&gt; the solutions found directly by moving up the recursive stack. This allows for the sub-problems to pass the solution to their "parent" problems, until getting to the "big" problem. (i.e. merge two sorted arrays)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The examples I have given to each steps sum up to explain the concept of one of the most efficient comparison-based sorting algorithms - &lt;a href="https://en.wikipedia.org/wiki/Merge_sort" rel="noopener noreferrer"&gt;Merge Sort&lt;/a&gt;, which follows a Divide and Conquer approach.&lt;br&gt;
&lt;a href="https://en.wikipedia.org/wiki/Quicksort" rel="noopener noreferrer"&gt;Quicksort&lt;/a&gt;, another efficient sorting algorithms also follows this design paradigm.&lt;/p&gt;

&lt;p&gt;However, in this article we will focus on another extremely interesting problem that can be solved using a Divide and Conquer approach. Let's state it.&lt;/p&gt;

&lt;p&gt;Consider an euclidean plane containing &lt;strong&gt;N&lt;/strong&gt; points given by their &lt;em&gt;x&lt;/em&gt; and &lt;em&gt;y&lt;/em&gt; coordinates. Determine the distance between the closest two points in the plane.&lt;/p&gt;

&lt;p&gt;We know that the distance between two points, &lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt; can be expressed as: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqceoejqh4y3erok5hbo8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqceoejqh4y3erok5hbo8.png" alt="distance" width="261" height="42"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From now on, we may denote this distance as d(&lt;em&gt;A&lt;/em&gt;, &lt;em&gt;B&lt;/em&gt;), for simplicity. &lt;/p&gt;

&lt;p&gt;We can devise a straightforward solution - consider every pair of points &lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt; and calculate their distance. This approach has an O(N^2) time complexity.&lt;/p&gt;

&lt;p&gt;Let's try and find another solution by following a Divide and Conquer approach.&lt;/p&gt;

&lt;p&gt;In order to do that, we have to consider subsets of points at each step. We have to find the point at which computing the closest two points of such a subset is direct. Let &lt;strong&gt;P&lt;/strong&gt; be a subset of points. &lt;br&gt;
The last subset of &lt;strong&gt;P&lt;/strong&gt; we can split so that we can cheaply calculate its two closest points and avoid searching for pairs in a subset of size 1 is 4. If we go smaller and try to split a subset of 3 into two, we would then have to find the closest pair of points in a one point subset.&lt;/p&gt;

&lt;p&gt;(in the lines below, &lt;strong&gt;|P|&lt;/strong&gt; means the size, or cardinal, or &lt;strong&gt;P&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if &lt;strong&gt;|P|&lt;/strong&gt; &amp;lt; 4, consider all &lt;strong&gt;|P|&lt;/strong&gt;/2 pairs and remember the smallest distance.&lt;/li&gt;
&lt;li&gt;if &lt;strong&gt;|P|&lt;/strong&gt; &amp;gt;= 4, let us follow the paradigm "recipe":

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Divide&lt;/strong&gt; - let's determine a vertical line, let it be called &lt;strong&gt;a&lt;/strong&gt;, which "cuts" our set of points &lt;strong&gt;P&lt;/strong&gt; in two subsets. Let us call them &lt;strong&gt;PL&lt;/strong&gt; and &lt;strong&gt;PR&lt;/strong&gt;, for &lt;strong&gt;P left&lt;/strong&gt; and &lt;strong&gt;P right&lt;/strong&gt;. We need to determine this line such that the subsets are as close in size as possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conquer&lt;/strong&gt; - recursively find the two closest points in &lt;strong&gt;PL&lt;/strong&gt; and &lt;strong&gt;PR&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine&lt;/strong&gt; - let us assume that the answer to the problem, for set &lt;strong&gt;P&lt;/strong&gt;, is &lt;strong&gt;D&lt;/strong&gt;. Then, &lt;strong&gt;D&lt;/strong&gt; can come from one of the two recursive calls or can be obtained from a point in &lt;strong&gt;PL&lt;/strong&gt; and the other in &lt;strong&gt;PR&lt;/strong&gt;. If we already have a candidate for &lt;strong&gt;D&lt;/strong&gt; from the recursive calls, let it be called &lt;strong&gt;d&lt;/strong&gt;, it is obvious that those two points can only come from a region of length &lt;strong&gt;d&lt;/strong&gt; at most on each side of the line. We then, have to pick the points from &lt;strong&gt;PL&lt;/strong&gt; that are situated at distance &lt;strong&gt;d&lt;/strong&gt; from &lt;em&gt;a&lt;/em&gt; at most and the same for &lt;strong&gt;PR&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can find those points at step 3 by building another set, &lt;strong&gt;Q&lt;/strong&gt;, storing the points on either side of line &lt;em&gt;a&lt;/em&gt; that are, at most, at distance &lt;strong&gt;d&lt;/strong&gt; from line &lt;em&gt;a&lt;/em&gt;. We can then brute-force our way to finding if there is any pair of points that can result in a smaller distance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffack9btmcnd2a6rao8ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffack9btmcnd2a6rao8ok.png" alt="Alt Text" width="358" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, considering the set of points I chose above, it is possible to obtain an answer to the problem by looking in the area determined by the red rectangle. Bear in mind that this drawing is not exact, as I have approximated the distances and how the area would look myself.&lt;/p&gt;

&lt;p&gt;Now let's look at a Python implementation of the method I presented. We will work with the squares of the distances to improve performance.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;That concludes our article for today. &lt;strong&gt;The Magic of Computing&lt;/strong&gt; will be back with another article in which we will discuss the other problem solving paradigm I mentioned in this article - &lt;strong&gt;dynamic programming&lt;/strong&gt;. Until then, you could take a look at some &lt;a href="https://dev.to/kruzzy/exploring-backtracking-25dp"&gt;backtracking insights&lt;/a&gt;. Or maybe you fancy &lt;a href="https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-1-5ii"&gt;Graph Theory&lt;/a&gt;. Or you're a big fan of the &lt;a href="https://dev.to/kruzzy/the-magic-of-the-fibonacci-numbers-why-we-love-computing-them-part-1-18gp"&gt;Fibonacci series&lt;/a&gt;?&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>algorithms</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Exploring backtracking</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Sat, 01 May 2021 09:58:36 +0000</pubDate>
      <link>https://dev.to/kruzzy/exploring-backtracking-25dp</link>
      <guid>https://dev.to/kruzzy/exploring-backtracking-25dp</guid>
      <description>&lt;p&gt;Everyone has had at least slight meeting with backtracking throughout their lives, be it unknowingly - for example, as little kids, when navigating mazes for fun, when faced with a turn, say left or right, we will always choose one. However, the choice might not always prove correct - and we will go back, coming at the crossroads again, now choosing the other way, and reaching the exit.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of work that backtracking algorithms do. Their aim is to find all (or some) solutions to some constraint satisfaction computational problems:&lt;br&gt;
    - &lt;strong&gt;decision problems&lt;/strong&gt;, using it to find a feasible solution.&lt;br&gt;
    - &lt;strong&gt;optimisation problems&lt;/strong&gt;, using it to find the best solution.&lt;br&gt;
    - &lt;strong&gt;enumeration problems&lt;/strong&gt;, using it to find all the solutions.&lt;/p&gt;

&lt;p&gt;Defined formally, backtracking is an algorithmic technique for solving problems recursively, aiming to build a solution incrementally. It removes the candidate solutions that fail to satisfy the constraint as soon as it builds them and backtracks, going to the previous solution, trying to derive other solutions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementing a basic backtracking problem
&lt;/h2&gt;

&lt;p&gt;Let us now take one of the most basic backtracking problems. Let &lt;strong&gt;N&lt;/strong&gt; be a natural number. Generate all the permutations of the numbers from 1 through &lt;strong&gt;N&lt;/strong&gt; in lexicographic order.&lt;/p&gt;

&lt;p&gt;For example, let &lt;strong&gt;N&lt;/strong&gt; equal 3. Then, the solutions to our problem will be:&lt;br&gt;
1 2 3 &lt;br&gt;
1 3 2 &lt;br&gt;
2 1 3 &lt;br&gt;
2 3 1&lt;br&gt;
3 1 2 &lt;br&gt;
3 2 1&lt;/p&gt;

&lt;p&gt;We can clearly see that this is an &lt;strong&gt;enumeration problem&lt;/strong&gt;, asking us to generate &lt;em&gt;all&lt;/em&gt; permutations of these numbers. &lt;/p&gt;

&lt;p&gt;A straightforward recursive backtracking approach is fairly easy to come up with. At any point in time, our recursive function should know &lt;strong&gt;how many numbers it generated until now&lt;/strong&gt;, and &lt;strong&gt;what does numbers are&lt;/strong&gt; (we can also call that a partial solution). Then, it should follow the next steps:&lt;br&gt;
    1. If it has already generated &lt;strong&gt;N&lt;/strong&gt; numbers, stop and print the solution.&lt;br&gt;
    2. Loop through all numbers from 1 to &lt;strong&gt;N&lt;/strong&gt; in ascending order. If a certain number &lt;strong&gt;i&lt;/strong&gt; is already added, skip it. If not, add it to the solution and make another recursive call.&lt;/p&gt;

&lt;p&gt;We have two ways in which to check if a number &lt;strong&gt;i&lt;/strong&gt; was already added:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loop through our partial solution and check if &lt;strong&gt;i&lt;/strong&gt; is already there.&lt;/li&gt;
&lt;li&gt;use an additional boolean array, which will &lt;em&gt;true&lt;/em&gt; at index &lt;strong&gt;i&lt;/strong&gt; if the element has already been added, or &lt;em&gt;false&lt;/em&gt; otherwise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's look at a basic implementation using the second method to check for added numbers:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Note that, in this example, I passed the arrays to the recursive function at &lt;strong&gt;every single call&lt;/strong&gt;. &lt;br&gt;
However, when constricted by memory limits, another approach might be better suited to our needs.&lt;/p&gt;

&lt;p&gt;Let's first talk about why this approach might become problematic in memory-limited scenarios.&lt;/p&gt;
&lt;h2&gt;
  
  
  The call stack
&lt;/h2&gt;

&lt;p&gt;Recursion uses something called a &lt;a href="https://en.wikipedia.org/wiki/Call_stack" rel="noopener noreferrer"&gt;call stack&lt;/a&gt; to store information about the function calls. We can imagine this stack like a stack of boxes, one on top of the other. &lt;/p&gt;

&lt;p&gt;Each consecutive recursive call adds up to the stack &lt;strong&gt;with all its parameters&lt;/strong&gt;. So, a function call with arrays as arguments will take up much more space than one with no arguments. And, as our number &lt;strong&gt;N&lt;/strong&gt; increases, the size of our call stack increases. For bigger and bigger values of &lt;strong&gt;N&lt;/strong&gt; and tight memory limits, it might overflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In Python, by default, the call stack limit is 1000&lt;/strong&gt;. This refers to the number of function calls "on top of one another", and does not specify any limit. Although not recommended due to the way Python handles recursion, the limit can be modified using the &lt;strong&gt;setrecursionlimit()&lt;/strong&gt; function from the &lt;strong&gt;sys&lt;/strong&gt; module.&lt;/p&gt;

&lt;p&gt;We can, then, make our two arrays, &lt;strong&gt;solution&lt;/strong&gt; and &lt;strong&gt;appears&lt;/strong&gt; global. As you can see, we already made the necessary resets when exiting recursion - in general, when making some operations on variables before recursion (in our case, modify the &lt;strong&gt;appears&lt;/strong&gt; array and add the number to our &lt;strong&gt;solution&lt;/strong&gt; array), and making the exact inverses after coming back from recursion will left the variables as they were before. That makes sense, considering the way in which the call stack is managed, following a &lt;strong&gt;First In, First Out&lt;/strong&gt; approach. The last recursively called function is the first to finish execution, resetting all the variables to their pre-recursion states everytime a function is "popped" from the call stack.&lt;/p&gt;

&lt;p&gt;Below is the modified snippet:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In Python, in order to make our global variables valid inside a funtion definition, we have to use the keyword &lt;strong&gt;global&lt;/strong&gt; before accessing them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A way to visualise backtracking
&lt;/h2&gt;

&lt;p&gt;As you may remember if you have read my previous articles, we had a mini-series of articles running regarding &lt;a href="https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-1-5ii"&gt;Graph Theory&lt;/a&gt;. Well, we can transpose backtracking into a graph theory problem by considering that our search space is a directed graph. For example, let us consider our problem and suppose we have already reached depth &lt;strong&gt;1&lt;/strong&gt; (i.e. we have already chosen the first number of our permutation).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyji6lh1ekgnwlmvszb2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyji6lh1ekgnwlmvszb2.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The exact steps of our algorithm are showcased in the picture above. Considering it already chose 1, the algorithm will try to put 1 again, dismissing the solution. Then choose 2, which will work, and try to add 1, 2 and 3 to it, of which only 3 will be feasible.&lt;/p&gt;

&lt;p&gt;Backtracking can be considered a &lt;a href="https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-2-depth-first-search-topological-sorting-jkg"&gt;depth first search&lt;/a&gt; on this state graph of our problem.&lt;/p&gt;

&lt;p&gt;I hope I have given you some insights regarding backtracking with the article. &lt;em&gt;The Magic of Computing&lt;/em&gt; will be back next week with another interesting computational topic, but, until then, why don't you dwelve into some mathematical subjects, say... &lt;a href="https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-1-224f"&gt;prime numbers&lt;/a&gt;? Or maybe the &lt;a href="https://dev.to/kruzzy/the-magic-of-the-fibonacci-numbers-why-we-love-computing-them-part-1-18gp"&gt;Fibonacci series&lt;/a&gt; is more to your liking.&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>python</category>
      <category>algorithms</category>
      <category>backtracking</category>
    </item>
    <item>
      <title>Why is Graph Theory so amazing? - part 4, working with weights &amp; Dijkstra</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Fri, 16 Apr 2021 16:41:56 +0000</pubDate>
      <link>https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-4-working-with-weights-dijkstra-450k</link>
      <guid>https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-4-working-with-weights-dijkstra-450k</guid>
      <description>&lt;p&gt;In the ending of the &lt;a href="https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-3-bfs-bipartite-graphs-2860"&gt;previous article&lt;/a&gt;, we said that breadth-first search can be modified to obtain completely different behaviours. Today, we are going to see how exactly we can modify BFS to obtain a brand new algorithm. Firstly though, we need to introduce the weighted graph notion. This is the last articles in this mini series, &lt;em&gt;Why is Graph Theory so amazing?&lt;/em&gt;, as next week we're going to explore another interesting computing subject!&lt;/p&gt;

&lt;h2&gt;
  
  
  Weighted Graphs
&lt;/h2&gt;

&lt;p&gt;A weighted graph is a graph in which each edge has an attached weight (or cost) to it. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjfvrwpkgobaxkayvqwk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjfvrwpkgobaxkayvqwk.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, the graph above, let it be called &lt;strong&gt;G&lt;/strong&gt;, represents a weighted graph: the edge &lt;strong&gt;(2, 5)&lt;/strong&gt; has weight 10, the edge &lt;strong&gt;(1,3)&lt;/strong&gt; has weight 15, and so on.&lt;/p&gt;

&lt;p&gt;From a practical standpoint, a weighted graph can mean a network of roads, with each edge cost representing the distance between two cities, or, when working with live data, a sort of time coefficient that can be affected by traffic events - accidents, maintenance work, and so on. &lt;/p&gt;

&lt;p&gt;Now, we can ask the following question: how can we go from a node &lt;strong&gt;i&lt;/strong&gt; to a node &lt;strong&gt;j&lt;/strong&gt; with minimal cost?&lt;/p&gt;

&lt;h2&gt;
  
  
  Computing The Shortest Path
&lt;/h2&gt;

&lt;p&gt;One popular algorithm to compute the shortest path from a node to another is called &lt;strong&gt;Dijkstra's Algorithm&lt;/strong&gt;, after the Dutch computer scientist (among other things), Edsger Wybe Dijkstra. In an interview given in 2001, Dijkstra said that the algorithm was designed without pen &amp;amp; paper, while sitting in cafe with his fiance.&lt;br&gt;
The algorithm follows a pretty simplistic and elegant approach. We can consider it a sibling of breadth first search, of which I gave a more detailed overview in &lt;a href="https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-3-bfs-bipartite-graphs-2860"&gt;this article&lt;/a&gt;. The main difference is the order in which the algorithm visits the nodes: instead of expanding nodes in &lt;strong&gt;the order in which they were put in the queue, in a First-In, First-Out manner&lt;/strong&gt;, the algorithm remembers, for each node, the cost with which it has been reached, &lt;strong&gt;expanding nodes reached with a lower cost first&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let the starting node be called &lt;strong&gt;start&lt;/strong&gt;, and the node to which we want to find the cost be called &lt;strong&gt;finish&lt;/strong&gt;. Let &lt;strong&gt;cost&lt;/strong&gt; be an array which contains the cost to reach each of the &lt;strong&gt;N&lt;/strong&gt; nodes in our graph. Formally, the algorithm should follow the next steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;mark all nodes as unvisited; moreover, mark the cost of node &lt;strong&gt;start&lt;/strong&gt; with 0 and the cost to get to any other node with a very high value; put &lt;strong&gt;start&lt;/strong&gt; in the queue.&lt;/li&gt;
&lt;li&gt;while there are nodes left in the queue, and we haven't found a cost for the &lt;strong&gt;finish&lt;/strong&gt; node:

&lt;ul&gt;
&lt;li&gt;pop a node from the queue, let it be called &lt;strong&gt;u&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;if the node has already been visited (i. e., we could get to it from multiple directions, so we put it in the queue multiple times, with &lt;strong&gt;different costs&lt;/strong&gt;), there is no need to visit it again, as we have already expanded the best way to reach it; continue the algorithm.&lt;/li&gt;
&lt;li&gt;if the node hasn't already been visited, search through its neighbours; for any neighbour &lt;strong&gt;i&lt;/strong&gt;, we check whether coming from &lt;strong&gt;u&lt;/strong&gt; can improve its respective cost (i. e., if the current cost to get to &lt;strong&gt;i&lt;/strong&gt; is greater than the cost to get to &lt;strong&gt;u&lt;/strong&gt; plus the cost of the edge &lt;strong&gt;(u, i)&lt;/strong&gt;; if so, put &lt;strong&gt;i&lt;/strong&gt; in the queue, with the associated cost. 

&lt;ul&gt;
&lt;li&gt;after looking at all the neighbours of &lt;strong&gt;u&lt;/strong&gt;, mark &lt;strong&gt;u&lt;/strong&gt; as visited, and go back to &lt;strong&gt;2.&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, let us work towards an implementation of Dijkstra's algorithm. &lt;/p&gt;
&lt;h2&gt;
  
  
  Representing Weighted Graphs in Memory
&lt;/h2&gt;

&lt;p&gt;You may remember the &lt;a href="https://github.com/KruZZy/magic-of-computing/blob/main/adjacency_list.py" rel="noopener noreferrer"&gt;adjacency_list.py&lt;/a&gt; file we wrote for the first article. In order for that graph "wrapping" to handle weighted graphs, we need to modify two fundamental things in its structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For a node &lt;strong&gt;i&lt;/strong&gt;, every entry in its neighbour list should now remember a tuple: the node which it leads to and the cost of the edge.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;is_valid_tuple&lt;/strong&gt; function, which we used for checking user input, should be modified to only check the first two elements of the tuple to be nodes; specifically, this line should be modified: 
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;

The length of the tuple should be modified to 3, and, as you can see, we used the Python built-in &lt;strong&gt;all()&lt;/strong&gt; function to check for the nodes. We can modify our iterable, &lt;strong&gt;x&lt;/strong&gt;, to only return the first two elements, by changing it to &lt;strong&gt;x[0:2]&lt;/strong&gt;.
This Python syntax is called extended slicing. When using it like &lt;strong&gt;[start:stop]&lt;/strong&gt;, it returns an iterable object containing elements from &lt;strong&gt;index start&lt;/strong&gt; to &lt;strong&gt;index stop-1&lt;/strong&gt; of the iterable on which we applied the syntax.
Finally, our line will become:
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One other thing we have to keep into account is the way in which we extract the result of our lowest-cost path yet, in each step of the algorithm (we consider a graph &lt;strong&gt;G&lt;/strong&gt; with &lt;strong&gt;N&lt;/strong&gt; nodes):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can use a regular array to construct the queue of our candidates, and look through all its entries every time - that's &lt;strong&gt;N&lt;/strong&gt; entries, for each candidate in our shortest path.&lt;/li&gt;
&lt;li&gt;We can optimise the runtime by using an appropiate data structure: a &lt;a href="https://en.wikipedia.org/wiki/Binary_heap" rel="noopener noreferrer"&gt;binary heap&lt;/a&gt;, with logarithmic minimum extraction and insertion - doing roughly &lt;strong&gt;log(N)&lt;/strong&gt; operations for each shortest path candidate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Luckily, the Python library contains a &lt;a href="https://docs.python.org/3/library/queue.html#queue.PriorityQueue" rel="noopener noreferrer"&gt;PriorityQueue&lt;/a&gt; collection, which satisfies our needs for implementing the faster Dijkstra variant, if we are not too interested in implementing a data structure such as this ourselves.&lt;/p&gt;

&lt;p&gt;The algorithm I have described below only provides us with the &lt;strong&gt;cost&lt;/strong&gt; of the path, but not with the path itself. However, we can compute it quite simply if, each time we computed a better candidate for a path cost, we would remember the "parent" node, the node which we expanded to get there, in addition to the cost itself. Then, we can use recursion to go from "parent" to "parent", and print the nodes we encounter after making the recursive calls.&lt;/p&gt;

&lt;p&gt;Below is the modified version of the graph wrapping, and the implementation of the algorithm:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The snippet outputs the smallest path cost as 7, with the route 1 -&amp;gt; 4 -&amp;gt; 3 when we want to compute the cost between 1 and 3. &lt;br&gt;
Looking at the graph, we can see that this is indeed the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This week's article ends here. This is the last entry in this mini-series, &lt;em&gt;Why is Graph Theory so amazing?&lt;/em&gt;, during which I hope I offered a somewhat clear image on what graphs are and some fun ways to use them. Unfortunately, the subject is too broad to be treated in just a series of articles.&lt;br&gt;
Next week, we're gonna present a completely new aspect of Computer Science. Until then, you can pass the time by reading another article in the series!&lt;/p&gt;

</description>
      <category>graphtheory</category>
      <category>computerscience</category>
      <category>algorithms</category>
      <category>python</category>
    </item>
    <item>
      <title>Why is Graph Theory so amazing? - part 3 - BFS, bipartite graphs</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Sat, 10 Apr 2021 13:48:27 +0000</pubDate>
      <link>https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-3-bfs-bipartite-graphs-2860</link>
      <guid>https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-3-bfs-bipartite-graphs-2860</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-2-depth-first-search-topological-sorting-jkg"&gt;previous&lt;/a&gt; article, we looked into one type of graph traversal - depth first search, which works by starting from a root node, and visit every one of its neighbours recursively.&lt;/p&gt;

&lt;p&gt;Today, we will explore another type of graph traversal - breadth first search (or BFS, for short).&lt;/p&gt;

&lt;h2&gt;
  
  
  Breadth-First Search - Analysis &amp;amp; Why It Matters
&lt;/h2&gt;

&lt;p&gt;As opposed to its sibling, BFS is not a recursive algorithm - it will visit nodes in a First In, First Out manner, by using a queue data structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Queue Data Structure
&lt;/h3&gt;

&lt;p&gt;A queue data structure is a sequential collection of entities (we can visualise it as an array, for simplicity, although it can be implemented in more ways) that has 2 fundamental operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;append an entity to the end of the queue&lt;/li&gt;
&lt;li&gt;pop an entity from the start of the queue&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the queue is represented properly in memory, both of these operations have an O(1) time complexity. Naturally, the space complexity of a queue is O(n). As the name suggests, it is very similar to a real-life queue.&lt;/p&gt;

&lt;p&gt;Back to the algorithm, it works by first appending the root node to the queue and marking it as visited. Then, as long as there are nodes in the queue, we start popping elements from the front. After popping a node, we put all of its unvisited neighbours in queue, we mark them as visited, and then continue with the popping.&lt;/p&gt;

&lt;p&gt;By and large, the algorithm works by following these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;let &lt;strong&gt;i&lt;/strong&gt; be the root node of the traversal; append &lt;strong&gt;i&lt;/strong&gt; to the queue and mark it as visited.&lt;/li&gt;
&lt;li&gt;while the queue is not empty:

&lt;ul&gt;
&lt;li&gt;pop the first element in the queue, let it be &lt;strong&gt;j&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;mark &lt;strong&gt;j&lt;/strong&gt; as visited.&lt;/li&gt;
&lt;li&gt;loop through the neighbours of &lt;strong&gt;j&lt;/strong&gt;, push each of them to the queue if they are not yet visited, and mark them as visited.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Analysing the time complexity of BFS yields the same results as with the DFS. Let &lt;strong&gt;G&lt;/strong&gt; be a graph with &lt;strong&gt;N&lt;/strong&gt; nodes and &lt;strong&gt;E&lt;/strong&gt; edges.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if &lt;strong&gt;G&lt;/strong&gt; is represented by using an &lt;a href="https://github.com/KruZZy/magic-of-computing/blob/main/adjacency_matrix.py" rel="noopener noreferrer"&gt;adjacency matrix&lt;/a&gt;, the time complexity of the algorithm is O(N*N)&lt;/li&gt;
&lt;li&gt;if &lt;strong&gt;G&lt;/strong&gt; is represented by using &lt;a href="https://github.com/KruZZy/magic-of-computing/blob/main/adjacency_list.py" rel="noopener noreferrer"&gt;adjancency lists&lt;/a&gt;, the complexity of the algorihm is O(N+E).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, again, we see an applicability for each of the graph representation methods.&lt;/p&gt;

&lt;p&gt;Now, let's see a Python implementation of the BFS algorithm. We will be using the &lt;a href="https://docs.python.org/3/library/collections.html#collections.deque" rel="noopener noreferrer"&gt;deque&lt;/a&gt; collection from the standard Python library for constant operations on the queue and the &lt;a href="https://github.com/KruZZy/magic-of-computing/blob/main/adjacency_list.py" rel="noopener noreferrer"&gt;adjacency_list.py&lt;/a&gt; for the class definition and we will run the algorithm on last week's graph.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnaxs72lvcufvsfknja97.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnaxs72lvcufvsfknja97.png" alt="alt text"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The output of the snippet above is "1 2 3 4".&lt;/p&gt;

&lt;h2&gt;
  
  
  Bipartite Graphs
&lt;/h2&gt;

&lt;p&gt;A bipartite graph, also called a bigraph, is a graph whose nodes can be divided into 2 disjoint sets, let them be &lt;strong&gt;U&lt;/strong&gt; and &lt;strong&gt;V&lt;/strong&gt;, so that every edge in the graph connects a node from &lt;strong&gt;U&lt;/strong&gt; to a node from &lt;strong&gt;V&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiykooz2x9eqt1y6gks1i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiykooz2x9eqt1y6gks1i.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, the graph above is bipartite, with &lt;strong&gt;U&lt;/strong&gt; = &lt;strong&gt;(1, 2, 6)&lt;/strong&gt; and &lt;strong&gt;V&lt;/strong&gt; = &lt;strong&gt;(3, 4, 5, 7)&lt;/strong&gt;. Do note that a bipartite graph is not necessarily connected (there is not necessarily a path between any two nodes).&lt;/p&gt;

&lt;p&gt;Now, let's see how we can use BFS to determine whether a given graph is bipartite or not. For this, we will try to traverse the graph and construct &lt;strong&gt;U&lt;/strong&gt; and &lt;strong&gt;V&lt;/strong&gt; as we go. For simplicity, we can color the nodes of &lt;strong&gt;U&lt;/strong&gt; with red and the nodes of &lt;strong&gt;V&lt;/strong&gt; with green.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fenk1tx1ffwzo0wwli62v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fenk1tx1ffwzo0wwli62v.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The colouring, and implicitly the set choices, are not unique, as we can either choose to put node 6 in U or in V.&lt;/p&gt;

&lt;p&gt;Let's see how we can determine if the first connected component is a bipartite graph. We can start a BFS from 1, and colour it in red. Then, colour all of its neighbours in blue. When looking at an arbitrary node, we can colour its neighbours in the opposite color (i. e. red neighbours if the node is blue, and vice-versa). Applying such a method would construct a part of &lt;strong&gt;U&lt;/strong&gt; and &lt;strong&gt;V&lt;/strong&gt;, made up of only connected component of the graph.&lt;/p&gt;

&lt;p&gt;To extend this idea to multiple connected components, we have to run multiple BF searches - we have to loop through all the nodes of the graph and, if we find an uncolored one, we have to start a traversal from there. &lt;/p&gt;

&lt;p&gt;Let us see a Python snippet of how we can do this.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The snippet above outputs &lt;strong&gt;U&lt;/strong&gt; as &lt;strong&gt;(1, 2, 6)&lt;/strong&gt; and &lt;strong&gt;V&lt;/strong&gt; as &lt;strong&gt;(3, 4, 5, 7)&lt;/strong&gt;, and concludes that the graph in the example is indeed bipartite.&lt;/p&gt;

&lt;p&gt;Bipartite graphs can model a wide range of problems. One very practical exampling is class scheduling. If we were to take two sets, &lt;strong&gt;U&lt;/strong&gt;, made up of students, and &lt;strong&gt;V&lt;/strong&gt; made up of the classes they can take, we can use this bipartite graph modelling to determine which classes should not happen at the same time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This week, we looked a little into what breadth-first search can do. However, its applications do not stop at bipartite graph checks - it can also be used to determine the shortest path from a node to all the others (which can be useful when trying to send packets through a network), or modified further to give the algorithm completely new behaviours.&lt;br&gt;
We will continue the series next week - until then, do delight yourself with some other articles in &lt;em&gt;The Magic of Computing&lt;/em&gt; series! Maybe you are into &lt;a href="https://dev.to/kruzzy/the-magic-of-the-fibonacci-numbers-why-we-love-computing-them-part-1-18gp"&gt;Fibonacci numbers&lt;/a&gt;, or you wanna brush up your knowledge about &lt;a href="https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-1-224f"&gt;prime numbers&lt;/a&gt;?&lt;/p&gt;

</description>
      <category>graphtheory</category>
      <category>computerscience</category>
      <category>algorithms</category>
      <category>python</category>
    </item>
    <item>
      <title>Why is Graph Theory so amazing? - part 2, depth first search &amp; topological sorting</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Thu, 01 Apr 2021 10:59:45 +0000</pubDate>
      <link>https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-2-depth-first-search-topological-sorting-jkg</link>
      <guid>https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-2-depth-first-search-topological-sorting-jkg</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-1-5ii"&gt;previous&lt;/a&gt; article, we explored the definition of a graph and gave some brief examples of how they are represented in computer memory. Today's article will focus on some basic graph algorithms showcasing graph traversals. &lt;/p&gt;

&lt;p&gt;Graph traversal is the process of visiting each node of a graph. Such traversals are classified into two categories, by the order in which the nodes are visited: depth-first search (at which we will look today) and breadth-first search (for next week!). For the rest of the article, let &lt;strong&gt;G&lt;/strong&gt; be a graph with &lt;strong&gt;N&lt;/strong&gt; nodes and &lt;strong&gt;E&lt;/strong&gt; edges.&lt;/p&gt;

&lt;p&gt;Before talking about traversals themselves, there is some graph terminology we have to introduce.&lt;/p&gt;

&lt;h2&gt;
  
  
  The degree of a node
&lt;/h2&gt;

&lt;p&gt;Below, I will be using the notion of &lt;strong&gt;degree&lt;/strong&gt; when speaking of a node. The degree of a node represents the number of direct connections that node has to other nodes in the graphs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvwh4ntawctq5va6ambj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcvwh4ntawctq5va6ambj.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, in the image above, the degree of node 1 is 3, the degree of nodes 4 and 2 is 2 and degree of node 3 is 1.&lt;/p&gt;

&lt;p&gt;In an undirected graph, the sum of the degree of each node equals half the number of edges (as for any nodes &lt;strong&gt;i&lt;/strong&gt; and &lt;strong&gt;j&lt;/strong&gt; such there is an edge between them, the edge &lt;strong&gt;(i, j)&lt;/strong&gt; is counted as both &lt;strong&gt;(i, j)&lt;/strong&gt; and &lt;strong&gt;(j, i)&lt;/strong&gt;)&lt;br&gt;
When speaking about directed graphs, for any node &lt;strong&gt;i&lt;/strong&gt; there exists an in degree (number of edges that lead to &lt;strong&gt;i&lt;/strong&gt;) and an out degree (number of edges that leave from &lt;strong&gt;i&lt;/strong&gt;).&lt;/p&gt;
&lt;h2&gt;
  
  
  Walks, trails, cycles in a graph.
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;walk&lt;/strong&gt; in a graph is a sequence of nodes in a graph such that for any two consecutive nodes, &lt;strong&gt;i&lt;/strong&gt; and &lt;strong&gt;j&lt;/strong&gt;, there exists the edge &lt;strong&gt;(i, j)&lt;/strong&gt;. This implies that nodes and edges can be repeated. For example, in the graph we presented above, 1 -&amp;gt; 3 -&amp;gt; 1 -&amp;gt; 2 -&amp;gt; 4 is a walk.&lt;br&gt;
A &lt;strong&gt;trail&lt;/strong&gt; in a graph is a walk with no repeated edges. For example, in the graph above, 1 -&amp;gt; 2 -&amp;gt; 4 or 2 -&amp;gt; 4 -&amp;gt; 1 -&amp;gt; 3 are trails.&lt;br&gt;
A &lt;strong&gt;cycle&lt;/strong&gt; in a graph is a trail in which only the first and last nodes are repeated. In our graph, 1 -&amp;gt; 2 -&amp;gt; 4 -&amp;gt; 1 is a cycle.&lt;/p&gt;
&lt;h2&gt;
  
  
  Depth-first search - analysis &amp;amp; why it matters
&lt;/h2&gt;

&lt;p&gt;The depth-first search algorithms follows a recursive approach and aims to traverse the graph in its depth, by following these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;start the traversal from an arbitrary node, let it be &lt;strong&gt;i&lt;/strong&gt;; mark &lt;strong&gt;i&lt;/strong&gt; as visited.&lt;/li&gt;
&lt;li&gt;search for the neighbours of &lt;strong&gt;i&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;if a neighbour, let it be &lt;strong&gt;j&lt;/strong&gt;, is found, then the algorithm makes a recursive call to &lt;strong&gt;step 1&lt;/strong&gt;, now starting from node &lt;strong&gt;j&lt;/strong&gt; instead of &lt;strong&gt;i&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;if there are no neighbours to be found, the call ends and the algorithm moves up the recursive stack.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Applying this recursive approach to a node &lt;strong&gt;i&lt;/strong&gt; (finding its neighbours and making correspondent recursive calls) can also be called, for short, &lt;strong&gt;expanding i&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, let's have a look at the time complexity of the DFS algorithm when representing &lt;strong&gt;G&lt;/strong&gt; using each of the methods showcased in the previous article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;G&lt;/strong&gt; is represented by using an &lt;a href="https://github.com/KruZZy/magic-of-computing/blob/main/adjacency_matrix.py" rel="noopener noreferrer"&gt;adjacency matrix&lt;/a&gt;, let it be &lt;strong&gt;A&lt;/strong&gt; of size &lt;strong&gt;N&lt;/strong&gt; * &lt;strong&gt;N&lt;/strong&gt;. If we have to search for the neighbours of an arbitrary node &lt;strong&gt;i&lt;/strong&gt;, we have to loop through all the &lt;strong&gt;N&lt;/strong&gt; columns of &lt;strong&gt;i&lt;/strong&gt;-th line of the matrix. We have to do this exactly &lt;strong&gt;N&lt;/strong&gt; times as we can't expand a note multiple times and therefore the complexity of DFS while using an adjacency matrix is O(N*N).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;G&lt;/strong&gt; is represented by using &lt;a href="https://github.com/KruZZy/magic-of-computing/blob/main/adjacency_list.py" rel="noopener noreferrer"&gt;adjacency lists&lt;/a&gt;, let &lt;strong&gt;A[i]&lt;/strong&gt; denote the list of the neighbours of &lt;strong&gt;i&lt;/strong&gt;. Then, for each node &lt;strong&gt;i&lt;/strong&gt;, we have to loop through its neighbours - equal in number to the degree of node &lt;strong&gt;i&lt;/strong&gt;. Adding those operations result in an O(N+E).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As we said in the previous article - for a dense graph (that is, when E approaches N*N) the adjacency matrix approach would prove more effective as it would use considerably less memory, while having the same asymptotic complexity - when E approaches N*N, O(N+E) is O(N*N).&lt;/p&gt;

&lt;p&gt;Let us now see an implementation of DFS, using a graph represented by adjacency lists. To avoid duplicate code, during this series, I will use the &lt;a href="https://github.com/KruZZy/magic-of-computing/blob/main/adjacency_list.py" rel="noopener noreferrer"&gt;adjacency_list.py&lt;/a&gt; I wrote for the first article, which includes a basic class definition for managing graphs represented by adjacency lists.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This snippet runs on the graph used above when explaining node degree and its output is "1 2 4 3". &lt;br&gt;
The algorithm starts from 1, searching for its neighbours (which are 2, 3, 4). The first one found is 2, and then the algorithm searches for 2's unvisited neighbours, finding 4. Node 4 has no unvisited neighbours left, and the algorithm moves up the stack. Same goes for 2. Node 3 is the last of node 1's unvisited neighbours, resulting in the output.&lt;/p&gt;
&lt;h2&gt;
  
  
  Topological sorting
&lt;/h2&gt;

&lt;p&gt;If &lt;strong&gt;G&lt;/strong&gt; is a directed graph with no cycles (also called a directed acyclic graph, or DAG for short), then the topological sorting of the nodes of graph &lt;strong&gt;G&lt;/strong&gt; is a linear ordering of nodes such that for any edge &lt;strong&gt;(i, j)&lt;/strong&gt;, &lt;strong&gt;i&lt;/strong&gt; comes before &lt;strong&gt;j&lt;/strong&gt; in the ordering.&lt;br&gt;
This sorting has a wide range of real world uses, most notably helping organize a schedule of tasks based on their dependencies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw86bslhpshielbrujovd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw86bslhpshielbrujovd.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, the graph above can represent dependencies between tasks (e. g., task 2 should be started after finishing task 1). By find a topological sorting of the graph, we can determine a way in which to start the tasks so there are no bottlenecks.&lt;/p&gt;

&lt;p&gt;A simple way to do this is to first find a node with in degree 0 (in our case, &lt;strong&gt;node 1&lt;/strong&gt;), and start a depth first search with it as root. Instead of using the node right away, we will use a stack to push it &lt;strong&gt;only after all of its neighbours have been visited&lt;/strong&gt;. Then, the reversed stack will hold a topological sorting for our graph, as the last node we push to the stack will be the root.&lt;/p&gt;

&lt;p&gt;For implementing topological sorting, I have modified the &lt;strong&gt;graph&lt;/strong&gt; class we used before for a directed graph and also calculated the in degree of each node at initialisation:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Then, I implemented the modified DFS using the modified version of the graph manager class as the parent: &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;For the directed graph above, the output of the snippet is "1 4 2 3 5", an ordering that satisfies the conditions of our definition. Note, though, that a topological sorting of a graph is not necessarily unique. For our example, "1 2 3 5 4" is also a correct ordering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was a brief introduction into depth first search and one of its real world applications. Next week, we will be talking about the other graph traversal - breadth first search (or BFS for short), and we will also showcase some of its powers.&lt;br&gt;
Until then, stay tuned, any why don't delight yourself with some of the other articles in &lt;strong&gt;The Magic of Computing&lt;/strong&gt; series? The part about the &lt;a href="https://dev.to/kruzzy/the-magic-of-the-fibonacci-numbers-why-we-love-computing-them-part-1-18gp"&gt;Fibonacci numbers&lt;/a&gt; is quite a hit!&lt;/p&gt;

</description>
      <category>graphtheory</category>
      <category>computerscience</category>
      <category>algorithms</category>
      <category>python</category>
    </item>
    <item>
      <title>Why is Graph Theory so amazing? - part 1</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Thu, 25 Mar 2021 12:16:07 +0000</pubDate>
      <link>https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-1-5ii</link>
      <guid>https://dev.to/kruzzy/why-is-graph-theory-so-amazing-part-1-5ii</guid>
      <description>&lt;p&gt;Graph Theory has a special influence in our daily lives. Unbeknown to most people, many aspects of our day-to-day life are modelled by graphs: the GPS we use every day, our Facebook friend suggestions, and even the web and the operating systems we use.&lt;br&gt;
How can a concept that looks so simple - models describing relations between objects - become so powerful? &lt;br&gt;
We are going to try and present some of the reasons behind this and explain some very interesting properties and facts about Graph Theory during a new entry in The Magic of Computing series - "Why is Graph Theory so amazing?"&lt;/p&gt;
&lt;h2&gt;
  
  
  A little bit of history
&lt;/h2&gt;

&lt;p&gt;The idea of a graph was firstly introduced by a very influential Swiss mathematician by the name of &lt;strong&gt;Leonhard Euler&lt;/strong&gt;. During the first half of the 18th century, Euler made attempts to solve the famous Königsberg bridge problem (the city is in Russia and is now called Kalinigrad), eventually suceeding in 1735.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.britannica.com%2F77%2F74877-050-F5DD4C34%2FLeonhard-Euler-route-each-question-bridges-Swiss.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.britannica.com%2F77%2F74877-050-F5DD4C34%2FLeonhard-Euler-route-each-question-bridges-Swiss.jpg" alt="bridges"&gt;&lt;/a&gt;&lt;br&gt;
(image courtesy of &lt;a href="https://www.britannica.com/" rel="noopener noreferrer"&gt;Encyclopedia Britannica, Inc&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;In the photo above we can see a visual representation of how the bridges the problem speaks about were set. The question was whether a citizen could cross the bridges in such a way that each bridges was crossed exactly once. Nowadays, this kind of traversal is called an Eulerian path.&lt;/p&gt;

&lt;p&gt;Euler demonstrated that such a path cannot be achieved in this setting. He firstly supposed that a path existed. When attempting a traversal, each time a citizen encounters a landmass, apart from the start and finish encounters, two bridges should be accounted for - the one on which the citizen just passed, and another one to take him to another landmass, so the number of bridges on each landmass should be an even number. However, the picture shows that there are no landmasses with an even number of bridges. Therefore, such a traversal is impossible in the current setting.&lt;/p&gt;

&lt;p&gt;Although Euler was one of the first to unknowingly experience with what was to become graph theory, graphs were only formally defined after 1870. In the formal definition, a graph is described by two sets. One set, called &lt;strong&gt;V&lt;/strong&gt;, containing vertices (or nodes), and &lt;strong&gt;E&lt;/strong&gt;, a set edges - pairs of vertices which can be unordered (resulting in an undirected graph), or ordered (resulting in a directed graph). &lt;/p&gt;
&lt;h2&gt;
  
  
  Graphs in the modern context
&lt;/h2&gt;

&lt;p&gt;Graph theory became exponentially more useful with the release of consumer computers. The concept is simple enough to easily represent in code and memory, and people even came up with various methods that aim to optimise how graphs are handled. Although the other entries in the series contain C++ snippets, handling graphs in C++ needs more code and I think it would make the article harder to read.&lt;/p&gt;

&lt;p&gt;Let &lt;strong&gt;G&lt;/strong&gt; be a graph with &lt;strong&gt;N&lt;/strong&gt; nodes and &lt;strong&gt;E&lt;/strong&gt; edges. Below, we will showcase two different methods to create a Python class to handle this graph, using two different forms of graph representation. Both representations will be of undirected graphs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Adjacency matrix
&lt;/h3&gt;

&lt;p&gt;A handy mode to represent &lt;strong&gt;G&lt;/strong&gt; is by using a square boolean matrix of size &lt;strong&gt;N&lt;/strong&gt; * &lt;strong&gt;N&lt;/strong&gt;. Let this matrix be called &lt;strong&gt;A&lt;/strong&gt;. Then, &lt;strong&gt;A[i][j]&lt;/strong&gt; will be true if there is an edge between nodes &lt;em&gt;i&lt;/em&gt; and &lt;em&gt;j&lt;/em&gt; and false otherwise. In an undirected graph, matrix &lt;strong&gt;A&lt;/strong&gt; is symmetric, as the edge from &lt;strong&gt;i&lt;/strong&gt; to &lt;strong&gt;j&lt;/strong&gt; is bidirectional, so &lt;strong&gt;A[i][j]&lt;/strong&gt; = &lt;strong&gt;A[i][j]&lt;/strong&gt; for any &lt;strong&gt;i&lt;/strong&gt; and &lt;strong&gt;j&lt;/strong&gt;.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;One advantage of this is that determining whether an edge &lt;strong&gt;(i, j)&lt;/strong&gt; is in the graph can be done in constant time. However, if we aim for example to store a graph with a lot of nodes and a relatively small number of edge, this method might prove inefficient from a memory standpoint (this graphs are also called sparse graphs). That leads us to another popular method of representing graphs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adjacency lists
&lt;/h3&gt;

&lt;p&gt;This method aims to represent a graph by using &lt;em&gt;N&lt;/em&gt; lists, enumerating the neighbours of each node. &lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;While when using this method, checking whether a given edge exists is not constant (having an O(E) worst case), looping through all the neighbours of a node is much time-efficient in sparse graphs, and there are also other benefits which we will see in future articles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other methods
&lt;/h3&gt;

&lt;p&gt;Alternatively, graphs can also be represented by using a list of edges or a so-called incidence matrix.&lt;br&gt;
The incidence matrix is a boolean &lt;strong&gt;E&lt;/strong&gt; * &lt;strong&gt;N&lt;/strong&gt; matrix, with one line for each edge. The columns representing the two extremities of the edge are marked with 1 on its corresponding line.&lt;/p&gt;

&lt;p&gt;All methods have their own advantages and disadvantages as we shall see in other articles.&lt;/p&gt;

&lt;p&gt;In the next article, we will delve deeper into graph theory, showing some interesting graph-related algorithms and their applications. Until then, be sure to check out other articles in &lt;em&gt;The Magic of Computing&lt;/em&gt;!&lt;/p&gt;

</description>
      <category>graphtheory</category>
      <category>computerscience</category>
      <category>algorithms</category>
      <category>python</category>
    </item>
    <item>
      <title>Prime numbers: Fast and Slow - part 3</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Tue, 19 Jan 2021 19:30:32 +0000</pubDate>
      <link>https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-3-2dgm</link>
      <guid>https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-3-2dgm</guid>
      <description>&lt;p&gt;Hello everyone! I am back with another entry in my &lt;em&gt;Magic of Computing&lt;/em&gt; series. Today, we are going to have one last look at prime numbers. During &lt;a href="https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-2-425o"&gt;the previous article&lt;/a&gt;, we learned about an influential figure in the world of science, Eratosthenes of Cyrene, and today we are going to see how a prime factorisation algorithm performs, use some of Erastothenes' findings to improve it, and explain why we care about prime factorisation in the first place.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is prime factorisation?
&lt;/h1&gt;

&lt;p&gt;We know that all non-prime (or composite) are a product of primes. Prime factorisation of a number &lt;em&gt;n&lt;/em&gt; is finding what primes multiply together to make up &lt;em&gt;n&lt;/em&gt; and how many times. &lt;br&gt;
For example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A-aEBv-V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/93sxd6paziul1pwyyp76.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A-aEBv-V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/93sxd6paziul1pwyyp76.png" alt="pr_fact_180"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Computing a prime factorisation
&lt;/h1&gt;

&lt;p&gt;We can compute the prime factorisation of a number using a fairly straightforward approach. Let &lt;em&gt;n&lt;/em&gt; be the number of which we want to compute the prime factorisation. All we have to do is search all its possible divisors, much like the approach I presented during the &lt;a href="https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-1-224f"&gt;first article about prime numbers&lt;/a&gt;, with a slight modification: when fiding a divisor, we will divide &lt;em&gt;n&lt;/em&gt; by it as many times as possible. This will guarantee us that we will only divide &lt;em&gt;n&lt;/em&gt; by its prime factors: let us suppose we found a composite divisor &lt;em&gt;d&lt;/em&gt; of &lt;em&gt;n&lt;/em&gt;; then, any number in the prime factorisation of &lt;em&gt;d&lt;/em&gt; (implicitly smaller than or equal to &lt;em&gt;d&lt;/em&gt;) is also in the prime factorisation of &lt;em&gt;n&lt;/em&gt;, which can only be true for &lt;em&gt;d&lt;/em&gt;, as all its prime factors should have already been handled. That would imply that &lt;em&gt;d&lt;/em&gt; is prime, resulting in a contradiction.&lt;/p&gt;

&lt;p&gt;We can skip even numbers as they will all be multiples of 2, which we will test separately, resulting in the following implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;primeFactorisation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;/// suppose n is &amp;gt;= 0&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;/// firstly, we will test n separately.&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;/// p will store the power of each number in the prime factorisation of n.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"2^"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"; "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;/// now we will search the possible divisors of n for prime factors.&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"^"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"; "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;It is obvious that, for large numbers, a vast amount of non-prime numbers would be tested as well. If we were to compute multiple queries asking for the prime factorisation of &lt;em&gt;n&lt;/em&gt;, we could use &lt;em&gt;The Sieve of Eratosthenes&lt;/em&gt;, described in &lt;a href="https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-2-425o"&gt;the previous article&lt;/a&gt; to precompute the primes up to a given limit and then, instead of searching for the possible divisors of &lt;em&gt;n&lt;/em&gt; for the prime factors, we would only search for divisors of &lt;em&gt;n&lt;/em&gt; across the precomputed prime numbers. &lt;/p&gt;
&lt;h1&gt;
  
  
  Why computing prime factorisations interest us
&lt;/h1&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-1-224f"&gt;first article about primes&lt;/a&gt; I mentioned that [RSA](&lt;a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)"&gt;https://en.wikipedia.org/wiki/RSA_(cryptosystem)&lt;/a&gt;, the most common encryption tool today makes extensive use of prime factorisation by using a private key which is the product of two big prime numbers. In theory, you can crack any RSA private key if you ran a modified version of the algorithm I wrote above. In practice, you do not have the time to do that. The time taken to compute the prime factorisation of a sufficiently large number is non-polynomial, and there is no known polynomial algorithm (running on a classic computer) to compute this prime factorisation. &lt;br&gt;
The latest RSA standard to be factored is &lt;a href="https://en.wikipedia.org/wiki/RSA_numbers#RSA-240%5D"&gt;RSA-240 in November 2019&lt;/a&gt;. For reference, computing this on a 2.1 GHz Intel Xeon Gold 6130 would take 900 years.&lt;/p&gt;

&lt;p&gt;That's it for &lt;em&gt;Primes: Fast and Slow&lt;/em&gt;. I will be updating this series of articles (&lt;em&gt;and some others!&lt;/em&gt;) with various computer science stuff regularly, so feel free to follow me if you haven't already to keep in touch with it.&lt;br&gt;
If you liked this article, you may like other articles I have written:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/kruzzy" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6eISi-2V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--JkU_qeG6--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/265486/0f8109c0-8afd-49b6-9584-a3165804115d.jpeg" alt="kruzzy"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/kruzzy/divide-pizzas-with-a-greedy-approach-python-2cnh" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Divide pizzas with a Greedy approach &amp;amp; Python&lt;/h2&gt;
      &lt;h3&gt;Andrei Visoiu ・ Nov 5 '19 ・ 2 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#computerscience&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#python&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#algorithms&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#greedy&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;




&lt;div class="ltag__link"&gt;
  &lt;a href="/kruzzy" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6eISi-2V--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--JkU_qeG6--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/265486/0f8109c0-8afd-49b6-9584-a3165804115d.jpeg" alt="kruzzy"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/kruzzy/bookboom-1-ai-to-the-mainstream-machines-that-think-instant-expert-by-newscientist-4h79" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;BookBoom 1: AI to the mainstream - "Machines that think" (Instant Expert by NewScientist)&lt;/h2&gt;
      &lt;h3&gt;Andrei Visoiu ・ Feb 8 '20 ・ 3 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#artificialintelligence&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#ai&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#machinelearning&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#books&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>computerscience</category>
      <category>cpp</category>
      <category>mathematics</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Prime Numbers: Fast and Slow - part 2 </title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Sun, 10 Jan 2021 15:02:54 +0000</pubDate>
      <link>https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-2-425o</link>
      <guid>https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-2-425o</guid>
      <description>&lt;p&gt;Hello everyone! It has been a while since I have written a blog post on dev.to - I lacked the motivation to do it for quite some time, but now I intend to pick up where I left on my series, &lt;em&gt;The Magic of Computing&lt;/em&gt;, more specifically prime numbers. During &lt;a href="https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-1-224f"&gt;the previous article&lt;/a&gt;, we explored what prime numbers are, some of their basic uses in computer science and some basic algorithms related to them. This article will focus on generating lists of prime numbers. &lt;/p&gt;

&lt;h1&gt;
  
  
  An influential figure - Eratosthenes of Cyrene
&lt;/h1&gt;

&lt;p&gt;Living for the most part of the 3rd century BC, Eratosthenes of Cyrene was a mathematician who is now considered the Founder of Geography. He was the chief librarian at the Library of Alexandria and the first person to compute an astonishignly accurate calculation of Earth's circumference, in approximately ~240BC, by comparing the positions of the Sun's rays in two locations. His calculations were only several kilometers off. More information on this can be found &lt;a href="https://www.khanacademy.org/humanities/big-history-project/solar-system-and-earth/knowing-solar-system-earth/a/eratosthenes-of-cyrene"&gt;here&lt;/a&gt; &lt;br&gt;
Apart from measuring the Earth, he also introduced an algorithm now considered ancient, but which is still used nowadays in optimised variations. It is called &lt;em&gt;The Sieve of Eratosthenes&lt;/em&gt; and is used for finding prime numbers up to any given limit.&lt;/p&gt;
&lt;h1&gt;
  
  
  The Sieve of Eratosthenes - a simple, yet ingenious approach to generate prime numbers
&lt;/h1&gt;

&lt;p&gt;The algorithm is based on a very simple idea - prime numbers cannot be the multiple of any prime number before them. Let the given limit for our algorithm be called &lt;em&gt;n&lt;/em&gt;. Firstly, it considers all numbers, starting from 2, to be prime. Then, incrementally, for every number &lt;em&gt;i&lt;/em&gt; from 2 up to &lt;em&gt;n&lt;/em&gt;, it checks whether &lt;em&gt;i&lt;/em&gt; is prime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if &lt;em&gt;i&lt;/em&gt; is prime, the algorithm marks all the multiples of &lt;em&gt;i&lt;/em&gt; lower than or equal to &lt;em&gt;n&lt;/em&gt; as non-prime, or composite.&lt;/li&gt;
&lt;li&gt;if &lt;em&gt;i&lt;/em&gt; is not prime, the algorithm continues.
The algorithm ends when &lt;em&gt;i&lt;/em&gt; has reached &lt;em&gt;n&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Straightforward implementation
&lt;/h1&gt;

&lt;p&gt;Below, you can find a basic C++ implementation of the algorithm, using it to print prime numbers up to a number &lt;em&gt;n&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;/// we need the numbers between 1 and n&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;/// we consider all number from 0 up to n to be prime&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;/// we take all the multiples of i up to n and mark them as non-rpime&lt;/span&gt;
                &lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;/// while printing, we don't consider 0 and 1 as primes.&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Optimisations
&lt;/h1&gt;

&lt;p&gt;An immediate optimisation comes from something also mentioned in the other article as well: we only need to check up to the square root of &lt;em&gt;n&lt;/em&gt;, because any divisor greater than that would have had a number to pair with in order to get &lt;em&gt;n&lt;/em&gt; by multiplying them. Also, when checking multiples, it is also worth observing that all the numbers less than i*i have already been marked when checking the primes smaller than &lt;em&gt;i&lt;/em&gt;.&lt;br&gt;
The implementation with the basic optimisations looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;sieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;/// we need the numbers between 1 and n&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;/// we consider all number from 0 up to n to be prime&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;/// we take all the multiples of i up to n and mark them as non-rpime&lt;/span&gt;
                &lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;/// while printing, we don't consider 0 and 1 as primes.&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prime&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sc"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When dealing with a big &lt;em&gt;n&lt;/em&gt;, the sieve can be improved further by only checking odd numbers and only using bits to store the primality (for example, by using bitset). A great article on these optimisations can be found &lt;a href="https://cp-algorithms.com/algebra/sieve-of-eratosthenes.html"&gt;here&lt;/a&gt;.&lt;br&gt;
The usage of sieves, such as The Sieve of Eratosthenes, has spawned a new area of expertise, called &lt;a href="https://en.wikipedia.org/wiki/Sieve_theory"&gt;Sieve Theory&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The article has come to and end. The next article will be the last in which we will explore prime numbers and will be centered upon prime factorisation, so stay tuned!&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>cpp</category>
      <category>mathematics</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Prime Numbers: Fast and Slow - part 1</title>
      <dc:creator>Andrei Visoiu</dc:creator>
      <pubDate>Sat, 22 Feb 2020 08:24:48 +0000</pubDate>
      <link>https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-1-224f</link>
      <guid>https://dev.to/kruzzy/prime-numbers-fast-and-slow-part-1-224f</guid>
      <description>&lt;p&gt;A prime number (or commonly a prime) is a natural number greater than 1 which has exactly two natural divisors (1 and itself). They are widely studied by number theorists and a fairly in competitive programming problems and interview questions. But why? &lt;/p&gt;

&lt;p&gt;Well, during 1-2 articles, we will be discussing some interesting uses, properties and means of computing primes that will help us understand why they are popular.&lt;/p&gt;

&lt;h1&gt;
  
  
  They are used for calculating hash codes
&lt;/h1&gt;

&lt;p&gt;Firstly, a hash table is a data structure that maps values to keys. Hash codes are number codes, computed through a function called a hash function, assigned to complex objects (which are the keys) in order to quickly retrieve them from a hash table. Ideally, a hash function should be &lt;a href="https://en.wikipedia.org/wiki/Injective_function"&gt;injective&lt;/a&gt; (no two objects should return the same value). However, in practice, this is a fairly hard task. This is where primes come into play. For example, the widely used function to compute a hash of a string is the following: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5Dq_oCZw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/qu1otEY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5Dq_oCZw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.imgur.com/qu1otEY.png" alt="fct"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where &lt;em&gt;m&lt;/em&gt; and &lt;em&gt;p&lt;/em&gt; are some chosen positive integers. This is called a &lt;a href="https://en.wikipedia.org/wiki/Rolling_hash"&gt;polynomial rolling hash function&lt;/a&gt;. &lt;br&gt;
Reasonably, &lt;em&gt;p&lt;/em&gt; should be a prime number around the number of characters in the alphabet and &lt;em&gt;m&lt;/em&gt; should be a large number (in practice, it should also be prime) &lt;/p&gt;
&lt;h1&gt;
  
  
  Modern cryptography requires extensive use of prime numbers
&lt;/h1&gt;

&lt;p&gt;Prime numbers are the fundamental tool that the most common type of encryption used today, &lt;a href="https://en.wikipedia.org/wiki/RSA_(cryptosystem)"&gt;RSA&lt;/a&gt;, uses.&lt;br&gt;
It depends on the fact that the prime factorization of large numbers takes a lot of time: there exists a public key (the product of two large prime numbers) and a secret key (those two large prime numbers). Everyone can use the public key to encrypt messages to send you, but only you can decrypt the message. Anyone else should find the prime factorization of the public key (which right now takes an unreasonable amount of time).&lt;/p&gt;
&lt;h1&gt;
  
  
  Algorithms related to prime numbers
&lt;/h1&gt;

&lt;p&gt;There are various algorithms related to prime numbers. We will study some approaches and analyse their performance in order to understand them. &lt;/p&gt;
&lt;h2&gt;
  
  
  Checking whether a number is prime or not
&lt;/h2&gt;

&lt;p&gt;The first algorithms we will look into are those that test for primality. Even though prime factorization is thought to be a computationally difficult problem, primality testing is not, as we will see below.&lt;/p&gt;
&lt;h3&gt;
  
  
  Naive method
&lt;/h3&gt;

&lt;p&gt;The most naive solution is to use the definition in order to get a correct algorithm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;primeCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;/// if i is a divisor of n &lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;/// we haven't found any divisor&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without much analysis, we can clearly see that the complexity of the algorithm is O(n), because it checks every number &lt;em&gt;i&lt;/em&gt; between 2 and &lt;em&gt;n-1&lt;/em&gt; to see if it divides &lt;em&gt;n&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;A rapid, small optimisation can quickly arise if we pay attention to a small observation: any number &lt;em&gt;n&lt;/em&gt; won't have any divisors greater than &lt;em&gt;n/2&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;primeCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;/// if i is a divisor of n &lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;/// we haven't found any divisor&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complexity is still O(n), but the runtime should be a little lower than the one of the first version.&lt;/p&gt;

&lt;p&gt;O(n) is the worst time complexity we can get and is still polynomial in the size of the input. That is why, as I said above, primality testing is not considered computationally difficult.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further improvements over the naive method.
&lt;/h3&gt;

&lt;p&gt;A property of the divisibility relation is that if &lt;em&gt;i&lt;/em&gt; is a divisor of &lt;em&gt;n&lt;/em&gt;, then &lt;em&gt;n/i&lt;/em&gt; is a divisor of &lt;em&gt;n&lt;/em&gt;. By taking this into account, we can make another optimisation: we only need to check up to the square root of &lt;em&gt;n&lt;/em&gt;, because any divisor greater than that would have had a number to pair with in order to get &lt;em&gt;n&lt;/em&gt; by multiplying them.&lt;br&gt;
Another improvement we can make is by using another observation: if &lt;em&gt;n&lt;/em&gt; is not divisible by 2, then it won't be divisible by any even number. So, we can check if 2 is a divisor of &lt;em&gt;n&lt;/em&gt; separately and then only look at odd numbers. With the improvements, the algorithm should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;improvedPrimeCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The time complexity of the algorithm above has now become O(sqrt(n)). &lt;/p&gt;

&lt;p&gt;That's it for this article. During the next article, we will be taking a look into generating prime numbers and prime factorisation, so stay tuned! &lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>mathematics</category>
      <category>algorithms</category>
      <category>cpp</category>
    </item>
  </channel>
</rss>
