Category Archives: Algorithms

Linear Recurrences

How often has it happened to you in a programming contest (or elsewhere) that you thought it was impossible to solve it faster than O(N) and yet the limits imposed suggest that it has to be done faster. Well, if not all, atleast a majority of them have a solution based on the idea of linear recurrences. In this blog post, I intend to help you out on this !!

In this post, we are going to do a – Solve and Learn strategy ; You will be given a question and I will show you how to apply  the concepts on them.

TYPE 1 :: The Simplest :

If a post mentions recurrences, then it has to mention Fibonacci (Gosh, if only I had a penny for every mention of Fibo in tutorials. )

The recurrence is of type : F(n) = F(n-1) + F(n-2).

I am pretty sure you know to code the linear version of it which runs in O(N) but can you do it in O(log N) ? If you throw google to good use, you will come up with a solution which says there is a Matrix M which when raised to power N, will give you the N-th fibonacci number. And since you can always exponentiate in logN time, you have your answer. But to those, who wondered if this Matrix is magical- read on!

Firstly the answer- No; Its not magical. How. Lets do a little Algebra (yumm… My favourite! )
F(n)=F(n-1)+F(n-2)\\ \\ F(n+1) =F(n)+F(n-1)\\ \\ F(n+2)=F(n+1)+F(n)

Obviously enough, the value of N-th term, depends on two previous terms (or states). This implies that all values depend on just the first two states in the sequence. As you can see here -

\begin{pmatrix}F(n+2)\\ F(n+1)\end{pmatrix}=\begin{pmatrix}1&1\\ 1&0\\ \end{pmatrix}\times\begin{pmatrix}F(n+1)\\ F(n)\end{pmatrix}\\ \\ and\\ \\ \begin{pmatrix}F(n+1)\\ F(n)\end{pmatrix}=\begin{pmatrix}1&1\\ 1&0\\ \end{pmatrix} \times \begin{pmatrix}F(n)\\ F(n-1)\end{pmatrix} \\ \\ Hence \\ \\ \begin{pmatrix}F(n+2)\\ F(n+1)\end{pmatrix}=\begin{pmatrix}1&1\\ 1&0\\ \end{pmatrix} ^2 \times \begin{pmatrix}F(n)\\ F(n-1)\end{pmatrix} \\ \\ \begin{pmatrix}F(n+2)\\ F(n+1)\end{pmatrix}=\begin{pmatrix}1&1\\ 1&0\\ \end{pmatrix}^3 \times \begin{pmatrix}F(n-1)\\ F(n-2)\end{pmatrix}

Hence in General, we may write ::
\begin{pmatrix}F(n)\\ F(n-1)\end{pmatrix}=\begin{pmatrix}1&1\\ 1&0\\ \end{pmatrix}^{n-1} \times \begin{pmatrix}1\\ 0\end{pmatrix}

I hope that has helped you in understanding how to frame such equations and solving it with a matrix.

TYPE 2 : Simplest ++

Now that we have a basic understanding. Try the following recurrence :

F(n) = F(n-1) + F(n-2) + F(n-3).

It is the same as the previous recurrence but with an additional state. I won’t go on explaining the hows (again!). I am going to share the solution.
\begin{pmatrix}F(n)\\ F(n-1)\\ F(n-2) \end{pmatrix}=\begin{pmatrix}1&1&1\\ 1&0&0\\ 0&1&0 \end{pmatrix}^{n-2} \times \begin{pmatrix}2\\ 1\\ 1\end{pmatrix}

TYPE 3: Simplest << 1

Consider the following scenario ::

G(n) = a . G(n-1) + b . G(n-2) + c . H(n)\\ \\ and \\ \\ H(n)= d . H(n-1) + e . H(n-2)

This one is a lot trickier. First thing to notice is that we will need 4 states in a matrix to fully define the next state. The reason for using 4 and not 3 is that H(n) depends on 2 states and thus we need 2 states (and not just 1) to represent it.

If you carefully write down the LHS matrix and the RHS matrix, then we can frame the solution as . . .

\begin{pmatrix}G(n)\\ G(n-1)\\ H(n+1)\\ H(n) \end{pmatrix}=\begin{pmatrix}a&b&c&0\\ 1&0&0&0\\ 0&0&d&e\\ 0&0&1&0 \end{pmatrix}^{n-1} \times \begin{pmatrix}G(1)\\ G(0)\\ H(2)\\ H(1)\end{pmatrix}

TYPE 4 : Ohhh !

The final hurdle can come in the name of a constant. If we add a constant C to the above recurrence we get -

G(n) = a . G(n-1) + b . G(n-2) + c . H(n) + C\\ \\ and \\ \\ H(n)= d . H(n-1) + e . H(n-2)

