def bucket_sort(numbers): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. Running time: O(n + k) for best case and average case and O(n^2) for the worst case Memory usage: O(n + k)""" # FIXME: Improve this to mutate input instead of creating new output list # Find range of given numbers (minimum and maximum values) max_val = max(numbers) # use length of the list to determine which value in the list goes into which bucket size = max_val / len(numbers) # Create list of buckets to store numbers in subranges of input range buckets_list = [] for x in range(len(numbers)): buckets_list.append([]) # Loop over given numbers and place each item in appropriate bucket based on size for i in range(len(numbers)): j = int(numbers[i] / size) if j != len(numbers): buckets_list[j].append(numbers[i]) else: buckets_list[len(numbers) - 1].append(numbers[i]) # Sort elements within the buckets using Insertion Sort for z in range(len(numbers)): insertion_sort(buckets_list[z]) # Concatenate buckets with sorted elements into a single list # Loop over buckets and append each bucket's numbers into output list output_list = [] for x in range(len(numbers)): output_list = output_list + buckets_list[x] return output_list
def bucket_sort(numbers, num_buckets=10): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. Running time: O(n+k) where k is the number of buckets. The time complexity is depended on both n and k because if k is large then our algorithm is also increased because we have to traverse the buckets Memory usage: O(n) because as the n increases the space increases linearly, we are not doing processes in place so space is increased with n""" # creating the buckets, in this case it's a list within a list buckets = [] for _ in range(num_buckets): buckets.append([]) # loop through all the numbers for num in numbers: # determine the bucket index; using a given formula b_index = bucket_index_formula(num, numbers) # add to bucket buckets[b_index].append(num) # note: ultimately we want to sort this list but we will do that later # initialize empty bucket list complete_sorted_list = [] # loop through bucket list with lists for array in buckets: # extend the list of the bucket into the complete sorted list if it's not empty if array != []: # sort then extend to completed_sorted_list insertion_sort(array) complete_sorted_list.extend(array) return complete_sorted_list
def bucket_sort(numbers, num_buckets=10): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. Running time: Best: O(n*k) You only need to traverse the original array once, and the new array or bucket k times, where k is the number of buckets that isn't empty Worst: O(n^2), There would be the same number of full buckets ast ehre are items in the original array, or else the array balues are so unevenly distributed that the sorting and arranging of the sub-arrays in the buckets takes more time Memory usage:O(n) memory usage will be linear as the algorithm only needs to create one extra data structure. Python uses dynamic arrays which allow for more flexibility""" arr = [] # Create empty buckets for i in range(num_buckets): arr.append([]) # isnert each elements from numbers into each bucket for num in numbers: index = int(num / max(numbers) * (num_buckets + 1)) bucket = arr[index] bucket.append(num) # Then sort each element using insertion sort for bucket in arr: insertion_sort(bucket) # get sorted elements k = 0 for i in range(num_buckets): for value in arr[i]: numbers[k] = value k += 1 # return sorted numbers array return numbers
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: O(n^2) where n is the number of elements in the items list. The runtime of this function asymptotically scales with the time take by the Insertion Sort algorithm, which is used as a helper twice. Although in the average case Insertion Sort runs in quadratic time, it is important to note that it can also run in only linear time; when a list of only 1 element is passed in, that counts as sorted. Memory usage: O(p). The space complexity of this function is dependent upon that of the merge() function, which scales with the size of the input items list. """ # Split items list into approximately equal halves left, right = split(items) # O(p) time # Sort each half using any other sorting algorithm insertion_sort(left) # O(p^2 / 2) time insertion_sort(right) # O(p^2 / 2) time # Merge sorted halves into one list in sorted order return merge(left, right) # O(n + m) = O(p) time + space
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: O(n/2)^2 = O(n^2)/4 Why and under what conditions? each sorting methods take quadratic time and items is divided into two parts before being sorted. Memory usage: 2*O(n) Why and under what conditions? we are making a copy for items1 and items2""" # print("items: ", items) if len(items) <= 1: return items mid = len(items)//2 #split array in to two parts items1 = items[:mid] items2 = items[mid:] # sort each half selection_sort(items1) insertion_sort(items2) # store returned mereged sorted_items = merge(items1, items2, items) # replace all indexes in items with items new arrangemnt items[:] = sorted_items return items
def bucket_sort(numbers, num_buckets=10): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. Running time: O((n/b)^2), at n>=b it is equivalant to an O(n) counting sort Memory usage: O(n) add'l space""" min_value = 0 max_value = 0 bucket_list = [] for number in numbers: if number < min_value: min_value = number if number > max_value: max_value = number bstep = (max_value - min_value) // num_buckets for _ in range(num_buckets): bucket_list.append([]) for number in numbers: put_index = number // bstep if put_index >= len(bucket_list): put_index = len(bucket_list) - 1 bucket_list[put_index].append(number) for bucket in bucket_list: insertion_sort(bucket) numbers.clear() for bucket in bucket_list: numbers.extend(bucket)
def bucket_sort(numbers, num_buckets=10): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. TODO: Running time: ??? Why and under what conditions? TODO: Memory usage: ??? Why and under what conditions?""" # TODO: Find range of given numbers (minimum and maximum values) # TODO: Create list of buckets to store numbers in subranges of input range # TODO: Loop over given numbers and place each item in appropriate bucket # TODO: Sort each bucket using any sorting algorithm (recursive or another) # TODO: Loop over buckets and append each bucket's numbers into output list # FIXME: Improve this to mutate input instead of creating new output list max_value = max(numbers) d_buckets = int(math.sqrt(len(numbers))) if num_buckets is None: buckets = [[] for _ in range(d_buckets)] else: buckets = [[] for _ in range(num_buckets)] for num in numbers: index = int(num / max_value * (d_buckets - 1)) bucket = buckets[index] bucket.append(num) for bucket in buckets: insertion_sort(bucket) write_index = 0 for bucket in range(len(buckets)): for value in buckets[bucket]: numbers[write_index] = value write_index += 1
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: Best case : same as insertion sort O(n) Worst case: same as insertion sort O(n^2) Memory usage: O(n): we make a new array to merge it back""" #Split items list into approximately equal halves # Sort each half using any other sorting algorithm # Merge sorted halves into one list in sorted order #the first half of the list items1 = items[:len(items) // 2] #second half of the list items2 = items[len(items) // 2:len(items)] #sort each lists individually items1 = insertion_sort(items1) items2 = insertion_sort(items2) #merge the two sorted lists back merged = merge(items1, items2) #copy everything from merged to items one by one # for i in range(len(merged)): # items[i] = merged[i] items[:] = merged return items
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: O(n^2) See insertion sort. Memory usage: O(n) An auxiliar array is created for each merge. """ left, right = items[:len(items) // 2], items[len(items) // 2:] insertion_sort(left) insertion_sort(right) items[:] = merge(left, right)
def bucket_sort(numbers, num_buckets=10): """Sort given numbers by distributing into buckets representing subranges, # then sorting each bucket and concatenating all buckets in sorted order. # Running time: # Best: 0(n*k) # Why and under what conditions? # You only need to traverse the original array once, # and the new array or buckets k times, where k is the number of # buckets that are not empty. # Worst: 0(n^2) # Why and under what conditions? # There would be the same number of full buckets as there are # items in the original array, or else the array values are so unevenly # distributed that the sorting and arranging of the sub-arrays in # the buckets takes up a lot of time # Memory usage: # 0(n) # Why and under what conditions? # Memory usage is minimal and linear, even under the worst # conditions, as the algorithm only needs # to create one extra data structure in the # form of the array of arrays, and in python # these are all dynamic arrays which have flexible space. # """ buckets = [] # Create empty buckets for i in range(num_buckets): buckets.append([]) # isnert each elements from numbers into each bucket for num in numbers: if type(num) is int: index = int(int(num / int(max(numbers))) * int((num_buckets - 1))) bucket = buckets[index] bucket.append(num) else: index = ((int(num) / int(max(numbers))) * ((num_buckets - 1))) bucket = buckets[index] bucket.append(num) # Then sort each element using insertion sort for bucket in buckets: insertion_sort(bucket) # get sorted elements k = 0 for i in range(num_buckets): for value in buckets[i]: numbers[k] = value k += 1 # return sorted numbers array return numbers
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: O(n^2) always, to sort and merge Memory usage: O(n) to create new list (merged)""" items1 = insertion_sort(items[:len(items) // 2]) items2 = insertion_sort(items[len(items) // 2:]) merged = merge(items1, items2) items[:] = merged return items
def split_sort_merge(items: List[int]) -> List[int]: """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: avg O(n^2) because of insertion_sort Memory usage: O(n) because we're creating an array of (n) elements to merge""" # Split items list into approximately equal halves mid_index = len(items)//2 first_half, second_half = items[:mid_index], items[mid_index:] # Sort each half using any other sorting algorithm insertion_sort(first_half) insertion_sort(second_half) # Merge sorted halves into one list in sorted order got_back = merge(first_half, second_half) # (n) items[:] = got_back
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. TODO: Running time: ??? Why and under what conditions? TODO: Memory usage: ??? Why and under what conditions?""" middle_index = len(items) // 2 right_half = items[middle_index:] insertion_sort(right_half) left_half = items[:middle_index] insertion_sort(left_half) for index, item in enumerate(merge(right_half, left_half)): items[index] = item
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. TODO: Running time: O(n) when sorted, O(n^2) average and worst(reverse sort) TODO: Memory usage: O(n) add'l space""" center = len(items) // 2 items1 = items[:center] items2 = items[center:] insertion_sort(items1) insertion_sort(items2) items.clear() items.extend(merge(items1, items2))
def bucket_sort(numbers, num_buckets=10): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. TODO: Running time: ??? 2*O(num_buckets) + O(n) + insertion_sort. Because we loop num_buckets times to initalize an array loop n times to place number in the right bucket loop num_buckets times TODO: Memory usage: ??? O(num_buckets)""" arr = [] for i in range(num_buckets): arr.append([]) # Put array elements in different buckets for j in numbers: index_b = int(num_buckets * j) arr[index_b].append(j) # Sort individual buckets for i in range(num_buckets): arr[i] = insertion_sort(arr[i]) # concatenate the result k = 0 for i in range(num_buckets): for j in range(len(arr[i])): numbers[k] = arr[i][j] k += 1 return numbers
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: O(n^2) Memory usage: O(n)""" if len(items) < 2: return items # Split items list into approximately equal halves split = len(items) // 2 # Sort each half using any other sorting algorithm sorted1, sorted2 = insertion_sort(items[:split]), insertion_sort(items[split:]) # Merge sorted halves into one list in sorted order for index, item in enumerate(merge(sorted1, sorted2)): items[index] = item return items
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: O(nlogn) because best or worst case we are diviiding the array into two halves Memory usage: O(n) because we are only running through items[list]once and not creating more space within the function""" items_length = len(items) halfway = math.floor(items_length)/2 # Split items list into approximately equal halves low = [] high = [] for i in range(halfway): low.append(items[i]) result_one = insertion_sort(low) for j in range(halfway+1, items_length-1): high.append(items[j]) result_two = insertion_sort(high) return merge(result_one,result_two)
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: O(n^2) since insertion sort takes O(n^2) for each n/2 halves and then merging and updating takes O(n). Memory usage: O(n^2) merging function returns a new variable after merging""" mid = len(items) // 2 # Split items list into approximately equal halves left = items[:mid] right = items[mid:] # Sort each half using any other sorting algorithm insertion_sort(left) insertion_sort(right) # Merge sorted halves into one list in sorted order merged = merge(left, right) items[:] = merged
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. TODO: Running time: ??? Why and under what conditions? TODO: Memory usage: ??? Why and under what conditions?""" # TODO: Split items list into approximately equal halves # TODO: Sort each half using any other sorting algorithm # TODO: Merge sorted halves into one list in sorted order middle = len(items) // 2 array1 = items[:middle] array2 = items[middle:] insertion_sort(array1) insertion_sort(array2) return merge(array1, array2)
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: O(n) Memory usage: O(n)""" # Split items list into approximately equal halves # Sort each half using any other sorting algorithm # Merge sorted halves into one list in sorted order items1, items2 = bisect_list(items) selection_sort(items1) insertion_sort(items2) items[:] = merge(items1,items2) return items
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. Running time: O((n/2)^2) -> O(n^2) When the left and right halves are in reverse order Memory usage: O(n) New memory is created to store new ordered list""" # Split items list into approximately equal halves middle = len(items) // 2 left = items[:middle] right = items[middle:] # Sort each half using any other sorting algorithm insertion_sort(left) # O(n^2) bubble_sort(right) # O(n^2) # Merge sorted halves into one list in sorted order # Overwrite content of original list with content of sorted list items[:] = merge(left, right)
def bucket_sort(numbers, num_buckets=None): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. TODO: Running time: O(??) TODO: Memory usage: O(??)""" max_value = max(numbers) # dynamic bucket allocation d_buckets = int(math.sqrt(len(numbers))) # create our empty buckets (if they specify a # value we use that many buckets otherwise we do it dynamically) if num_buckets is None: buckets = [[] for _ in range(d_buckets)] else: buckets = [[] for _ in range(num_buckets)] # disperse our data into the buckets - O(n) for num in numbers: index = int(num / max_value * (d_buckets - 1)) # get the index of the bucket that we are going to insert the item into # find the bucket based off of the index (after 'hash') bucket = buckets[index] # append the value to said bucket bucket.append(num) # sort the elements in each bucket using insertion sort - O(n) for bucket in buckets: insertion_sort(bucket) # keep track of the value to write to write_index = 0 # merge all the buckets back together in sorted order - O(n) for bucket in range(len(buckets)): # loop through every value in the bucket for value in buckets[bucket]: # write the value (sorted) into our input array numbers[write_index] = value # increase the element of the place we are going to write to write_index += 1
def get_pivot(collection): """Returns the index of the pivot element in items, after applying the 'median of three' algorithm. How a Pivot is Chosen: "Median of 3" 1) Take three random indices in the collection (a Python list)/ 2) The pivot is the element that is the median of the above three 3) The three elements go back in the array in sorted order, relative to whichever 3 index positions they were pulled from. (i.e. if the element pulled from the last index was found to the median, it would go back into items at the index the middle element orignally occupied). """ # pull out three random indices in the collection - will revisit when # we can prevent the same index being chosen more than once # first_rand = randint(0, len(collection) - 1) # second_rand = randint(0, len(collection) - 1) # third_rand = randint(0, len(collection) - 1) first_index = 0 mid_index = len(collection) // 2 last_index = len(collection) - 1 first, middle, last = ( collection[first_index], collection[len(collection) // 2], collection[last_index] ) sub_three = [first, middle, last] # sort the three insertion_sort(sub_three) # print(sub_three) # place back in the items array collection[first_index] = sub_three[0] pivot = collection[mid_index] = sub_three[1] collection[last_index] = sub_three[2] # print(pivot) # print(collection) # return the index position pivot return first_index
def split_sort_merge(items): """Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. TODO: Running time: ??? Why and under what conditions? TODO: Memory usage: ??? Why and under what conditions?""" # TODO: Split items list into approximately equal halves # TODO: Sort each half using any other sorting algorithm # TODO: Merge sorted halves into one list in sorted order if len(items) <= 4: return insertion_sort(items) else: return merge(split_sort_merge(items[:int(len(items) / 2)]), split_sort_merge(items[int(len(items) / 2):]))
def bucket_sort(numbers=[2, 3, 1, 12, 4], num_buckets=10): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. TODO: Running time: ??? Why and under what conditions? TODO: Memory usage: ??? Why and under what conditions?""" # TODO: Find range of given numbers (minimum and maximum values) max_val = max(numbers) min_val = min(numbers) size = max_val / len(numbers) # TODO: Create list of buckets to store numbers in subranges of input range buckets = [[] for _ in range(num_buckets)] # TODO: Loop over given numbers and place each item in appropriate bucket for num in numbers: index = num / size # to find the index, divide the num by the size buckets[index].append(num) # add the num to it's appropriate bucket # TODO: Sort each bucket using any sorting algorithm (recursive or another) # TODO: Loop over buckets and append each bucket's numbers into output list for i in range(len(buckets)): insertion_sort(buckets[i])
def test_insertion_sort_on_sorted_integers(self): # Positive test cases (examples) with lists of sorted integers assert insertion_sort([]) == [] # Empty lists are vacuously sorted assert insertion_sort([3]) == [3] # Single item is trivially sorted assert insertion_sort([3, 3]) == [3, 3] # Duplicate items are in order assert insertion_sort([3, 5]) == [3, 5] assert insertion_sort([3, 5, 7]) == [3, 5, 7] # new added test cases sample_1 = [3, 7, 9, 11, 13, 15, 16, 17, 19, 20] assert insertion_sort(sample_1) == sample_1 sample_2 = [1, 2, 4, 5, 7, 8, 10, 15, 25, 25, 32, 37, 37, 38, 38, 39, 39, 42, 42, 47] assert insertion_sort(sample_2) == sample_2 sample_3 = [6, 9, 13, 13, 16, 17, 21, 22, 22, 22, 23, 24, 25, 29, 30, 34, 34, 36, 40, 43] assert insertion_sort(sample_3) == sample_3
def bucket_sort(numbers, num_buckets=10): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. Running time: O(n + k) n being the values in the list and k being the bucket's we iterate over. Memory usage: O(n + k) n being the values in the list and k being the bucket's we iterate over. """ num_len, max_num = len(numbers), max(numbers) # Create list of buckets to store numbers in subranges of input range buckets = [[] for _ in range(num_buckets)] # Used to put the elements in the buckets divider = (max_num + 1) / num_buckets # print(max_num/num_buckets) # print('Confused:', int(numbers[7] / (max_num/num_buckets))) # print('Witchcraft:', int(numbers[7] / ((max_num + 1)/num_buckets))) # print(int(numbers[7]/divider)) # Loop over given numbers and place each item in appropriate bucket for i in range(num_len): j = int(numbers[i] / divider) buckets[j].append(numbers[i]) # Sort each bucket using any sorting algorithm (recursive or another) for i in range(len(buckets)): insertion_sort(buckets[i]) # Loop over buckets and mutate the input counter = 0 for i in range(len(buckets)): for item in buckets[i]: numbers[counter] = item counter += 1 print(numbers)
def split_sort_merge(items): """ Sort given items by splitting list into two approximately equal halves, sorting each with an iterative sorting algorithm, and merging results into a list in sorted order. where: N = length of the original list, n = length of the first half, m = length second half. Also n and m is approximately(almost) equal length. so n + m approximately 2n or 2m which is, 2n = N or 2m = N Running time: O(n^2) + O(m^2) = O(2n^2) using the properties above, overall O(N^2) running time Memory usage: O(N), because when we are merging two sorted list at the end we are creating new empty list with the same length of the original list. Args: items(list): unsorted list of elements Returns: merged_list(list): sorted list of elements of original list. Reference of the original list didnt change. """ # splitting part mid = len(items) // 2 # copy the first half of the array items_1 = items[:mid] # O(n) # copy the second half of the array items_2 = items[mid:] # O(m) # sorting the splitted lists using iterative algorithms bubble_sort(items_1) # O(n^2) refer to the bubble sort docstring insertion_sort(items_2) # O(m^2) refer to insertion sort docstring # now merge two sorted arrays merged_list = merge(items_1, items_2) # O(n+m) = O(N) # to make sorting in place items[:] = merged_list
def bucket_sort(numbers, slots=10): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in count order.""" buckets = [] [buckets.append(list()) for x in range(slots)] for num in numbers: buckets[int(num * slots)].append(num) sorted = [] for bucket in buckets: if len(bucket) > 0: bucket = insertion_sort(bucket) [sorted.append(bucket[x]) for x in range(len(bucket))] return sorted
def bucket_sort(numbers): """Sort given numbers by distributing into buckets representing subranges, then sorting each bucket and concatenating all buckets in sorted order. Running time: O(n + k) for best case + average case, O(n^2) for worst case Memory usage: O(n + k)""" # FIXME: Improve this to mutate input instead of creating new output list # Find range of given numbers (minimum and maximum values) max_val = max(numbers) # Use the length of the list to figure out which value in the list goes in what bucket # DIVIDER to check which bucket the number should be placed size = max_val / len(numbers) # Create list of buckets to store numbers in subranges of input range bucket_list = [] for x in range(len(numbers)): bucket_list.append([]) # Loop over given numbers and place each item in appropriate bucket for i in range(len(numbers)): # figure out which bucket the current number should be in j = int(numbers[i] / size) if j != len(numbers): bucket_list[j].append(numbers[i]) else: bucket_list[len(numbers) - 1].append(numbers[i]) # Sort each bucket using any sorting algorithm (recursive or another) for z in range(len(numbers)): insertion_sort(bucket_list[z]) # Loop over buckets and append each bucket's numbers into output list outputList = [] for x in range(len(numbers)): outputList = outputList + bucket_list[x] return outputList