Shell sort

This page briefly introduces Shell sort.

Definition

Shell sort (English: Shell sort), also called diminishing-increment sort, is an improved version of insertion sort. Shell sort is named after its inventor, Donald Shell.

Process

The sort compares and moves non-adjacent records:

  1. Divide the sequence to be sorted into several subsequences (the elements of each subsequence have the same spacing in the original array);
  2. Apply insertion sort to these subsequences;
  3. Reduce the spacing between elements in each subsequence and repeat the above process until the spacing is reduced to 11.

Properties

Stability

Shell sort is an unstable sorting algorithm.

Time complexity

The best-case time complexity of Shell sort is O(n)O(n).

The average-case and worst-case time complexities of Shell sort depend on the choice of the gap sequence. Let the gap sequence be HH. Two classic choices of HH are given below; both reduce the complexity of the sorting algorithm to the o(n2)o(n^2) level.

Proposition 1

If the gap sequence is H={2k−1∣k=1,2,…,⌊log⁡2n⌋}H= \{ 2^k-1\mid k=1,2,\ldots,\lfloor\log_2 n\rfloor \} (from large to small), then the time complexity of Shell sort is O(n3/2)O(n^{3/2}).

Proposition 2

If the gap sequence is H={k=2p⋅3q∣p,q∈N,k≤n}H= \{ k=2^p\cdot 3^q\mid p,q\in \mathbb N,k\le n \} (from large to small), then the time complexity of Shell sort is O(nlog⁡2n)O(n\log^2 n).

To prove these two propositions, we first give and prove an important theorem that reflects the most important characteristic of Shell sort.

Theorem 1

Once the program has executed InsertionSort(h)\text{InsertionSort}(h), no matter how the InsertionSort\text{InsertionSort} function is called afterward or how the array AA changes, the following properties will always be maintained:

A1,A1+h,A1+2h,…A2,A2+h,A2+2h,…⋮Ah,Ah+h,Ah+2h,…\begin{array}{c} A_1,A_{1+h},A_{1+2h},\ldots \\ A_2,A_{2+h},A_{2+2h},\ldots \\ \vdots \\ A_h,A_{h+h},A_{h+2h},\ldots \end{array}

Next we prove Theorem 1.

We first prove Lemma 1.

Lemma 1

For integers n,mn,m, a positive integer ll, and two arrays X(x1,x2,…,xn+l),Y(y1,y2,…,ym+l)X(x_1,x_2,\ldots,x_{n+l}),Y(y_1,y_2,\ldots,y_{m+l}) satisfying:

y1≤xn+1,y2≤xn+2,…,yl≤xn+ly_1 \le x_{n+1},y_2 \le x_{n+2},\ldots,y_l \le x_{n+l}

after sorting the two arrays in ascending order separately, the above requirements still hold.

Proof of Lemma 1

Let the sorted array XX be X′(x1′,…,xn+l′)X'(x'_1,\ldots,x'_{n+l}) and the sorted array YY be Y′(y1′,…,ym+l′)Y'(y'_1,\ldots,y'_{m+l}).

