360 likes | 380 Views
Learn about insertion sort and shellsort algorithms in programming. Analyze their performance and understand their time complexities.
E N D
Programming, Data Structures and Algorithms (Sorting) Anton Biasizzo
Preliminaries • Problem of sorting an array of elements. • We will assume that array contains only integers. • We will assume that N is the number of elements passed to the sorting algorithm. • For some algorithms it is convenient to place a sentinel in position 0, thus array will range from 0 to N. • Actual data starts at position 1. • Only allowed operations on the input data are comparisons and assignment. • Comparison-based sorting.
Insertion sort • Insertion sort is one of the simplest algorithms. • It consists of N-1 passes. • For pass p=2 through N, insertion sort ensures that the elements in positions 1 through p are in sorted order. • It makes use of the fact that elements in positions 1 through p-1 are already in sorted order. • Algorithm: • In pass p we move p-th element left until its correct place is found among the first p elements. • The sentinel in a[0] terminates the loop in the event that in some pass an element is moved all the way to the front.
Insertion sort • Insertion sort: void insertion_sort(input_type a[], unsigned int n) { unsigned int j, p; input_type tmp; a[0] = MIN_DATA; for ( p=2; p<=n; p++) { tmp = a[p]; for ( j=p; tmp<a[j-1]; j-- ) a[j] = a[j-1]; a[j] = tmp; } }
Insertion Sort analysis • Because of nested loops, each of which can take N iterations, insertion sort is O(N2). • Comparison in the inner for loop is executed at most • This bound is tight because input in reverse order actually achieve that bound. • If the input is pre-sorted, the running time is O(N) because the test of the inner for loop fails immediately. • Running time of an average case for insertion sort is O(N2).
Lower bound for simple sorting algorithm • An inversion in an array of numbers is any ordered pair (i,j) having the property that i<j but a[i]>a[j]. • Number of inversions is the number of swaps that needed to be performed in the insertion sort. • Sorted array has no inversions. • Swapping two adjacent elements that are out of place removes exactly one inversion. • In the insertion sort there is O(N) other work involved in algorithm. • The running time of insertion sort is O(I+N), where I is the number of inversions in the original array. • Insertion sort runs in linear time if the number of inversions is N.
Lower bound for simple sorting algorithm • The average number of inversions in an array of N distinct numbers is N (N - 1)/4. • Proof: • For any list, L, of numbers, consider Lr, the list in reverse order. • Consider any pair of two numbers in the list (i,j), with j>i. Clearly, in exactly one of L and Lr this ordered pair represents an inversions. • The total number of these pairs in L and Lr is N(N-1)/2. • An average list has half of this amount: N (N-1)/4. • Insertion sort is quadratic on average, • Any algorithm that sorts by exchanging adjacent elements requires Ω(N2) time on average. • Proof: • Initially, the average number of inversions is N (N-1) / 4 = Ω(N2). • Each swap removes only one inversion, so Ω(N2) swaps are required.
Shellsort • Named by inventor Donald Shell. • Breaks the quadratic barrier: • Compare distant elements, • The distance is decreased in each phase, • In the last phase adjacent elements are compared. • Shellsort uses a sequence, h1, h2, …, ht, called the increment sequence. • Any sequence is suitable as long as ht =1. • The algorithm performance depends on the sequence. • In a k-th phase, using increment hk, all elements spaced hk apart are sorted. • After a phase, using increment hk, for every i, we have A[i] ≤ A[i+ hk] and array is said to be hk-sorted. • An hk-sorted array that is then hk+1-sorted remains hk-sorted.
Shellsort • Strategy for hk-sort: • For each position i, in hk+1, hk+2,…,N, place the element in the correct spot among i, i-hk, i-2hk, … • An hk-sort performs an insertion sort on hk independent sub-arrays. • A common (but poor) choice of increment sequence is h1=N/2, hk+1=hk /2 (Shell’s increments). • Increment sequences significantly affect the performance of shellsort algorithm.
Analysis of Shellsort • Average-case analysis of Shellsort is a long-standing open problem (except for most trivial increment sequences). • The worst-case running time, using Shell’s increments, is θ(N2). Proof: • Determine lower Ω(N2) and upper bound O(N2). • Determine lower bound by constructing a bad case: • Let N be a power of 2 – all the increments are even except h1, which is 1. • Let the N/2 largest elements be in even positions and N/2 smallest elements be in odd positions. • Since all increments except the last are even, the N/2 largest elements are still all in even positions, and N/2 smallest elements are in odd positions, when we come to the last pass. • The ith smallest element (i ≤ N/2) is in position 2i-1 before beginning the last pass and requires moving for i-1 places. • To place the N/2 smallest elements in the correct place require
Analysis of Shellsort • Determine upper bound: • A pass with increment hk consists of hk insertion sorts of N/hk elements. • Since insertion sort is quadratic, the total cost of the pass is O(hk (N/hk)2) = O(N2/hk) • Summing over all passes gives a total bound • Increments form a geometric series with common ratio 2, hence the • Obtained total bound is O(N2).
Analysis of Shellsort • Problem with Shell’s increments: they are not necessarily relatively prime and some increments can have little effect. • Hibbard suggested a slightly different increment sequence of form 1, 3, 7, …, 2k-1. • Key difference: the consecutive increments have no common factors. • The worst-case running time of Shellsort, using Hibbard’s increments, is θ(N3/2). • Based on simulations, the average-case running time of Shellsort, using Hibbard’s increments, is thought to be O(N5/4). • Sedgewick proposed several increments sequences that give an O(N4/3) worst-case running time, the average running time is conjectured to be O(N7/6). • One of the sequences is {1, 5, 19, 41, 109, …}, in which terms are either of form 9·4i – 9·2i + 1 or 4i – 3·2i +1. • These sequences are most easily implemented by placing its values in an array.
Heapsort • Priority queues can be used to sort in O(N log N) time. • Heapsort gives the best running time growth rate so far. • In practice it is slower than a Shellsort with Sedgewick’s increment sequence. • Sort strategy: • Build a binary heap of N elements – it takes O(N) time. • Perform N DeleteMin operations, the elements leave the heap smallest first, in sorted order. • By recording these elements in a second array and copying the array back we sort N elements. • Each DeleteMin requires O(log N) time. • The total running time is O(N log N).
Heapsort • The clever way to avoid additional array is to use the fact, that after each DeleteMin, the heap shrinks by 1 thus the cell that was last in the heap can store just deleted (minimum) element. • This way we got array sorted in decreasing order. • Change the ordering property of the heap, so that the parent has a larger key than the child (max Heap).
Analysis of Heapsort • Building the heap uses at most 2N comparisions. • In second phase, the ith DeleteMax uses at most 2 log i comparisons, for total at most 2N log N – O(N) comparisons. • Consequently, in the worst case, at most 2N log N – O(N) comparisons are used by heapsort. • Experiments have shown that heapsort is extremely stable algorithm: on average it uses only slightly fewer comparisons than the worst case bound suggest. • Heapsort’s average running time is hard to estimate because successive DeleteMax operations destroy the heap’s randomness.
Mergesort • Mergesort runs in in O(N log N) worst-case running time. • It is good example of a recursive algorithm. • The fundamental operation is merging two sorted lists. Because the lists are sorted this can be done in one pass. • The basic merging algorithm takes: • two input arrays A and B, and output array C. • three counters (aptr, bptr, and cptr) for the corresponding arrays which are initially set to the beginning of the arrays. • The smaller of A[aptr] and B[bptr] is copied to the next entry in C[cptr] and appropriate counters are advanced.
Mergesort • When one array is exhausted, • the remainder of the other array is copied to output array
Mergesort • The time to merge two sorted lists is clearly linear, because at most N-1 comparisons are made. • Every comparison adds an element to set C, except the last one, which adds two. • The mergesort algorithm is easy to describe: • If N=1 the is one element to sort and the array is already sorted. • Otherwise recursively mergesort first and second half of the array. • This algorithm presents classic divide and conquer strategy. • The problem is divided into smaller problems and solved recursively. • The conquer phase consist of patching together the answers. • If temporary array is declared locally, then there could be log N temporary arrays active – dangerous on machine with small memory. • Only one temporary array is needed. • It can be rewritten without recursion.
Analysis of Mergesort • Let us assume that N is a power of 2. • For N=1 the time to mergesort is constant and denoted by 1. • The time to mergesort N elements is equal to time to do two recursive mergesorts of size N/2, plus the time to merge, which is linear: • T(1) = 1 • T(N) = 2 T(N/2) + N • Dividing second equation by N we get • And finally
Analysis of Mergesort • After everything is added we get because there is log N equations. • T(N) = N log N + N = O(N log N) • Although mergesort’s running time is O(N log N), it is rarely used for main memory sorts. • It needs linear extra memory and additional work spent for copying the temporary array slows down the algorithm considerably.
Quicksort • The quicksort is the fastest known sorting algorithm in practice. • Its average running time is O(N log N). • It has O(N2) worst-case performance, but it can be made very unlikely with little effort. • Like mergesort, quicksort is divide and conquer recursive algorithm. • The basic steps to sort an array S are: • If the number of elements in S is 0 or 1, then return. • Pick an element v in S. It is called the pivot. • Partition S-{v} into two disjoint groups: S1 is a set of elements that are smaller or equal to the pivot and S2 is a set of elements that are bigger or equal to the pivot. • Return { quicksort(S1), v, quicksort(S2) }.
Quicksort • The algorithm is ambiguous about what to do with the elements equal to the pivot – this becomes a design decision. • Why is quicksort better then mergesort? • Like mergesort, it recursively solves two sub-problems and requires linear additional work. • The sub-problems are not guaranteed to be of equal size (potentially bad). • Partitioning step can be performed in place and very efficient.
Picking the Pivot • A wrong way: • The popular choice is to use the first element as the pivot. • This is acceptable if the input is random. • If the input is pre-sorted or in the reverse order than all the elements go into S1 or S2, throughout the recursive calls. In such case it takes quadratic time. • This is quite frequent. • A Safe Manoeuvre • Pick the pivot randomly. • Depend on random number generator (might be poor). • Random number generator is expensive. • Median of Three Partitioning • The median of a group of N numbers is the N/2-th largest number. • Best choice is the median of complete file but is hard to calculate. • A good estimate is a median of three elements: left, right, and center elements.
Partitioning strategy • There are several partitioning strategies in practice. • Partitioning strategy, which yields good results: • Get the pivot out of the way by swapping it with last element. • i starts at the first element and j starts at the next-to-last element • While i is left to j, we move i right skipping over elements smaller than the pivot. We move j left, skipping over elements that are larger than the pivot. • When i and j have stopped, i is pointing at a large element and j is pointing at a small element. • If i is left of j, those elements are swapped.
Partitioning strategy • How to handle elements that are equal to the pivot: • If both pointers stop there are extra swapping, but they will cross in the middle. • If pointers skip equal elements, no swap is performed, however the pivot is put to the next-to-last position, which gives O(N2) running time if all elements are equal.
Partitioning strategy • For small files quicksort does not perform as well as insertion sort (N≤20). • Because quicksort is recursive these cases will occur frequently. • Use other sorting algorithm for small files. • If the pivot is median of left, right, and center element, we can order these elements and hide pivot. • Left and right element perform as a sentinel • The starting point of both pointers can be moved by one.
Analysis of Quicksort • Like mergesort quicksort is recursive • T(0)=T(1)=1 • The running time of quicksort is equal to running time of the two recursive calls plus linear time spent in partition: • T(N) = T(i) + T(N-i-1) + cN • Worst-case analysis (pivot is smallest element; i=0, we ignore T(0)=1): • T(N) = T(N-1) + cN, N>1
Analysis of Quicksort • Best-Case analysis: • The pivot is in the middle • T(N) = 2 T(N/2) + cN, N>1 • We add all equations and get • Which yields
Selection Problem • Selection problem: Find k-th largest (smallest) element • Using priority queue, we can find it in O(N+k logN). Finding median requires O(N logN) time. • Since we can sort the array in O(N logN) time we expect to obtain better time bound for selection. • Quick-select algorithm (|S| denotes number of elements in S): • If |S| = 1 (only when k=1), return the element of S as answer. • Pick a pivot element v. • Partition S - {v} into S1 and S2, as in quicksort. • If k ≤ |S1| then the k-th element is in S1 and return quickselect(S1, k) • If k = 1+|S1| then the pivot is the k-th smallest element. • Otherwise, the k-th smallest element is in S2 and return quickselect(S2, k-|S1|-1). • The worst-case running time is O(N2), however average-case running time is O(N).
A General Lower Bound for Sorting • We have O(N log N) algorithms for sorting, but can we do better? • Algorithm that uses only comparisons requires Ω(N log N) comparisons in the worst case. • The same bound can be proven for average case. • Decision tree is an abstraction used to prove lower bounds: • It is a binary tree. • Each node represents a set of possible orderings, consistent with the comparisons that have been made, among the elements. • The results of the comparisons are the tree edges.
A General Lower Bound for Sorting • A decision tree for three element insertion sort:
A General Lower Bound for Sorting • Every algorithm that sorts by using only the comparisons can be represented by a decision tree. • The maximum number of comparisons used by the algorithm is equal to the depth of the deepest leaf. • In our case this algorithm uses three comparisons in the worst case. • The average number of comparisons used is equal to the average depth of the leaves. • Lemma 1: Let T be a binary tree of depth d. Then T has at most 2d leaves. • Proof: By induction: • If d=0, the there is at most one leaf. • Otherwise, we have root, which cannot be a leaf, and left and right subtree, each of depth at most d-1. By induction hypothesis they can each have at most 2d-1 leaves, giving a total of 2d leaves.
A General Lower Bound for Sorting • Lemma 2: A binary tree with L leaves must have depth at least [log L]. • Proof: Follows from previous lemma. • Theorem 1: Any sorting algorithm that uses only comparisons between elements requires at least [log (N!)] comparisons in the worst case. • Proof: A decision tree to sort N elements have N! leaves. The theorem follows from previous lemma. • Theorem 2: Any sorting algorithm that uses only comparisons between elements requires Ω(N log N) comparisons.
A General Lower Bound for Sorting • Proof: From previous theorem, log(N!) comparisons is required. log(N!) = log( N (N-1) (N-2) ··· 2 1) = log N + log(N-1) + log (N-2) + ··· + log 2 + log 1 ≥ log N + log(N-1) + log (N-2) + ··· + log (N/2) ≥ N/2 log (N/2) ≥ N/2 log N – N/2 = Ω( N log N)
Bucket Sort • In special cases it is possible to sort in linear time. • The input consists of only positive integers smaller than M. • Algorithm: • Array Count, of size M, is initialized to 0, (Count has M cells or buckets, which are initially empty), • When Ai is read, increment Count[Ai] by 1, • After all the input is read, scan the Count array and printout indexes of non-empty cells. • Algorithm violates the lower bound? • It does not use simple comparisons. • Essentially performs M-way comparison.