Algoritmen: Merge Sort


Home 4BSW1 4BSW2 4MTLAT/4LAT 4MWW1 4MWW2 4NWE2 5BWE 5EWI/5LWI/5WWI1 5WWI2 About

1. Merge Sort

Voor de vakantie hebben we al eventjes geprobeert, spelkaarten te sorteren. Nu gaan we dit thema verdiepen door een “merge sort” aan te maken.

  • “Merge sort” gaat eerst de te sorterende reeks opsplitsen, en dan bij het samenvoegen sorteren.
  • Een reeks van maar één nummer is altijd gesorteerd.
  • Die strategie noemt men ook “Divide and Conquer”.
  • We kunnen dit best recursief aanpakken, een concept uit “functional programming”.
  • Meer info: https://nl.wikipedia.org/wiki/Mergesort

Dit is wat ingewikkeld! MAAR: we hebben een beneden de algoritme gegeven.

  • Jullie hebben duidelijke instructies (gedefinieerd, ondubbelzinnig, implementeerbaar).
  • Volg de stappen!
  • Werkbestand voor vandaag is hier: ../code/merge_sort.py

We gaan samen de volgende algoritme implementeren:

  • We maken een functie aan.
    • [1a] De “base case”: als er maar eentje overblijft, is het zeker gesorteerd. Geef dan gewoon de input-reeks terug.
    • — Fase 1: “Split”, het opsplitsen —
    • [1b]… anders: splits het op in twee evenmatige reksen
      • [2] vind het middelpunt van de reeks, als index
      • [3] splits de reeks op in twee deelreeksen: een linke en een rechte
      • [4] ga zeker dat de deelreeksen gesorteerd zijn: verwerk ze eens met de functie `mijnMergeSort(reeks)`!
    • — Fase 2: “Merge”, het samenvoegen —
    • [5] twee indices aanmaken, eentje voor de “linke” helft, eentje voor “rechts”; allebij starten bij null.
    • [6] maak er een lege “result” lijst aan.
    • [7] ga door een “while” loop, die kijkt of een van de indices al volledig door de bijhorende reeks gegaan is (dus, kijk dat de index kleiner is als de lengte van links|rechts).
      • [8] vergelijk de elementen links en rechts.
        • [9] als het element links kleiner is dan rechts, voeg het aan het resultaat
        • [10] en verhoog de linkse loopindex om 1.
      • [11+12] anders voeg het rechte element aan, en verhoog de rechtse index
    • [13] dan moeten we nog de overblijfsels van “links” en “rechts” aan het “result” aanvoegen
    • [14] geef het resultaat terug.
  • [15] einde van de les: upload python-bestand → smartschool

2. Puzzel Rand

2.1. opdracht

opdracht vorige les:

  • Bedenk (en schrijf) een algoritme om het aandeel randdelen in een rechthoekige puzzel te berekenen.
  • Noot: dit hangt af van de puzzelgrootte en de aspectverhouding.
  • Definitie “rand”: een deel dat maximaal drie buurdelen heeft.
  • Alle deeltjes zijn even groot.

puzzle.svg

2.2. algoritme

Hier een mogelijke algoritme:

  • Neem de puzzelgrootte g en aspectverhouding v.
  • Ga zeker dat deze allebei groter dan null zijn.
  • Bereken de hoogte met de formule: h = sqrt(g*v)
  • Bereken de basis met de formule: b = round(g/h)
  • Corrigeer de puzzelgrootte indien het afronden tot een fout leidt, en geef er een waarschuwing aan de gebruiker.
  • Bereken de omtrek/rand met de formule: r = 2*(b+h) - 4
  • Het resultaat is de verhouding van omtrek en puzzelgrootte: return r/g

2.3. oplossing

We gebruiken de volgende bibliotheken:

import numpy # numerieke berekening, zoals afronden en de vierkantewortel
from warnings import warn as Warn # waarschuwingen

