Serwis Edukacyjny
Nauczycieli

w I-LO w Tarnowie
obrazek Materiały głownie dla uczniów liceum

  Wyjście       Spis treści       Wstecz       Dalej  

Autor artykułu: mgr Jerzy Wałaszek

©2026 mgr Jerzy Wałaszek

Sortowanie Shella
Shell Sort

SPIS TREŚCI
Podrozdziały

Algorytm

W latach 50-tych ubiegłego wieku informatyk Donald Shell zauważył, iż algorytm sortowania przez wstawianie pracuje bardzo efektywnie w przypadku gdy zbiór jest w dużym stopniu uporządkowany (sprawdź wyniki naszych badań czasów sortowania dla tego algorytmu). Z kolei algorytm ten pracuje nieefektywnie w zbiorach nieuporządkowanych, ponieważ elementy są przesuwane w każdym obiegu o jedną pozycję przy wstawianiu elementu wybranego na listę uporządkowaną.

Pomysł Shella polegał na tym, iż sortowany zbiór dzielimy na podzbiory, których elementy są odległe od siebie w sortowanym zbiorze o pewien odstęp h. Każdy z tych podzbiorów sortujemy algorytmem przez wstawianie. Następnie odstęp zmniejszamy. Powoduje to powstanie nowych podzbiorów (będzie ich już mniej). Sortowanie powtarzamy i znów zmniejszamy odstęp, aż osiągnie on wartość 1. Wtedy sortujemy już normalnie za pomocą sortowania przez wstawianie. Jednakże z uwagi na wcześniejsze obiegi sortujące mamy ułatwione zadanie, ponieważ zbiór został w dużym stopniu uporządkowany. Dzięki początkowym dużym odstępom elementy były przesuwane w zbiorze bardziej efektywnie - na duże odległości. W wyniku otrzymujemy najlepszy pod względem szybkości czasu wykonania algorytm sortujący w klasie O(n2). Algorytm ten nosi również nazwę algorytmu sortowania przez wstawianie z malejącym odstępem (ang. Diminishing Increment Sort).

Efektywność algorytmu sortowania metodą Shella zależy w dużym stopniu od ciągu przyjętych odstępów. Pierwotnie Shell proponował pierwszy odstęp równy połowie liczby elementów w sortowanym zbiorze. Kolejne odstępy otrzymujemy dzieląc odstęp przez 2 (dzielenie całkowitoliczbowe).

Przykład:

Posortujemy metodą Shella zbiór ośmiu liczb: { 4 2 9 5 6 3 8 1 } w porządku rosnącym. Zbiór posiada osiem elementów, zatem przyjmiemy na wstępie odstęp h równy 4. Taki odstęp podzieli zbiór na 4 podzbiory, których elementy będą elementami zbioru wejściowego odległymi od siebie o 4 pozycje. Każdy z otrzymanych podzbiorów sortujemy algorytmem sortowania przez wstawianie. Ponieważ zbiory te są dwuelementowe, to sortowanie pędzie polegało na porównaniu pierwszego elementu podzbioru z elementem drugim i ewentualną zamianę ich miejsc, jeśli będą w niewłaściwym porządku.

Podział, h=4 Sortowanie Wynik
   
 1  
- Zbiór wejściowy
1
         
    
         
2
            
   
            
3
               
   
               
4
                   1 
   
                   5 
Zbiór wyjściowy -
 5  

Zmniejszamy odstęp h o połowę, więc h = 2. Zbiór podstawowy zostanie podzielony na dwa podzbiory. Każdy z tych podzbiorów sortujemy przez wstawianie:

Podział, h=2 Sortowanie Wynik
   
- Zbiór wejściowy
1
         
     
         
2
            
    
             
Zbiór wyjściowy -

Zmniejszamy odstęp h o połowę, h = 1. Taki odstęp nie dzieli zbioru wejściowego na podzbiory, więc teraz będzie sortowany przez wstawianie cały zbiór. Jednak algorytm sortujący ma ułatwioną pracę, ponieważ dzięki poprzednim dwóm obiegom zbiór został częściowo uporządkowany - elementy małe zbliżyły się do początku zbioru, a elementy duże do końca.