For any 1≤i≤l1\le i\le l, xn+i′x'_{n+i} is no smaller than l−il-i elements in array X′X', and is also no smaller than l−il-i elements in array XX (because XX and X′X' have the same multiset of elements).

Therefore, among the multiset {xn+1,…,xn+l}⊂X\{x_{n+1},\ldots,x_{n+l} \} \subset X, no more than l−il-i elements are greater than or equal to xn+i′x'_{n+i}.

Thus at least ii elements are smaller than xn+i′x'_{n+i}. Take ii of them and denote them by xn+k1,xn+k2,…,xn+kix_{n+k_1},x_{n+k_2},\ldots,x_{n+k_i}. Then:

yk1≤xn+k1≤xn+i′,yk2≤xn+k2≤xn+i′,…,yki≤xn+ki≤xn+i′y_{k_1}\le x_{n+k_1}\le x'_{n+i},y_{k_2}\le x_{n+k_2}\le x'_{n+i},\ldots,y_{k_i}\le x_{n+k_i}\le x'_{n+i}

Hence xn+i′x'_{n+i} is at least as large as ii elements in YY, and therefore also in Y′Y', so naturally yi′≤xn+i′ (1≤i≤l)y'_i\le x'_{n+i}\,(1\le i\le l).

Returning to the proof of the original proposition:

In fact, we only need to prove that after the immediate next call to InsertionSort(k)\text{InsertionSort}(k) following InsertionSort(h)\text{InsertionSort}(h), the hh subsequences remain sorted; the rest then follows easily by induction. We consider only the next call below:

After executing InsertionSort(h)\text{InsertionSort}(h), the following groups have been sorted:

A1,A1+h,A1+2h,…A2,A2+h,A2+2h,…⋮Ah,Ah+h,Ah+2h,…\begin{array}{c} A_1,A_{1+h},A_{1+2h},\ldots \\ A_2,A_{2+h},A_{2+2h},\ldots \\ \vdots \\ A_h,A_{h+h},A_{h+2h},\ldots \end{array}

Executing InsertionSort(k)\text{InsertionSort}(k) afterward sorts the following groups:

A1,A1+k,A1+2k,…A2,A2+k,A2+2k,…⋮Ak,Ak+k,Ak+2k,…\begin{array}{c} A_1,A_{1+k},A_{1+2k},\ldots \\ A_2,A_{2+k},A_{2+2k}, \ldots \\ \vdots \\ A_k,A_{k+k},A_{k+2k},\ldots \end{array}

For each ii (1≤i≤min⁡(h,k))(1\le i\le \min(h,k)), consider the following two groups:

Ai,Ai+k,Ai+2k,……,Ai+h,Ai+h+k,Ai+h+2k,…\begin{array}{c} A_i,A_{i+k},A_{i+2k},\ldots \\ \ldots,A_{i+h},A_{i+h+k},A_{i+h+2k},\ldots \end{array}

The reason the second group also begins with 「…\ldots」 is that there may be elements before it when i+h≥ki+h\ge k.

The second group is the XX array in Lemma 11, the first group is the YY array, ll is the length from i+hi+h in the second group to its end, nn is the length of the preceding 「…\ldots」 in the second group, and mm is the number of elements remaining in the first group after removing its first ll elements.

Moreover, we have:

Ai≤Ai+h,Ai+k≤Ai+h+k,…A_i\le A_{i+h},A_{i+k}\le A_{i+h+k},\ldots

Therefore, by Lemma 11, after InsertionSort(k)\text{InsertionSort}(k) sorts the two groups separately, this relation still holds; that is, Ai≤Ai+h (1≤i≤min⁡(h,k))A_i\le A_{i+h}\,(1\le i\le \min(h,k)) still holds.

If i>min⁡(h,k)i>\min(h,k), it is easy to see that adding several kk's to a positive integer ww (1≤w≤min⁡(h,k))(1\le w\le \min(h,k)) can obtain ii, so the previous case already implies the proof of this case.

Combining the above discussion, we have Ai≤Ai+h (1≤i≤n−h)A_i\le A_{i+h}\,(1\le i\le n-h) after executing InsertionSort(k)\text{InsertionSort}(k).

Thus Theorem 1 is proved.

This theorem reveals the key reason why Shell sort can optimize its complexity under a particular set HH: throughout the process, it consistently preserves the previous results from being destroyed (that is, the hh subsequences remain sorted), greatly reducing the number of movements of pointer ii in later calls.

Next we single out and prove a number-theoretic lemma. This theorem became famous in the OI community because of the problem Xiaokai's Confusion. In the proof of Shell sort's complexity, it also greatly extends Theorem 11.

Lemma 2

If a,ba,b are both positive integers and coprime, then the largest positive integer not in the set {ax+by∣x,y∈N}\{ax+by\mid x,y\in \mathbb N \} is ab−a−bab-a-b.

Proof of Lemma 2

We prove it in two steps:

  • First prove that the equation ax+by=ab−a−bax+by=ab-a-b has no solution where x,yx,y are both non-negative integers:

    Without the non-negative-integer restriction, two solutions are readily obtained: (b−1,−1),(−1,a−1)(b-1,-1),(-1,a-1).

    From the general solution x=x0+tb,y=y0−tax=x_0+tb,y=y_0-ta, it is easy to see that the two solutions above are 「adjacent」 (because b−1−b=−1b-1-b=-1).

    As tt increases, xx increases and yy decreases. Therefore, if the equation had a non-negative integer solution, it would have to lie between these two solutions, but these two solutions are 「adjacent」, with no other solution between them.

    Hence no non-negative integer solution exists.

  • Next prove that for every integer c>ab−a−bc > ab-a-b, the equation ax+by=cax+by=c has a non-negative integer solution:

    Find a solution (x0,y0)(x_0,y_0) satisfying 0≤x0<b0\le x_0 < b (this can be done from the general solution).

    Then:

    by0=c−ax0≥c−a(b−1)>ab−a−b−ab+a=−bby_0=c-ax_0\ge c-a(b-1)>ab-a-b-ab+a=-b

    Therefore b(y0+1)>0b(y_0+1) > 0. Since b>0b>0, y0+1>0y_0+1>0, so y0≥0y_0\ge 0.

    Thus (x0,y0)(x_0,y_0) is a solution in non-negative integers.

This completes the proof.

The following theorem reveals how Lemma 22 extends Theorem 11.

Theorem 2

If gcd⁡(ht+1,ht)=1\gcd(h_{t+1},h_t)=1, then after the program executes InsertionSort(ht+1)\text{InsertionSort}(h_{t+1}) and InsertionSort(ht)\text{InsertionSort}(h_t), the time complexity of executing InsertionSort(ht−1)\text{InsertionSort}(h_{t-1}) is O(nht+1htht−1)O\left(\dfrac{nh_{t+1}h_t}{h_{t-1}} \right), and for each jj, the number of movements of ii is of order O(ht+1htht−1)O\left(\dfrac{h_{t+1}h_t}{h_{t-1}} \right).

Proof of Theorem 2

For the part where j≤ht+1htj\le h_{t+1}h_t, the number of movements of ii is clearly of order O(ht+1htht−1)O\left(\dfrac{h_{t+1}h_t}{h_{t-1}} \right).

Therefore, below we assume j>ht+1htj>h_{t+1}h_t.

For any positive integer kk satisfying 1≤k≤j−ht+1ht1\le k\le j-h_{t+1}h_t, note that: ht+1ht−ht+1−ht<ht+1ht≤j−k≤j−1h_{t+1}h_t-h_{t+1}-h_t<h_{t+1}h_t\le j-k\le j-1

Since gcd⁡(ht+1,ht)=1\gcd(h_{t+1},h_t)=1, Lemma 22 implies that there exist non-negative integers a,ba,b such that aht+1+bht=j−kah_{t+1}+bh_t=j-k.

Therefore:

k=j−aht+1−bhtk=j-ah_{t+1}-bh_t

By Theorem 11:

Aj−bht≤Aj−(b−1)ht≤…≤Aj−ht≤AjA_{j-bh_t}\le A_{j-(b-1)h_t}\le \ldots\le A_{j-h_t}\le A_j

and

Aj−bht−aht+1≤Aj−bht−(a−1)ht+1≤…≤Aj−bht−ht+1≤Aj−bhtA_{j-bh_t-ah_{t+1}}\le A_{j-bh_t-(a-1)h_{t+1}}\le \ldots\le A_{j-bh_t-h_{t+1}}\le A_{j-bh_t}

Combining these gives Ak=Aj−aht+1−bht≤AjA_k=A_{j-ah_{t+1}-bh_t}\le A_j.

Therefore, for any 1≤k≤j−ht+1ht1\le k\le j-h_{t+1}h_t, we have Ak≤AjA_k\le A_j.

In the Shell-Sort pseudocode, pointer ii decreases by ht−1h_{t-1} each time. After decreasing O(ht+1htht−1)O\left(\dfrac{h_{t+1}h_t}{h_{t-1}} \right) times, we have i≤j−ht+1hti\le j-h_{t+1}h_t, and therefore Ai≤AjA_i\le A_j; the while-loop condition is not satisfied and the loop exits.

After proving the movement complexity for every jj, we obtain the total time complexity:

∑j=ht−1+1nO(ht+1htht−1)=O(nht+1htht−1)\sum_{j=h_{t-1}+1}^n{O\left(\frac{h_{t+1}h_t}{h_{t-1}} \right)}=O\left(\frac{nh_{t+1}h_t}{h_{t-1}}\right)

This proves the theorem.

By carefully examining the proof of Theorem 22, we can see that Theorem 1 can be linearly combined: if AA is ordered with spacing hh and also ordered with spacing kk, then it remains ordered with any non-negative integer linear combination of hh and kk. This 「linearity」 is guaranteed by Lemma 22.

With these two theorems, we can prove Propositions 11 and 22.

Proof of Proposition 1

Write HH in sequence form:

H(h1=1,h2=3,h3=7,…,h⌊log⁡2n⌋=2⌊log⁡2n⌋−1)H(h_1=1,h_2=3,h_3=7,\ldots,h_{\lfloor \log_2 n\rfloor}=2^{\lfloor \log_2 n\rfloor}-1)

The execution order of Shell sort is: InsertionSort(h⌊log⁡2n⌋),InsertionSort(h⌊log⁡2n⌋−1),…,InsertionSort(h2),InsertionSort(h1)\text{InsertionSort}(h_{\lfloor \log_2 n\rfloor}),\text{InsertionSort}(h_{\lfloor \log_2 n\rfloor-1}),\ldots,\text{InsertionSort}(h_2),\text{InsertionSort}(h_1).

Analyze the complexity in two parts:

  • For the initial terms satisfying ht≥nh_t\ge \sqrt{n}, of which the terms are hth_t, the time complexity of InsertionSort(ht)\text{InsertionSort}(h_t) is clearly O(n2ht)O\left(\dfrac{n^2}{h_t} \right).

    Consider the term hkh_k closest to n\sqrt{n}:

    O(n2ht)=O(n3/2)O\left(\frac{n^2}{h_t} \right)=O(n^{3/2})

    For i>ki> k terms hih_i, since 2hi<hi+12h_i< h_{i+1}, we obtain:

    O(n2hi)=O(n3/2/2i−k) (i>k)O\left(\frac{n^2}{h_i} \right)=O(n^{3/2}/2^{i-k})\,(i>k)

    Thus, the total time complexity of the part at least n\sqrt n is:

    ∑i=k⌊log⁡2n⌋O(n3/2/2i−k)=O(n3/2)\sum_{i=k}^{\lfloor \log_2 n\rfloor}{O(n^{3/2}/2^{i-k})}=O(n^{3/2})
  • For the remaining terms satisfying ht<nh_t< \sqrt{n}, the complexity of the first two terms is still O(n3/2)O(n^{3/2}). For the later terms hth_t, Theorem 22 gives:

    O(nht+2ht+1ht)=O(nht+2⋅ht+2/2ht+2/4)=O(nht+2)O\left(\frac{nh_{t+2}h_{t+1}}{h_t} \right)=O\left(\frac{nh_{t+2}\cdot h_{t+2}/2}{h_{t+2}/4} \right)=O(nh_{t+2})

    Using the property 2hi<hi+12h_i < h_{i+1} again, the total time complexity of this part is (where kk retains its meaning from the previous case):

    2O(n3/2)+∑i=1k−3O(nhi+1)=O(n3/2)+∑i=1k−3O(nhk−1/2k−i−3)=O(n3/2)+O(nhk−1)=O(n3/2)2O(n^{3/2})+\sum_{i=1}^{k-3}{O(nh_{i+1})}=O(n^{3/2})+\sum_{i=1}^{k-3}{O(nh_{k-1}/2^{k-i-3})}=O(n^{3/2})+O(nh_{k-1})=O(n^{3/2})

Therefore, the total time complexity is O(n3/2)O(n^{3/2}).

Proof of Proposition 2

Observe the following fact: if InsertionSort(2)\text{InsertionSort}(2) and InsertionSort(3)\text{InsertionSort}(3) have already been executed, then because 2⋅3−2−3=12\cdot 3-2-3=1, Theorem 22 shows that for each element only the previous adjacent element may be greater than it; all preceding elements are smaller than it. Therefore, pointer ii needs at most two iterations to exit the while loop. In other words, executing InsertionSort(1)\text{InsertionSort}(1) at this point has complexity O(n)O(n).

More generally, if InsertionSort(4)\text{InsertionSort}(4) and InsertionSort(6)\text{InsertionSort}(6) have already been executed, consider the subsequence of elements with odd indices and the subsequence of elements with even indices. This is equivalent to executing InsertionSort(2)\text{InsertionSort}(2) and InsertionSort(3)\text{InsertionSort}(3) on these two subsequences separately. Thus, executing InsertionSort(2)\text{InsertionSort}(2) now is equivalent to executing InsertionSort(1)\text{InsertionSort}(1) on each of the two subsequences; it only needs the order of the sum of the two sequence lengths, namely O(n)O(n) complexity, to make the array ordered with spacing 22.

Continuing by induction, we obtain: if InsertionSort(2h)\text{InsertionSort}(2h) and InsertionSort(3h)\text{InsertionSort}(3h) have already been executed, then executing InsertionSort(h)\text{InsertionSort}(h) also has complexity only O(n)O(n).

Analyze the complexity in two parts:

  • For the terms with ht>n/3h_t>n/3, each InsertionSort(ht)\text{InsertionSort}(h_t) has complexity O(n2/ht)O(n^2/h_t).

    Since n2/ht<3nn^2/h_t<3n, the complexity of each insertion sort is O(n)O(n).

    The number of elements in this part is of order O(log⁡2n)O(\log^2 n), so its time complexity is O(nlog⁡2n)O(n\log^2 n).

  • For the terms with ht≤n/3h_t\le n/3, since 3ht≤n3h_t\le n, InsertionSort(2ht)\text{InsertionSort}(2h_t) and InsertionSort(3ht)\text{InsertionSort}(3h_t) have already been executed before this. Therefore, executing InsertionSort(ht)\text{InsertionSort}(h_t) has time complexity O(n)O(n).

    Likewise, the number of elements in this part is also of order O(log⁡2n)O(\log^2 n), so its time complexity is O(nlog⁡2n)O(n\log^2 n).

Therefore, the total time complexity is O(nlog⁡2n)O(n\log^2 n).

Space Complexity

The space complexity of Shell sort is O(1)O(1).

Implementation

C++1

template <typename T>
void shell_sort(T array[], int length) {
  int h = 1;
  while (h < length / 3) {
    h = 3 * h + 1;
  }
  while (h >= 1) {
    for (int i = h; i < length; i++) {
      for (int j = i; j >= h && array[j] < array[j - h]; j -= h) {
        std::swap(array[j], array[j - h]);
      }
    }
    h = h / 3;
  }
}

Python

def shell_sort(array, length):
    h = 1
    while h < length / 3:
        h = int(3 * h + 1)
    while h >= 1:
        for i in range(h, length):
            j = i
            while j >= h and array[j] < array[j - h]:
                array[j], array[j - h] = array[j - h], array[j]
                j -= h
        h = int(h / 3)

References and notes

Footnotes

  1. Shell sort - Wikipedia, the free encyclopedia ↩

This content comes from OI Wiki: Shell sort. See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.