I was reading a set of posts on reddit about functional programming. At some point in the thread, the topic went to order of algorithms. Someone commented that he had implemented something in an order n-squared since he was only using a few elements in the list. He said that since no one noticed, it didn’t matter. It was very frustrating to read that.
I wrote about this in my blog when I worked at Adobe and will write a bit about it now. In all of my travels as a specialist in code optimization, the thing I see the most is people implementing code in O(n^2) that could be written in linear or constant time. I know Netscape had a few of them when I was working on that project. Since it is so pervasive, let me give some concrete examples of why/when/how to look for this.
Let’s create a table with elements being the number of elements in question, linear and n-squared being the time it would take to processes that number of elements with that algorithm:
| Elements |
Linear |
n-squared |
| 1 |
1 |
1 |
| 10 |
10 |
100 |
| 100 |
100 |
10,000 |
| 1,000 |
1,000 |
1,000,000 |
See, it grows pretty fast! If your n is 1, 10, or even 100, no one will notice a difference in the time. But, if the number of elements goes a just a bit more, suddenly the time take will be noticeable. But, there is a catch, sometimes it pays to use n-squared to much higher ranges. The actual order is M*O(n) + K for linear, and M*O(n^2) + K for n-squared, with M and K different for each. So, let us create a new table: 1000 * n + 1000 will be the linear, and 1 * n^2 + 1 will be the n-squared:
| Elements |
Linear |
n-squared |
| 1 |
2000 |
2 |
| 10 |
11,000 |
101 |
| 100 |
101,000 |
10,001 |
| 1,000 |
1,001,000 |
1,000,001 |
In this case, the linear takes more than a thousand elements before it is better than the n-squared algorithm. M represents something that happens with each operation, like a data copy, while the K represents things like setup time, pre-calculating, etc.
So, the important thing to remember, algorithm selection depends on the dataset it will be used on. In most cases, the number of elements will grow to be a lot larger than initially expect, so it almost always pays to switch to a linear algorithm if you can find one. There are rare cases where the number of elements, the setup cost, and per operation cost of the linear doesn’t make it worth using, so you have to be aware of that. This is only considering the speed cost of an algorithm, there is also the same set of arguments for memory cost.
I’ll leave with this: at one point Netscape’s browser used linked lists with no tail pointer (and it still might), which means the operation to build a linked list would be n-squared. This saved a 4-byte pointer, and if the lists were small enough, there was no impact on performance. The easiest way to find n-squared is feed in a thousand or even ten thousand elements and see where the code “freezes” the machine. Creating a website with a thousand elements would create a thousand link list elements, which would cause a million operations, which would then take many seconds to display while the user is waiting. Switching it over to linear would have meant that one could create a page with a million elements before seeing this kind of slowdown, and the thousand element page would have been instantaneous from the users perspective.
-Edward