骑士游览算法C实施表现

时间:2018-05-04 18:00:15

标签: c algorithm performance

我正在玩Java中的Knight Tour算法实现。从那时起,我完全确定C上的实现必须更快。因此,在阅读GNU C Reference之后,代码就完成了,逻辑的实现与Java相同。

你能想象我的奇迹何时C变体需要更多时间来处理6x6板。

所以我的问题是如何从技术角度优化下面的代码(即没有启发式优化)。

一些性能提示:在我使用Ubuntu的i5笔记本电脑上,提供的实现需要4个多小时才能解决6x6主板问题。 Java程序可以使用单线程方法在大约3小时18分钟内解决此任务。

一些算法说明:此实现可以从电路板上的所有单元格中找到所有可能的巡视路线,而不仅仅是闭路巡视。此外,还没有使用启发式优化,因为它有助于找到更快的首次巡回演出。

编辑:使用以下命令编译而未经任何优化的代码

gcc knight_tour.c -o knight-tour

#include "stdio.h"

#define BOARD_SIZE 5
#define MAX_MOVE_COUNT BOARD_SIZE*BOARD_SIZE

void printBoard(int[][BOARD_SIZE], int);
void clearBoard(int[][BOARD_SIZE], int);
int knight_move(int[][BOARD_SIZE], int, int, int);
int is_valid_position(int, int);
void calc_all_knight_jumps();

static int ALL_KNIGHT_COL_JUMPS[BOARD_SIZE][BOARD_SIZE][9];
static int ALL_KNIGHT_ROW_JUMPS[BOARD_SIZE][BOARD_SIZE][8];

int main() {

    int board[BOARD_SIZE][BOARD_SIZE];
    clearBoard(board, BOARD_SIZE);

    calc_all_knight_jumps();

    int result[BOARD_SIZE][BOARD_SIZE];
    for (int i = 0; i < BOARD_SIZE; i++) {
        for (int j = 0; j < BOARD_SIZE; j++) {
            result[i][j] = knight_move(board, i, j, 1);
        }
    }
    printBoard(result, BOARD_SIZE);

    return 0;
}

int knight_move(int board[][BOARD_SIZE], int cpos, int rpos, int level) {
    if (level == MAX_MOVE_COUNT) return 1;

    board[cpos][rpos] = level;

    int solved_count = 0;
    int jump_count = ALL_KNIGHT_COL_JUMPS[cpos][rpos][8];
    for (int i = 0; i < jump_count; i++) {
        int next_cpos = ALL_KNIGHT_COL_JUMPS[cpos][rpos][i];
        int next_rpos = ALL_KNIGHT_ROW_JUMPS[cpos][rpos][i];

        if (board[next_cpos][next_rpos] == 0) {
            solved_count += knight_move(board, next_cpos, next_rpos, level + 1);
        }
    }

    board[cpos][rpos] = 0;
    return solved_count;
}

void clearBoard(int board[][BOARD_SIZE], int size) {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
              board[i][j] = 0;
        }
    }
}

void printBoard(int board[][BOARD_SIZE], int size) {
    for (int i = 0; i < size; i++) {
        for (int j = 0; j < size; j++) {
            printf("%8d", board[i][j]);
        }
        printf("\n");
    }
}

int is_valid_position(int cpos, int rpos) {
    if (cpos < 0 || cpos >= BOARD_SIZE) return 0;
    if (rpos < 0 || rpos >= BOARD_SIZE) return 0;

    return 1;
}

void calc_all_knight_jumps() {
    int col_jumps[] = { 1,  2,  2,  1, -1, -2, -2, -1};
    int row_jumps[] = { 2,  1, -1, -2, -2, -1,  1,  2};

    int next_cpos, next_rpos;
    for (int i = 0; i < BOARD_SIZE; i++) {
        for (int j = 0; j < BOARD_SIZE; j++) {

            int jump_count = 0;
            for (int k = 0; k < 8; k++) {
                next_cpos = i + col_jumps[k];
                next_rpos = j + row_jumps[k];
                if (is_valid_position(next_cpos, next_rpos) == 1) {
                    ALL_KNIGHT_COL_JUMPS[i][j][jump_count] = next_cpos;
                    ALL_KNIGHT_ROW_JUMPS[i][j][jump_count] = next_rpos;
                    jump_count++;
                }
            }

            ALL_KNIGHT_COL_JUMPS[i][j][8] = jump_count;
        }
    }
}

2 个答案:

答案 0 :(得分:1)

以下是有关您的代码的一些建议以及答案中发布的更新版本:

  • 对标准头文件使用<>

    #include <stdio.h>
    
  • 在宏定义中带有括号的环绕表达式:

    #define MAX_MOVE_COUNT (BOARD_SIZE * BOARD_SIZE)
    
  • 在声明不带参数的函数时使用(void)

    void pre_calc_all_knight_jumps(void);
    
  • 避免将浮点数和整数计算与隐式转换混合使用。改用它:

    int center = (BOARD_SIZE + 1) / 2;
    