But to tell you the truth, its not that difficult if your concepts are clean. Now there is another additional state to hold the information about C. The solution will look like -

\begin{pmatrix}G(n)\\ G(n-1)\\ H(n+1)\\ H(n)\\ C \end{pmatrix}=\begin{pmatrix}a&b&c&0&1\\ 1&0&0&0&0\\ 0&0&d&e&0\\ 0&0&1&0&0\\ 0&0&0&0&1 \end{pmatrix}^{n-1} \times \begin{pmatrix}G(1)\\ G(0)\\ H(2)\\ H(1)\\ C\end{pmatrix}

I hope this post lived up to your expectations and I hope it was worth the wait :P . Please feel free to post comments/corrections/improvements to this post to make it really useful.


Return to Roots: Tree 101

What is a Tree :

Tree is a heirarchial arrangement of nodes. From the literal meaning of Tree we know that it has root, branches, fruits and leaves. Well, in Algorithms also, we have a root – which is the origin of the tree. We have branches which connect to smaller trees and we have leaves, which do not have outgoing branches. And as far as the fruits are concern – depending on the complexity of operations that can be perform, we may label the fruits as sweet and sour !

The simplest tree would be a node which branches to exactly one other node, or in other words – a singly Link List. If every node branches to its child and also to its parent, we have a doubly link list. But in this post, we are not going to discuss these.

The next level of trees would be – where a single node may branch out to a maximum of two other nodes. Such a tree is call a binary tree. Binary trees are some of the most widely us datastructures in computers and we are going to discuss them in a series of posts. So lets begin.

One of the most important things to do is : Create a tree.
So what is it that we ne to create one. We will ne to represent the nodes and the links between nodes. And since we ne to connect to a maximum of two nodes, we will have two branches. We shall call these branches – left and right. Also, it will store some data in it. Our tree will be us to just store integers.

We will use the following structure to create it. FYI, everything here is in C++ and not C.

struct NODE {
    int data;
    NODE *left;
    NODE *right;
};

Now whenever we ne to insert a node, we ne to make sure that there is a fix position at which the node will be insert given its value (Data in the node). Let us follow a simple strategy.
We will insert a node to the left of a ‘Parent node’, if its value is lesser than the value of the Parent, otherwise to the right. The binary trees which use such a strategy are call Binary Search Trees.

The obvious advantage of such a strategy is that we can search for elements in the tree in O(h) time, where h is the height of the tree. Do note that, in general, h does not equal logN. If we could actually have a tree where the height is inde logN, we would call such trees as Balanc Binary Search Trees.

Alright then, lets get our hands dirty with a code that will create the tree for us. The function insert takes as input the root of the tree and the value to be insert and returns the node which contains the data.

NODE * insert(NODE *root, int data) {
    if(root==NULL) {
        root=(NODE*)malloc(sizeof(NODE));
        root->left=root->right=NULL;
        root->data=data;
        return root;
    }
    else {
        while(root!=NULL) {
            if(root->data>data) {
                if(root->left!=NULL) root=root->left;
                else break;
            }
            else {
                if(root->right!=NULL) root=root->right;
                else break;   
            }
        }
        NODE *new_node=new NODE;
        new_node->data=data;
        new_node->left=new_node->right=NULL;
        if(root->data > data) {
            root->left=new_node;
        }
        else root->right=new_node;
        return new_node;
    }
}

Another very useful and important property when using the above strategy is, that the INORDER traversal is sort!

Lets backup a bit. What are Traversals. It is like visiting many homes using the roads which connect them. Only that, the homes here are the NODEs and the roads are the links between each node.

There are many traversals but the three us very often are – PreOrder, InOrder and PostOrder.

In PreOrder, you print the current node and then visit its left and then its right children, recursively.
In InOrder, you first visit the left child, once you have return, you print the current value and then visit the right child.
In PostOrder, you visit both your children and then print the current value.

Here is the code snippet for the InOrder traversal (recursive version).

void inorder(NODE *root) {
    if(root!=NULL) {
        inorder(root->left);
        printf("%d ",root->data);
        inorder(root->right);
    }
}

You could write an iterative version, where you would simulate the operations in a system stack, using your own stack. The obvious advantage is that you would be saving space (since you would now push as many values as the system would for a function call.)

However, there exists a really beautiful iterative version which does not use a stack. It assumes that two pointers can be check for equality. It is bas on thread trees and it was first written in 1979 by Morris and hence the name!

How does it work.

The only reason we ne a stack is so that we can do the “RETURN” from child nodes to parent nodes. This return is ne only from one node really. Consider a 5 node tree.

                                      20
                                    /     \
                                   /       \
                                 10        30
                                /   \     
                               /     \
                             5       15

Now our stack would work like this.

