Algorithmic Efficiency and Heuristics: Reasonable and Unreasonable Time
Efficiency is how much computing work an algorithm needs, and it decides whether a program answers in the blink of an eye or would need longer than the age of the universe. Two correct algorithms for the same job can sit at opposite ends of that range.
What is the difference between a problem and an instance?
A problem is a general description of a task that may or may not be solvable with an algorithm: "sort a list of numbers" or "find the shortest route between two places". An instance of a problem includes specific input: sorting the list [29, 4, 17, 8], or finding the shortest route from your house to school on today's map.
Problems also come in two kinds that the exam names:
Here is one decision question and one optimization question about the same data:
Is there a route under 6 km? True Shortest route: River Rd 5.9 km
How do you measure an algorithm's efficiency?
Efficiency is an estimate of the computational resources an algorithm uses, such as time or memory. Since a bigger input means more work, efficiency is usually described in terms of the size of the input: "for a list of n names, this algorithm does about n checks".
Computer scientists determine an algorithm's efficiency through formal, mathematical reasoning. You can also measure it informally by counting how many times a statement or group of statements runs. The program below counts the steps of three algorithms: one loop over n items, a loop inside a loop, and a loop over every possible group (subset) of the n items, where each item is either in or out of the group.
n one loop nested loops every subset 4 4 16 16 8 8 64 256 12 12 144 4096 16 16 256 65536
Each time n goes up by 4, the one-loop count goes up by 4, the nested-loop count grows a few times over, and the every-subset count is multiplied by 16. Adding one item doubles the number of subsets.
What makes a running time reasonable?
Algorithms whose step counts grow like a polynomial or slower are said to run in a reasonable amount of time. That includes constant (the same number of steps for any n), linear (n steps), square (n times n) and cube (n times n times n). Algorithms with exponential growth (like 2 to the power n) or factorial growth (n times n - 1 times n - 2, down to 1) are examples of algorithms that run in an unreasonable amount of time.
The difference is not about today's computers being slow. Here is how long each growth rate takes on a computer doing a billion steps every second:
n = 10: n squared under a second, 2 to the n under a second, n! under a second n = 20: n squared under a second, 2 to the n under a second, n! 77 years n = 40: n squared under a second, 2 to the n 0.0 days, n! over a trillion years n = 60: n squared under a second, 2 to the n 37 years, n! over a trillion years n = 80: n squared under a second, 2 to the n 38,334,786 years, n! over a trillion years
The universe is about 14 billion years old. A computer a thousand times faster only moves an exponential algorithm about 10 items further before it hits the same wall, because 2 to the power 10 is about 1,000.
Different correct algorithms for the same problem can have very different efficiencies. Both functions below decide whether a list of 1,000 student IDs contains a duplicate. The first compares every pair of IDs. The second walks the list once and remembers the IDs it has already seen:
Compare every pair: (False, 499500) Remember what was seen: (False, 1000)
Same answer, but 499,500 checks against 1,000. Both are still reasonable (square and linear), so for 1,000 IDs either one finishes quickly; for a million IDs the gap becomes hours against a moment.
When is a heuristic the smart choice?
Some problems cannot be solved in a reasonable amount of time because there is no efficient algorithm for them. For those, people look for an approximate solution: a good answer, found quickly, instead of the perfect answer, found never.
A famous example is planning a delivery route: visit every stop once and come back home, driving the shortest total distance. Nobody has found an efficient algorithm that always gives the shortest route. The sure way is to try every order of the stops, and the number of orders grows factorially: 6 stops have 720 orders, 10 stops have 3,628,800, and 20 stops have more than 2 billion billion.
A heuristic is an approach that produces a solution that is not guaranteed to be the best, used when a technique that always finds the best solution is impractical. One simple heuristic for routes is nearest neighbor: from wherever you are, drive to the closest stop you have not visited yet. First, the sure way, checking every order of 6 stops (distances in km):
Checked 720 routes Shortest: Home -> Library -> School -> Museum -> Bakery -> Pool -> Park -> Home (27.1 km)
Now the heuristic. It never compares whole routes: at each stop it looks only at the distances to the stops still left. This box uses stops and route_length from the box above, so run that one first.
Nearest neighbor: Home -> Library -> School -> Bakery -> Museum -> Park -> Pool -> Home (33.5 km) That is 24% longer than the best route
The nearest-neighbor route is longer than the best one, because one greedy choice near the start leaves an expensive trip back at the end. But the heuristic did 21 distance comparisons in total (6 + 5 + 4 + 3 + 2 + 1), while the sure way built 720 complete routes. With 60 stops, the sure way would need more routes than there are atoms in the Earth; nearest neighbor would need fewer than 2,000 comparisons. A route that is a bit longer, delivered today, beats a perfect route delivered never.
Specific heuristics like nearest neighbor are not on the exam. What the exam asks is when a heuristic is the right call: when finding the guaranteed best answer would take an unreasonable amount of time.
Check your understanding
Which of the following is an instance of a problem, rather than a general problem?
Which of the following is an optimization problem?
Each option describes how many steps an algorithm takes for an input of n items. Which algorithms run in a reasonable amount of time? Select two answers.
Select 2 answers.
How many times does the statement count ← count + 1 run in this code segment?
count ← 0REPEAT 5 TIMES{REPEAT 4 TIMES{count ← count + 1}count ← count + 1}DISPLAY(count)
The procedure below is called with a list of n numbers. How many times does total ← total + 1 run?
PROCEDURE countPairs(numbers){total ← 0FOR EACH a IN numbers{FOR EACH b IN numbers{total ← total + 1}}RETURN(total)}
An algorithm finds the best team by checking every possible group of members in a club of n members. One more person joins the club. What happens to the number of groups the algorithm checks?
A delivery company plans a route through 60 stops each morning. Checking every possible order of the stops would take far too long. Which approach is most appropriate?
Which of the following best describes a heuristic?
Practice exercises
Write count_routes(stops) that returns how many different orders there are to visit stops places. The first place can be any of the stops, the second any of the remaining stops - 1, and so on, so the answer is stops x (stops - 1) x ... x 1. Use a loop.
count_routes(3) -> 6
count_routes(5) -> 120The "compare every pair" duplicate check from the lesson runs this loop over a list of n items:
for i in range(n):
for j in range(i + 1, n):
checks += 1Write pair_checks(n) that returns how many times checks += 1 runs. You can run the loop and count, or find a pattern.
pair_checks(4) -> 6
pair_checks(10) -> 45Write nearest_neighbor_route(stops). stops is a dictionary from a place name to its (x, y) position, and the first key is the starting point. Build a route with the nearest-neighbor heuristic: from the current place, go to the closest place not visited yet, until every place is visited, then return to the start.
Return the route as a list of names, starting and ending with the first key. If two places are equally close, choose the one that comes first in the dictionary. Use math.dist(a, b) for the distance between two positions.
nearest_neighbor_route({"A": (0, 0), "B": (5, 0), "C": (1, 0)})
-> ['A', 'C', 'B', 'A']Exam topics: 3.17 Algorithmic Efficiency (AAP-4.A)