Category Archives: Algorithms

Linked lists, “do I need them?”

I noticed a few months back, several posts on LinkedIn and Twitter where people are saying things like “what’s the point of a linked list?”, “do I need linked lists?” etc.

The answer, like in many cases, to these sorts of questions are “it depends”.

What is a linked list?

A singularly linked list is a container structure where you usually maintain a HEAD link and from that you have a pointer/reference to a NEXT link in the list, each link points to a NEXT link OR a NULL. The NULL simply indicates there are no more links.

Let’s create a really simple example of this in C#. This will act list a Queue, hence we’ll put to the end of the list and get from the front of the list, i.e. FIFO (First In First Out). It should be pretty obvious how it can be written in most other languages

class Link<T>
{
   public Link<T>(T data)
   {
      Data = data;
   }
  
   public T Data { get; }
   public Link<T> Next { get; set; }
}

class Queue<T>
{
   private Link<T> _head;
   private Link<T> _tail;
 
   public void Put(T data)
   {
      if(_head == null)
      {
         _head = _tail = new Link<T>(data);
      }
      else
      {
         _tail.Next = new Link<T>(data);
         _tail = _tail.Next;
      }
   }

   public T Get()
   {
      if(_head != null) 
      {
         var data = _head.Data;
         _head = _head.Next;
         return data;
      }
      return default(T);
   }
}

In the code above we’ve added a little optimization by including a TAIL link, which just allows us to add to the end of the linked list without having to iterate of the links each time, but otherwise it’s about as simple as it gets.

What are the problems with Linked Lists?

So immediately from the fairly trivial example we can see a few issues with this type of container. The most obvious is the lack of direct indexing, i.e. we cannot just peek or get the third item in a O(1) time complexity. Instead, we must go to the HEAD and iterate to the third item via the NEXT links. Ofcourse, an array would have far better performance than a Linked List in such situations.

With a linked list, we are (potentially) going to end up, with quite a bit of memory fragmentation as each link can be stored anywhere in memory. So, that’s another downside of using a linked list.

I suppose one might find the implementation complex compared to just creating an array, although once implemented and fully tested that’s probably not a concern. What might be more concerned with is that for each item in memory we have to store a link to the next item.

But let’s be honest the main issue people would have with a linked list is performance especially compared to an array.

So, what’s the point of a linked list when we could just use an array?

Way back, when I learned to use linked lists, I was using C++ on 8088 processors and we had very little memory available. Now, whilst each link in a linked list does have this overheard of a NEXT link, a link list can grow in a way that an array cannot.

Let’s assume we have an array of 1000 items and we want to add one more item. Then we need to create a new array buffer the size of the previous buffer + 1 and then copy all the items from the old to the new buffer then add our new item to the next extended buffer then free the original array buffer.

This means we have two arrays in memory, one with a size of 1000 items and another with 1001 items being held in memory whilst we do the copy. When memory is scarce or heavily fragmented, there’s the potential of an Out of Memory exception as an array would need a block of memory that’s contiguous and hence indexable. Along with the fact that this will end up being slow, creating a new array every time we add an item and copying.

The first thing the reader might say is – well instead of creating a new array with 1 extra item, meaning we have to create a new allocation for every item added, let’s instead create an array with more memory than we currently need, so we might then create an array with a delta (a value that says, when I create a new buffer it will always be old buffer length + delta) where delta might be a value such as 100. Now, we only need to allocate more memory every 100 times we add an item. In other words in our example, instead of creating a 1001 buffer we create a 1100 and simply track length as being 1001 but buffer size being 1100, hence we do not need to allocate more memory until the developer reaches the 1100 and wants to add an item.

This reduces memory allocation, but we still have the issues around maintaining two buffers in memory whilst we copy from one to another and, again, on systems with smaller amounts of memory such as microprocessors and still has the potential of Out of Memory exceptions due to this copy process and/or fragmentation.

Conclusion

So, in today’s modern desktop, laptops and even phones, we probably do not care about the extra issues around creating arrays and resizing them because we have plenty of memory and with some languages using garbage collection with heap defragmentation because we’ve memory to spare and the O(1) indexing is far more important to us, but if you’re using a microprocessor/microcontroller where memory is a premium linked lists still make a lot of sense. Ofcourse you will lose out on O(1) indexing but everything is a trade-off where memory is at a premium.

Big O and time complexity

Introduction

The time complexity of a function (or algorithm) can be seen as the relative time the function takes to process data of different sizes.

