Big O Notation (OCR A Level Computer Science)

Revision Note

Last updated

Big O Notation

What is Big O Notation?

  • Big O Notation is used to express the complexity of an algorithm

  • Describes the scalability of an algorithm as it's order (O), referred to in Computer Science as Big O Notation

  • Named after the German mathematician Edmund Landau 

  • Has two simple rules

    • Remove all terms except the one with the largest factor or exponent

    • Remove any constants

  • There are several classifications used in Big O Notation, ranging from fast and efficient to slow and inefficient. These are all covered below

Constant

  • O(1), read as Big-O One, describes an algorithm that takes constant time, that is, requires the same number of instructions to be executed, regardless of the size of the input data

  • For example, to calculate the length of a string ‘s’ the following code could be used:

    • totalLength = len(s)

  • No matter how long the string is, it will always take the same number of instructions to find the length

Linear

  • O(n), read as Big-O n, describes an algorithm that takes linear time, that is, as the input size increases, the number of instructions to be executed grows proportionally

  • For example, a linear search of 1000 items will take 10 times longer to search than an array of 100 items

  • Implementing linear time requires use of iteration i.e. for loops and while loops

  • In the example of sumNumbers1, a basic (for i = 1 to n) loop is used

  • Below is an example of an algorithm that uses multiple for loops

function loop

x = input
for i = 1 to x
	print(x)
next i

a = 0
for j = 1 to x
	a = a + 1
next j
print(a)

b = 1
for k = 1 to x
	b = b + 1
next k
print(b)

endfunction
  • When the user inputs a value of x, each loop will run x times, performing varied instructions in each loop

  • The total number of instructions executed in this algorithm are as follows:

    • +1 for (x = input)

    • +x for the first for loop

    • +1 for (a = 0)

    • +x for the second for loop

    • +1 for (output a)

    • +1 for (b =1)

    • +x for the third for loop

    • +1 for (output b)

  • The total number of instructions executed would therefore be: 3x + 5

  • As x increases the total number of instructions executed also increases as shown in the table below

Linear Time: Number of Instructions Executed

x

3x

5

3x + 5

1

3

5

8

10

30

5

35

100

300

5

305

10,000

30,000

5

30,005

1,000,000

3,000,000

5

3,000,005

  • The constant term (5) contributes less and less to the overall time complexity as x increases. 3x is the significant contributing term and causes the overall time complexity to grow linearly in a straight line as x increases

Polynomial

  • O(n2), read as Big-O n-squared, describes an algorithm that takes squared time to complete. This means the performance is proportional to the square of the number of data inputs

  • For example, a bubble sort is a polynomial time complexity algorithm. n passes are made, and for each pass n comparisons are made hence n*n or O(n2)

  • Implementing polynomial time requires the use of nested iteration i.e. for loops or while loops, where each loop executes n times

  • An example of a polynomial algorithm is shown below:

function timesTable

x = 12
for i = 1 to x
	for j = 1 to x
		print(i, “ x “, j, “ = “, i*j)
	next j
next i

for k = 1 to x
	print(k)
next k
print(“Hello World!”)

endfunction
  • The above algorithm outputs the times tables from the 1 times table to the 12 times table. An arbitrary for loop (for k = 1 to 10) and output (print(“Hello World!”)) has been added for demonstration purposes as shown below

  • The total number of instructions executed by this algorithm are as follows:

    • +1 for (x = 12)

    • +x * x for the nested for loop

    • +x for the non-nested for loop

    • +1 for (print(“Hello World!”))

  • The total number of instructions executed would therefore be: x*x + x + 2 or, x2 + x + 2

  • As x increases the total number of instructions executed also increases as shown in the table below

Polynomial Time: Number of Instructions Executed

x

x2

2

x2 + x + 2

1

1

2

4

10

100

2

112

100

10,000

2

10,102

10,000

100,000,000

2

100,010,002