某些对称性未正确反映在result数组中。您应该将main循环更改为:

    int border = BOARD_SIZE - 1;
    int center = (BOARD_SIZE + 1) / 2;
    for (int i = 0; i < center; i++) {
        for (int j = i; j < center; j++) {
            int res = knight_move(board, i, j, 1);
            result[i][j] = res;
            result[j][i] = res;
            result[border - i][j] = res;
            result[j][border - i] = res;
            result[i][border - j] = res;
            result[border - j][i] = res;
            result[border - i][border - j] = res;
            result[border - j][border - i] = res;
        }
    }

通过将板子设置为8x8并在游戏区域的大小上使用其他参数,我还获得了一些缓存使用方面的改进。

肯定需要一种更有效的算法来解决较大尺寸的问题。

答案 1 :(得分:0)

考虑到我修改了源代码的所有注释。

  • 使用gcc编译器尝试了-O2和-O3优化选项;
  • 减少了knight_move()方法的顶级调用次数。所以现在只计算出独特的结果,然后水平,垂直和对角地反射;
  • 添加了代码以衡量性能而不使用printf();
  • 检查C和Java变体尽可能相同;

最后我预计会有结果 - C代码更快(但有优化选项)

  • 带有-O2选项的C代码:持续时间 - 1348秒(22:28)
  • Java代码:持续时间 - 1995秒(33:15)
  • 没有优化的C代码:持续时间 - 3518秒(58:38)
  • 使用-O3选项的C代码:持续时间 - 2143秒(35:43)

如果有人对C和Java上的骑士游览算法感兴趣,以下是两种实现: - )

GNU C

#include "stdio.h"
#include "time.h"

#define BOARD_SIZE 6
#define MAX_MOVE_COUNT BOARD_SIZE*BOARD_SIZE

int knight_move(int[][BOARD_SIZE], int, int, int);
void pre_calc_all_knight_jumps();
void print_result(int[][BOARD_SIZE]);

static int ALL_KNIGHT_COL_JUMPS[BOARD_SIZE][BOARD_SIZE][9];
static int ALL_KNIGHT_ROW_JUMPS[BOARD_SIZE][BOARD_SIZE][8];

int main() {
    // init board
    int board[BOARD_SIZE][BOARD_SIZE];
    for (int i = 0; i < BOARD_SIZE; i++) {
        for (int j = 0; j < BOARD_SIZE; j++) {
            board[i][j] = 0;
        }
    }

    pre_calc_all_knight_jumps();

    int result[BOARD_SIZE][BOARD_SIZE];

    struct timespec s_time, e_time;
    clock_gettime(CLOCK_MONOTONIC, &s_time);

    int border = BOARD_SIZE - 1;
    int center = BOARD_SIZE / 2.0 + 0.5;
    for (int i = 0; i < center; i++) {
        for (int j = i; j < center; j++) {
            int res = knight_move(board, i, j, 1);
            result[i][j] = res;
            result[border - i][j] = res;
            result[i][border - j] = res;
            result[border - i][border - j] = res;
            if (i != j) result[j][i] = res;
        }
    }
    clock_gettime(CLOCK_MONOTONIC, &e_time);
    printf("Duration in seconds: %ld\n", e_time.tv_sec - s_time.tv_sec);

    print_result(result);
    return 0;
}

int knight_move(int board[][BOARD_SIZE], int cpos, int rpos, int level) {
    if (level == MAX_MOVE_COUNT) return 1;

    board[cpos][rpos] = level;

    int solved_count = 0;
    int valid_move_count = ALL_KNIGHT_COL_JUMPS[cpos][rpos][8];
    for (int i = 0; i < valid_move_count; i++) {
        int next_cpos = ALL_KNIGHT_COL_JUMPS[cpos][rpos][i];
        int next_rpos = ALL_KNIGHT_ROW_JUMPS[cpos][rpos][i];

        if (board[next_cpos][next_rpos] == 0) {
            solved_count += knight_move(board, next_cpos, next_rpos, level + 1);
        }
    }

    board[cpos][rpos] = 0;
    return solved_count;
}

void print_result(int board[][BOARD_SIZE]) {
    for (int i = 0; i < BOARD_SIZE; i++) {
        for (int j = 0; j < BOARD_SIZE; j++) {
            printf("%8d", board[i][j]);
        }
        printf("\n");
    }
}

