Our sorting algorithm had the following general operation on a list of size $n$:
As we saw the number of steps looked something like this:
We will show the merge sort algorithm that does the following on a list of size $n$:
It turns out that the number of steps it takes looks like the following:
# illustration of number of steps sorting 20 numbers in selection sort vs merge sort
# comparison of running time of selection sort and merge sort
#With Recursion
def merge_lists(L1,L2):
i=0
j=0
res = []
while i<len(L1) and j<len(L2):
if L1[i] < L2[j]:
res += [L1[i]]
i += 1
else:
res += [L2[j]]
j += 1
res += L1[i:]+L2[j:]
return res
x1=[3,9]
x2=[5,15]
x=merge_lists(x1,x2)
#With recursion
def merge_sort(L):
if len(L) <= 1:
return L
m = len(L)//2 #m=1
L1 = merge_sort(L[0:m]) #first half of list
L2 = merge_sort(L[m:]) #2nd half of list
return merge_lists(L1,L2)
L=[0,14,8,2,5,1,3,4]
merge_sort([3,1,4,1,5,9,2])
[1, 1, 2, 3, 4, 5, 9]
#Without Recursion
def merge_sort_nr(L):
lists = [ [x] for x in L]
while len(lists)>1:
new_lists = []
if len(lists) % 2:
lists += []
for i in range(0,len(lists)-1,2):
new_lists += merge_lists(lists[i],lists[i+1])
lists = new_lists
return lists[0]
merge_sort_nr([3,1,4,1,5,9,2])
[1, 1, 2, 3, 4, 5, 9]
Many times, recursion gives us a clean way to think about problems and solve them.
But a recursive program is often slower than non recursive version.
So sometimes, after finding a recursive solution, we want to transform it to a non recursive solution.
def find_min_index(L):
current_index = 0
current_min = L[0]
for j in range(1,len(L)):
if current_min > L[j]:
current_min = L[j]
current_index = j
return current_index
def selection_sort(L):
if len(L)<=1:
return L # a one-element list is always sorted
min_idx = find_min_index(L) #non-recursive helper function
L[0], L[min_idx] = L[min_idx], L[0]
return [L[0]] + sort(L[1:len(L)])
def selection_sort_nr(L):
for i in range(len(L)):
min_idx = i+find_min_index(L[i:])
L[i], L[min_idx] = L[min_idx], L[i]
return L
selection_sort_nr([3,1,4,1,5,9,2])
[1, 1, 2, 3, 4, 5, 9]
#write a function bin_search
#which takes in a sorted list L and an item i
#and returns the index of i if it exists & -1 if not.
#don't use recursion
def binarysearch(li,element):
top=len(li) #li=[0,1,2,3,4,5], element=1
bottom=0
while top>bottom: #top=6, bottom=0
middle=(top+bottom)//2 #middle 3
if element==li[middle]:
return middle
elif element < li[middle]:
top=middle
elif element > li[middle]:
bottom=middle
return -1
binarysearch([0,1,2,3,4,5],4)
4
#With recursion
def bin_search_1(L,item):
n = len(L) #L=[1,2,3,4,5], m=2
if n==0:
return -1
m = n//2
if L[m]==item:
return m
#print(m)
if L[m]>item:
return bin_search_1(L[:m],item) #Search left half
res = bin_search_1(L[m+1:],item) #Search right half #what happens if I don't have the following 3 lines?
if res==-1:
return -1
return m+1+res
bin_search_1([1,2,3,4,5], 4)
3
#Without recursion
def bin_search_nr(L,item):
left = 0
right= len(L)
while right-left >0:
#print('*')
m = int((left+right)/2)
#print(m) #what would this print?
if L[m]==item:
return m
if L[m]>item: #Search left half
right = m
else:
left = m+1 #Search right half
return -1
L=range(10) #=[0,1,2,3,4,5,6,7,8,9]
#print(bin_search_nr(L,2))
Write a function sort4
that sorts a list of 4 elements. The function should make two calls to sort2
def sort4(L):
L_first_sorted = sort2(L[0:2])
L_last_sorted = sort2(L[2:4])
#
# do something to return a sorted list
#
sort4([10,2,5,7])
Write a function merge_lists
that takes two sorted lists L1 and L2 and returns a sorted list that of length len(L1)+len(L2)
that contains all their elements.
Write a function sort32
that sorts a list of 32 elements. The function should make two recursive calls to a provided function sort16
def sort32(L):
L_first_sorted = sort16(L[0:4])
L_last_sorted = sort16(L[4:8])
#
# do something to return a sorted list
#
sort16 = sorted
sort32([32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1])
Write a function merge_sort
that will sort a list of any size. The function should make two recursive calls to itself
def merge_sort(L):
#
# do something here
#
L_first_sorted = merge_sort(L[0:int(len(L)/2)])
L_last_sorted = merge_sort(L[int(len(L)/2):len(L)])
#
# do something to return a sorted list
merge_sort([78, 39, 50, 43, 3, 30, 34, 75, 33, 7, 30, 71, 76, 44, 27, 4, 68, 21, 51, 78, 11, 53, 71, 60, 64, 9, 28, 63, 55, 34, 44, 52, 28, 52, 43, 44, 4, 41, 40, 17])
Use a stopwatch to compare selection sort and merge sort on random lists of 4000 numbers. Run each one of them 10 times andrecord the average time.
(if your computer crashes then you can use smaller lists, you can also use the %timeit option as below)
def gen_random_list(n):
return [random.randint(0,2*n) for i in range(n)]
# run this code so that Python allows you to run the sorts on inputs larger than 100
import sys
sys.setrecursionlimit(10**6)
% timeit merge_sort(gen_random_list(4000));
% timeit sort(gen_random_list(4000));
# your selection sort function from the previous labwork or from the lecture
Write a function cannon_ball_no_gravity(angle,speed,t)
that on input an angle angle
between $0$ and $90$ and a speed speed
in meters per second, and time $t$ in seconds, returns two values height,distance
.
The value height
will be which are the height in meters that a cannon ball shot at angle $angle$ from the ground and speed $speed$ would be at after $t$ seconds if there is no gravity.
The value distance
will be the distance in meters that the projection of this ball on the ground would be from the initial point after $t$ seconds.
Write a function cannon_ball_earth(angle,speed,t)
that does the same thing if the ball is shot on earth and takes gravity into account.
(You don't need to worry about air resistance nor about whether height becomes negative).
You can use the following helper functions:
import math
def sine(angle):
return math.sin((angle/360.0)*2*math.pi)
def cosie(angle):
return math.cos((angle/360.0)*2*math.pi)