Gyakorlati alapok II.

Rekurzió

 

Az N királynő-probléma

 

Az N királynő-problémát először egy Max Friedrich William Bezzel nevű sakkjátékos (1824 – 1871) vetette fel: hányféleképpen lehet 8 királynőt (vezért) egy 8×8-as sakktáblán úgy elhelyezni, hogy a sakkszabályok szerint ne üssék egymást. Ehhez a királynő lépési lehetőségeinek ismeretében az szükséges, hogy 2 bábu ne legyen azonos sorban, oszlopban vagy átlóban.

 

www.informatika-programozas.hu

 

Tekintsünk el a feladat matematikai megoldásaitól, amelyek végeredményeit alapjában véve kombinatorikai, valamint egyéb képletekkel ki tudjuk számítani és fókuszáljunk a kódmegoldásra.

 

A probléma ciklikusan, azaz iterációval, valamint rekurzív módszerrel is megoldható. Amint ezt már megtanultuk, a kétféle eljárás szabadon egymásba alakítható. A feladat tanulmányozása során könnyen felfedezhetjük annak rekurzív jellegét: az alkalmazott algoritmusnak ciklikusan kell végeznie azonos jellegű tevékenységet.

 

Azt a rekurzív algoritmust, amelyik ezt a számításigényes műveletcsomagot elvégzi nekünk, először Edsger Dijkstra (1930 - 2002), holland matematikus és informatikus vázolta fel 1972-ben. Ő arra használta fel az úgynevezett backtrack vagy backtracking algoritmust, hogy bemutassa a strukturált programozás előnyeit.

 

A backtrack algoritmus egy olyan általános algoritmus, amivel (elméletileg) minden programozási feladat megoldható, bár jegyezzük meg, hogy erre az univerzalitásra sok más algoritmus is képes, ám egy jó ideje az informatikának ez már nem lehet elégséges, hiszen adatszerkezetek menedzselése során döntő mértékben számít:

A fenti szempontok szigorú figyelembevételével a backtrack algoritmus sok esetben nem elég gyors (átlagosan exponenciális lépésszámú), de jól használható alacsony adatmennyiségű és/vagy kis bonyolultságú bemenetek esetén (például keresés labirintusban vagy könyvtárszerkezetben), illetve abban az esetben is, ha a megoldandó feladatról olyan többlettudással rendelkezünk, amellyel lényegesen csökkenthető a futási idő. Ez legtöbbször azt jelenti, hogy a keresés bizonyos ágait nem szükséges bejárni.

 

(Forrás - Source: mialmanach.mit.bme.hu)

A backtrack algoritmus akkor használható fel leghatékonyabban, ha a problémát meg tudjuk fogalmazni egymás utáni döntések sorozataként. Ez kiválóan érzékeltethető egy fa struktúra szerkezettel...

 

www.informatika-programozas.hu

 

...amelynek teljes bejárása nyilvánvalóan függ a korábbi döntések értékeitől, azaz annak eltárolásától, hogy mi volt a döntés előtti utolsó lépés állapota. A backtrack algoritmus pontosan így tesz, ugyanis nem számolja azt, hogy az egyes pozíciókban a királynő hány másik királynővel kerülne ütésbe, hanem egyszerűen azt az első alkalmas pozícióba helyezi. Ütés esetén kizárólag a szomszédos királynő pozícióját befolyásoljuk közvetlenül. Az algoritmus további jellegzetessége, hogy futásának kezdetén egyetlen királynő sem tartózkodik a sakktáblán. A lerakás során a királynők elhelyezésével oszloponként balról jobbra haladunk. Minden egyes királynő elhelyezése során soronként vizsgáljuk, hogy az alkalmas-e a királynő lerakására. Ha nem, úgy haladunk a következő sorra, egészen addig, amíg meg nem találjuk az első alkalmas sort (ahol a királynőt el is helyezzük). Előfordulhat, hogy ilyet nem találunk, ebben az esetben visszalépünk az előző oszlopra, majd eltávolítjuk az ott tartózkodó királynőt. A korábbi pozíciójából eltávolított királynőnek ugyanebben az oszlopban, a korábbi pozícióját követő sorokban keresünk helyet. Ha ilyet találtunk, úgy a visszalépegetés befejeződik és ismét megpróbáljuk az eredeti királynőnket elhelyezni. Ha a korábbi királynőnknek nem találtunk alkalmas (új) pozíciót, úgy ismét visszalépünk és ez ismétlődik mindaddig, amíg valamelyik oszlopban az adott királynőt nem sikerül végre új pozícióban elhelyezni. Természetesen, ha a sorok végére értünk és ezáltal adott lépésben nem adódott megoldás az elhelyezésre, akkor az újbóli elhelyezés alkalmával a sor elejétől kezdjük újra a próbálgatást.
 

