Updated on Apr 23, 2023 19:59 IST

Shell Sort was introduced by Donald Shell in 1959 and is known for its efficiency compared to other sorting algorithms. It is a variation of insertion sort that provides better performance by sorting the elements at a distance, rather than comparing neighboring elements.

In this article, we will explore Shell Sort algorithm in detail with the help of examples.

What is shell sort algorithm?

Sorting is the process of arranging a set of elements in some specified order. Shell Sort algorithm sorts an array by dividing the array into smaller sub-arrays and then sorting those sub-arrays using insertion sort.

Algorithm:

The algorithm for Shell Sort is as follows:

1. Select a gap sequence, which is an array of positive integers that determine the sub-array sizes.

gap = floor(n/2) where n = the number of elements in the array.

The gap value is calculated like this:

gap = floor(n/2)

gap1 = floor(gap/2)

gap2 = floor(gap1/2) and so on till gap=1.

Original List:

N = 10

gap = floor(N/2) = floor(10/2) = 5

Pass 1:

Select the two elements at the gap of 5 (same color elements in the image below), compare them, and swap them.

1. Divide the array into sub-arrays of the size determined by the first gap value.

Array before swaps

1. Sort each sub-array using insertion sort.

Array after swaps

1. Repeat the above two steps for each gap value, in descending order.

Pass 2:

gap = floor(5/2) = 2

Select the elements at the gap of 2 (same color elements in the image below), compare them, and swap them.

Array before swaps

Array after swaps

Pass 3:

Array before swaps

gap = floor(2/2) = 1

Select the elements at the gap of 1 (same color elements in the image below) and keep comparing the adjacent elements, at the end, you get the sorted array.

1. The final pass is performed with a gap size of one, equivalent to using insertion sort.

Final Array

Implementation of Shell Sort in Different Languages

Let’s have a look into the algorithm for performing shell sort along with its implementation in different languages.

function shellSort(arr):

// Find the length of the array

n = length(arr)

// Start with a gap sequence, and keep reducing the gap until it becomes 1

for gap = floor(n/2) down to 1:

// Perform insertion sort on each sub-array defined by the gap sequence

for i = gap to n-1:

// Save the current element as a temporary variable

temp = arr[i]

// Compare elements that are ‘gap’ distance apart

j = i

while j >= gap and arr[j – gap] > temp:

// Shift elements in the sub-array by ‘gap’ distance

arr[j] = arr[j – gap]

// Move the pointer to the previous element in the sub-array

j = j – gap

// Insert the temporary variable into the correct position

arr[j] = temp

In this code, arr is the array to be sorted, n is the length of the array, and gap is the gap sequence used in the algorithm. The for loop iterates over the gap sequence until the gap size is equal to 1. Within the for loop, the algorithm performs insertion sort on each sub-array defined by the gap sequence. The while loop within the for loop performs the insertion sort operation on each sub-array.

The time complexity of this algorithm depends on the gap sequence used. The best gap sequence is still an open research problem, but some popular choices include the Knuth sequence and the Sedgewick sequence. The worst-case time complexity of Shell sort is O(n^2), but on average, it has a faster time complexity of O(n log n) or O(n^(3/2)) depending on the gap sequence used.

Java Implementation

` `
```public static void shellSort(int[] arr) { int n = arr.length; // Start with a large gap size and keep reducing it for (int gap = n / 2; gap > 0; gap /= 2) { // Perform insertion sort on each sub-array defined by the gap sequence for (int i = gap; i < n; i++) { int temp = arr[i]; int j = i; // Compare elements that are 'gap' distance apart while (j >= gap && arr[j - gap] > temp) { // Shift elements in the sub-array by 'gap' distance arr[j] = arr[j - gap]; // Move the pointer to the previous element in the sub-array j -= gap; } // Insert the temporary variable into the correct position arr[j] = temp; } }}Copy code```

C++ Implementation

` `
```void shellSort(int arr[], int n) { // Start with a large gap size and keep reducing it for (int gap = n / 2; gap > 0; gap /= 2) { // Perform insertion sort on each sub-array defined by the gap sequence for (int i = gap; i < n; i++) { int temp = arr[i]; int j = i; // Compare elements that are 'gap' distance apart while (j >= gap && arr[j - gap] > temp) { // Shift elements in the sub-array by 'gap' distance arr[j] = arr[j - gap]; // Move the pointer to the previous element in the sub-array j -= gap; } // Insert the temporary variable into the correct position arr[j] = temp; } }}Copy code```