Time complexity is sometimes referred to as simply “complexity”, or “algorithmic complexity” or “asymptotic complexity”. We might measure the time complexity of a function with respect to best case, the average case or more likely and more generally the worst case.

Note: We generally talk about the time complexity of a function in terms of its worst case, however it’s possible we might declare the best or average case but we should therefore explicitly state that this time complexity is best or average case when using it, otherwise worst case is assumed.

As we’ll see, and you probably worked out from the title of this post, we have notation for this called Big O.

When I say “relative time” we’re not talking about the actual performance characteristics of a function as this may depend on the language used as well and the hardware running the function as well as the actual algorithm used. We’re not really interested directly in the actual time to run an algorithm or function but we’re more interested in how long it takes relative to the data or input growth. In other words whilst a function might take milliseconds for 100 items, what it we increase the data presented to the function to 10,000 items.

Disclaimer: This post is based upon a computer science view/usage of time complexity and Big O. It is not as rigorously written (or intended to be) as one would require far a mathematics based analysis and frankly it’s primarily based upon what I recall from my learning this stuff way back in the early nineties along with some refresher reading.

A simple example

Let’s look at something equivalent of the “Hello World” app. but for time complexity…

We have function which counts the number of elements in an array

func count(array: [Int]) -> Int {
  var c = 0
  for item in array {
    c += 1
  }
  return c
}

The language is immaterial, although as I’m messing with Swift at the moment, that’s what I’m presenting here, but it’s so basic, hopefully it’s pretty obvious how this works.

Now let’s pretend I ran this code against an array of 100 items and it ran in 2ms then I ran the function again with 200 elements and measure the time to be 4ms. We can see that this might indicate a linear time complexity – in other words if we continually double the number of items we’d see a double of the time taken. Hence the time against size or growth of the data creates a linear graph (i.e. y = mx+c). Ofcourse we’d need to run a fair few more samples to prove this, but you hopefully get the idea.

But wait… I said at the start that we’re interested in relative time.

What we know about this simple function is that, as the array size grows the time complexity of the function grows in proportion to size, so now we need to figure out a way to represent this information.

Big O

Big O is used to represent our relative time using notation to represent time complexity. The notation starts with a capitalized (big) O (which represents order of) followed by parenthesis and within the parenthesis we have a constant or a formula using n usually, to represent the size of data.

In the count code (above), we’re having to iterate the array to count the number of items. Therefore, to find the length of the array we must iterate over N items, hence this function has a Big O of O(n). You’ll notice though that we have essentially three statements within this function, so let’s be a little more precise about how we calculate the Big O of this function.

func count(array: [Int]) -> Int {
  var c = 0            // O(1)  
  for item in array {  // O(n)
    c += 1             // O(1) 
  }
  return c             // O(1)
}

The line where we assign the initial value to c has a constant time complexity (denoted as O(1)), in other words, this line and for that matter the incrementing of the c variable takes the same time whatever the size of input. However, the for loop must loop through each item in the array, hence has a time complexity of O(n), so we really end up with a total time complexity of

O(1) + O(n) + O(1) + O(1)

We can remove the constants as these become immaterial and are unaffected by the growth of the array, hence removing the O(1) we’re left with the O(n) time complexity.

There are a bunch of (if you like) standard time complexities you’ll see a lot, these are (in order of dominance)

O(1) = constant time
O(log n) = logarithmic time
O(n) = linear time 
O(n log n) = linear log time
O(n^2) = quadratic time
O(2^n) = exponential time
O(n!) = factorial time 

We ignore lower order terms when the algorithm is dominated by higher order terms, in other words O(n) dominates the O(log n) and hence a function that has both terms would have an overall term of O(n).

With regards our sample code, hopefully all languages will store the length of the array alongside the array data. Hence to access the length of an array we simply return this length variable and this happens in constant time O(1). Therefore, we can see that regardless of the size of the array the time to get the length variable will never change in such implementations. But if you’re used to C# think of the difference of using a List Count property against a Linq Count(). The first will return the stored count whereas the second would try to iterate the list giving quite different time complexities.

Working out the Big O of a function

We had a quick look at how to work out the time complexity of the count function, whereby we look at each line and apply the Big O notation to it, then we look for the dominant term and essentially this becomes our time complexity for the function.

Let’s now look at another function

func display() {
   for x in 0..100 {    // O(n^2)
     for y in 0..100 {  
        print(x * y)    // O(1)
     }
   }
}

This gives us O(n^2) + O(1), we can discard the constant and hence this function has a time complexity of O(n^2)