1. Push 20.
2. Push 10.
3. Push 5.
4. Pop 5 and print 5.
5. Pop 10 and print 10.
6. Push 15.
7. Pop 15 and print 15.
8. Pop 20 and print 20.
9. Push 30.
10. Pop 30 and print 30.

If I write a non-resursive and non-stack version, my greatest headache would be to go to 20 from 15 (statements 7-8). So we need to link 15 and 20 so that we can go to 20 without problems. But that would mean that we are modifying the tree. Well, we could do it in two steps. First we link the two and in the next step once we have printed 20, we can destroy that link.

                                        20
                                      / | \
                                     /  |  \
                                   9    |   30
                                  /   \ |   
                                 /     \|
                               5       15

And thus we have the following -

1. SET current as root.
2. if current is not null do -
2.a. if current has no left child, print current , set current as right child and REPEAT 2.
2.b. else goto the rightmost child of current’s left child.
2.b.a. If this is NULL, then link it to current and set current as left child of current and REPEAT 2.
2.b.b. else set the right child to NULL. Print Current. Set current as Current’s right child . REPEAT 2.

As a pseudocode we may write it as -

Morris-InOrder ( root )
current = root
while current != NULL do
	if LEFT(current) == NULL then
	   print current
	   current=RIGHT(current)
	else do
	   // set pre to left child of current
	   pre=LEFT(current)
	   // find rightmost child of the left child of current
	   while (RIGHT(pre) != NULL  and RIGHT(pre) != current) do
	       pre=RIGHT(pre)
	    //if thus is null, link it to current and set current's left as current
	    if RIGHT(pre) == NULL then
	       RIGHT(pre)=current
	       current=LEFT(current)
	    // else unlink it, print current and set right child of current as current
	    else do
	       RIGHT(pre)=NULL
	       print current
	       current=RIGHT(current)

Looks nice aah. Let’s just write the code.

void MorrisInorder(NODE *root) {
    NODE* current,*pre;
    current=root;
    while(current!=NULL) {
        if(current->left==NULL) {
            printf("%d ",current->data);
            current=current->right;
        }
        else {
            pre=current->left;
            while(pre->right != NULL && pre->right !=current) 
                pre=pre->right;
            if(pre->right==NULL) {
                pre->right=current;
                current=current->left;
            }
            else {
                pre->right=NULL;
                printf("%d ",current->data);
                current=current->right;
            }
        }
    }
}

Now, lets talk about the fruits!

Insert happens in O(h) time. Each of the traversals (recursive and iterative versions using stack) are in O(N) time and O(N) space (system stack or normal stack).

Morris Inorder runs in O(NlogN) time and O(1) space. One could say that it is slower which is true, but the fact that it does not use additional space can be a huge boost in situations where you are low on system memory!

The entire code is available on :PASTEBIN
I hope you gathered all that info well! I will post a Tree 102, in which I shall discuss the delete operation and talk more about balanced trees!


Quick-Short

Hi,


The cheapest, fastest and most reliable components of a computer system are those that aren’t there.
                                                                                                                                                  -Gordon Bell

I am back with a new post. And this time its about one of my favourite algorithms (Yes, as a geek I am allowed to have fav algos :-P ) – Quick Sort ! Whats so special about it – It is amazingly simple and very clearly complex. Quick sort can be implemented as horribly as follows -


void quicksort(int *x,int l,int u)
{
	int i,j,t;
	if(l>=u)return;
	t=x[l];
	i=l;
	j=u+1;
	for(;;)
	{
		do i++; while(i<=u && x[i]<t);
		do j--;while(x[j]>t );
		if(i>j)break;
		swap(x[i],x[j]);
	}
	swap(x[l],x[j]);
	quicksort(x,l,j-1);
	quicksort(x,j+1,u);
}

or as simple as


void quicksort(int *x,int l,int u)
{	
	int i,j,t;
	if(l>=u)return;
	t=x[l];
	i=l;
	for(j=l+1;j<=u;j++)
	{
		if(x[j]<x[l])
			swap(x[++i],x[j]); 
	}
	swap(x[l],x[i]);
	quicksort(x,l,i-1);
	quicksort(x,i+1,u);	
}

But in this post, we are not going to see its looks but rather we are going to explore its performance (real beauty).
Anyone who attended a class on ‘Algorithms and Data-structures’ or had the pleasure of learning it on your own (like me) knows that Quick Sort runs in O(nlogn) expected average time. Its a known fact that for any given quick-sort (standard implementation) there exists a case which will ensure that it runs in O(n^2) time (even for the purely randomized version. If you don’t know about it, feel free to comment at the bottom and I will let the secret out :-P ) .

But what if I wanted to find the average expected time. I know there exists a mathematical derivation using Expectation and it shows that it is nlogn but what if I wanted to find out the exact number of comparisons made on the average. We are going to make an attempt on that.