Podział, h=1 Sortowanie Wynik
   
 4  1  6  2  8  3  9  5  
- Zbiór wejściowy
1
 4  1  6  2  8  3  9  5 
 1  2  3  4  5  6  8  9  
 1  2  3  4  5  6  8  9  
Zbiór wyjściowy -
 1  2  3  4  5  6  8  9 

Dobór optymalnych odstępów

obrazek
prof. Donald Knuth

Kluczowym elementem wpływającym na efektywność sortowania metodą Shella jest właściwy dobór ciągu odstępów. Okazuje się, iż ciąg zaproponowany przez twórcę algorytmu jest jednym z najgorszych, ponieważ w kolejnych podzbiorach uczestniczą wielokrotnie te same elementy (możesz to prosto sprawdzić w podanym powyżej przykładzie).

Dotąd problem optymalnych odstępów w algorytmie sortowania metodą Shella nie został rozwiązany matematycznie, ponieważ w ogólnym przypadku jest niezwykle trudny. Wielu badaczy proponowało na wybór tych odstępów różne ciągi liczbowe otrzymując lepsze lub gorsze rezultaty.

Na przykład profesor Donald Knuth (autor "Sztuki Programowania Komputerów" -  "The Art of Computer Programming") zbadał bardzo dokładnie algorytm sortowania metodą Shella i doszedł do wniosku, iż dobry ciąg odstępów dla n elementowego zbioru można wyznaczyć następująco:
Przyjmujemy h1 = 1
obliczamy hs = 3hs-1 + 1 aż do momentu, gdy hs obrazek n
Ostatecznie h = [hs : 9]

Przykład:

Obliczmy pierwszy odstęp dla zbioru o n = 200 elementach:

h1 = 1
h2 = 3h1 + 1 = 3 + 1 = 4 - mniejsze od 200, kontynuujemy
h3 = 3h2 + 1 = 12 + 1 = 13 - kontynuujemy
h4 = 3h3 + 1 = 39 + 1 = 40 - kontynuujemy
h5 = 3h4 + 1 = 120 + 1 = 121 -  kontynuujemy
h6 = 3h5 + 1 = 363 + 1 = 364 - stop, ponieważ jest większe od 200

h = [h6 : 9] = [364 : 9] = [40 4/9] = 40
(zwróć uwagę, iż jest to zawsze element wcześniejszy o dwie pozycje, czyli h4)

Kolejne odstępy obliczamy dzieląc całkowitoliczbowo bieżący odstęp przez 3. Taki właśnie sposób wyliczania odstępów przyjmiemy w naszym algorytmie.


do podrozdziału  do strony 

Opis algorytmu

Specyfikacja problemu

Dane wejściowe

n - liczba elementów w sortowanym zbiorze, n ∈ N
d[ ] - zbiór n-elementowy, który będzie sortowany. Elementy zbioru mają indeksy od 1 do n.

Dane wyjściowe

d[ ] - posortowany zbiór n-elementowy. Elementy zbioru mają indeksy od 1 do n.

Zmienne pomocnicze

i - indeks elementu listy uporządkowanej,   i  ∈ N
j - zmienna sterująca pętli,  j ∈ N
h - odstęp pomiędzy kolejnymi elementami podzbiorów,  h  ∈ N
x - zawiera wybrany ze zbioru element

Lista kroków

K01: h ← 1
K02: Powtarzaj h ← 3h + 1,
h ≥ n
K03: h ← h div 9
K04: Jeśli h = 0,
to h ← 1
K05: Dopóki h > 0:
    wykonuj kroki K06...K10
K06:     Dla j = n - h, n - h - 1, ..., 1:
        wykonuj kroki K07...K09
K07:         x ← d[j];   i ← j + h
K08:         Dopóki (i  ≤ n) i (x > d[i]):
            wykonuj d[i - h] ← d[i];   i ← i + h