void pre_calc_all_knight_jumps() {
    int col_jumps[] = { 1,  2,  2,  1, -1, -2, -2, -1};
    int row_jumps[] = { 2,  1, -1, -2, -2, -1,  1,  2};

    int next_cpos, next_rpos;
    for (int i = 0; i < BOARD_SIZE; i++) {
        for (int j = 0; j < BOARD_SIZE; j++) {

            int jump_count = 0;
            for (int k = 0; k < 8; k++) {
                next_cpos = i + col_jumps[k];
                next_rpos = j + row_jumps[k];
                if (next_cpos < 0 || next_cpos >= BOARD_SIZE) continue;
                if (next_rpos < 0 || next_rpos >= BOARD_SIZE) continue;

                ALL_KNIGHT_COL_JUMPS[i][j][jump_count] = next_cpos;
                ALL_KNIGHT_ROW_JUMPS[i][j][jump_count] = next_rpos;
                jump_count++;
            }

            ALL_KNIGHT_COL_JUMPS[i][j][8] = jump_count;
        }
    }
}

<强>爪哇

import java.util.Arrays;

public class KnightTour1 {

    private final static int BOARD_SIZE     = 6;
    private final static int MAX_MOVE_COUNT = BOARD_SIZE * BOARD_SIZE;

    private static final int[][][] ALL_KNIGHT_COL_MOVES;
    private static final int[][][] ALL_KNIGHT_ROW_MOVES;

    static {
        final int[] knightColJumps = { 1,  2,  2,  1, -1, -2, -2, -1};
        final int[] knightRowJumps = { 2,  1, -1, -2, -2, -1,  1,  2};

        ALL_KNIGHT_COL_MOVES = new int[BOARD_SIZE][BOARD_SIZE][];
        ALL_KNIGHT_ROW_MOVES = new int[BOARD_SIZE][BOARD_SIZE][];

        int[] tmpColMoves = new int[8];
        int[] tmpRowMoves = new int[8];
        for (int c = 0; c < BOARD_SIZE; c++) {
            for (int r = 0; r < BOARD_SIZE; r++) {
                int jumpCount = 0;
                for (int i = 0; i < 8; i++) {
                    int nextColPos = c + knightColJumps[i];
                    int nextRowPos = r + knightRowJumps[i];
                    if (isValidBoardPos(nextColPos, nextRowPos)) {
                        tmpColMoves[jumpCount] = nextColPos;
                        tmpRowMoves[jumpCount] = nextRowPos;
                        jumpCount++;
                    }
                }

                ALL_KNIGHT_COL_MOVES[c][r] = Arrays.copyOf(tmpColMoves, jumpCount);
                ALL_KNIGHT_ROW_MOVES[c][r] = Arrays.copyOf(tmpRowMoves, jumpCount);
            }
        }
    }

    private static boolean isValidBoardPos(int colPos, int rowPos) {
        return colPos > -1 && colPos < BOARD_SIZE && rowPos > -1 && rowPos < BOARD_SIZE;
    }

    public static void main(String[] args) {
        long sTime = System.currentTimeMillis();
        int[][] result = findNumberOfTours();
        long duration = (System.currentTimeMillis() - sTime) / 1000;

        System.out.println("Duration in seconds: " + duration);
        printResult(result);
    }

    private static int knightMove(int[][] board, int colPos, int rowPos, int level) {
        if (level == MAX_MOVE_COUNT) return 1;

        board[colPos][rowPos] = level;

        final int[] validColMoves = ALL_KNIGHT_COL_MOVES[colPos][rowPos];
        final int[] validRowMoves = ALL_KNIGHT_ROW_MOVES[colPos][rowPos];
        final int validMoveCount = validColMoves.length;

        int solvedTourCount = 0;
        for (int i = 0; i < validMoveCount; i++) {
            final int nextColPos = validColMoves[i];
            final int nextRowPos = validRowMoves[i];
            if (board[nextColPos][nextRowPos] == 0) {
                solvedTourCount += knightMove(board, nextColPos, nextRowPos, level + 1);
            }
        }

        board[colPos][rowPos] = 0;
        return solvedTourCount;
    }

    private static int[][] findNumberOfTours() {
        final int[][] result = new int[BOARD_SIZE][BOARD_SIZE];
        final int[][] board = new int[BOARD_SIZE][BOARD_SIZE];

        final int border = BOARD_SIZE - 1;
        final int center = (int)(BOARD_SIZE / 2f + 0.5);
        for (int i = 0; i < center; i++) {
            for (int j = i; j < center; j++) {
                int res = knightMove(board, i, j, 1);
                result[i][j] = res;
                result[border - i][j] = res;
                result[i][border - j] = res;
                result[border - i][border - j] = res;
                if (i != j) result[j][i] = res;
            }
        }

        return result;
    }

    private static void printResult(int[][] res) {
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                System.out.print(String.format("%8d", res[i][j]));
            }
            System.out.println();
        }
    }
}