Time complexity as a range

It was mentioned earlier that we could calculate the time complexity for best, average or worst case. Whilst best and worst case give us a range to define the efficiency of our function and one might say that average gives us a useful value, we tend to concern ourselves most with the worst case. A simple example of this would be if we now change our count function to search an array for the first occurrence of a value, for example

func find(array: [Int], search: Int) -> Int{
  var pos = 0            
  for item in array {    
    if item == search {
      return pos
    }
    pos += 1
  }
  return -1
}

In the above code we can see that the only real difference from the previous count function is there’s a break from the O(n) loop when a match is found, therefore we can determine if the array’s first item was a match to our search parameter then this function would have a best case time complexity of O(1), on the other hand if the match was the last item we’d have a time complexity of O(n). Whilst we know that a best case gives us a constant time, we always need to look at the worst case. So, this function would still have a time complexity of O(n) as a worst case.

Why not just time our runs?

Earlier I mentioned some hypothetical timings for the count algorithm. Why don’t we just use those timings to represent our algorithm’s efficiency?

Well as you probably ascertained, we’re looking at have a machine independent measurement. Obviously if we’re comparing timings on a powerful development machine with lots of CPU power and memory our timings will not be indicative of the same algorithm on a low powered machine. Hence, we’re aiming to get a representation of a machine independent value.

As per the start of this post “The time complexity of a function (or algorithm) can be seen as the relative time the function takes to process data of different sizes.
“.

So, one must also remember that we’re measuring how an algorithm’s time complexity changes as data of different sizes are applied to it. But there’s a catch to this. What if we have function countA and it does the count process as implemented above but has a sleep, or some other constant time delay and compare it’s time complexity against the same function countB without this constant time delay, the Big O will essentially be the same, but obviously measured performance on the same machine will differ (potentially vastly differ).

For example, if our implementations looked like this

func countA(array: [Int]) -> Int {
  var c = 0            // O(1)  
  for item in array {  // O(n)
    c += 1             // O(1) 
  }
  return c             // O(1)
}

func countB(array: [Int]) -> Int {
  var c = 0            // O(1)  
  for item in 1..1000 { // O(1)
  }
  for item in array {  // O(n)
    c += 1             // O(1) 
  }
  return c             // O(1)
}

So, in countB we essentially have a constant time loop but it’s obvious from these two functions that countA will be more performant than countB but both will have the same O(n) time complexity.

So how do we deal with this difference?

In the response to that question, at this time, I’m not wholly certain. I need to research this further – but what’s obvious here is that the two algorithms will both be O(n) regarding the changes in the amount of data but not in a true comparison with one another as it’s obvious that countB will always be slower than countA.

The Vending Machine Change problem

I was reading about the “Vending Machine Change” problem the other day. This is a well known problem, which I’m afraid to admit I had never heard of, but I thought it was interesting enough to take a look at now.

Basically the problem that we’re trying to solve is – you need to write the software to calculate the minimum number of coins required to return an amount of change to the user. In other words if a vending machine had the coins 1, 2, 5 & 10, what is the minimum number of coins required to make up the change of 43 pence (or whatever units of currency you want to use).

The coin denominations should be supplied, so the algorithm is not specific to the UK or any other country and the amount in change should also be supplied to the algorithm.

First Look

This is a standard solution to the Vending Machine problem (please note: this code is all over the internet in various languages, I’ve just made a couple of unimpressive changes to it)

static int Calculate(int[] coins, int change)
{
   int[] counts = new int[change + 1];
   counts[0] = 0;

   for(int i = 1; i <= change; i++)
   {
      int count = int.MaxValue;
      foreach(int coin in coins)
      {
         int total = i - coin;
         if(total >= 0 && count > counts[total])
         {
            count = counts[total];
         }
      }
      counts[i] = (count < int.MaxValue) ? count + 1 : int.MaxValue;
   }
   return counts[change];
}

What happens in this code is that we create an array counts which will contains the minimum number of coins for each value between 1 and the amount of change required. We use the 0 index as a counter start value (hence we set counts[0] to 0).

Next we look through each possible change value calculating the number of coins required to make up each value, we use the int.MaxValue to indicate no coins could be found to match the amount of change.

This algorithm assumes an infinite number of coins of each denomination, but this is obviously an unrealistic scenario, so let’s have a look at my attempt to solve this problem for a finite number of each coin.

Let’s try to make things a little more complex

So, as mentioned above, I want to now try to calculate the minimum number of coins to produce the amount of change required, where the number of coins of each denomination is finite.

