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:
- Divide the sequence to be sorted into several subsequences (the elements of each subsequence have the same spacing in the original array);
- Apply insertion sort to these subsequences;
- Reduce the spacing between elements in each subsequence and repeat the above process until the spacing is reduced to .
Properties
Stability
Shell sort is an unstable sorting algorithm.
Time complexity
The best-case time complexity of Shell sort is .
The average-case and worst-case time complexities of Shell sort depend on the choice of the gap sequence. Let the gap sequence be . Two classic choices of are given below; both reduce the complexity of the sorting algorithm to the level.
Proposition 1
If the gap sequence is (from large to small), then the time complexity of Shell sort is .
Proposition 2
If the gap sequence is (from large to small), then the time complexity of Shell sort is .
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 , no matter how the function is called afterward or how the array changes, the following properties will always be maintained:
Next we prove Theorem 1.
We first prove Lemma 1.
Lemma 1
For integers , a positive integer , and two arrays satisfying:
after sorting the two arrays in ascending order separately, the above requirements still hold.
Proof of Lemma 1
Let the sorted array be and the sorted array be .
For any , is no smaller than elements in array , and is also no smaller than elements in array (because and have the same multiset of elements).
Therefore, among the multiset , no more than elements are greater than or equal to .
Thus at least elements are smaller than . Take of them and denote them by . Then:
Hence is at least as large as elements in , and therefore also in , so naturally .
Returning to the proof of the original proposition:
In fact, we only need to prove that after the immediate next call to following , the subsequences remain sorted; the rest then follows easily by induction. We consider only the next call below:
After executing , the following groups have been sorted:
Executing afterward sorts the following groups:
For each , consider the following two groups:
The reason the second group also begins with 「」 is that there may be elements before it when .
The second group is the array in Lemma , the first group is the array, is the length from in the second group to its end, is the length of the preceding 「」 in the second group, and is the number of elements remaining in the first group after removing its first elements.
Moreover, we have:
Therefore, by Lemma , after sorts the two groups separately, this relation still holds; that is, still holds.
If , it is easy to see that adding several 's to a positive integer can obtain , so the previous case already implies the proof of this case.
Combining the above discussion, we have after executing .
Thus Theorem 1 is proved.
This theorem reveals the key reason why Shell sort can optimize its complexity under a particular set : throughout the process, it consistently preserves the previous results from being destroyed (that is, the subsequences remain sorted), greatly reducing the number of movements of pointer 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 .
Lemma 2
If are both positive integers and coprime, then the largest positive integer not in the set is .
Proof of Lemma 2
We prove it in two steps:
-
First prove that the equation has no solution where are both non-negative integers:
Without the non-negative-integer restriction, two solutions are readily obtained: .
From the general solution , it is easy to see that the two solutions above are 「adjacent」 (because ).
As increases, increases and 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 , the equation has a non-negative integer solution:
Find a solution satisfying (this can be done from the general solution).
Then:
Therefore . Since , , so .
Thus is a solution in non-negative integers.
This completes the proof.
The following theorem reveals how Lemma extends Theorem .
Theorem 2
If , then after the program executes and , the time complexity of executing is , and for each , the number of movements of is of order .
Proof of Theorem 2
For the part where , the number of movements of is clearly of order .
Therefore, below we assume .
For any positive integer satisfying , note that:
Since , Lemma implies that there exist non-negative integers such that .
Therefore:
By Theorem :
and
Combining these gives .
Therefore, for any , we have .
In the Shell-Sort pseudocode, pointer decreases by each time. After decreasing times, we have , and therefore ; the while-loop condition is not satisfied and the loop exits.
After proving the movement complexity for every , we obtain the total time complexity:
This proves the theorem.
By carefully examining the proof of Theorem , we can see that Theorem 1 can be linearly combined: if is ordered with spacing and also ordered with spacing , then it remains ordered with any non-negative integer linear combination of and . This 「linearity」 is guaranteed by Lemma .
With these two theorems, we can prove Propositions and .
Proof of Proposition 1
Write in sequence form:
The execution order of Shell sort is: .
Analyze the complexity in two parts:
-
For the initial terms satisfying , of which the terms are , the time complexity of is clearly .
Consider the term closest to :
For terms , since , we obtain:
Thus, the total time complexity of the part at least is:
-
For the remaining terms satisfying , the complexity of the first two terms is still . For the later terms , Theorem gives:
Using the property again, the total time complexity of this part is (where retains its meaning from the previous case):
Therefore, the total time complexity is .
Proof of Proposition 2
Observe the following fact: if and have already been executed, then because , Theorem shows that for each element only the previous adjacent element may be greater than it; all preceding elements are smaller than it. Therefore, pointer needs at most two iterations to exit the while loop. In other words, executing at this point has complexity .
More generally, if and 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 and on these two subsequences separately. Thus, executing now is equivalent to executing on each of the two subsequences; it only needs the order of the sum of the two sequence lengths, namely complexity, to make the array ordered with spacing .
Continuing by induction, we obtain: if and have already been executed, then executing also has complexity only .
Analyze the complexity in two parts:
-
For the terms with , each has complexity .
Since , the complexity of each insertion sort is .
The number of elements in this part is of order , so its time complexity is .
-
For the terms with , since , and have already been executed before this. Therefore, executing has time complexity .
Likewise, the number of elements in this part is also of order , so its time complexity is .
Therefore, the total time complexity is .
Space Complexity
The space complexity of Shell sort is .
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)