C语言回溯算法之八皇后、迷宫、素数环、数独
昨天中午,因玩了一把数独,突然间就勾起了我对回溯算法的回忆,于是乎,将几个经典的回溯算法的例子写了一遍,记录一下。
1、八皇后
问题描述:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
解题思路:最简单的做法就是穷举法,将8^8 =16,777,216 种情况全部穷举一遍,判断是否符合要求,显然这种方法太过于耗费时间,不可取,于是引入了回溯算法,虽然回溯算法也是暴力搜索,但是优于穷举法。思路是,先将第一列放上一个皇后,在第二列不冲突的位置上放上另一个皇后,在第三列不冲突的位置放上第三个皇后,依次类推,如果下一个皇后没有一个安放的位置,则返回上一步,放在第二个不冲突的位置上,如果不满足,依次退回。简单的C语言代码如下所示:
char Board[N + 2][N + 2] ; //! 棋盘数组
int SolveCount = 0 ; //! 解法计数
//! 打印显示棋盘
void Display_Board(void)
{
int row , col ;
for(row = 0 ; row < N + 2 ; ++ row)
{
for(col = 0 ; col < N + 2 ; ++ col)
{
printf(" %c" , Board[row][col]) ;
}
printf("\n") ;
}
}
//! 初始化棋盘
void Init_Board(void)
{
int row , col ;
for(row = 0 ; row < N + 2 ; ++ row)
{
for(col = 0 ; col < N + 2 ; ++ col)
{
if(row > 0 && row < N + 1 && col > 0 && col < N + 1)
Board[row][col] = ' ' ;
else
Board[row][col] = '#' ;
}
}
}
//! 检测该位置的皇后是否冲突
int Check_Board(int row , int col)
{
int nCol = col - 1 , nCol1 = col + 1 ;
for(-- row ; row > 0 ; -- row , -- nCol , ++ nCol1)
{
if(Board[row][col] == '*') return 0 ;
if(nCol > 0)
{
if(Board[row][nCol] == '*') return 0 ;
}
if(nCol1 < N + 1)
{
if(Board[row][nCol1] == '*') return 0 ;
}
}
return 1;
}
//! 求解棋盘
void Solve_Boad(int row)
{
if(row > N) //! 判断是否所有位置已安放完成
{
SolveCount ++ ;
printf("Solution: %d\n" , SolveCount) ;
Display_Board() ;
getchar() ; //! 按下Enter键获取下一种解法
}
else
{
int col = 1;
for( ; col <= N ; ++ col)
{
if(Check_Board(row , col))
{
Board[row][col] = '*' ;
Solve_Boad(row + 1) ;
Board[row][col] = ' ' ;
}
}
}
}
int main()
{
Init_Board() ;
Solve_Boad(1) ;
return 0;
}
2、迷宫
问题描述:在一堆复杂的建筑物中,找到一条,由入口通向出口的道路。
解题思路:用程序解迷宫时,为了简化迷宫,采用二维数组表示,0表示可以通过,1表示不可以通过,#表示墙,*表示最后的路径,首先从入口开始寻找下一个可以走的位置,如有合适的位置,继续向前,否则退回到上一步,选择下一个合适的位置,依次类推,直到找到最后一个出口位置,代码如下:
#include <stdio.h>
#include <stdlib.h>
#define ROW 10
#define COL 10
int SolveCount = 0 , AllStep = 0 ; //! 解法计数 步骤计数
char MazeArr[ROW + 2][COL + 2] = //! 迷宫数组,0表示可走,1表示不可走
{
{'#','#','#','#','#','#','#','#','#','#','#','#'} ,
{'#','0','0','1','1','1','0','0','0','1','1','#'} ,
{'#','1','0','1','1','1','0','1','0','0','0','#'} ,
{'#','1','0','1','1','1','0','1','0','1','0','#'} ,
{'#','1','0','0','0','0','0','1','0','1','0','#'} ,
{'#','1','1','1','0','1','1','1','0','0','0','#'} ,
{'#','1','0','0','0','0','1','1','0','1','0','#'} ,
{'#','1','0','1','1','0','0','0','0','1','0','#'} ,
{'#','1','0','1','1','0','1','1','1','0','0','#'} ,
{'#','1','0','0','0','0','1','1','1','0','1','#'} ,
{'#','1','1','1','1','1','1','1','1','0','0','#'} ,
{'#','#','#','#','#','#','#','#','#','#','#','#'} ,
} ;
//! 打印显示迷宫盘数据
void Display_Maze(void)
{
int row , col ;
for(row = 0 ; row < ROW + 2 ; ++ row)
{
for(col = 0 ; col < COL + 2 ; ++ col)
{
printf(" %c" , MazeArr[row][col]) ;
}
printf("\n") ;
}
}
const int Position[4][2] = { {-1 , 0} , {0 , 1} , {1 , 0} , {0 , -1} };
//! 检测该位置的可走方向,记录可走的位置
int Check_Maze(int pos , int dire , int * nPos)
{
int row = pos / (ROW + 2) , col = pos % (ROW + 2) , nRow , nCol ;
nRow = row + Position[dire][0] ; nCol = col + Position[dire][1] ;
if(MazeArr[nRow][nCol] == '0') { * nPos = nRow * (ROW + 2) + nCol ; return 1 ;}
return 0 ;
}
//! 求解迷宫
void Solve_Maze(int pos)
{
if(pos == ROW * (COL + 2) + COL ) //! 判断是否到达目的地
{
SolveCount ++ ; MazeArr[ROW][COL] = '*' ;
printf("Solution: %d Step: %d\n" , SolveCount , AllStep) ;
Display_Maze() ; MazeArr[ROW][COL] = '0' ;
getchar() ; //! 按下Enter键获取下一个解法
}
else
{
int dire = 0 , nPos ;
for( ; dire < 4 ; ++ dire) //! 轮流检测4个方位是否可走
{
if(Check_Maze(pos , dire , &nPos)) //! 判断该方向是否可走
{
int row = pos / (ROW + 2) , col = pos % (ROW + 2) ;
MazeArr[row][col] = '*' ; AllStep ++ ;
Solve_Maze(nPos) ;
MazeArr[row][col] = '0' ; AllStep -- ;
}
}
}
}
int main()
{
Solve_Maze(1 * (COL + 2) + 1) ;
return 0;
}
3、素数环
问题描述:将从1到n这n个整数围成一个圆环,若其中任意2个相邻的数字相加,结果均为素数,那么这个环就成为素数环。
例如:n=20时,下面的序列就是一个素数环:
1 2 3 4 7 6 5 8 9 10 13 16 15 14 17 20 11 12 19 18
解题思路:创建一个长度为N的一维数组,初始化数组为0,然后采用回溯算法依次安放下一个位置的数字,直到所有数据安放完成。代码如下:
int SolveCount = 0 ; //! 解法计数
int PrimeArr[N] ; //! 素数环数组
//! 素数环数初始化
void Prime_Arr_Init(void)
{
int n = 0 ;
for( ; n < N ; ++ n) PrimeArr[n] = 0 ;
}
//! 打印显示素数环
void Dosplay_Prime_Ring(void)
{
int n = 0 ;
for( ; n < N ; ++ n) printf(" %d" , PrimeArr[n]) ;
printf("\n") ;
}
//! 判断是否是素数
int isPrime(int num)
{
int i = 2 ;
double k = sqrt(num) ;
for( ; i <= k ; ++ i )
{
if(num % i == 0) return 0 ;
}
return 1 ;
}
//! 检测素数环数据是否满足要求
int Check_Prime_Ring(int index , int num)
{
int n = 0 , sum ;
//! 判断这个数字之前的位置是否已经使用
for( ; n < index ; ++ n)
{
if(PrimeArr[n] == num) return 0 ;
}
//! 当是最后一个位置的时候,需要判断和环首的和是否为素数
if(index == N - 1)
{
sum = num + PrimeArr[0] ;
if(!isPrime(sum)) return 0 ;
}
//! 计算和前领居的和
sum = num + PrimeArr[index - 1] ;
return isPrime(sum) ;
}
//! 求解素数环
void Solve_Prime_Ring(int index)
{
if(index == N)
{
SolveCount ++ ;
printf("Solution: %d\n" , SolveCount) ;
Dosplay_Prime_Ring() ;
getchar() ; //! 按下Enter键获取下一个解法
}
else
{
int num = 1 ;
for( ; num <= N ; ++ num)
{
if(Check_Prime_Ring(index , num))
{
PrimeArr[index] = num ;
Solve_Prime_Ring(index + 1) ;
PrimeArr[index] = 0 ;
}
}
}
}
int main()
{
Prime_Arr_Init() ;
Solve_Prime_Ring(0) ;
return 0;
}
4、数独
问题描述:数独是源自于18世纪瑞士的一种数学游戏,是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9不重复。数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
解题思路:为了简化模型,采用一个9*9的二维数组表示数独表,每一个格的数据填写在对应的数组中,未知的数字用0表示,采用回溯法暴力搜索,代码如下所示:
int SolveCount = 0 ; //! 解法计数
//! 需要求解的数独数据 0表示未知数,需要求解的值,不同的数独,修改此数组的值
int SudokuArr[9][9] =
{
{0 , 0 , 4 , 2 , 5 , 0 , 0 , 0 , 0 } ,
{0 , 0 , 1 , 0 , 0 , 4 , 9 , 0 , 0 } ,
{9 , 0 , 0 , 0 , 0 , 8 , 0 , 0 , 0 } ,
{0 , 0 , 0 , 0 , 3 , 1 , 0 , 0 , 0 } ,
{0 , 0 , 0 , 9 , 0 , 2 , 0 , 0 , 0 } ,
{3 , 0 , 9 , 0 , 8 , 0 , 0 , 7 , 0 } ,
{0 , 1 , 0 , 0 , 0 , 6 , 0 , 0 , 0 } ,
{0 , 5 , 0 , 0 , 0 , 7 , 0 , 6 , 0 } ,
{6 , 9 , 0 , 0 , 0 , 3 , 0 , 0 , 0 } ,
} ;
//! 打印显示数独
void Display_Sudoku(void)
{
int row , col ;
for(row = 0 ; row < 9 ; ++ row)
{
for(col = 0 ; col < 9 ; ++ col)
printf(" %d" , SudokuArr[row][col]) ;
printf("\n") ;
}
}
//! 寻找下一个需要填的空位
int Find_Next_Empty(int * pos)
{
int row , col ;
for(row = 0 ; row < 9 ; ++ row)
{
for(col = 0 ; col < 9 ; ++ col)
{
if(SudokuArr[row][col] == 0)
{
* pos = row * 9 + col ;
return 1 ;
}
}
}
return 0 ;
}
//! 检查该位置的数字是否满足要求,1 满足 0 不满足
int Check_Sudoku(int pos , int num)
{
int row = pos / 9 , col = pos % 9 , i , j , x , y ;
//! 判断行重复
for(i = 0 ; i < 9 ; ++ i)
{
if(SudokuArr[row][i] == num) return 0;
}
//! 判断列重复
for(i = 0 ; i < 9 ; ++ i)
{
if(SudokuArr[i][col] == num) return 0;
}
//! 判断小九宫格重复
x = col / 3 * 3 ; y = row / 3 * 3 ;
for(i = 0 ; i < 3 ; ++ i)
{
for(j = 0 ; j < 3 ; ++ j)
{
if(SudokuArr[y + i][x + j] == num) return 0 ;
}
}
return 1 ; //! 无重复,满足要求
}
//! 求解数独
void Solve_Sudoku(void)
{
int pos ;
if(!Find_Next_Empty(&pos)) //! 判断是否填完
{
SolveCount ++ ;
printf("Solution: %d\n" , SolveCount) ;
Display_Sudoku() ;
getchar() ; //! 按下Enter键获取下一个解法
}
else
{
int num = 1 ;
for( ; num < 10 ; ++ num)
{
if(Check_Sudoku(pos , num)) //! 判断该值是否满足要求
{
int row = pos / 9 , col = pos % 9 ;
SudokuArr[row][col] = num ;
Solve_Sudoku() ;
SudokuArr[row][col] = 0 ;
}
}
}
}
int main()
{
Solve_Sudoku() ;
return 0;
}