1,000,000

1,000,000,000,000

2

1,000,001,000,002

  • The constant term (2) and linear term (x) contribute very little as x increases, making x2 the significant contributing term

    • To put this algorithm into perspective, assuming it took one second per instruction, printing up to the 1,000,000 times table would take roughly 32,000 years

  • Loops can be nested any number of times. In the example above, there are two loops nested. Each loop nested increases the polynomial time as shown below. Regardless of the number of loops nested, all are still referred to as polynomial time!

Big-O of Nested Loops

Number of loops nested

Big-O time

2

O(n2)

3

O(n3)

4

O(n4)

5

O(n5)

6

O(n6)

  • Below is an example of a six nested algorithm. Note that the algorithm does nothing useful except demonstrate this nesting

for a = 1 to n
	for b = 1 to n
		for c = 1 to n
			for d = 1 to n
				for e = 1 to n
					for f = 1 to n
					next f
				next e
			next d
		next c
	next b
next a

Exponential

  • Exponential time, read as Big-O 2-to-the-n, describes an algorithm that takes double the time to execute for each additional data input value. The number of instructions executed becomes very large, very quickly for each input value

  • Few everyday algorithms use exponential time due to its sheer inefficiency. Some problems however can only be solved in exponential time

Exponential time: Number of Instructions Executed

x

2x

1

2

10

1024

100

1,267,650,600,228,229,401,496,703,205,376

Examiner Tips and Tricks

You do not need to know any specific examples of exponential time complexity algorithms, but you do need to know how these algorithms compare to other time complexities.

Logarithmic

  • Logarithmic time, read as Big-O log-n, describes an algorithm that takes very little time as the size of the input grows larger

  • Logarithmic time in computer science is usually measured in base 2. A logarithm is the value the base must be raised to to equal another number, for example:

    • 23 = 8, log2(8) = 3, i.e. to get to the value 8, 2 must be raised to the power of 3

  • A binary search is an example of logarithmic time complexity. Doubling the size of the input data has very little effect on the overall number of instructions executed by the algorithm, as shown below:

Logarithmic Time: Number of Instructions Executed

x

log2(x)

1 (20)

0

8 (23)

3

1,024 (210)

10

1,048,576 (220)

20

1,073,741,824 (230)

30

  • As shown above, increasing the number of input values to over one billion did very little to the number of instructions taken to complete the algorithm

Comparison of Complexity

  • All algorithms have a best case, average case and worst case complexities.

  • These are defined as the best possible outcome, the average outcome and the worst possible outcome in terms of the number of instructions required to complete the algorithm

  • Best case complexity is where the algorithm performs most efficiently such as a linear search or binary search finding the correct item on the first element O(1), or a bubble sort being supplied with an already sorted list O(n). A bubble sort must always make at least one pass, hence O(n)

  • Average case complexity is where the algorithm performs neither at its best or worst on any given data. A linear search may find the item in the middle of the list and therefore have a Big-O notation of O(n/2), or a bubble sort sorting only half a list O(n2 / 2)

  • Worst case complexity is where an algorithm is at its least efficient such as a linear search finding the item in the last position, or not at all O(n), or a bubble sort sorting a reversed list O(n2), i.e. a list whose items are ordered highest to lowest

  • Big-O notation is almost always measured by the worst case performance of an algorithm. This allows programmers to accurately select appropriate algorithms for problems and have a guaranteed minimum performance level

  • The graphical representation of constant, linear, polynomial, exponential and logarithmic times is shown below

Big-O Time Complexity Graph

figure-1--graph-of-big-o-notation-functions-revision-notes-computer-science

