搜索有以下幾種算法:
- 枚舉算法:
- 也即列舉問題的所有狀態從而尋找符合問題的解的方法。
- 適合用於狀態較少,比較簡單的問題上。
- 廣度優先搜索:
- 從初始點開始,根據規則展開第一層節點,並檢查目標節點是否在這些節點上,若沒有,再將所有的第一層的節點逐一展開,得到第二層節點,如沒有,則擴展下去,直到發現目標節點爲止。
- 比較適合求最少步驟或最短解序列的題目。
- 一般設置一個隊列queue ,將起始節點放入隊列中,然後從隊列頭取出一個節點,檢查是否是目標節點,如不是則進行擴展,將擴展出的所有節點放到隊尾,然後再從隊列頭取出一個節點,直至找到目標節點。
- 深度優先搜索:
- 一般設置一個棧stack ,將起始節點放入棧中,然後從棧中彈出一個節點,檢查是否是目標節點,如不是則進行擴展,將擴展出的所有節點入棧,然後再從棧頂彈出一個節點,直到找到目標節點。
- 深度優先搜索得到的第一個解,不一定是最優解。
- 雙向廣度優先搜索:
- 雙向搜索:從起始節點向目標節點方向搜索,同時從目標節點向起始節點方向搜索。
- 雙向搜索只能用於廣度優先搜索中。
- 雙向搜索擴展的節點數量要比單向少的多。
- A*算法
- 利用問題的規則和特點來制定一些啓發規則,由此來改變節點的擴展順序,將最有希望擴展出最優解的節點優先擴展,使得儘快找到最優解。
- 對每一個節點,有一個估價函數F來估算起始節點經過該節點到達目標節點的最佳路徑的代價。
- 每個節點擴展的時候,總是選擇具有最小的F的節點。
- F=G+B×H:G爲從起始節點到當前節點的實際代價,已經算出,H爲從該節點到目標節點的最優路徑的估計代價。F要單調遞增。
- B最好隨着搜索深度成反比變化,在搜索深度淺的地方,主要讓搜索依靠啓發信息,儘快的逼近目標,而當搜索深的時候,逐漸變成廣度優先搜索。
- 回溯算法:
- 和深度優先相似,不同之處在於對一個節點擴展的時候,並不將所有的子節點擴展出來,而只擴展其中的一個。因而具有盲目性,但內存佔用少。
- 搜索中的優化:
- 在搜索前,根據條件降低搜索規模。
- 廣度優先搜索中,被處理過的節點,充分釋放空間。
- 給據問題的約束條件進行剪枝。
- 利用搜索過程中的中間解,避免重複計算。
一、馬的走法
題目:
在一個4X5的棋盤上,馬的起始座標由用戶輸入,求馬返回初始位置的所有不同的走法的總數和步驟。馬走過的位置不能重複。
//Chess.h
#include
using namespace std;
class Chess
{
public:
Chess(int w, int h):width(w),height(h)
{
chess = new int*[w];
for(int i = 0; i < w; i++){
chess[i] = new int[h];
for(int j=0; j < h; j++){
chess[i][j] = 0;
}
}
}
int FindPath(int startx, int starty);
protected:
private:
const static int stepx[8];
const static int stepy[8];
int ** chess;
int width;
int height;
struct Pos {
Pos():x(0), y(0){}
Pos(int _x, int _y):x(_x), y(_y){}
int x;
int y;
};
Pos startp;
void jump(int posx, int posy, int &count, vector &path);
};
//Chess.cpp
#include "Chess.h"
const int Chess::stepx[8]={-2, -1, 1, 2, 2, 1, -1, -2};
const int Chess::stepy[8]={1, 2, 2, 1, -1, -2, -2, -1};
int Chess::FindPath(int startx, int starty)
{
for(int i = 0; i < width; i++)
for(int j=0; j < height; j++)
chess[i][j] = 0;
startp.x = startx;
startp.y = starty;
vector path;
int count = 0;
chess[startx][starty] = 1;
Pos p(startx, starty);
path.push_back(p);
jump(startx, starty, count, path);
return count;
}
void Chess::jump(int posx, int posy, int &count, vector &path)
{
for(int i = 0; i < 8; i++){
int nextx = posx + stepx[i];
int nexty = posy + stepy[i];
if (nextx >= 0 && nextx < width && nexty >= 0 && nexty < height && chess[nextx][nexty] == 0) {
chess[nextx][nexty] = 1;
Pos p(nextx, nexty);
path.push_back(p);
jump(nextx,nexty,count,path);
path.pop_back();
chess[nextx][nexty] = 0;
} else if (nextx == startp.x && nexty == startp.y) {
count++;
Pos p(nextx, nexty);
path.push_back(p);
printf("The %dth path : ", count);
vector::iterator iter;
for(iter = path.begin(); iter != path.end(); iter++){
Pos cur = *iter;
printf("(%d, %d) -> ", cur.x, cur.y);
}
printf("/n");
path.pop_back();
}
}
}
//main
#include "stdafx.h"
#include "Chess.h"
int _tmain(int argc, _TCHAR* argv[])
{
Chess ch(4,5);
int count = ch.FindPath(1,1);
printf("The total number of path is : %d./n", count);
return 0;
}
二、漢諾塔,雙層漢諾塔
漢諾塔是源自印度神話裏的玩具。上帝創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上大小順序摞着64片黃金圓盤。上帝命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤。
從最簡單的方式考慮,當僅僅有兩個盤子的時候,移動過程應該如下:
當有多個盤子的時候,把除最後一個盤子以外的其他盤子看成一個整體,應用遞歸進行移動,過程如下:
所以得出漢諾塔的算法如下:
void hanoi(int count, char pillarA, char pillarB, char pillarC)
{
if (count == 1) {
printf("move from %c to %c./n", pillarA, pillarC);
} else {
hanoi(count - 1, pillarA, pillarC, pillarB); //將A中的count - 1移到B,剩餘一個(也就是最大的)移到C
printf("move from %c to %c./n", pillarA, pillarC);
hanoi(count - 1, pillarB, pillarA, pillarC); //然後再把B中的count-1個移到C即可
}
}
雙層漢諾塔問題:雙層漢諾塔是由單層漢諾塔衍生出來,相同大小的盤都有顏色不同的兩片,其目的是將不同顏色的盤分別移動到右面的兩個柱子上。在移動過程中,依舊是遵守大盤必須在小盤之下,而顏色順序無限制。
當僅僅又四個盤子的情況如下:
當有多個盤子的時候,把除最後兩個盤子以外的其他盤子看成一個整體,應用遞歸進行移動,過程如下:
雙層漢諾塔實現算法如下:
void hanoi(int numofpairs, char pillarA, char pillarB, char pillarC)
{
if (numofpairs == 1)
{
printf("move from %c to %c./n", pillarA, pillarC);
printf("move from %c to %c./n", pillarA, pillarC);
}
else
{
hanoi(numofpairs - 1, pillarA, pillarC, pillarB);
printf("move from %c to %c./n", pillarA, pillarC);
printf("move from %c to %c./n", pillarA, pillarC);
hanoi(numofpairs - 1, pillarB, pillarA, pillarC);
}
}
void hanoi_double(int count, char pillarA, char pillarB, char pillarC)
{
for (int i = count/2; i > 1; i--)
{
hanoi(i - 1, pillarA, pillarB, pillarC);
printf("move from %c to %c./n", pillarA, pillarB);
printf("move from %c to %c./n", pillarA, pillarB);
hanoi(i - 1, pillarC, pillarB, pillarA);
printf("move from %c to %c./n", pillarB, pillarC);
}
printf("move from %c to %c./n", pillarA, pillarB);
printf("move from %c to %c./n", pillarA, pillarC);
}
三、騎士走棋盤
騎士的走法爲走日字,騎士從任意位置出發,求他要如何才能走完所有的位置。
騎士的走法,基本上可以用遞歸來解決,然而純粹的遞歸維度很大,很沒有效率,從而應用A*算法,騎士沒選擇下一步,優先走再下一步所能走的步數最少的一步,也即先將最難的位置走完 。
//knight.h
class knight
{
public:
knight(int w, int h):width(w), height(h)
{
board = new int*[width];
for(int i = 0; i < width; i++)
{
board[i] = new int[height];
for(int j = 0; j < height; j++)
{
board[i][j] = 0;
}
}
}
~knight()
{
for(int i = 0; i < height; i++)
{
delete[] board[i];
}
delete[] board;
}
int travel(int startx, int starty);
void output()
{
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
printf("%d/t", board[i][j]);
}
printf("/n");
}
}
protected:
private:
int ** board;
int width;
int height;
const static int stepx[8];
const static int stepy[8];
};
//knight.cpp
#include "knight.h"
#include
using namespace std;
const int knight::stepx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};
const int knight::stepy[8] = {1, 2, 2, 1, -1, -2, -2, -1};
int knight::travel(int startx, int starty)
{
board[startx][starty] = 1;
int totalsteps = width * height;
vector nextx;
vector nexty;
vector nextsteps;
int currentx = startx;
int currenty = starty;
for(int step = 2; step <= totalsteps; step++)
{
nextx.clear();
nexty.clear();
nextsteps.clear();
for(int i = 0; i < 8; i++)
{
int x = currentx + stepx[i];
int y = currenty + stepy[i];
if (x >= 0 && x < width && y >=0 && y < height && board[x][y] == 0)
{
nextx.push_back(x);
nexty.push_back(y);
}
}
int;
if (nextx.size() == 0)
{
return 0;
}
else if (nextx.size() == 1)
{
;
}
else
{
nextsteps.resize(nextx.size(), 0);
for (int i = 0; i < nextx.size(); i++)
{
for (int j = 0; j < 8; j++)
{
int x = nextx[i] + stepx[j];
int y = nexty[i] + stepy[j];
if (x >= 0 && x < width && y >=0 && y < height && board[x][y] == 0)
{
nextsteps[i]++;
}
}
}
int tmp = nextsteps[0];
;
for (int i = 0; i < nextsteps.size(); i++)
{
if (nextsteps[i] > tmp)
{
tmp = nextsteps[i];
selected = i;
}
}
}
currentx = nextx[selected];
currenty = nexty[selected];
board[currentx][currenty] = step;
}
}
int main(int argc, char ** argv){
knight k(3, 3);
k.travel(0, 0);
k.output();
return 0;
}
四、老鼠走迷宮,老鼠遍歷迷宮
老鼠走迷宮,用二維矩陣中,用2表示牆壁,用1表示老鼠的行走路徑,求由入口至出口的路徑。
老鼠的走法有上下左右四個方向,每次選擇一個方向前進,當無法前進的時候,退回選擇下一個方向前進,直到走到出口爲止。
由於迷宮的設計,老鼠走迷宮的入口到出口的路徑可能不止一條,如何求出所有的路徑?
求所有的路徑其實很簡單,只要在老鼠走到出口的時候不退出,僅僅顯示經過的路徑,然後退回上一個重新選擇下一個位置就可以了 。
class MouseMaze
{
public:
MouseMaze(int* maze, int width, int height, int startx, int starty, int endx, int endy)
{
this->maze = maze;
this->width = width;
this->height = height;
this->startx = startx;
this->starty = starty;
this->endx = endx;
this->endy = endy;
}
int visit(int x, int y)
{
maze[x*width + y] = 1;
if (x == endx && y == endy)
{
output();
return TRUE;
}
int success = FALSE;
if (success == FALSE && x + 1 < width && maze[(x+1)*width+y] == 0)
{
success = visit(x + 1, y);
}
if (success == FALSE && x - 1 >= 0 && maze[(x-1)*width+y] == 0)
{
success = visit(x - 1, y);
}
if (success == FALSE && y + 1 < height && maze[x*width+y+1] == 0)
{
success = visit(x, y + 1);
}
if (success == FALSE && y - 1 >= 0 && maze[x*width+y-1]== 0)
{
success = visit(x, y - 1);
}
if (success == FALSE)
{
maze[x*width + y] = 0;
}
return success;
}
void visitAll(int x, int y)
{
maze[x*width + y] = 1;
if (x == endx && y == endy)
{
output();
maze[x*width + y] = 0;
return;
}
if (x+1 < width && maze[(x+1)*width+y]== 0)
{
visitAll(x + 1, y);
}
if (x-1 >= 0 && maze[(x-1)*width+y]== 0)
{
visitAll(x - 1, y);
}
if (y+1 < height && maze[x*width+y+1]== 0)
{
visitAll(x, y + 1);
}
if (y-1 >=0 && maze[x*width+y-1]== 0)
{
visitAll(x, y - 1);
}
maze[x*width + y]= 0;
}
void output()
{
if (maze == NULL)
{
return;
}
for (int i = 0; i < width; i++)
{
for (int j = 0; j < height; j++)
{
printf("%d/t",maze[i*width + j]);
}
printf("/n");
}
printf("-------------------------------------------------------------/n");
}
protected:
private:
int* maze;
int width;
int height;
int startx;
int starty;
int endx;
int endy;
};
int _tmain(int argc, _TCHAR* argv[])
{
int maze[9][9] = {{2, 2, 2, 2, 2, 2, 2, 2, 2},
{2, 0, 0, 0, 0, 0, 0, 0, 2},
{2, 0, 2, 2, 0, 2, 2, 0, 2},
{2, 0, 2, 0, 0, 2, 0, 0, 2},
{2, 0, 2, 0, 2, 0, 2, 0, 2},
{2, 0, 0, 0, 0, 0, 2, 0, 2},
{2, 2, 0, 2, 2, 0, 2, 2, 2},
{2, 0, 0, 0, 0, 0, 0, 0, 2},
{2, 2, 2, 2, 2, 2, 2, 2, 2}};
MouseMaze m(&maze[0][0], 9, 9, 1, 1, 7, 7);
m.visitAll(1, 1);
return 0;
}
五、三色旗問題
假設有一條繩子,上面有紅、白、藍三種顏色的旗子,起初繩子上的旗子顏色並沒有順序,要求將旗子排列爲藍,白,紅的順序,要如何移動,才最少。一次只能調換兩個旗子。
設定一個隊列,B,W,R是三種旗子的分界標,初始時,B,W在隊首,R在隊尾,然後從隊首逐個掃描:
- 如果爲白色則W+1,表示未處理部分移至白色組。
- 如果爲藍色則B與W的元素對調,B+1,W+1,表示藍白兩組都多了一個元素。
- 如果爲紅色則R與W的元素對調,表明紅色多了一個元素。
void swap(char &a, char &b)
{
char tmp = a;
a = b;
b = tmp;
}
void colorflags(char* &flags, int length)
{
int wflag = 0;
int bflag = 0;
int rflag = length - 1;
while (wflag <= rflag)
{
if (flags[wflag] == 'w')
{
wflag++;
}
else if(flags[wflag] == 'b')
{
swap(flags[bflag], flags[wflag]);
bflag++;
wflag++;
}
else if (flags[wflag] == 'r')
{
while (wflag < rflag && flags[rflag] == 'r')
{
rflag--;
}
swap(flags[wflag],flags[rflag]);
rflag--;
}
}
}
六、八皇后問題
國際象棋中,皇后可以橫,豎,斜直線前進,吃掉所有的棋子,如果棋盤上有八個皇后,如何放置才能使八個皇后相安無事。
class Queen
{
public:
Queen(int n, int l):number(n), length(l)
{
chess = new int*[length];
for (int i = 0; i < length; i++)
{
chess[i] = new int[length];
for (int j = 0; j < length; j++)
{
chess[i][j] = 0;
}
}
column.resize(number, 0);
upright.resize(2*number, 0);
downright.resize(2*number, 0);
}
~Queen()
{
for (int i = 0; i < length; i++)
{
delete[] chess[i];
}
delete[] chess;
}
void output()
{
for (int i = 0; i < length; i++)
{
for (int j = 0; j < length; j++)
{
printf("%d/t", chess[i][j]);
}
printf("/n");
}
printf("--------------------------------------------------/n");
}
void placeQueen(int i)
{
if (i >= number)
{
output();
}
else
{
for (int j = 0; j < length; j++)
{
if (column[j] == 0 && upright[i+j] == 0 && downright[i-j+number]==0)
{
chess[i][j] = 1;
column[j] = upright[i+j] = downright[i-j+number] = 1;
placeQueen(i+1);
chess[i][j] = 0;
column[j] = upright[i+j] = downright[i-j+number] = 0;
}
}
}
}
protected:
private:
int number;
int length;
int ** chess;
vector column;
vector upright;
vector downright;
};
int _tmain(int argc, _TCHAR* argv[])
{
Queen q(8, 8);
q.placeQueen(0);
return 0;
}