Kirjautuminen

Haku

Tehtävät

Koodit: C: Kuningatarten ongelma

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ä
}

Kommentit

Päärynämies [06.03.2009 18:02:00]

#

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ä.

Laitinen [07.03.2009 00:12:46]

#

Jos laudan koko on yli 8, tuo koodi ei enää toimi. Muuta orderin kokoa.

Torgo [31.03.2009 17:41:44]

#

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.

Kirjoita kommentti

Muista lukea kirjoitusohjeet.
Tietoa sivustosta