Python Implementation

` `
```def shellSort(arr): n = len(arr) # Start with a large gap size and keep reducing it gap = n // 2 while gap > 0: # Perform insertion sort on each sub-array defined by the gap sequence for i in range(gap, n): temp = arr[i] j = i # Compare elements that are 'gap' distance apart while j >= gap and arr[j - gap] > temp: # Shift elements in the sub-array by 'gap' distance arr[j] = arr[j - gap] # Move the pointer to the previous element in the sub-array j -= gap # Insert the temporary variable into the correct position arr[j] = temp # Reduce the gap size gap //= 2Copy code```

• Efficiency: Shell sort has faster average-case performance than many other sorting algorithms such as bubble sort and selection sort.
• In-place sorting: Shell sort can be implemented as an in-place sorting algorithm, meaning that it does not require additional memory space for sorting.
• Adaptive: Shell sort is adaptive in nature, meaning that it can adjust its performance depending on the input data. For example, if the input data is almost sorted, the algorithm will perform faster than if the data is completely unsorted.

• Complexity: Shell sort has a more complex implementation compared to simpler sorting algorithms such as bubble sort and selection sort.
• Not stable: Shell sort is not a stable sorting algorithm, meaning that it does not preserve the order of equal elements in the input data.
• Non-optimal worst-case performance: While Shell sort performs better than many sorting algorithms on average, its worst-case performance is still O(n^2), which could be more optimal.

Comparison with Other Sorting Algorithms

When it comes to sorting algorithms, different algorithms have different trade-offs in terms of performance and efficiency. Here are some key trade-offs between Shell sort, Merge sort, Quick sort, and Insertion sort:

Shell sort:

• Efficient for medium-sized data sets.
• Adaptive, meaning that it can adjust its performance depending on the input data.
• In-place sorting, meaning that it does not require additional memory space for sorting.

• The complexity of implementation.
• Not stable, meaning that it does not preserve the order of equal elements in the input data.
• Non-optimal worst-case performance of O(n^2).

Merge sort:

• Guaranteed worst-case performance of O(n log n).
• Stable, meaning that it preserves the order of equal elements in the input data.
• Efficient for large data sets.

• Requires additional memory space for sorting.
• The complexity of implementation.
• Not as adaptive as other algorithms, meaning that it does not adjust its performance depending on the input data.

Quick sort:

• Average-case performance is O(n log n).
• In-place sorting, meaning that it does not require additional memory space for sorting.
• Easy to implement and understand.

• The worst-case performance is O(n^2), which is non-optimal.
• Not stable, meaning that it does not preserve the order of equal elements in the input data.
• Not as efficient as merge sort for very large data sets.

Insertion sort:

• Simple and easy to implement.
• Efficient for small data sets.
• In-place sorting, meaning that it does not require additional memory space for sorting.

• The worst-case performance is O(n^2), which is non-optimal.
• Not efficient for large data sets.
• Not as adaptive as other algorithms, meaning that it does not adjust its performance depending on the input data.

When to use Shell Sort?

Shell sort is a sorting algorithm that can be a good choice in certain scenarios. Here are some cases when you may choose to use Shell sort:

1. When the data set is medium-sized: Shell sort is efficient for medium-sized data sets, making it a good choice for sorting data that is too large for Insertion sort but too small for more complex algorithms like Merge sort or Quick sort.
1. When you need an in-place sorting algorithm: Shell sort is an in-place sorting algorithm, meaning that it does not require additional memory space for sorting. This can be useful when memory is a limiting factor.
1. When the input data is not already sorted: Shell sort can adapt its performance based on the input data, which means that it can perform better than other algorithms in cases where the input data is not already partially sorted.
1. When stability is not required: Shell sort is not a stable sorting algorithm, meaning that it does not preserve the order of equal elements in the input data. However, stability is not always necessary, and in cases where it is not important, Shell sort can be a good choice due to its efficiency.

Conclusion

The choice of sorting algorithm depends on the specific requirements of the problem at hand. For example, if we need a stable sorting algorithm and have enough memory, Merge sort may be the best choice. On the other hand, if we are sorting a small data set and need an algorithm that uses minimal memory, Insertion sort may be the best choice.