K09:         d[i - h] ← x
K10:     h ← h div 3
K11: Zakończ

Schemat blokowy

obrazek

Algorytm sortowania metodą Shella jest ulepszonym algorytmem sortowania przez wstawianie. Aby się o tym przekonać, wystarczy spojrzeć na schemat blokowy. Kolorem szarym zaznaczyliśmy na nim bloki, które dokładnie odpowiadają algorytmowi sortowania przez wstawianie. Jedyną modyfikacją jest wprowadzenie odstępu h zamiast liczby 1.

Na początku algorytmu wyznaczamy wartość początkowego odstępu h. Wykorzystujemy tu sugestie prof. Donalda Knutha.

Po wyznaczeniu h rozpoczynamy pętlę warunkową nr 1. Pętla ta jest wykonywana dotąd, aż odstęp h przyjmie wartość 0. Wtedy kończymy algorytm, zbiór będzie posortowany.

Wewnątrz pętli nr 1 umieszczony jest opisany wcześniej algorytm sortowania przez wstawianie, który dokonuje sortowania elementów poszczególnych podzbiorów wyznaczonych przez odstęp h. Po zakończeniu sortowania podzbiorów odstęp h jest zmniejszany i następuje powrót na początek pętli warunkowej nr 1.

Uwaga techniczna:

Każdy obieg pętli nr 2 sortuje przemiennie jeden element z kolejnych podzbiorów. Najpierw będą to elementy przedostatnie w kolejnych podzbiorach wyznaczonych odstępem h, później wcześniejsze i wcześniejsze. Takie podejście znacząco upraszcza algorytm sortowania.

do podrozdziału  do strony 

Przykładowe programy

C++
// Sortowanie metodą Shella
//-------------------------
// (C)2012 mgr Jerzy Wałaszek
// I Liceum Ogólnokształcące
// im. K. Brodzińskiego
// w Tarnowie

#include <cmath>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>

using namespace std;

const int N = 20; // Liczebność zbioru.

// Program główny
//---------------

int main()
{
  int d[N],h,i,j,x;

  cout << " Sortowanie metoda Shella\n"
          "--------------------------\n"
          "  (C)2005 Jerzy Walaszek\n\n"
          "Przed sortowaniem:\n\n";

// Najpierw wypełniamy tablicę d[]
// liczbami pseudolosowymi, a następnie
// wyświetlamy jej zawartość

  srand((unsigned)time(NULL));

  for(i = 0; i < N; i++)
    d[i] = rand() % 100;
  for(i = 0; i < N; i++)
    cout << setw(4) << d[i];
  cout << endl;

// Wyznaczamy wartość początkowego przesunięcia

  for(h = 1; h < N; h = 3 * h + 1);
  h /= 9;
  if(!h) h++; // istotne dla małych N,
              // dla większych można pominąć!

// Sortujemy

  while(h)
  {
    for(j = N - h - 1; j >= 0; j--)
    {
      x = d[j];
      i = j + h;
      while((i < N) && (x > d[i]))
      {
        d[i - h] = d[i];
        i += h;
      }
      d[i - h] = x;
    }
    h /= 3;
  }

// Wyświetlamy wynik sortowania

  cout << "Po sortowaniu:\n\n";
  for(i = 0; i < N; i++)
    cout << setw(4) << d[i];
  cout << endl << endl;
  system("pause");
  return 0;
}
Pascal
// Sortowanie Metodą Shella
//-------------------------
// (C)2012 mgr Jerzy Wałaszek
// I Liceum Ogólnokształcące
// im. K. Brodzińskiego
// w Tarnowie

program Shell_Sort;

const N = 20; // Liczebność zbioru.

var
  d : array[1..N] of integer;

// Program główny
//---------------

var
  h,i,j,x : integer;
begin
  writeln(' Sortowanie metoda Shella ');
  writeln('--------------------------');
  writeln('  (C)2005 Jerzy Walaszek  ');
  writeln;

