Data Structures, Algorithms, & Applications in Java
Shell Sort
Copyright 1999 Sartaj Sahni

Let a1, ..., an be a sequence of n elements. Elements ai and aj, i < j, are inverted iff ai > aj. The number of pairs (i,j) such that ai and aj are inverted is the inversion number of the element sequence. Exercise 19.46 shows that the inversion number of an unsorted sequence in O(n2) while that of a sorted sequence is 0. Consequently, sort-methods such as those of Chapter 2, which reduce the inversion number by at most one each time two elements are compared, must take O(n2) time to sort n elements.

Asymptotically faster sort methods are possible only if we can reduce the inversion number by more than Omega(1) following a comparison. The sort method due to Donald Shell and known as Shell Sort compares elements that are far apart rather than adjacent pairs of elements in an attempt to make larger reductions in the inversion number. For example, suppose we are sorting the sequence 5, 4, 3, 2, 1. By comparing and swapping the adjacent pair (5, 4), as is done in bubble sort, the inversion number is reduced by 1. However, when we compare and swap the pair (5, 1), the inversion number reduces by 4.

In Shell sort, we use a sequence of diminishing increments s1 > s2 > s3 ... sq = 1. (Because of this, Shell sort is also known as diminishing increment sort.) For any increment x, we view the sequence a[0:n-1] to be sorted as being comprised of several subsequences. The first is a[0], a[0+x], a[0+2x], ...; the second is a[1], a[1+x], a[1+2x], ...; the third is a[2], a[2+x], a[2+2x], ...; etc. These subsequences are comprised of elements that are x units apart in the array. Each subsequence is sorted using insertion sort and then the process is repeated using the next increment. Since the last increment is 1, a[0:n-1] has only one subsequence when the last increment is used. This subsequence is a[0], a[1], a[2], ... Performing an insertion sort in this subsequence guarantees that the result is sorted. The insertion sorts done with the earlier increments allow elements to move toward their final spots by making large jumps. The net result is a faster sort than when we simply use insertion sort. Notice that insertion sort is identical to shell sort when the increment sequence is just 1.

Suppose we use the increment sequence 3, 1 to sort the elements 5, 4, 3, 2, 1. For the increment 3, the subsequences are 5, 2; 4, 1; and 3. Sorting these subsequences using the insertion sort method yields 2, 1, 3, 5, 4. The next and final increment to use is 1. Sorting with this increment results in the sorted sequence 1, 2, 3, 4, 5.

Although any diminishing sequence of increments (with the last one being 1) can be used, the performance of Shell sort depends acutely on the particular increment sequence that is used. Donald Knuth's book The art of computer programming::Sorting and searching, volume 3, 2nd Edition, Addison-Wesley, 1998 contains a discussion of the asymptotic complexity of Shell sort using various increment sequences. An empirical evaluation appears in the paper ``Empirical results on the running time of Shellsort,'' by M. Weiss, Computer Journal, 1991, 88-91. The complexity of Shell sort is O(n1.5) when the increment sequence is of the form si = 2t-i-1 for t = floor(log2n) and 0 <= i <= t as well as when the sequence is derived in the following way: start with an increment of n/2; to get the next increment, divide the current increment by 2, if the result is even, add 1 to make the increment odd; continue until the increment becomes 1. Weiss has observed that the observed performance is better when the next increment is obtained by dividing by 2.2 rather than by 2.

The code for Shell sort using the divide by 2.2 strategy to generate succeeding increments is given below.
public class ShellSort
{
   /** sort the elements a[0 : a.length - 1] using
     * the Shell sort method */
   public static void shellSort(Comparable [] a)
   {
      int incr = a.length / 2;  // initial increment
      while (incr >= 1)
      {// insertion sort all sequences spaced by incr
         for (int i = incr; i < a.length; i++)
         {// insert a[i] into a[i - incr], a[i - 2*incr], ...
            Comparable insertElement = a[i];
            int j;
            for (j = i - incr;
                 j >= 0 && insertElement.compareTo(a[j]) < 0;
                 j -= incr)
               a[j + incr] = a[j];
            a[j + incr] = insertElement;
         }
   
         // reduce increment by a factor of 2.2
         if (incr == 2)
            incr = 1;  // last increment must be 1
         else
            incr = (int) (incr / 2.2);
      }
   }
}