65.9K
CodeProject 正在变化。 阅读更多。
Home

8 皇后问题

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.82/5 (7投票s)

2008年8月27日

GPL3

3分钟阅读

viewsIcon

52647

downloadIcon

1258

尝试使用回溯法解决皇后问题

screenshot.JPG

引言

8Queen 程序试图解决著名的国际象棋皇后问题。我开始编写这个程序只是为了练习。完成之后,我想把它上传到 Code Project。这是我的第一个程序。我从 Code Project 学到了很多,因此想贡献一些东西,以便其他人可以使用此代码并学习一些东西。

该程序可以解决 n = 5n = 15 的皇后问题。虽然它能够解决 'n' 的任何值的问题,但我采用了 15 的上限范围。它可以成为学习基本递归,在界面上进行位图打印以及一点多线程的一个很好的例子。

背景

大多数程序员肯定都知道国际象棋皇后问题,因为这是数据结构课程中的一个著名问题。我们必须将 'n' 个皇后放在 n X n 的棋盘上,使得每个皇后都是安全的。国际象棋中的皇后可以水平,垂直和对角线移动到任何长度。因此,皇后的放置方式应使其不阻碍其他皇后。因此,想法是自动将皇后放在棋盘上。

Using the Code

该代码非常易于使用和理解。只有一个重要的文件,8Queen.cpp。由于代码是使用 Win32 API 编写的,因此没有类。

有两个皇后的位图,一个带有白色背景,一个带有黑色背景。在 WM_CREATE 中,它使用 LoadBitmap 方法加载两个位图,并使用 GetBitmapDimensionEx 保存位图的大小。之所以获取该大小,是因为皇后位图很大,而棋盘方格很小。现在,要将位图放入棋盘方格中,我们需要调整大小,因此我们需要位图的原始大小。

//load the bitmaps
hBitmap[0] = LoadBitmap((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), 
        MAKEINTRESOURCE(IDB_BITMAP1));

hBitmap[1] = LoadBitmap((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), 
        MAKEINTRESOURCE(IDB_BITMAP2));

GetBitmapDimensionEx(hBitmap[0], &BitmapSize);

棋盘数据使用 BOOL 的二维数组存储在内存中。它看起来像这样

BOOL **g_QueenArray; 

TRUE 值表示该方格有皇后,FALSE 值表示该方格为空。内存已分配给全局变量。

//allocate memory        
g_QueenArray = (BOOL**)calloc(g_BoardSize, sizeof(BOOL));
for(i = 0;i < g_BoardSize;i++)
{
    *(g_QueenArray + i) = (BOOL*)calloc(g_BoardSize, sizeof(BOOL));
} 

编写了一个递归回溯函数,该函数不断计算新位置并将皇后放在这些位置上。如果发现棋盘上没有剩余位置,它将回溯一行并检查皇后是否有任何新位置。现在,在这一点上,它要么会找到一个新位置并继续前进,要么找不到任何位置。如果找不到任何新位置,它将再次回溯一行。当所有皇后都放置好时,该函数返回。

 int Backtracking(HWND hWnd, int Pos_X, int Pos_Y)
{
    int New_Y;
    RECT    rect;    

    if(g_BoardSize == 0)
        return TRUE;

    if(Pos_X == g_BoardSize)
        return TRUE;

    while(Pos_Y < g_BoardSize)
    {
        if(!SetNextQueen(Pos_X, Pos_Y, &New_Y))
            return false;

        int OneBoxSize = WND_WIDTH / g_BoardSize;

        Pos_Y = New_Y;    

        rect.left = Pos_Y * OneBoxSize;
        rect.right = (Pos_Y + 1) * (OneBoxSize);
        rect.top = Pos_X * OneBoxSize;
        rect.bottom = (Pos_X + 1) * OneBoxSize;
        
        InvalidateRect(hWnd, &rect, TRUE);
        //UpdateWindow(hWnd);
        Sleep(400);
        if(g_Stop)
        {
            g_Stop = FALSE;
            return TRUE;
        }

        if(Backtracking(hWnd, Pos_X + 1, 0))
        {
            return TRUE;
        }
        else
        {
            rect.left = Pos_Y * OneBoxSize;
            rect.right = (Pos_Y + 1) * (OneBoxSize);
            rect.top = Pos_X * OneBoxSize;
            rect.bottom = (Pos_X + 1) * OneBoxSize;

            g_QueenArray[Pos_X][Pos_Y++] = FALSE;
            
            InvalidateRect(hWnd, &rect, TRUE);
            UpdateWindow(hWnd);
            Sleep(400);
            
            if(Pos_Y == g_BoardSize)
                return false;
        }
    }
    
    return TRUE;
} 

回溯在一个线程中完成,并且相应地更新变量 g_QueenArray 。更新后,为棋盘调用 InvalidateRect WM_PAINT 调用 DrawBoard(),后者通过读取 g_QueenArray 来更新棋盘。因此,通过这种方式,在 Backtracking() 计算的每次移动之后,棋盘都会自行更新。

case WM_PAINT:
    BeginPaint(hWnd, &ps);
    if(g_BoardSize)
        DrawBoard(hWnd, g_BoardSize, ps, hBitmap, BitmapSize);    
                        
    EndPaint(hWnd, &ps);
    //UpdateWindow(hWnd);
            
    break;

函数 DrawQueen() 用于在正方形上绘制皇后。它使用 CreateCompatibleDC 在内存中创建一个区域,然后使用 SelectObject 在该区域中选择位图。然后,它使用 StretchBlt 将此位图放置在棋盘上。该函数可以帮助理解如何在窗口上 bitblt 位图。

short DrawQueen(HWND hWnd, PAINTSTRUCT ps, RECT rect, 
	HBITMAP  hQueenBitmap, SIZE QueenBitmapSize)
{
    if(hQueenBitmap == NULL || hWnd == NULL)
        return ERROR_EXIT;

    //bitmap is already loaded, just StretchBlt it on the square    

    HDC hdcSource = CreateCompatibleDC(NULL);
    SelectObject(hdcSource, hQueenBitmap);

    StretchBlt(ps.hdc,rect.left + 5, rect.top,
	rect.right - rect.left - 5, rect.bottom - rect.top,
        hdcSource, 0, 0, 142, 284, SRCCOPY);

    DeleteDC(hdcSource);

    return SUCCESS_EXIT;
}

关注点

当我创建程序而没有使用线程时,当我想在计算过程中停止它时,它没有响应。每当窗口最小化时,它都会丢失其界面。因此,我考虑将 Backtracking 函数放入线程中。现在,经过此增强之后,该程序非常灵敏。当最小化并恢复时,它会保留其界面。因此,线程可能是使您的应用程序响应更快的好方法。程序中可能还存在一些错误,但是我认为可以学习我想讨论的基本思想。

历史

  • 2008 年 8 月 27 日:初始发布
© . All rights reserved.