Heap Sort - Peter Klein

Klein 1
Peter D. Klein
Research Project for Algorithms CSC320
Professor Shana Watters
Spring 2016
Sorting Algorithm’s in Java
Introduction
Algorithms have existed far longer than the creation of the first computer. Algorithms are
the backbone of searching, sorting or solving any problem and are used subconsciously every
day. We as humans sort clothes, food, cars and countless other things in our daily lives, so it is
important for us to understand the process it takes to perform these sorting algorithms.
Algorithms are a necessity in the field of computing. They give step by step solutions in a finite
amount of time to specific functions. Sorting algorithms are especially useful in computing
because they give order to data or databases, which guide readability and further computation.
All algorithms are based on certain criteria which judge their power. One of these major criteria
is the algorithms efficiency. Efficiency is based on the algorithms process, as well as the state of
data it is sorting. These factors make some algorithms more efficient than others in certain
situations. This project will test the growth of six sorting algorithms based on the order of the
data, to analyze its efficiency.
Algorithm and hypothesis discussion
Bubble Sort
Bubble sort is a sort that contains a for loop embedded in a for loop. It looks at a list’s
last position and compares it to the position one lower to see which one is a smaller value.
Bubble sort takes the smaller value and puts it in the lower position of the two. Bubble sort then
looks at the second to last position and compares it to the position one lower. It does the same
Klein 2
process of comparing and swapping. It continues that process until it eventually “bubbles down”
the lowest value in the list to the first position. The outer loop then starts this whole process over
again, except since we know the lowest value is in the first position, we check every position
except for the first. This whole process is continued until our list is sorted.
Bubble sort is always going to have a run time of O(𝑛2 ) due to its embedded for loop,
seen below in its pseudocode. The first for loop will be entered 𝑛 number of times, 𝑛 being the
number of elements in the array; and its embedded loop will be entered n-1 number of times. It
will always compare the elements but the swapping does not . It does not matter the order of the
elements, therefore it will always run in a O(𝑛2 ).
Taking the Big O into account, I hypothesize that bubble sort’s run time will grow
exponentially as more elements are sorted.
Selection Sort
Selection sort is an algorithm that has a for loop embedded in a for loop. Starting at the
lists first position, selection sort runs through the entire list and finds the smallest value. Once it
is found, it puts that value in the lists first position and repeats the process starting at the second
position in the list. It then continues this until the whole list is sorted.
Selection sort is similar to bubble sort, as it will also always have a run time of O(𝑛2 )
because of its embedded for loop. As shown in the pseudocode below, the loops run on order 𝑛,
𝑛 being the number of elements in the array. Both loops will always be entered no matter the
Klein 3
arrays condition and the swapping will always have a constant time. Because of this, the runtime
will always be O(𝑛2 ). It does not matter the order of the elements.
Taking the Big O into account, I hypothesizes that selection sort’s run time will grow
exponentially as more elements are sorted.
SELECTION-SORT(A)
1 for i = 1 to n-1
2
currentSmallest = A[i]
3
for j = i to n
4
If A[j] < currentSmallest
5
A[j] = currentSmallest
6
exchange A[i] with currentSmallest
Insertion Sort
Insertion sort is a sort that contains two sub lists that make up a greater list, one list is
sorted and the other is not. Insertion sort starts by taking the left most element of a list and
considers that one element to be sorted. It then checks the second element of the list and
compares it to the first element to see if it is larger or smaller. If it is larger, it stays in the same
spot If it’s smaller it goes behind and becomes the first element. It then checks the third element
and compares it to the first two, placing it in its properly sorted position. It continues this with
the whole list until it is sorted.
Insertion sort has a worst case scenario of O(𝑛2 ). This occurs when the list is sorted in
descending order because we must compare and shift each element behind the last. As shown in
the pseudocode below, the worst case will cause the embedded while loop to be entered during
every run through of the outer for loop. Since the both loop runs on order 𝑛, 𝑛 being the number
of elements in the array, its worst case will be O(𝑛2 ). It has a best case of O(𝑛) when the list is
Klein 4
sorted in ascending order because it will never enter its embedded loop, so no swapping and
shifting is needed.
Taking the Big O into account, I hypothesize that insertion sort’s run time will grow
exponentially in its worst case, and linear in its best case.
Heap Sort
Heap sort is an algorithm that uses a tree structure to sort elements of a list. The first
thing heap sort does is build a max heap out of a list. A max heap is a tree the satisfies these
properties: all children nodes must have a lesser value than that of its parent; the tree must be
filled left to right, top to bottom; each parent can have 0 to 2 children. Once the max heap has
been built, the largest element will have been placed at the top of the tree. This is now the
“heap.” The top element is then swapped with the last element in the tree. Then using maxheapify is built since the tree is now not a max heap, but heap sort does not check the last
element because it is in its proper position. Once swapped, the “heap” is now one element
smaller. Every time it repeats this process the largest element will be placed at the top of the tree,
then it will be swapped with the heaps last position. This process is repeated until the list is
properly sorted.
Heap sort has a best and worst case scenario of 𝑂(𝑛 log 𝑛), so asymptotically is does not
matter the order of the elements in the list. The reason it runs in 𝑂(𝑛 log 𝑛) is due to the amount
Klein 5
of times the algorithm will max-heapify the list. As seen in the pseudocode below, the original
“heapsort” and “build max heap” have non-embedded for loops that run on order 𝑛, 𝑛 being the
number of elements in the array.
Max-heapify will recursively call itself only if certain conditions are met. When run on a
tree 𝐴, of size 𝑛, rooted at 𝑖, max-heapify has a run time of 𝜃(1). It has this run time to sort 𝐴[𝑖],
𝐴[𝑅𝐼𝐺𝐻𝑇(𝑖)], 𝑎𝑛𝑑 𝐴[𝐿𝐸𝐹𝑇(𝑖)], to meet the max heap properties. It must also run max-heapify
on the subtree rooted at the child nodes of 𝐴[𝑖]. Each subtree has a worst case size of 2𝑛/3,
which occurs when the bottom level of the tree is half full. Thus, Max-heapify’s running time
can be described as:
𝑇(𝑛) ≤ 𝑇 (
2𝑛
) + 𝜃(1)
3
By case 2 of the master theorem, it can be determined 𝑇(𝑛) = 𝑂(𝑙𝑔𝑛). The run time of
Max-heapify, combined with the linear times discussed earlier, give the total run time of heap
sort to be 𝑂(𝑛 log 𝑛).
Taking the Big O into account, I hypothesize that heap sort’s run time will grow
logarithmically as more elements are sorted.
Klein 6
Quick Sort
Quick sort is a sort that uses pointers and swapping to sort elements. The algorithm starts
by taking the last element of the array and naming it the pivot, then it takes the first element and
names it the wall. Quick sort goes through each element in the list and compares it to the pivot, if
the element is larger, it does nothing, but if it’s smaller, it takes that element and swaps it with
the wall, then moves the wall up one position. By doing this, everything to the left of the wall is
smaller than the pivot, and everything right of the wall is larger. It then takes the pivot and swaps
it with the wall, and now the pivot is in its proper location. Quick sort then shifts the wall up one
so it’s right of the previous pivot, and performs the previous process on the two sub lists divided
by the previous pivot, creating two new walls and pivots. It continues this until the list is
properly sorted.
Quick sort has a worst case scenario of 𝑂(𝑛2 ). This occurs when the list is already in
sorted order. The pivot will always be the largest element and will therefore swap with its own
position every time. This means it will quick sort a list one size less, costing 𝑂(𝑛 − 1). The
partition will always run on 𝜃(𝑛), because of its for loop. This means the total cost will equate to
a total cost of 𝑂(𝑛2 ).
Klein 7
The best case of quick sort is 𝑂(𝑛 log 𝑛), when our partition is as balanced as possible.
Our pivot should always be as close to the median of list as possible, which would divide our
problem in half every time. The partition will produce two sub problems, each of size no larger
than 𝑛/2. This time combined with the partition time produces the equation:
𝑛
𝑇(𝑛) = 2𝑇 ( ) + 𝜃(𝑛)
2
By case 2 of the master theorem, it can be determined 𝑇(𝑛) = 𝑂(𝑛𝑙𝑔𝑛). When dividing
the sub problems in half, quicksort becomes asymptotically faster.
Quick sort’s average time is closer to 𝑂(𝑛𝑙𝑜𝑔𝑛) than 𝑂(𝑛2 ). With an average case, the
partition will have a mix of bad splits resulting in an 𝑛 − 1, and good splits resulting in 𝑛/2. To
explain this, let us assume the worst and best case alternate between partitions. The root will
always cost 𝑛 for the partition, and the worst case will produces 2 sub-problems of 0 and 𝑛 − 1.
Then both of these sub problems will be partitioned in the best case. The sub problem of 0 will
produce two children of 0/2 and 0/2. The sub-problem of 𝑛 − 1 will produce the two children
𝑛−1
2
− 1 and
𝑛−1
2
. In total, the cost will be 𝜃(𝑛) + 𝜃(𝑛 − 1) = 𝜃(𝑛). In a sense, the bad partition
cost is absorbed by the best partition cost. So when alternating between good and bad splits, it is
like quick sort is running in its best case 𝑂(𝑛𝑙𝑜𝑔𝑛), but with a higher constant, not represented
in the Big O.
Taking the Big O into account, I hypothesize that quick sort’s run time will grow
exponentially in its worst case, and logarithmically in its best case.
Klein 8
Merge Sort
Merge sort is a divide and conquer sort. Merge sort continuously splits a list in half, sorts
the two lists, then merges and sorts the two back together. It starts by splitting a list in half and
then splitting those halves in half and doing this until each element is in a list by itself. It then
merges each list by looking at the first element of each list, and taking the smaller of the two and
putting it in a new list. It continues this until the final two lists are sorted and merged together.
This is an easy process when each list contains only one element, but the lists grow as they
merge and the bigger lists get compared and merged with each other. It continues this process
until it merges the two halves of the full list, which sorts the full list properly.
Merge sort has a best and worst case scenario of 𝑂(𝑛 log 𝑛), so no matter the order of the
list, the run time will be the same. The constant time may change slightly, but asymptotically
they are the same. The reason it remains 𝑂(𝑛 log 𝑛) is due to the growth of the sorted list. Every
time merge sort runs, the sorted lists starts as one element. As it merges with the other sorted
lists, its size grows from 1, to 2, then 4, 8, 16, etc. Merge sort does 𝑙𝑜𝑔𝑛 halvings of a list, and
subsequently it will take 𝑙𝑜𝑔𝑛 doublings to reach a fully sorted list with 𝑛 elements. Since there
are 𝑛 elements being sorted on a time of 𝑂(𝑙𝑜𝑔𝑛), merge sorts total run time will be 𝑂(𝑛𝑙𝑜𝑔𝑛).
Klein 9
Taking the Big O into account, I hypothesize that merge sort’s run time will grow
logarithmically as more elements are sorted.
Methodology
To show each algorithms’ efficiency, an experiment will be set up that will time each
algorithms’ sort on many different arrays, in many different states. The six algorithms being
tested are bubble sort, insertion sort, selection sort, heap sort, quick sort and merge sort. Each
sort has been implemented into Java, using the IDE Eclipse. The algorithms will be timed using
the method “System.nanoTime().” I will call this method before and after the algorithm is run
and take the difference of the two times. This will calculate the number of nanoseconds elapsed
for each sorting algorithm, which will then be converted to seconds. There will be five sizes of
arrays: 100; 1,000; 10,000; 100,000; and 1,000,000. Each array size will have three different
orderings: one random, one in ascending order, and one in descending order, for a total of 15
arrays. Each array will contain n elements and will include the values 1 to n, with no duplicates.
Klein 10
All arrays will be created using a for loop. The random arrays will be created as
ascending order arrays at first, then will be shuffled using “Collections.shuffle(arrayList)” seven
times in order to assure maximum randomization. All arrays will be generated and saved to files
in order to make sure the same array is being tested for each algorithm, and eliminate miscued
data. This experiment will be run on a HP Pavilion laptop in high performance mode with no
other apps running at the same time, excluding background processes. Eclipse will also be
allocated more RAM so the sorts do not crash. Each test will be run in these same conditions for
the most accurate data. This whole process will be executed three separate times, and the average
times will be taken for each array size and order for each algorithm. The number of tests, as well
as different orders of each array size, will give us the most accurate time for each sort.
After the runtimes have been collected, the data will be analyzed using the Big O. Simply
put, the Big O characterizes the growth of a sorts runtime based on the number of elements it is
sorting. One must understand that if an algorithm runs on 𝑂(𝑛2 ), that does not mean that when 𝑛
= 1,000,000, the run time will be 1,000,0002 . Rather, if an algorithm runs on 𝑂(𝑛2 ), the run
time will grow exponentially as 𝑛 grows. The formal definition of the Big O is
𝑓(𝑛) = 𝑂(𝑔(𝑛)) when there exists positive constants c and k, such that 0 ≤ 𝑓(𝑛) ≤ 𝑐𝑔(𝑛)
for all 𝑛 ≥ 𝑘. Once the raw data has been gathered, it will be graphed and compared to the Big
O hypothesis of each algorithm. If the graphs of each sort will reflect the hypothesized Big O
growth, the hypothesis is correct.
Hypothesis
The efficiency of an algorithm is based on its process, the number of elements in a list as
well as the list’s order. When hypothesizing which run time will be “best,” one must take all of
these elements into account. In many cases, certain algorithms will perform better when certain
Klein 11
criteria are met. For example, some algorithms may be very efficient in small test cases but be
exponentially more costly in large test cases. And in other cases some algorithms may be very
efficient when a list is random but will lose efficiency if the list is already sorted. Many
algorithms lack simplicity, but make up for it in efficiency, or vice versa.
In my personal opinion, to truly see an algorithms efficiency, one must test the algorithm
on lists of one million elements or larger. I believe this because when sorting small lists such as
one hundred elements, most algorithms perform relatively the same.
That being said, I believe bubble sort will always have the largest asymptotic running
time as we test larger and larger lists. Bubble sort’s constant comparing and swapping will
become very noticeable when it begins to test bigger and bigger sets of elements. Also its
constant 𝑂(𝑛2 ) will lead to exponential time growth as more and more elements are tested.
I believe that the most efficient algorithms will be heap sort or merge sort. I believe this
due to the fact that they both utilize the “divide and conquer” method, which divides problems
into much smaller problems, creating greater efficiency. Both sorts also have a consistent
𝑂(𝑛𝑙𝑜𝑔𝑛), no matter the order of the data. When comparing the Big O of all the tested
algorithms, these algorithms should consistently have faster times as the number of elements
increases.
Big O Table
Bubble sort
Selection sort
Insertion sort
Heap sort
Quick sort
Merge sort
Best case
O(𝑛2 )
O(𝑛2 )
O(n)
O(n log(n))
O(n log(n))
O(n log(n))
Worst case
O(𝑛2 )
O(𝑛2 )
O(𝑛2 )
O(n log(n))
O(𝑛2 )
O(n log(n))
Klein 12
Table 1 – Chart of each algorithms Big O.
Number of
elements
100
1,000
10,000
100,000
1,000,000
INSERTION
2.13E-04
3.61E-03
4.59E-02
2.09E+00
1.74E+02
SELECTION
1.38E-04
2.34E-03
2.61E-02
2.50E+00
2.71E+02
BUBBLE
QUICK
3.12E-04
4.09E-03
9.68E-02
1.03E+01
9.97E+02
MERGE
1.74E-04
2.06E-03
4.67E-02
4.79E+00
4.83E+02
1.42E-04
3.85E-04
3.48E-03
1.66E-02
1.51E-01
HEAP
1.29E-04
5.07E-04
2.58E-03
1.73E-02
1.60E-01
Table 2 – Average times of each algorithm.
100 elements
4.00E-04
3.50E-04
TIME (SECONDS)
3.00E-04
2.50E-04
2.00E-04
1.50E-04
1.00E-04
5.00E-05
0.00E+00
Insertion
Selection
Bubble
Quick
Merge
SORT
Random
Low to High
High to Low
Figure 1 - Time elapsed for each sort on the arrays of 100 elements.
Heap
Klein 13
1,000 elements
8.00E-03
TIME ()SECONDS
7.00E-03
6.00E-03
5.00E-03
4.00E-03
3.00E-03
2.00E-03
1.00E-03
0.00E+00
Insertion
Selection
Bubble
Quick
Merge
Heap
SORTS
Random
Low to High
High to Low
Figure 2 - Time elapsed for each sort on the arrays of 1,000 elements.
TIME ()SECONDS
10,000 elements
0.2
0.18
0.16
0.14
0.12
0.1
0.08
0.06
0.04
0.02
0
Insertion
Selection
Bubble
Quick
Merge
SORTS
Random
Low to High
High to Low
Figure 3 - Time elapsed for each sort on the arrays of 10,000 elements.
Heap
Klein 14
100,000 elements
25
TIME (SECONDS)
20
15
10
5
0
Insertion
Selection
Bubble
Quick
Merge
Heap
SORTS
Random
Low to High
High to Low
Figure 4 - Time elapsed for each sort on the arrays of 100,000 elements.
1,000,000 elements
2500
TIME (SECONDS)
2000
1500
1000
500
0
Insertion
Selection
Bubble
Quick
Merge
SORTS
Random
Low to High
High to Low
Figure 5 - Time elapsed for each sort on the arrays of 1,000,000 elements.
Heap
Klein 15
Heap Sort
Time (Seconds)
0.25
0.2
0.15
0.1
0.05
0
0
200000
400000
600000
800000
1000000
Number of elements
Ascending
Descending
Random
Figure 6 - Run time of Heap sort.
Merge Sort
Time (Seconds)
0.25
0.2
0.15
0.1
0.05
0
0
200000
400000
600000
800000
Number of elements
Ascending
Descending
Figure 7 - Run time of Merge sort.
Random
1000000
Klein 16
Time (Seconds)
Quick Sort
900
800
700
600
500
400
300
200
100
0
0
200000
400000
600000
800000
1000000
Number of elements
Ascending
Descending
Random
Figure 8 - Run time of Quick sort.
Bubble Sort
Time (Seconds)
2000
1500
1000
500
0
0
200000
400000
600000
800000
Number of elements
Ascending
Descending
Figure 9 - Run time of Bubble sort.
Random
1000000
Klein 17
Selection Sort
400
Time (Seconds)
350
300
250
200
150
100
50
0
0
200000
400000
600000
800000
1000000
Number of elements
Ascending
Descending
Random
Figure 10 - Run time of Selection sort.
Insertion Sort
400
Time (Seconds)
350
300
250
200
150
100
50
0
0
200000
400000
600000
800000
1000000
Number of elements
Ascending
Descending
Random
Figure 11 - Run time of Insertion sort.
Results
The preceding graphs represent the data collected from the experiment. Three tests of
each sort, on each array size, in each order have been run. The data was then averaged and
produced into the previous 11 graphs. Overall, the data produced from this experiment was as
expected. Each sort held true to the hypothesized Big O, and few errors occurred.
Klein 18
When testing small amount of elements, such as 100, the difference in run time of these
algorithms is next to nothing. But as the array size increases, it is apparent that certain algorithms
perform much better in certain situations. It is clear to see from figures 1 – 5 that bubble sort
appears to have the biggest increase in run time as more elements are added. These figures also
show that as the number of elements increase, both merge and heap sort seem to be the most all
around efficient algorithms.
As the number of elements grew, quick sort performed very poorly when the lists were in
ascending and descending order. However, quick sort actually had the overall fastest average
time for one million elements. It clocked in at an average of 0.14 seconds when sorting one
million randomly sorted elements.
So what sort is best to use? When using small data sizes such as 100, 1,000, or even
10,000 elements, most sorts perform within 1 second of each other. However when sorting a list
of 100,000 or 1,000,000 elements, efficiency is important.
When sorting a list, most would assumes that the list is in a random order, or else why
else would you want to sort it? If that is the case, the data collected and expressed in figures 1 – 5
of this experiment prove that the best sort to use would be quick sort if your list is random. If one
knows that the list to be sorted could possibly be already sorted, or have some kind of trend
simulating slight order, this experiment proves that the best sort to use should be either merge or
heap sort. I believe that it is best to use merge and heap sort in any situation. Quick sort was
faster in a certain situation, but all around, merge and heap sort are the most efficient algorithms
in this experiment.
Bubble Sort
Klein 19
Bubble sort has a 𝑂(𝑛2 ) for both its best and worst case scenario. Using this information,
it was hypothesized that bubble sort would have exponential growth as the number of elements
to be sorted increased. As seen in figures 1 - 5 as well as figure 9, no matter the order of the lists,
bubble sort’s run time increased exponentially. The times start off very similar with less
elements, but the differences become more prominent when more elements are sorted. In fact,
with every array size increase, the time it takes to perform the sort is multiplied by roughly 100.
This can be seen in table 2, the time it takes to sort random goes from roughly 0.18 seconds, to
19 and then 1918 seconds. Ascending sort time goes from 0.03 seconds, then jumps to 3 and then
312 seconds. Descending sort time goes from roughly 0.07 seconds, to 7 then 760 seconds. This
pattern is seen through all of bubble sort’s tests. Random order consistently took the longest to
sort, while descending was always second longest, and ascending order was always fastest.
The data collected supports the hypothesized 𝑂(𝑛2 ), however there is question as to why
the random ordered sorting takes far longer than the descending ordered sort. It seems intuitive to
believe that random should take less time. Descending order has to swap each element, every
time, because elements to the left will always be of a smaller value, while random order should
not have this occur every time. The reason this occurs is due to branch prediction. A branch
predictor is a digital circuit that tries to guess which way an “if” statement will go before it is
known. Branch predictors improve the efficiency of an algorithm if the same condition happens
over and over and it had an effect on the descending ordered sort. Since the “if” statement was
always entered, the Branch predictor improved bubble sort’s efficiency.
Selection Sort
Selection sort has a 𝑂(𝑛2 ) for both its best and worst case scenario as well. Using this
information, it was hypothesized that selection sort would have exponential growth as the
Klein 20
number of elements to be sorted increased. As seen in figures 1 - 5 as well as figure 10, no matter
the order of the lists, selection sort’s run time increases exponentially. Just as bubble sort, the
times start off very similar with less elements, but the differences become more prominent when
more elements are sorted. With every array size increase, the time it takes to perform the sort is
multiplied by roughly 100. The run time for sorting each list order are within a few seconds of
each other until testing one million elements. As seen in figure 10, the random ordered and
ascending ordered lists remain similar, and the descending order consistently takes longer.
Taking a look back at the insertion sort pseudocode, random and ascending order will not swap
for the smallest element every time. Since descending order is high to low, selection sort must
swap every time, which is why it’s run time is always longer.
Even though bubble and selection sort have different physical run times, and the order of
the list changes the run time, they still both hold 𝑂(𝑛2 ). Both algorithms run times grow
exponentially no matter the lists order.
Insertion Sort
Insertion sort has a best case run time of 𝑂(𝑛), and a worst case run time of 𝑂(𝑛2 ). Using
this information, it was hypothesized that insertion sort would have exponential growth in its
worst case when a list is in descending order, and it would have linear growth in its best case
when a list is already sorted in ascending order. As seen in figures 1 – 5, insertion sort’s time for
sorting random lists and descending lists grows exponentially, while the time for sorting
ascending lists remains linear. This is better seen in figure 11. Ascending sorting time remains
linear because the inner while loop of insertion sort is never entered, so only its outer for loop is
accounted for. Descending sorting time grows exponentially because the inner while loop of
insertion sort is entered every single time it is able. As seen in figure 11, random sorting time
Klein 21
grows exponentially, but not as rapidly as descending sorting time. This is because it enters the
inner while loop only on certain iterations, giving it a mixture of worst and best case scenario.
Heap Sort
Heap sort also has a both a best case and worst case run time of 𝑂(𝑛𝑙𝑜𝑔𝑛). As seen from
figures 1 – 5 and figure 6, heap sort has a very similar run time growth as merge sort. It will
always use the same process to develop the max heap, and it will always run on the same time
because there are many different max heaps that can be generated using the same data. Heap
sort’s big O is more efficient than most other algorithms tested, and this becomes apparent in
figures 1 – 5. It can also be seen that even when sorting one million elements, the sorting time
for each order of the lists remains within 0.12 seconds of each other, which would go unnoticed
by the average user. Ascending and random ordered list have nearly identical times, even when
reaching one million elements. Descending order sorting is also nearly identical, but is a little
faster. It’s slightly faster due to the fact that the list starts out in a max heap, saving some
constant time thereby reflected in figure 6.
Quick Sort
Quick sort has a best case run time of 𝑂(𝑛𝑙𝑜𝑔𝑛), and a worst case run time of 𝑂(𝑛2 ).
Using this information, it was hypothesized that quick sort would have exponential growth in its
worst case when a list is in ascending order, and it would have logarithmic growth in its best case
when a list has a most balanced pivot.
As seen in figure 8, the time of both ascending and descending sorting grows
exponentially, while random sorting grows logarithmically. As discussed earlier in quick sort’s
hypothesis, the reason the time of ascending and descending sort grows exponentially is because
the pivot is always going to be the next largest or smallest value. In the ascending case, every
Klein 22
quick sort recursion will be run on a list of n-1, because the pivot is swapped with itself. In the
descending case every quick sort recursion will also run on a list of n-1, because the wall will
never move and the pivot will just move to the first position.
As seen in figure 8, random order sorting time has a logarithmic growth. The reason for
this is because random is a mix of worst and best case partitioning. As discussed earlier in quick
sort’s hypothesis, with enough elements, the best case “outweighs” the worst case and appears
logarithmic. With a randomly sorted list, the pivot is more likely to be the median of the list. The
closer it is to the median, the greater the division of the problem, which will induce faster,
logarithmic running time.
Merge Sort
Merge sort has a both a best case and worst case run time of 𝑂(𝑛𝑙𝑜𝑔𝑛). It always has this
run time because it performs the same subdivision and merging process no matter the data.
Merge sort’s big O is more efficient than the other algorithms tested, and this becomes apparent
in figures 1 – 5. It can also be seen that even when sorting one million elements, the sorting time
for each order of the lists remains within 0.12 seconds of each other, which would go unnoticed
by the average user. Figure 7 shows the ascending and descending ordered lists have nearly the
same run time, while random takes slightly longer. When merging ascending and descending
ordered lists, one of the sub lists will contain all the smaller values, so one sub list is just
appended to the other. Random ordered lists must switch between taking the smallest value from
the front of each sub list. This gives random slightly more constant time. Each list order runs on
𝑂(𝑛𝑙𝑜𝑔𝑛), so even when sorting one million elements, the wait time is miniscule.
Conclusion
Klein 23
Algorithms have existed far longer than the creation of the first computer. We sort
clothes, food, cars and countless other things in our daily lives, so we must understand the
process it takes to perform these sorting algorithms. Algorithm creation and understanding are
used by every computer scientist and one must understand how algorithms truly work before
creating new ones. Algorithms are what solve the problems thought to be once impossible, and
will forever be used in computer science.
This experiment has given insight into the world of algorithms and their efficiency. This
experiment has taught me the tools to determine what algorithm should be used in certain
situations. I now have a greater understanding of algorithms and the important role they continue
to play in computer science as well as everyday life.