// Najpierw wypełniamy tablicę d[]
// liczbami pseudolosowymi, a następnie
// wyświetlamy jej zawartość

  randomize;
  for i := 1 to N do d[i] := random(100);
  writeln('Przed sortowaniem:'); writeln;
  for i := 1 to N do write(d[i] : 4);
  writeln;
  writeln;

// Wyznaczamy wartość początkowego przesunięcia

  h := 1;
  repeat
    h := 3 * h + 1;
  until h >= N;
  h := (h div 9);
  if h = 0 then h := 1; // istotne dla małych N,
                        // dla większych można
                        // pominąć!

// Sortujemy

  while h > 0 do
  begin
    for j := N - h downto 1 do
    begin
      x := d[j];
      i := j + h;
      while (i <= N) and (x > d[i]) do
      begin
        d[i - h] := d[i];
        inc(i, h);
      end;
      d[i - h] := x;
    end;
    h := h div 3;
  end;

// Wyświetlamy wynik sortowania

  writeln('Po sortowaniu:'); writeln;
  for i := 1 to N do write(d[i] : 4);
  writeln;
  writeln;
  writeln('Nacisnij Enter...');
  readln;
end.
Basic
' Sortowanie Metodą Shella
'-------------------------
' (C)2012 mgr Jerzy Wałaszek
' I Liceum Ogólnokształcące
' im. K. Brodzińskiego
' w Tarnowie
  
CONST N = 20 ' Liczebność zbioru.

DIM AS INTEGER d(1 TO N),h,i,j,x

PRINT " Sortowanie metoda Shella "
PRINT "--------------------------"
PRINT "  (C)2005 Jerzy Walaszek  "
PRINT

' Najpierw wypełniamy tablicę d()
' liczbami pseudolosowymi, a następnie
' wyświetlamy jej zawartość

RANDOMIZE TIMER
FOR i = 1 TO N
  d(i) = INT(RND * 100)
NEXT
PRINT "Przed sortowaniem:"
PRINT
FOR i = 1 TO N
  PRINT USING "####"; d(i);
NEXT
PRINT
PRINT

' Wyznaczamy wartość
' początkowego przesunięcia

h = 1
DO
  h = 3 * h + 1
LOOP UNTIL h >= N
h = INT(h / 9)
IF h = 0 THEN h = 1 ' istotne dla małych N,
                    ' dla większych można
                    ' pominąć!
' Sortujemy

WHILE h > 0
  FOR j = N - h TO 1 STEP -1
    x = d(j)
    i = j + h
    WHILE (i <= N) AND (x > d(i))
      d(i - h) = d(i)
      i = i + h
    WEND
    d(i - h) = x
  NEXT
  h = INT(h / 3)
WEND

' Wyświetlamy wynik sortowania

PRINT "Po sortowaniu:"
PRINT
FOR i = 1 TO N
  PRINT USING "####"; d(i);
NEXT
PRINT
PRINT
PRINT "Nacisnij Enter..."
SLEEP
END
Python (dodatek)
# Sortowanie Metodą Shella
#-------------------------
# (C)2012 mgr Jerzy Wałaszek
# I Liceum Ogólnokształcące
# im. K. Brodzińskiego
# w Tarnowie

import random

n = 20 # Liczebność zbioru.

d = [random.randrange(100) for i in range(n)]

print(" Sortowanie metodą Shella ")
print("--------------------------")
print("  (C)2026 Jerzy Walaszek  ")
print()

# wyświetlamy d[]

print("Przed sortowaniem:")
print()
for i in range(n):
    print("%4d" % (d[i]),end="")
print()
print()

# Wyznaczamy wartość
# początkowego przesunięcia

h = 1
while h < n:
    h = 3 * h + 1
h //= 9
if h == 0: h = 1 # istotne dla małych N,
                 # dla większych można
                 # pominąć!
# Sortujemy

while h:
    for j in reversed(range(n - h)):
        x = d[j]
        i = j + h
        while (i < n) and (x > d[i]):
            d[i - h] = d[i]
            i += h
        d[i - h] = x
    h //= 3