Er was een foutje in mijn bordberekeningen, sorry!

Met een gegeven puzzelgrootte \(g\) en aspectverhouding \(v\) kunnen we de zijkantenlengtes (\(b,\ h\) voor basis en hoogte) berekenen. We weten: \[g = b \cdot h\] \[v = \frac{b}{h}\quad \Leftrightarrow \quad b = v \cdot h\]

Als we de tweede vergelijking omgevormt in de eerste steken krijgen we: \[g = b \cdot h = v \cdot h^2\] \[b = +\sqrt{\frac{g}{v}}\]

Of: \[v = \frac{b}{h}\quad \Leftrightarrow \quad h = \frac{b}{v}\] \[g = b \cdot h = \frac{b^2}{v}\] \[h = +\sqrt{g\cdot v}\]

Dit kunnen we in een python-functie brengen:

def BerekenZijkanten(grootte: int, aspectverhouding: float) -> (int, int):
    # bereken de zijkantenlengtes van een gegeven grootte en aspectverhouding

    # formule van boven
    # zeker gaan dat er geen te kleine puzzelkanten uitkomen, minimum-hoogte is 1
    hoogte = max([1, int(numpy.round(numpy.sqrt(grootte * aspectverhouding)))])

    # en de andere kant volgt eruit
    basis = int(grootte / hoogte)

    # eventjes checken of het resultaat klopt
    if grootte != basis * hoogte:
        Warn(f"De aspectverhouding {aspectverhouding} geeft bij een puzzelgrootte {grootte} geen gehele kanten; benadering door aangepaste puzzelgrootte {basis} * {hoogte} = {basis*hoogte}.")

    return basis, hoogte


# een kleine test is altijd goed, zie afbeelding boven
print (BerekenZijkanten(int(6*4), 6/4))

Dit kunnen we voor een stap in de algoritme gemakkelijk overnemen:

def RandAanDeel(puzzelgrootte: int, aspectverhouding: float) -> float:

    # gebruiksfouten opsporen
    if aspectverhouding <= 0:
        raise IOError(f"De aspectverhouding moet echt groter dan 0 zijn; {aspectverhouding} is niet mogelijk.")

    if puzzelgrootte <= 0:
        raise IOError(f"De puzzelgrootte moet groter dan 0 zijn; {puzzelgrootte} is niet mogelijk.")

    # zijkanten berekenen
    basis, hoogte = BerekenZijkanten(puzzelgrootte, aspectverhouding)

    # de puzzelgrootte zeker aanpassen
    grootte_benadering = basis * hoogte

    # vier hoekstukken zijn deel van de basiskant en de hoogte,
    #    tenzij dat we een extreme aspectverhouding hebben
    hoekstukken = 4 / (1 if basis > 1 else 2) / (1 if hoogte > 1 else 2)

    # bereken de rand
    if (basis > 1 and hoogte > 1):
        rand = (basis+hoogte)*2 - hoekstukken
    else:
        # indien de aspectverhouding extreem is, dan hebben we niet echt een "omtrek"
        rand = basis * hoogte

    # en de uitkomst
    return rand/grootte_benadering

# en weer een kleine test
print (' 24, 1.50:', RandAanDeel(int(6*4), 6/4))
print (' 24, 0.01:', RandAanDeel(int(6*4), 0.01))
print ('100, 1.50:', RandAanDeel(100, 6/4))
print ('  1, 0.67:', RandAanDeel(1, 4/6))

 24, 1.50: 0.6666666666666666
 24, 0.01: 1.0
100, 1.50: 0.375
  1, 0.67: 1.0

Bonusopdracht voor gevoorderden:

  • Probeer er een reeks van puzzelgrootten en aspectverhoudingen.
  • Maak er een afbeelding aan van de samenhang van die twee parameters en de randaandeel (3D surface plot: r = f(g, v)).

Date: 2024-01-19 Fri 00:00

Author: Falk Mielke