Before we do that, we should take a minute to observe that there are two variables on which quicksort’s performance can be measured. One is the Number of SWAPS made and the second is the Number of Comparisons. We must select the variable which has the most impact in reducing its complexity. In this post I am using Comparisons over Swaps, Why? Simple because, the impact of a comparison is more than the impact of a Swap. How to prove it? Simple- Write a piece of CODE! (I will post the code a little later)

We will just add a new counter before the comparison inside the loop and when the sort exits, we will have the exact count of the comparisons made.


void quicksort(int *x,int l,int u)
{	
	int i,j,t;
	if(l>=u)return;
	t=x[l];
	i=l;
	for(j=l+1;j<=u;j++)
	{
            cmp++;
		if(x[j]<x[l])
			swap(x[++i],x[j]); 
	}
	swap(x[l],x[i]);
	quicksort(x,l,i-1);
	quicksort(x,i+1,u);	
}

A very basic optimization would be to add it outside the loop as shown.


void quicksort(int *x,int l,int u)
{	
	int i,j,t;
	if(l>=u)return;
	t=x[l];
	i=l;
    cmp+=u-l;
	for(j=l+1;j<=u;j++)
	{
		if(x[j]<x[l])
			swap(x[++i],x[j]); 
	}
	swap(x[l],x[i]);
	quicksort(x,l,i-1);
	quicksort(x,i+1,u);	
}

It is still slow and I want to speed it up. Is there any way I can get rid of that for loop. Actually, YES. I can remove it clearly. I know you are throwing away your thinking hat saying that -” WHAT WILL YOU SORT ? AND IF YOU AREN’T SORTING ANYTHING WHATS THE POINT ?” Well, you are right. I am not interested in sorting. I am only interested in estimating the time it will take to run on average. To do this, I don’t need to sort any array, I just need to simulate it and to simulate it quicker, I will remove the for loop and everything associated with it.
Now our simulator code looks like this. I have also removed the two variables and replaced it with the length I want to partition.


int quicksort_count(int L)
{
	int m;
	if(n<=1)return 0;
	m=l+(rand()%L);
	return n-1 + quicksort_count(m-1) + quicksort_count(L-m-1);
}

But, if we want to find the true average, we need to do this for every possible m that may be chosen. Hence we can modify our code to.

double quicksort_avg(int L)
{
	if(n<=0)return 0;
	double sum=0.0;
	for(int m=1;m<=L;m++)
		sum+=L-1 + quicksort_avg(m-1) + quicksort_avg(L-m);
	return sum/L;
}

We can improve its runtime by using Dynamic Programming. We could use the Top-Down approach where we store the values that were previously computed in an array and look it up or we could do Bottom-Up and compute the values in increasing order.


double quicksort_avg(int L)
{
    double dp[L+5];//5 is just taken for safety !
    dp[0]=0;
	for(int n=1;n<=L;n++)
	{  
        double sum=0.0;
	    for(int m=1;m<=n;m++)
	       	sum+=n-1 + dp[m-1] + dp[n-m];
	     dp[n]=sum/n;  	
    }   
	return dp[L];
}

I am still not happy. It is using O(N^2) time which I obviously do not like. It may seem like its impossible to reduce it but in reality that is not the case. For example, if n=5, then the look ups would be :

0 and 5-1
1 and 5-2
2 and 5-3
3 and 5-4
4 and 5-5.

As you can see, I am looking up the same elements twice !
So, I could remove the two lookups and instead multiply it by 2. Also, I can remove the n-1 added every time in the loop (n times to be accurate) and then I divide it by n, leaving me n-1. With those changes, I can convert it to O(N) time.

double quicksort_avg(int L)
{
    double dp[L+5],sum=0;
    dp[0]=0;
    for(int n=1;n<=L;n++)
    {
        sum+=2*dp[n-1];
        dp[n]=n-1  + sum/n;   
    }
    return dp[L];
}

Even now, I am not happy. ( Its impossible to make me happy, right ?). We can actually improve on the O(N) space, since we are only looking at the previous state. Now, our final piece looks like :

double quicksort_avg(int L)
{
    double dp,sum=0;
    dp=0;
    for(int n=1;n<=L;n++)
    {
        sum+=2*dp;
        dp=n-1 + sum/n;   
    }
    return dp;
}

Beautiful isn’t it. In one for-loop using 2 variables, I can actually find out the average comparions made by quicksort for a given length of numbers.

What I (rather Jon Bentley) is trying to show is that – sometimes and almost always we can add functionality by actually removing code! Though this was a pretty small example (and a beautiful example), it seems to explain the idea pretty well.

Do watch the video.

The original video: Three Beautiful QuickSorts


Follow

Get every new post delivered to your Inbox.

Join 82 other followers