Shell Sort (D.L. Shell, 1959) by via28446

VIEWS: 47 PAGES: 2

									Shell Sort                                                                                                  v 1.2   8/2002


Shell Sort (D.L. Shell, 1959)
Warum ist Insertion Sort langsam: ein kleines Element wird immer in Vergleichen mit dem direkten linken
Nachbarn, also in Einerschritten, nach unten gebracht. Es wäre doch viel rascher, könnte man etwa mit
Dreierschritten arbeiten – kleine Werte könnten dann dreimal so schnell nach unten kommen!
Zerlegen wir also die Liste gedanklich:

7   9   4   5   1   82      die ursprüngliche Liste
7   9   4   5   1   8 2     jedes dritte Element
2   9   4   5   1   8 7     sortieren
2   9   4   5   1   8 7     wiederum jedes dritte Element
2   1   4   5   9   8 7     sortieren
2   1   4   5   9   8 7     nochmals jedes dritte Element
2   1   4   5   9   8 7     sortieren

Was haben wir erreicht: offensichtlich ist die Liste immer noch nicht richtig sortiert, ABER: die Elemente
sind UNGEFÄHR in der richtigen Position. Und jetzt wäre der übliche Insertion-Sort sehr schnell fertig!

Und wenn wir nicht mit Schrittweite 3 arbeiten, sondern mit 4 oder 10 oder bei großen Listen mit 100 – da
können die kleinen Werte doch noch schneller nach unten kommen. Naja. Ist die Weite zu groß, dann
können sie wiederum viel zu tief hinunter fallen.

Die Idee hinter ShellSort: nimm zuerst eine große Schrittweite (natürlich nicht größer als die Liste lang ist),
und mach damit eine Folge verschobener Insertion-Sorts. Dann verringere die Schrittweite und verfahre
genauso. Den Abschluss bildet ein gewöhnlicher Insertion-Sort, der die Elemente bereits an fast der
endgültigen Position vorfindet.

Die einzige ungeklärte Frage: Welche Schrittweiten sind optimal??? Fragen wir doch die Mathematiker.
Doch die haben leider bis heute keine 'Theorie des ShellSort' entwickeln können. Nichteinmal die Ordnung
der Methode können sie berechnen, nur für den schlechtesten Fall abschätzen....
Das kann aber den Informatiker nicht entmutigen. In vielen großangelegten Testläufen mit
unterschiedlichsten Listen und Schrittweitenfolgen konnten einige Folgen gewonnen werden, die 'immer gut
funktionieren', das heißt gute Laufzeit bringen.
Die zumeist angewendete 'beste' Folge lautet <...,121,40,13,4,1> (wenn die Liste nicht viele tausend
Elemente hat. Sonst siehe Knuth).
Also an=3an-1+1 mit a0=1 von den großen zu den kleinen Werten.
Interessanterweise ist es also besser, NICHT von der Länge der Liste auszugehen. In zahlreichen Lehrbuch-Versionen des ShellSort
wird mit Schrittweite=Listenlänge begonnen und fortlaufend gedrittelt. Diese Methode ist SCHLECHT!

Unser ShellSort sieht nun also so aus

def shell(L):
    erzeuge die Liste der Schrittweiten

        für jede Schrittweite in der Liste
            für jede Verschiebung von 0 bis Schrittweite-1
                InsertionSort ab Verschiebung mit jedem Schrittweite-ten Element


Alles was wir noch brauchen, ist ein adaptierter Insertion-Sort. Statt mit dem Nachbarn stelle-1 vergleichen
wir nun mit stelle-Schrittweite, das unterste Element ist nicht mehr bei 0 sondern bei Index Verschiebung.
Sonst gibt's keine Änderung. Da die letzte Schrittweite 1 ist, haben wir sogar den abschließenden
gewöhnlichen Insertion-Sort automatisch integriert.


Wolfgang Urban, HIB Wien                                                                                                       1
Shell Sort                                                                                  v 1.2   8/2002




def fullrange(a,b,step=1):                                              die üblichen Hilfsfunktionen
    if step>0: return range(a,b+1,step)
    else: return range(a,b-1,step)

def swap(L,a,b):
    L[a],L[b] = L[b],L[a]
def stepinsertion(L,step,schieb):                                       der adaptierte Insertion Sort
    for stelle in fullrange(schieb,len(L)-1,step):
        wert = L[stelle]
        i = stelle
        while 1:
            if i<=schieb: break
            if L[i-step]<=wert: break
            L[i] = L[i-step]
            i = i-step
        L[i] = wert
def shell(L):
    steps = []                                                          - Liste der Schrittweiten aufbauen
    weite = 1                                                           - erster Wert
    while weite<len(L):                                                 - bis er zu groß wird
        steps = [weite]+steps                                           - VORNE dranhängen
        weite = 3*weite+1                                               - nächster Folgenwert
      for step in steps:
          for schieb in fullrange(0,step-1):                            - siehe oben
              stepinsertion(L,step,schieb)

Theorie: die Ordnung der Methode hängt von der Wahl der Schrittweitenfolge ab, ist in unserem Fall aber
besser als n3/2. Der Zusatzaufwand lohnt sich also für größere Listen durchaus.

Mit ein paar kleinen Tricks können wir die erweiterete Insertion-Routine sogar in die shell-Funktion
integrieren und sparen Funktionsaufrufe. Weiters bereiten wir nur die maximal nötige Schrittweite vor und
berechnen die nächstkleineren Werte. Bei der Ganzzahldivision genügt die Division durch 3, das -1 ist
einsparbar. Die beiden Schleifen für step und schieb können wir zu einer einzigen machen, da sie ab step
alle Werte durchläuft. Die optimierte Version ist damit:

def shell1(L):                                                          ShellSort
    step=1                                                              - erstes Folgenelement
    while step<len(L):                                                  - Folge aufwärts
        step=3*step+1                                                   - nächster Wert
                                                                        - der letzte ist schon zu groß
      while step>1:
          step = step/3
          for i in fullrange(step,len(L)-1):                            - nächstkleinerer Wert
              wert = L[i]                                               - step-insertion für alle Elemente
              stelle = i
              while 1:
                  if stelle<step: break
                  if L[stelle-step]<=wert: break
                  L[stelle] = L[stelle-step]
                  stelle = stelle-step
              L[stelle] = wert

Und dies ist tatsächlich eine der weltbesten Sortierroutinen.



Wolfgang Urban, HIB Wien                                                                                     2

								
To top