Figure 1: Graph of Time Complexity for Big-O Notation Functions

  • From the graph, exponential time (2n) grows the quickest and therefore is the most inefficient form of algorithm, followed by polynomial time (n2), linear time (n), logarithmic time (logn) and finally constant time (1)

  • Further time complexities exist such as factorial time (n!) which performs worse than exponential time and linear-logarithmic time (nlogn) which sits between polynomial and linear time. Merge sort is an example of an algorithm that uses O(nlogn) time complexity

  • The Big-O time complexity of an algorithm can be derived by inspecting its component parts

  • It is important to know that when determining an algorithm's Big-O that only the dominant factor is used.

  • This means the time complexity that contributes the most to the algorithm is used to describe the algorithm. For example:

    • 3x2 + 4x + 5, which describes three nested loops, four standard loops and five statements is described as O(n2)

    • x2 is the dominating factor. As the input size grows x2 contributes proportionally more than the other factors

    • Coefficients are also removed from the Big-O notation. The 3 in 3x2 acts as a multiplicative value whose contribution becomes smaller and smaller as the size of the input increases.

      • For an arbitrarily large value such as 10100, which is 10 with one hundred 0’s after it, multiplying this number by 3 will have little contribution to the overall time complexity. 3 is therefore dropped from the overall time complexity

Examiner Tips and Tricks

  • The most likely type of Big-O calculation question you will face in an exam is deriving a constant, linear or polynomial time complexity

  • A very challenging question may ask to derive a logarithmic time complexity which will require familiarity with logarithmic algorithms from your course to recognise them

  • The simplest way of deriving time complexity is to count the number of loops and nested loops in an algorithm.

function sumNumbers1(n)
	sum = 0
	for i = 1 to n
		sum = sum + n
	next i
	return sum
endfunction
  • In the example above for sumNumbers1, a single for loop is present. As loops are O(n) and single statements are O(1), the overall Big-O notation is O(n), linear time, as the loop dominates constant time

function sumNumbers2(n)
	sum = n * (n+1)/2
	return sum
endfunction
  • In the example above for sumNumbers2, a single statement is present with no loops. Statements are O(1) therefore making the overall Big-O notation O(1), constant time

function loop

x = input
for i = 1 to x
	print(x)
next i

a = 0
for j = 1 to x
	a = a + 1
next j
print(a)

b = 1
for k = 1 to x
	b = b + 1
next k
print(b)

endfunction
  • In the example above for function loop, there are three loops present 3*O(n) and five statements 5*O(1). As the input size grows, the quantity of loops or statements becomes negligible, meaning coefficients eventually become factored out or removed. This leaves O(n) and O(1). As O(n) dominates O(1), the overall Big-O notation of this algorithm becomes O(n), linear time

function timesTable

x = 12
for i = 1 to x
	for j = 1 to x
		print(i, “ x “, j, “ = “, i*j)
	next j
next i

for k = 1 to x
	print(k)
next k
print(“Hello World!”)

endfunction
  • In the example above for function timesTable, there is a single nesting of two loops O(n2), a single loop O(n) and a single statement O(1). As polynomial time dominates linear and constant time and that no coefficients are involved, the overall Big-O notation is O(n2)

Worked Example

Dexter is leading a programming team who are creating a computer program that will simulate an accident and emergency room to train hospital staff.

Two of Dexter's programmers have developed different solutions to one part of the problem. Table 3.1 shows the Big O time complexity for each solution, where n = the number of data items.

 

Solution A

Solution B

Time

O(n)

O(n)

Space

O(kn) (where k > 1)

O(log n)

Table 3.1

  1. Explain, with reference to the Big O complexities of each solution, which solution you would suggest Dexter chooses.
    [4]

I would recommend solution B [1]. As both have the same time complexity we would need to look at both algorithms' space complexity [1]. A’s space does not scale well [1] when the number of items is increased whereas B’s space does scale well [1]

Remember to relate your answer to the context of the question to achieve marks.

You've read 0 of your 5 free revision notes this week

Sign up now. It’s free!

Join the 100,000+ Students that ❤️ Save My Exams

the (exam) results speak for themselves:

Did this page help you?