A fenti gondolatmenetet követhetjük végig az alábbi videóban:

 

Az alábbi futtatható Java-kód a www.stackoverflow.com oldalról származik, az N királynő problémát oldja meg 4 királynő esetére backtrack algoritmussal. Az érthetőség kedvéért az eredeti kommenteket is benne hagyom a kódban:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

/* Java program to solve N Queen Problem using
backtracking */


public class Main {
    final int N = 4;

/* A utility function to print solution */


void printSolution(int board[][]) {
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++)
            System.out.print(" " + board[i][j]+ " ");
            System.out.println();
            }
        }

/* A utility function to check if a queen can
be placed on board[row][col]. Note that this
function is called when "col" queens are already
placeed in columns from 0 to col -1. So we need
to check only left side for attacking queens */


boolean isSafe(int board[][], int row, int col {
    int i, j;

/* Check this row on left side */


    for (i = 0; i < col; i++)
        if (board[row][i] == 1)
            return false;

/* Check upper diagonal on left side */


    for (i=row, j=col; i>=0 && j>=0; i--, j--)
        if (board[i][j] == 1)
            return false;

/* Check lower diagonal on left side */


    for (i=row, j=col; j>=0 && i<N; i++, j--)
        if (board[i][j] == 1)
            return false;

    return true;
}

/* A recursive utility function to solve N
Queen problem */


boolean solveNQUtil(int board[][], int col) {


/* base case: If all queens are placed
then return true */


    if (col >= N)
        return true;

/* Consider this column and try placing
this queen in all rows one by one */


    for (int i = 0; i < N; i++) {
/* Check if the queen can be placed on
board[i][col] */


    if (isSafe(board, i, col)) {


/* Place this queen in board[i][col] */


    board[i][col] = 1;

/* recur to place rest of the queens */


    if (solveNQUtil(board, col + 1) == true)
        return true;

/* If placing queen in board[i][col]
doesn't lead to a solution then
remove queen from board[i][col] */


    board[i][col] = 0; // BACKTRACK
    }
}

/* If the queen can not be placed in any row in
this colum col, then return false */


    return false;
}

/* This function solves the N Queen problem using
Backtracking. It mainly uses solveNQUtil () to
solve the problem. It returns false if queens
cannot be placed, otherwise, return true and
prints placement of queens in the form of 1s.
Please note that there may be more than one
solutions, this function prints one of the
feasible solutions.*/


boolean solveNQ() {
    int board[][] = {{0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0},
    {0, 0, 0, 0}
    };

    if (solveNQUtil(board, 0) == false) {
        System.out.print("Solution does not exist");
        return false;
    }

    System.out.println();
    printSolution(board);
    return true;
}

// driver program to test above function


public static void main(String args[]) {
    NQueenProblem Queen = new NQueenProblem();
    Queen.solveNQ();
    }
}

 

 

Végeredmény:


0 0 1 0
1 0 0 0
0 0 0 1
0 1 0 0