Kirjoittaja: anttil
Kirjoitettu: 14.02.2009 – 14.02.2009
Tagit: koodi näytille, vinkki
Kahdeksan kuningattaren ongelmassa pitää laittaa kahdeksan kuningatarta shakkilaudalle niin, etteivät ne uhkaa toisiaan (http://en.wikipedia.org/wiki/Eight_queens_problem). Tämä ohjelma laskee ja tulostaa ratkaisut myös erikokoisille laudoille.
Ohjelma tulostaa myös vain symmetrisesti toisistaan eroavat ratkaisut. Ohjelma toimisi luultavasti nopeammin backtracking-tekniikkaa käyttämällä mutta ainakin omalla koneellani ratkaisut tulevat hetkessä kun lauta on pienempi kuin 10×10. Löydettyjen ratkaisujen määrä on ainakin pienillä laudoilla sama kuin Wikipediassa olevat, joten ohjelma toimii luultavasti oikein.
#include <stdio.h> int order[8]; //taulukko, jossa on kuningattarien paikat laudalla. //Esimerkiksi order[2] sisältää linjalla c(kolmas linja vasemmalta) //olevan kuningattaren rivin int c = 0; //löydettyjen ratkaisujen määrä int main() { int n; //laudan koko printf("Laudan koko?\n"); //kysytään käyttäjältä laudan kokoa scanf("%d",&n); printf("\n\n%d ratkaisua löytyi.\n",check(0,n)); //kutsutaan pääfunktiota, joka tulostaa yhdistelmät //ja palauttaa niiden lukumäärän, joka tulostetaan return(0); } int check(int a,int n) //funktio ottaa parametrikseen aiemmin laitettujen //kuningattarien määrän ja laudan koon { int i,j,t,u; if (a>n-1) //mikäli kuningattaria on laitettu laudan koon verran //voidaan alkaa tutkia kelpaako yhdistelmä ratkaisuksi { t=1; //käytetään määrittämään onko yhdistelmä kelpo ratkaisu //t asetetaan nollaksi jos yhdistelmä ei käy for(i=0;i<n-1;i++) //käydään kaikki paitsi viimeinen kuningatar läpi uhkauksin varalta //viimeistä ei tarvitse käydä läpi sillä jos se uhkaisi jotain toista //se olisi selvinnyt jo aiemmin { for(j=1;j<n;j++)//tutkitaan vinot uhkaukset – suorat uhkaukset on jo aiemmin karsittu pois { if(i+j<n && ((order[i+j] == order[i]+j) || (order[i+j] == order[i]-j))) { //mikäli toinen kuningatar on yhtä monta ruutua oikealla //kuin yläällä tai alaalla, ne uhkaavat toisiaan t=0; //tällöin t asetetaan nollaksi, koska yhdistelmä ei käy break; //silmukkaa on enään turha jatkaa } } if(t==0) //myöskään ulompaa silmukka ei kannata jatkaa uhkauksen löytymisen jälkeen break; } if(t) //jos ei löytynyt uhkauksia, yhdistelmä tulostetaan { c++; //kasvatetaan löytyneiden ratkaisujen määrää yhdellä for(i=0;i<n;i++) { for(j=0;j<n;j++)//käydään laudan jokainen ruutu läpi { if(j == order[i]) printf("o"); //mikäli ruudussa on kuningatar, tulostetaan 'o' else printf("#"); //muulloin '#' } printf("\n"); //jokaisen rivin jälkeen rivinvaihto } printf("\n"); //rivinvaihto myös lopuksi erottamaan ratkaisut toisistaan return(0); //palataan takaisin tutkimaan seuraavaa yhdistelmää } } //mikäli kuningattaria on laitettu vähemmän kuin laudan koko jatketaan tästä for(i=0;i<n;i++) { //käydään kaikki rivivaihtoehdot läpi //ja edetään seuraavalle linjalle mikäli rivi on kelvollinen t=1; //0 mikäli rivi ei kelvollinen for(j=0;j<a;j++)//käydään edelliset linjat läpi { if(order[j]==i) //mikäli jollain edellisellä linjalla t=0; //on sama rivi kuin nyt tämä rivi ei kelpaa //koska yhdellä rivillä voi olla vain yksi kuningatar } if(a>1 && abs(order[a-1]-i)==1) //poikkeustapaus etsinnän nopeuttamiseksi t=0; //mikäli edellisen linjan kuningatar oli //yhden rivin ylempänä tai alempana niin tämä //rivi ei kelpaa, koska kuningattaret uhkaisivat toisiaan vinottain if(t) //mikäli rivivalinta on kelvollinen edetään seuraavalle linjalle { order[a]=i; //tallennetaan rivivalinta check(a+1,n); //kutsutaan funktiota uudestaan yhtä suuremmalla laitettujen kuningattarien //määrällä eli edetään seuraavalle linjalle } } return(c); //palautetaan löydettyjen ratkaisujen määrä }
Siinä on ainakin kommentoitua koodia. Hyvähän noita kommentteja on näissä koodivinkeissä olla. Ehkä kuitenkin jopa turhankin paljon kommentoitua, selitetty yksittäisiä kohtia, joiden merkityksen pitäisi olla selvä (esim. "return(c);" ja selitys perässä).
Minulle heräsi ajatus tuota koodia lukiessa, että olisiko tuota saanut selvemmäksi muuttujien parammalla nimeämisellä. Nythän koodissa on monessa kohtaa kommentteja, joita ei välttämättä tarvitsisi, jos muuttujien nimet olisivat kuvaavia.
Esimerkkinä nyt vaikka muuttuja c, jonka tarkoitus on selitetty sen esittelyä seuraavassa kommentissa. Kuitenkin näkisin, että tuota kommenttia ei olisi tarvinnut, jos c:n olisi nimennyt vaikkapa solutions-nimiseksi, jolloin siitä heti näkee mihin sitä käytetään. Samoin ainakin muuttujat n,t ja u (jota ei käytetä?) sekä check-funktion parametrit voisi nimetä kuvaavammin. Tämä selventäisi tuota check-funktiota. Lisäksi tuosta funktiosta voisi varmaan kirjoittaa sellaisen yleiskuvauksen, josta selviää miten funktio pääpiirteissään toimii.
Nuo nyt ensimmäisenä tuli tuosta mieleen. Koodia vain vilkuilin läpi, lähinnä tuon kommentoinnin osalta.
Hienoa kuitenkin saada uusia koodivinkkejä.
Jos laudan koko on yli 8, tuo koodi ei enää toimi. Muuta orderin kokoa.
Aika lailla samoilla linjoilla olen päärynämiehen kanssa. Tuota olisi paljon mukavempi lukea, jos kommentteja olisi reilusti vähemmän ja jos muuttujanimet kuvaisivat jollain tapaa sitä mitä ne tekevät. Esim. row_is_valid = TRUE; tjsp.
Virheitä tuolla on ainakin että funktion check esittely puuttuu ja samoin funktion abs. Abs löytyy ainakin c-standardikirjastosta stdlib.h tai sitten sen voi tehdä tuohon vaikka inlinena itse. Ne kun lisää niin pitäisi jo kääntyäkin.
Lisäksi tuon käyttämättömän u-muuttujan voisi siistiä pois.
Muuten aika mainio esimerkki rekursion käytöstä ja näyttäisi pikalukemalla täysin toimivalta logiikalta. Voittaa aina jotkin turhat kertoma rekursio-esimerkit (älkää muuten ikinä käyttäkö rekursiota kertoman laskemiseen!). Tuo rekursio tuossa kuitenkin on se vaikein asia seurata, niin olisin mieluummin sitä selittänyt vähän enemmän ja korvannut noita turhia kommentteja luettavammalla koodilla.