Let’s start by defining a Coin class

public class Coin
{
   public Coin(int denomition, int count)
   {
      Denomination = denomition;
      Count = count;
   }

   public int Denomination { get; set; }
   public int Count { get; set; }
}

Before I introduce my attempt at a solution, let’s write some tests, I’ve not got great names for many of the tests, they’re mainly for me to try different test scenarios out, but I’m sure you get the idea.

public class VendingMachineTests
{
   private bool Expects(IList<Coin> coins, int denomination, int count)
   {
      Coin c = coins.FirstOrDefault(x => x.Denomination == denomination);
      return c == null ? false : c.Count == count;
   }

   [Fact]
   public void Test1()
   {
      List<Coin> coins = new List<Coin>
      {
         new Coin(10, 100),
         new Coin(5, 100),
         new Coin(2, 100),
         new Coin(1, 100),
      };

      IList<Coin> results = VendingMachine.Calculate(coins, 15);
      Assert.Equal(2, results.Count);
      Assert.True(Expects(results, 10, 1));
      Assert.True(Expects(results, 5, 1));
   }

   [Fact]
   public void Test2()
   {
      List<Coin> coins = new List<Coin>
      {
         new Coin(10, 100),
         new Coin(5, 100),
         new Coin(2, 100),
         new Coin(1, 100),
      };

      IList<Coin> results = VendingMachine.Calculate(coins, 1);
      Assert.Equal(1, results.Count);
      Assert.True(Expects(results, 1, 1));
   }

   [Fact]
   public void Test3()
   {
      List<Coin> coins = new List<Coin>
      {
         new Coin(10, 1),
         new Coin(5, 1),
         new Coin(2, 100),
         new Coin(1, 100),
      };

      IList<Coin> results = VendingMachine.Calculate(coins, 20);
      Assert.Equal(4, results.Count);
      Assert.True(Expects(results, 10, 1));
      Assert.True(Expects(results, 5, 1));
      Assert.True(Expects(results, 2, 2));
      Assert.True(Expects(results, 1, 1));
   }

   [Fact]
   public void NoMatchDueToNoCoins()
   {
      List<Coin> coins = new List<Coin>
      {
         new Coin(10, 0),
         new Coin(5, 0),
         new Coin(2, 0),
         new Coin(1, 0),
      };

      Assert.Null(VendingMachine.Calculate(coins, 20));
   }

   [Fact]
   public void NoMatchDueToNotEnoughCoins()
   {
      List<Coin> coins = new List<Coin>
      {
         new Coin(10, 5),
         new Coin(5, 0),
         new Coin(2, 0),
         new Coin(1, 0),
      };

      Assert.Null(VendingMachine.Calculate(coins, 100));
   }

   [Fact]
   public void Test4()
   {
      List<Coin> coins = new List<Coin>
      {
         new Coin(10, 1),
         new Coin(5, 1),
         new Coin(2, 100),
         new Coin(1, 100),
      };

      IList<Coin> results = VendingMachine.Calculate(coins, 3);
      Assert.Equal(2, results.Count);
      Assert.True(Expects(results, 2, 1));
      Assert.True(Expects(results, 1, 1));
   }

   [Fact]
   public void Test5()
   {
      List<Coin> coins = new List<Coin>
      {
         new Coin(10, 0),
         new Coin(5, 0),
         new Coin(2, 0),
         new Coin(1, 100),
      };

      IList<Coin> results = VendingMachine.Calculate(coins, 34);
      Assert.Equal(1, results.Count);
      Assert.True(Expects(results, 1, 34));
   }

   [Fact]
   public void Test6()
   {
      List<Coin> coins = new List<Coin>
      {
         new Coin(50, 2),
         new Coin(20, 1),
         new Coin(10, 4),
         new Coin(1, int.MaxValue),
      };

      IList<Coin> results = VendingMachine.Calculate(coins, 98);
      Assert.Equal(4, results.Count);
      Assert.True(Expects(results, 50, 1));
      Assert.True(Expects(results, 20, 1));
      Assert.True(Expects(results, 10, 2));
      Assert.True(Expects(results, 1, 8));
   }

   [Fact]
   public void Test7()
   {
      List<Coin> coins = new List<Coin>
      {
         new Coin(50, 1),
         new Coin(20, 2),
         new Coin(15, 1),
         new Coin(10, 1),
         new Coin(1, 8),
      };

      IList<Coin> results = VendingMachine.Calculate(coins, 98);
      Assert.Equal(3, results.Count);
      Assert.True(Expects(results, 50, 1));
      Assert.True(Expects(results, 20, 2));
      Assert.True(Expects(results, 1, 8));
   }
}

