Practicum Datastructuren – voorjaar 2006 – Opgave 9: Backtrack–algoritmen 1 Achtergrond Veel problemen zijn niet analytisch op te lossen. We kunnen zon probleem soms met een computer oplossen door van alle denkbare megelijkheden na te gaan of ze inderdaad werkelijke oplossingen zijn. Een te volgen aanpak kan dan zijn: zolang niet alle denkbare mogelijkheden geprobeerd doe: pak een volgende mogelijkheid en controleer of die een oplossing is. Afhankelijk van het specifieke probleem moet deze aanpak bijgeschaafd en verder uitgewerkt worden. Vaak kan zon trial–and–error –aanpak elegant op een recursieve wijze geformuleerd worden. Een simpele aanpak hierbij gaat op de brute force manier (alle mogelijke combinaties proberen). Een intelligentere maar toch relatief eenvoudige aanpak, is die met een ‘terugkrabbel’ backtrack-algoritme. 2 Leerdoelen Na afloop van deze opdracht ben je in staat om: • aan te geven wat de basisprincipes zijn van backtrack -algoritmen; • een situatie voor toepassing van een backtrack -aanpak te herkennen; • voor eenvoudige problemen een correcte backtrack -oplossing in de vorm van een algoritme te bedenken. 3 Instructie Bestudeer allereerst de onderdelen over backtracking uit het Algoritmiek-dictaat (het [eerste deel van] hoofdstuk 12) en de sheets over dit onderwerp zoals die gebruikt zijn op het college (bekijk beslist ook de programma-voorbeelden) en uiteraard je op het hoorcollege gemaakte eigen aantekeningen en begin pas daarna aan de volgende deelopdrachten. 4 Probleemschets 1: Aantal combinaties Beschouw de volgende situatie met een rijtje getallen, die allemaal groter dan 1 zijn, bijvoorbeeld: const int Rijlengte = 10; int rij [ Rijlengte ] = { 2 , 3 , 5 , 7 , 8 , 11 , 13 , 17 , 19 , 23 , 29 } ; We kunnen ons nu afvragen op hoeveel manieren een gegeven getal als product van de getallen in de rij te schrijven is. De getallen in de rij mogen hierbij zo vaak gebruikt worden als je maar wilt. Het getal 20 is bijvoorbeeld te schrijven als 2 ∗ 2 ∗ 5, of 2 ∗ 5 ∗ 2, of 5 ∗ 2 ∗ 2, op 3 manieren dus. Er blijkt geen enkele manier te zijn om het getal 31 als product van getallen in deze rij te schrijven. Het moet mogelijk zijn om via het toetsenbord de waarde van het getal in te voeren, waarvoor bepaald moet worden óf (hoe vaak etc.) het als product van die getallen geschreven kan worden. Deelopdracht 1 Ontwerp en implementeer de functie int bepaalAantalCombinaties ( int doelwaarde, int product) waarmee backtrackend bepaald wordt hoe vaak het getal doelwaarde gemaakt kan worden uit de gegeven globale rij van getallen. De parameter product wordt gebruikt om het product van de getallen die op het moment van de aanroep uit de gegeven rij zijn gekozen. De beginwaarde bij de hoofdaanroep (bijvoorbeeld vanuit je main-functie) is 1. Waarschijnlijk kan je backtrack-aanpak impliciet zijn. Deelopdracht 2 Hetzelfde als bij 1 waarbij echter oplossingen waarvoor precies dezelfde reeks cijfers is gebruikt maar één keer meetellen. Dus 2 ∗ 2 ∗ 5, 2 ∗ 5 ∗ 2 en 5 ∗ 2 ∗ 2 tellen als één oplossing voor het doelgetal 20. Bovendien moet(en) de oplossing(en) getoond worden. Test je programma uit en neem de uitvoer op als commentaarregel(s) aan het einde van je programmacode. 5 Probleemschets 2: Som van kwadraten Voor sommige gehele getallen is het mogelijk hun kwadraat te schrijven als de som van de kwadraten van andere gehele getallen. Zo kunnen we bijvoorbeeld 5 schrijven als 52 = 32 + 42 . We beperken ons tot positieve gehele getallen en tot situaties waarbij in de kwadratenserie elk getal slechts éénmaal mag voorkomen (niet toegestaan is dus bijvoorbeeld: 22 = 12 +12 +12 +12 ). Voor sommige getallen is het niet mogelijk om het kwadraat als de som van andere kwadraten te schrijven, voor andere getallen kan het wel, soms zelfs op meerdere manieren (bijvoorbeeld 92 = 82 + 42 + 12 = 62 + 52 + 42 + 22 ). Deelopdracht 3: Bepaal alle mogelijke kwadratenseries Stel nu een backtrack procedure void bepaalKwadratenSeries(int getal) op (plus eventueel benodigde hulpprocedures), die alle mogelijke manieren afdrukt om het kwadraat van opgegeven getal als een som van kwadraten te schrijven. Hint: je zult hier een hulprij en zeer waarschijnlijk expliciet backtracken bij nodig hebben. Bij de hoofdaanroep: bepaalKwadratenSeries( 9 ) ; krijg je als mogelijke uitvoer (je mag kiezen of je de kwadraten van laag naar hoog of van hoog naar laag afdrukt): Gevonden kwadratenserie(s) voor het getal 9 : 9∗9 = 1∗1 + 4∗4 + 8∗8 9∗9 = 2∗2 + 4∗4 + 5∗5 + 6∗6 Test het programma uit en neem de uitvoer op als commentaarregel(s) op het einde van je programma-code. Deelopdracht 4: Bepaal kortste kwadratenserie Breid je vorige procedure(s) nu zodanig uit, dat via een aanroep als bepaalKortsteKwadratenSeries(9); je bij de uitvoer alleen de kortste kwadratenserie te zien krijgt. 6 Producten Als producten moet je (uitgeteste!) C++-code hebben, die ervoor zorgt dat je programma voldoet aan de gevraagde specificaties van de diverse deelopdrachten en waarbij je uitwerking voldoet aan de kwaliteitscriteria zoals die gesteld worden (zie bij punt 7. Zelfreflectie). Geef bovendien de gevraagde uitvoer van deelopdracht 2 en 3. 7 Zelfreflectie Via de cursuswebpagina kom je uit op een aantal criteria die we ook bij deze cursus gebruiken voor de kwaliteit van je ingeleverde werk. Ga voor jezelf na [voor zover dat nu al van toepassing is] in hoeverre je uitwerkingen voldoen aan deze criteria. Je uitwerking van deze zelfreflectie moet je echter niet inleveren. 8 Inleveren van je producten Vóór maandag 24 april, 9.00 uur, en wel door jullie programma één keer via e-mail met de tekst PI2 opgave9 in de Subject:-regel naar [email protected] te sturen. Vergeet niet om in de uitwerking duidelijk jullie namen te vermelden.