Mergesort – an O(n log n) Sorting Algorithm

Một phần của tài liệu Thuật toán và cấu trúc dữ liệu (Trang 113 - 116)

Mergesort is a straightforward application of the divide-and-conquer principle. The unsorted sequence is split into two parts of about equal size. The parts are sorted recursively, and the sorted parts are merged into a single sorted sequence. This ap- proach is efficient because merging two sorted sequences a and b is quite simple.

The globally smallest element is either the first element of a or the first element of b. So we move the smaller element to the output, find the second smallest element using the same approach, and iterate until all elements have been moved to the out- put. Figure5.2gives pseudocode, and Figure5.3illustrates a sample execution. If the sequences are represented as linked lists (see, Sect. 3.1), no allocation and deal- location of list items is needed. Each iteration of the inner loop of merge performs one element comparison and moves one element to the output. Each iteration takes constant time. Hence, merging runs in linear time.

Theorem 5.1. The function merge, applied to sequences of total length n, executes in time O(n)and performs at most n−1 element comparisons.

For the running time of mergesort, we obtain the following result.

Theorem 5.2. Mergesort runs in time O(n log n)and performs no more thann log n element comparisons.

104 5 Sorting and Selection

Function mergeSort(e1, . . . ,en) : Sequence of Element if n=1 then returne1

else return merge( mergeSort(e1, . . . ,en/2), mergeSort(en/2+1, . . . ,en)) // merging two sequences represented as lists

Function merge(a,b : Sequence of Element) : Sequence of Element c :=

loop

invariant a, b, and c are sorted and∀ec,eab : ee if a.isEmpty then c.concat(b); return c

if b.isEmpty then c.concat(a); return c if a.firstb.first then c.moveToBack(a.first)

else c.moveToBack(b.first)

Fig. 5.2. Mergesort 2,7,1,8,2,8,1

1,1,2,2,7,8,8 2,7,1

1,2,7 2

2 7,1

1,7 7

8,2,8,1

1,2,8,8 8,1

1,8 1 18 8

8,2

merge 2,8 merge merge split split split

a b c operation

1,2,7 1,2,8,8 move a

2,7 1,2,8,8 1 move b

2,7 2,8,8 1,1 move a

7 2,8,8 1,1,2 move b

7 8,8 1,1,2,2 move a

8,8 1,1,2,2,7 concat b

1,1,2,2,7,8,8

Fig. 5.3. Execution of mergeSort(2,7,1,8,2,8,1). The left part illustrates the recursion in mergeSort and the right part illustrates the merge in the outermost call

Proof. Let C(n)denote the worst-case number of element comparisons performed.

We have C(1) =0 and C(n)≤C( n/2) +C(n/2) +n−1, using Theorem5.1. The master theorem for recurrence relations (2.5) suggests that C(n) =O(n log n). We shall give two proofs. The first proof shows that C(n)2nlog n, and the second proof shows that C(n)≤nlog n.

For n a power of two, we define D(1) =0 and D(n) =2D(n/2) +n. Then D(n) = n log n for n a power of two, by the master theorem for recurrence relations. We claim that C(n)≤D(2k), where k is such that 2k−1<n≤2k. Then C(n)≤D(2k) =2kk≤ 2nlog n. It remains to argue the inequality C(n)≤D(2k). We use induction on k.

For k=0, we have n=1 and C(1) =0=D(1), and the claim certainly holds. For k>1, we observe that n/2 ≤ n/22k−1, and hence

C(n)≤C( n/2) +C(n/2) +n−12D(2k−1) +2k1≤D(2k). This completes the first proof. We turn now to the second, refined proof. We prove that

C(n)≤nlog n −2log n+1≤n log n

by induction over n. For n=1, the claim is certainly true. So, assume n>1. We distinguish two cases. Assume first that we have 2k−1< n/2 ≤ n/22k for some integer k. Thenlog n/2=logn/2=k andlog n=k+1, and hence

C(n)≤C( n/2) +C(n/2) +n−1

n/2k−2k+1 +

n/2k−2k+1 +n−1

=nk+n−2k+1+1=n(k+1)2k+1+1=nlog n −2log n+1. Otherwise, we have n/2=2k−1 andn/2=2k−1+1 for some integer k, and thereforelog n/2=k−1,logn/2=k, andlog n=k+1. Thus

C(n)≤C( n/2) +C(n/2) +n−1

2k1(k−1)2k1+1 +

(2k1+1)k−2k+1

+2k+11

= (2k+1)k−2k12k1+1+1

= (2k+1)(k+1)2k+1+1=nlog n −2log n+1.

The bound for the execution time can be verified using a similar recurrence relation.

Mergesort is the method of choice for sorting linked lists and is therefore fre- quently used in functional and logical programming languages that have lists as their primary data structure. In Sect.5.3, we shall see that mergesort is basically optimal as far as the number of comparisons is concerned; so it is also a good choice if compar- isons are expensive. When implemented using arrays, mergesort has the additional advantage that it streams through memory in a sequential way. This makes it efficient in memory hierarchies. Section5.7has more on that issue. Mergesort is still not the usual method of choice for an efficient array-based implementation, however, since merge does not work in-place. (But see Exercise5.17for a possible way out.) Exercise 5.12. Explain how to insert k new elements into a sorted list of size n in time O(k log k+n).

Exercise 5.13. We discussed merge for lists but used abstract sequences for the de- scription of mergeSort. Give the details of mergeSort for linked lists.

Exercise 5.14. Implement mergesort in a functional programming language.

Exercise 5.15. Give an efficient array-based implementation of mergesort in your fa- vorite imperative programming language. Besides the input array, allocate one aux- iliary array of size n at the beginning and then use these two arrays to store all inter- mediate results. Can you improve the running time by switching to insertion sort for small inputs? If so, what is the optimal switching point in your implementation?

106 5 Sorting and Selection

Exercise 5.16. The way we describe merge, there are three comparisons for each loop iteration – one element comparison and two termination tests. Develop a variant using sentinels that needs only one termination test. Can you do this task without appending dummy elements to the sequences?

Exercise 5.17. Exercise 3.20 introduced a list-of-blocks representation for sequences.

Implement merging and mergesort for this data structure. During merging, reuse emptied input blocks for the output sequence. Compare the space and time efficiency of mergesort for this data structure, for plain linked lists, and for arrays. Pay attention to constant factors.

Một phần của tài liệu Thuật toán và cấu trúc dữ liệu (Trang 113 - 116)

Tải bản đầy đủ (PDF)

(305 trang)