Now, here’s the code for my attempt at solving this problem, it uses a “Greedy” algorithm, i.e. trying to find the largest coin(s) first. The code therefore requires that the coins are sorted largest to smallest, I have not put the sort within the Calculate method because it’s recursively called, so it’s down to the calling code to handle this.

There may well be a better way to implement this algorithm, obviously one might prefer to remove the recursion (I may revisit the code when I have time to implement that change).

public static class VendingMachine
{
   public static IList<Coin> Calculate(IList<Coin> coins, int change, int start = 0)
   {
      for (int i = start; i < coins.Count; i++)
      {
         Coin coin = coins[i];
         // no point calculating anything if no coins exist or the 
         // current denomination is too high
         if (coin.Count > 0 && coin.Denomination <= change)
         {
            int remainder = change % coin.Denomination;
            if (remainder < change)
            {
               int howMany = Math.Min(coin.Count, 
                   (change - remainder) / coin.Denomination);

               List<Coin> matches = new List<Coin>();
               matches.Add(new Coin(coin.Denomination, howMany));

               int amount = howMany * coin.Denomination;
               int changeLeft = change - amount;
               if (changeLeft == 0)
               {
                   return matches;
               }

               IList<Coin> subCalc = Calculate(coins, changeLeft, i + 1);
               if (subCalc != null)
               {
                  matches.AddRange(subCalc);
                  return matches;
               }
            }
         }
      }
      return null;
   }
}

Issues with this solution

Whilst this solution does the job pretty well, it’s not perfect. If we had the coins, 50, 20, 11, 10 and 1 the optimal minimum number of coins to find the change for 33 would be 3 * 11 coins. But in the algorithm listed above, the result would be 1 * 20, 1 * 11 and then 2 * 1 coins.

Ofcourse the above example assumed we have 3 of the 11 unit coins in the Vending machine

To solve this we could call the same algorithm but for each call we remove the largest coin type each time. Let’s look at this by first adding the unit test

[Fact]
public void Test8()
{
   List<Coin> coins = new List<Coin>
   {
      new Coin(50, 1),
      new Coin(20, 2),
      new Coin(11, 3),
      new Coin(10, 1),
      new Coin(1, 8),
   };

   IList<Coin> results = VendingMachine.CalculateMinimum(coins, 33);
   Assert.Equal(1, results.Count);
   Assert.True(Expects(results, 11, 3));
}

To just transition the previous solution to the new improved solution, we’ve simply added a new method named CalculateMinimum. The purpose of this method is to try and find the best solution by calculate with all coins, then we reduce the types of available coins by remove the largest coin, then find the best solution, then remove the next largest coin and so on. Here’s some code which might better demonstrate this

public static IList<Coin> CalculateMinimum(IList<Coin> coins, int change)
{
   // used to store the minimum matches
   IList<Coin> minimalMatch = null;
   int minimalCount = -1;

   IList<Coin> subset = coins;
   for (int i = 0; i < coins.Count; i++)
   {
      IList<Coin> matches = Calculate(subset, change);
      if (matches != null)
      {
         int matchCount = matches.Sum(c => c.Count);
         if (minimalMatch == null || matchCount < minimalCount)
         {
            minimalMatch = matches;
            minimalCount = matchCount;
         }
      }
      // reduce the list of possible coins
      subset = subset.Skip(1).ToList();
   }

   return minimalMatch;
}

Performance wise, this (in conjunction with the Calculate method) are sub-optimal if we needed to calculate such minimum numbers of coins many times in a short period of time. A lack of state means we may end up calculating the same change multiple times. Ofcourse we might save such previous calculations and first check whether the algorithm already has a valid solution each time we calculate change if performance was a concern and/or me might calculate a “standard” set of results at start up. For example if we’re selling can’s of drinks for 80 pence we could pre-calculate change based upon likely inputs to the vending machine, i.e. 90p, £1 or £2 coins.

On and we might prefer to remove to use of the Linq code such as Skip and ToList to better utilise memory etc.

References

http://onestopinterviewprep.blogspot.co.uk/2014/03/vending-machine-problem-dynamic.html
http://codercareer.blogspot.co.uk/2011/12/no-26-minimal-number-of-coins-for.html
http://techieme.in/techieme/minimum-number-of-coins/
http://www.careercup.com/question?id=15139685