# Wyświetlamy wynik sortowania

print("Po sortowaniu:")
print()
for i in range(n):
    print("%4d" % (d[i]),end="")
print()
print()
input("Naciśnij Enter...")
Wynik:
 Sortowanie metodą Shella 
--------------------------
  (C)2026 Jerzy Walaszek  

Przed sortowaniem:

  18  78  65  32  19  20   0  80  80  27  84  90  95   9  29  23  74   5  39   4

Po sortowaniu:

   0   4   5   9  18  19  20  23  27  29  32  39  65  74  78  80  80  84  90  95

Naciśnij Enter...
JavaScript
<html>
  <head>
  </head>
  <body>
    <form style="BORDER-RIGHT: #ff9933 1px outset;
                 PADDING-RIGHT: 4px; BORDER-TOP: #ff9933 1px outset;
                 PADDING-LEFT: 4px; PADDING-BOTTOM: 1px;
                 BORDER-LEFT: #ff9933 1px outset; PADDING-TOP: 1px;
                 BORDER-BOTTOM: #ff9933 1px outset;
                 BACKGROUND-COLOR: #ffcc66" name="frmshellsort">
      <h3 style="text-align: center">Sortowanie Metodą Shella</h3>
      <p style="TEXT-ALIGN: center">
        (C)2012 mgr Jerzy Wałaszek - I LO w Tarnowie
      </p>
      <hr>
      <p style="TEXT-ALIGN: center">
        <input onclick="main()" type="button" value="Sortuj" name="B1">
      </p>
      <p id="t_out" style="TEXT-ALIGN: center">...</p>
    </form>

<script language=javascript>

// Sortowanie Metodą Shella
//--------------------------------------------------------
// (C)2012 mgr Jerzy Wałaszek
// I Liceum Ogólnokształcące
// im. K. Brodzińskiego
// w Tarnowie
//--------------------------------------------------------

var N = 20; // Liczebność zbioru.

function main()
{
  var d = new Array(N);
  var i,j,ip,ik,x,t;

  // Najpierw wypełniamy tablicę d[] liczbami pseudolosowymi

  for(i = 0; i < N; i++) d[i] = Math.floor(Math.random() * 100);
  t = "Przed sortowaniem:<BR><BR>";
  for(i = 0; i < N; i++) t += d[i] + " ";
  t += "<BR><BR>";

  // Wyznaczamy początkowe przesunięcie

  for(h = 1; h < N; h = 3 * h + 1);
  h = Math.floor(h / 9);
  if(!h) h++; // istotne dla małych N, dla większych można pominąć!

  // Sortujemy

  while(h)
  {
    for(j = N - h - 1; j >= 0; j--)
    {
      x = d[j];
      i = j + h;
      while((i < N) && (x > d[i]))
      {
        d[i - h] = d[i];
        i += h;
      }
      d[i - h] = x;
    }
    h = Math.floor(h / 3);
  }

  // Wyświetlamy wynik sortowania

  t += "Po sortowaniu:<BR><BR>";
  for(i = 0; i < N; i++) t += d[i] + " ";
  document.getElementById("t_out").innerHTML = t;
}

</script> 

  </body>
</html>

Sortowanie Metodą Shella

(C)2012 mgr Jerzy Wałaszek - I LO w Tarnowie


...


do podrozdziału  do strony 

Podsumowanie

Cechy Algorytmu Sortowania Metodą Shella
klasa złożoności obliczeniowej optymistyczna O(n1,14)
klasa złożoności obliczeniowej typowa
klasa złożoności obliczeniowej pesymistyczna
Sortowanie w miejscu TAK
Stabilność NIE

do podrozdziału  do strony 

Zespół Przedmiotowy
Chemii-Fizyki-Informatyki

w I Liceum Ogólnokształcącym
im. Kazimierza Brodzińskiego
w Tarnowie
ul. Piłsudskiego 4
©2026 mgr Jerzy Wałaszek

Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.

Informacje dodatkowe.