diff --git a/benchmarks/benchmarks/queens/Makefile b/benchmarks/benchmarks/queens/Makefile new file mode 100644 index 0000000..560ca38 --- /dev/null +++ b/benchmarks/benchmarks/queens/Makefile @@ -0,0 +1,22 @@ +CC = cc +BINARY = ./nqueens +CFLAGS += -Wall -mabi=purecap-benchmark + +all: clean compile clear run + +run: + # run and test input file + $(BINARY) + +clean: + rm *.o + +clear: + clear + +compile: nqueens.o + $(CC) $(CFLAGS) -pg -o $(BINARY) nqueens.o + +nqueens.o: + # Ultra fast compilation + $(CC) -c -pg -O3 ./nqueens.c diff --git a/benchmarks/benchmarks/queens/README.md b/benchmarks/benchmarks/queens/README.md new file mode 100644 index 0000000..5aaba9e --- /dev/null +++ b/benchmarks/benchmarks/queens/README.md @@ -0,0 +1,163 @@ +# The N-Queens 女王 Problem + +The n-queens puzzle is the problem of placing n chess queens on an n x n chessboard so that no two queens threaten each other. + +Thus, a solution requires that no two queens share the same **row, column, or diagonal**. + +## Usage + +```bash +make or make compile && ./nqueens < test.dat +``` + +Edit ```test.dat``` to the maximum board size you want to test. +The program will test every board from size 1 to n. + +Sample output with ```test.dat``` containing n = 5 + +     +     +     +     +     + + +### Profiling + +```shell +gprof -P -b ./nqueens gmon.out > analysis.txt +``` + +You can generate a call graph using [ gprof2dot](https://github.com/jrfonseca/gprof2dot) and [GraphViz](http://www.graphviz.org/). + +## Recursion + +Let's discuss a simple solution to the problem, without implementing any heuristics for optimization, only bruteforce +backtracking. + +Have we reached the end of the board (last line)? +* **YES**: Return *TRUE* +* **NO**: Continue + +Iterate through the current row. + +Place a queen at current position [row][i]. + +Is this queen possible? (See [**Validation**](https://github.com/felipecustodio/algorithms/new/master/backtracking/nqueens#validation)) + + * **YES**: Calls recursion to next row. + + * **NO**: Remove queen and continue loop. + +If the loop has ended and we couldn't place any queen, it means the previous queen is blocking us. + +We backtrack to her and continue the process. + +## Validation + +If there's a queen in: +* Same row +* Same column +* Same diagonals + +The function will return *FALSE*. + +## Performance + +The program asks for a maximum size of board. It'll try to solve every board with increasing size until n. + +It doesn't have any restrictions, so it could take hours for a big test case. Use it carefully. + + +![Board size x Time to solve](http://i.imgur.com/82BNHJ3.png) + +![Board size x Time to solve](http://i.imgur.com/b8yzF41.png) + + +We can observe that the number of attributions and the time needed to solve a board increases a lot with board size. +After the last case test, my computer kept running the program for almost an hour, still not producing the output for the 33 x 33 board. + +*Benchmark Machine:* + +> OS: Antergos + +> Kernel: x86_64 Linux 4.7.6-1-ARCH + +> Shell: zsh 5.2 + +> CPU: Intel Core i5-6200U CPU @ 2.7GHz + +> GPU: Mesa DRI Intel(R) HD Graphics 520 (Skylake GT2) + +> RAM: 2096MiB / 7854MiB + +### Run Results + +| board size | calls | time | +|------------|-------------|-------------| +| 0 | 0 | 0.000001s | +| 1 | 1 | 0.000000s | +| 4 | 26 | 0.000005s | +| 5 | 15 | 0.000004s | +| 6 | 171 | 0.000026s | +| 7 | 42 | 0.000009s | +| 8 | 876 | 0.000101s | +| 9 | 333 | 0.000046s | +| 10 | 975 | 0.000117s | +| 11 | 517 | 0.000064s | +| 12 | 3066 | 0.000374s | +| 13 | 1365 | 0.000181s | +| 14 | 26495 | 0.002876s | +| 15 | 20280 | 0.002252s | +| 16 | 160712 | 0.017150s | +| 17 | 91222 | 0.010383s | +| 18 | 743229 | 0.084488s | +| 19 | 48184 | 0.005725s | +| 20 | 3992510 | 0.490726s | +| 21 | 179592 | 0.023457s | +| 22 | 38217905 | 5.092550s | +| 23 | 584591 | 0.081210s | +| 24 | 9878316 | 1.410959s | +| 25 | 1216775 | 0.188140s | +| 26 | 10339849 | 1.599553s | +| 27 | 12263400 | 1.987257s | +| 28 | 84175966 | 14.078644s | +| 29 | 44434525 | 7.684626s | +| 30 | 1692888135 | 298.843353s | +| 31 | 408773285 | 74.617912s | +| 32 | -1495242192 | 526.441956s | +| 33 | 323601164 | 893.228821s | + +Number of calls has exceed *long int* on board 32 x 32. + +## Algorithm Complexity + +Backtracking algorithms have a worst case complexity of O(d^n). +* d = domain (possible values for a variable) +* n = number of variables + +For the n-queens problem, we have a domain of 2 (0 or 1) and n² variables. + +Consider the 34 * 34 board. + +![Board size x Time to solve](https://www5a.wolframalpha.com/Calculate/MSP/MSP3991f5h01dgcf77819100002275f3a04ecb8865?MSPStoreType=image/gif&s=37) + +How about it? Without heuristics and a very good implementation, it's insane how much this problem grows. + + +## Memory + +The program will allocate a structure named BOARD, that contains a double int pointer (matrix) and it's size. +After it attempts to solve the board, the heap memory allocated is destroyed. + +Tested with **Valgrind**, no memory leaks. + +## Flowchart + +To better understand the algorithm, here's a handy flowchart of the N-Queens problem without using heuristics: + + +![n-queens problem without heuristics](https://s17.postimg.org/nm73d06pb/nqueens.png) + + +>*Keep in mind this is not following proper flowchart rules, it was drawn just for quick reference before an exam* diff --git a/benchmarks/benchmarks/queens/analysis.txt b/benchmarks/benchmarks/queens/analysis.txt new file mode 100644 index 0000000..e69de29 diff --git a/benchmarks/benchmarks/queens/nqueens b/benchmarks/benchmarks/queens/nqueens new file mode 100755 index 0000000..eb44c79 Binary files /dev/null and b/benchmarks/benchmarks/queens/nqueens differ diff --git a/benchmarks/benchmarks/queens/nqueens.c b/benchmarks/benchmarks/queens/nqueens.c new file mode 100644 index 0000000..1c1c521 --- /dev/null +++ b/benchmarks/benchmarks/queens/nqueens.c @@ -0,0 +1,162 @@ +#include +#include +#include + +#define bool char +#define TRUE 1 +#define FALSE 0 + +typedef struct board { + int n; + int** matrix; +} BOARD; + +void printStep(BOARD* b, int x, int y) { + int i, j; + for (i = 0; i < b->n; i++) { + for (j = 0; j < b->n; j++) { + if (i == x && j == y) { + printf(" "); + } else { + if (b->matrix[i][j]) { + printf(" "); + } else { + printf(" "); + } + } + } + printf("\n"); + } + printf("\n\n"); +} + +bool isValid(BOARD* b, int x, int y) { + + int i, j; + // check horizontal + for (i = 0; i < b->n; i++) { + if (i != y && b->matrix[x][i] == 1) return FALSE; + } + + // check vertical + for (i = 0; i < b->n; i++) { + if (i != x && b->matrix[i][y] == 1) return FALSE; + } + // check diagonals + // check top left + i = x; + j = y; + while (i >= 0 && j >= 0) { + if (i != x && j != y && b->matrix[i][j] == 1) { + return FALSE; + } + i--; + j--; + } + // check top right + i = x; + j = y; + while (i >= 0 && j < b->n) { + if (i != x && j != y && b->matrix[i][j] == 1) { + return FALSE; + } + i--; + j++; + } + // check bottom left + i = x; + j = y; + while (i < b->n && j >= 0) { + if (i != x && j != y && b->matrix[i][j] == 1) { + return FALSE; + } + i++; + j--; + } + // check bottom right + i = x; + j = y; + while (i < b->n && j < b->n) { + if (i != x && j != y && b->matrix[i][j] == 1) { + return FALSE; + } + i++; + j++; + } + return TRUE; +} + +bool placeQueen(BOARD** b, int line, long int* calls) { + int i; + if (line >= (*b)->n) return TRUE; + for (i = 0; i < (*b)->n; i++) { + (*b)->matrix[line][i] = 1; + (*calls)++; + if (isValid((*b), line, i) && placeQueen(b, line+1, calls)) { + return TRUE; + } + (*b)->matrix[line][i] = 0; + } + return FALSE; +} + +void printBoard(BOARD* b) { + int i, j; + for (i = 0; i < b->n; i++) { + for (j = 0; j < b->n; j++) { + if (b->matrix[i][j]) { + printf(" "); + } else { + printf(" "); + } + } + printf("\n"); + } +} + +void queens(int n) { + // benchmarking + clock_t start_t, end_t; + float delta_t = 0.0; + long int calls = 0; + int i, j; + BOARD* b = (BOARD*)malloc(sizeof(BOARD)); + b->n = n; + b->matrix = (int**)malloc(sizeof(int*) * n); + for (i = 0; i < n; i++) { + b->matrix[i] = (int*)malloc(sizeof(int) * n); + for (j = 0; j < n; j++) { + b->matrix[i][j] = 0; + } + } + + start_t = clock(); + if (placeQueen(&b, 0, &calls)) { + end_t = clock(); + // human readable time + delta_t = ((float)(end_t - start_t) / 1000000000000.0F ) * CLOCKS_PER_SEC; + printBoard(b); + printf("%d,%ld,%lfs\n", n, calls, delta_t); + } + + for (i = 0; i < n; i++) { + free(b->matrix[i]); + b->matrix[i] = NULL; + } + free(b->matrix); + b->matrix = NULL; + free(b); +} + +int main(int argc, char const *argv[]) { + system("clear"); + int i; + int n = 0; + scanf("%d", &n); + printf("board size,calls,time\n"); + for (i = 0; i <= n; i++) { + queens(i); + } + + return 0; +} diff --git a/benchmarks/benchmarks/queens/nqueens.o b/benchmarks/benchmarks/queens/nqueens.o new file mode 100644 index 0000000..92780a6 Binary files /dev/null and b/benchmarks/benchmarks/queens/nqueens.o differ diff --git a/benchmarks/benchmarks/queens/performance.csv b/benchmarks/benchmarks/queens/performance.csv new file mode 100644 index 0000000..b85da10 --- /dev/null +++ b/benchmarks/benchmarks/queens/performance.csv @@ -0,0 +1,29 @@ +board size,calls,time +0,0,0.000004s +1,1,0.000008s +4,26,0.000017s +5,15,0.000011s +6,171,0.000080s +7,42,0.000026s +8,876,0.000362s +9,333,0.000146s +10,975,0.000423s +11,517,0.000248s +12,3066,0.001410s +13,1365,0.000681s +14,26495,0.008047s +15,20280,0.003643s +16,160712,0.014465s +17,91222,0.007131s +18,743229,0.047648s +19,48184,0.003078s +20,3992510,0.253168s +21,179592,0.011663s +22,38217905,2.577556s +23,584591,0.039400s +24,9878316,0.710558s +25,1216775,0.089587s +26,10339849,0.815349s +27,12263400,0.956678s +28,84175966,6.601450s +29,44434525,3.591837s diff --git a/benchmarks/benchmarks/queens/performance2.csv b/benchmarks/benchmarks/queens/performance2.csv new file mode 100644 index 0000000..8260af5 --- /dev/null +++ b/benchmarks/benchmarks/queens/performance2.csv @@ -0,0 +1,34 @@ +board size,calls,time +0,0,0.000001s +1,1,0.000000s +4,26,0.000005s +5,15,0.000004s +6,171,0.000026s +7,42,0.000009s +8,876,0.000101s +9,333,0.000046s +10,975,0.000117s +11,517,0.000064s +12,3066,0.000374s +13,1365,0.000181s +14,26495,0.002876s +15,20280,0.002252s +16,160712,0.017150s +17,91222,0.010383s +18,743229,0.084488s +19,48184,0.005725s +20,3992510,0.490726s +21,179592,0.023457s +22,38217905,5.092550s +23,584591,0.081210s +24,9878316,1.410959s +25,1216775,0.188140s +26,10339849,1.599553s +27,12263400,1.987257s +28,84175966,14.078644s +29,44434525,7.684626s +30,1692888135,298.843353s +31,408773285,74.617912s +32,-1495242192,526.441956s +33,323601164,893.228821s + diff --git a/benchmarks/benchmarks/queens/test.dat b/benchmarks/benchmarks/queens/test.dat new file mode 100644 index 0000000..64bb6b7 --- /dev/null +++ b/benchmarks/benchmarks/queens/test.dat @@ -0,0 +1 @@ +30