廣度優先搜索-八數碼問題


算法簡介:廣度優先搜索

問題

給定一個一幅圖和一個起點s,回答“從s到給定的頂點v是否存在一條路徑?如果有,找出其中最短的那條(所含邊數最少)。“

思路

邊數最少,很自然想到從從經過1條邊能到達的節點有哪些?然后經過這些邊再到達的節點有哪些?這樣我不就能夠想出來最短的路徑了嗎?沒錯,這是非常簡單的想法,然而真正的廣度優先算法也是這樣,簡單而有效。

解決方法

圖片來源:https://commons.wikimedia.org/w/index.php?curid=1547848

上面這幅圖我要找到從1到12的最短路徑,則我會從1經過1條邊可以到達的節點(2 3 4)搜索,發現沒有,接下來搜索節點(2 3 4)通過1條邊能夠到達的頂點(5 6 7 8),發現沒有,接下來搜索節點(5 6 7 8),發現沒有,接下來搜索節點(5 6 7 8)通過1條邊能夠到達的節點(9 10 11 12),搜索到最后一個,發現搜索到了。如果你每次經過一條邊則記錄加1,則現在經過了三條邊,即最短路徑是3條邊,

實現方案

當已經理解了算法思想,接下來就應該實現了。最重要的一步是思考使用什么樣的數據結構,想想這里的搜索一個集合,那自然就是遍歷他們,接下來還要遍歷他們通過一條邊能夠到達節點,普通的想法是先判斷他們,如果沒有,則再遍歷他們,然后生成的所有節點再加入一個新的集合當中。

問題:發現了沒,這里遍歷了兩遍集合。還有就是新的節點加入一個新的集合,每次都要聲明新的數組嗎?這是有多麻煩。

解決方案:這里遍歷了兩遍,其實我先生成新的節點和后生成新的節點只是多占用一點空間而已,我要是在遍歷第一個節點如果判斷不是就直接生成新的節點也可以,這樣就不用遍歷第二遍了,加入新的集合解決起來有點棘手,如果我可以仍然放在集合里面繼續遍歷就好了,對!就是這樣,所以在集合里會有順序的關系,不能我先遍歷第一個節點,接着直接遍歷新的節點,所以說我們的數據就要有順序之分,所以想想有什么樣的數據存儲方式能夠有順序,常見的數組就夠了,如果稍微學過數據結構,鏈表也可以。

編程的思路:遍歷數組,從第一個節點開始,如果不是,則生成新節點加入到數組的最后一個節點后面,所以如果是c語言,一定要將數組聲明大一些。然后不斷遍歷即可,直到找到。

八數碼實現

問題

八數碼問題也稱為九宮問題。在3×3的棋盤,擺有八個棋子,每個棋子上標有1至8的某一數字,不同棋子上標的數字不相同。棋盤上還有一個空格,與空格相鄰的棋子可以移到空格中。要求解決的問題是:給出一個初始狀態和一個目標狀態,找出一種從初始轉變成目標狀態的移動棋子步數最少的移動步驟。所謂問題的一個狀態就是棋子在棋盤上的一種擺法。解八數碼問題實際上就是找出從初始狀態到達目標狀態所經過的一系列中間過渡狀態。

思路

移動棋子最少,當然是使用廣度優先搜索來解決。所以這里的每個節點就要是一個3
*3的矩陣。如果在c語言中,可以使用結構體。

struct node
{
	int xy[3][3];
};

實現方案

  • 空白棋子用0代替
  • 接受初始節點的信息和目標節點的信息
  • 找到空白棋子很簡單,直接遍歷就好,但是如何返回它的x和y坐標,試着能不能使用一個數字代替,后來發現確實可以。
int loction(int num)
{
	int i;
	for (i = 0; i < 9; i++)
		if (sh[num].xy[i / 3][i % 3] == 0) return i;
}
  • 從初始節點開始判斷,然后擴展,即上下左右移動,當然我們要考慮具體的位置,比如說已經到邊界了,就不能越出邊界。還要考慮以前移動過的方向,所以記錄下來以前移動過的方向,可以直接加在結構體里。(代碼如下所示)每次擴展的節點就加在數組后面。
struct node
{
	int xy[3][3];
	int dir;
};
  • 如何實現判斷相等,就是如何標志該狀態的唯一性,方案1:9個數字拼接生成字符串直接判斷是否相等。方案二:由於數也不大,所以直接使用9個數字直接生成一個數,使用long long 聲明生成的數字即可。
long long sign(int num)
{
	long long  sum;
	sum = sh[num].xy[0][0]*100000000 + sh[num].xy[0][1]*10000000 + sh[num].xy[0][2]*1000000 + sh[num].xy[1][0]*100000 + sh[num].xy[1][1]*10000 + sh[num].xy[1][2]*1000 + sh[num].xy[2][0]*100 + sh[num].xy[2][1]*10 + sh[num].xy[2][2];
	return sum;
}
  • 如果保證能夠到達目標節點,可以一直搜索下去,如果不知道能不能,則可以設置搜索次數。

代碼框架

具體代碼實現

#include<stdio.h>

struct node
{
	int xy[3][3];
	int dir;
};
struct node sh[102], end;
int count = 1;

void init()
{
	printf("輸入起始節點的位置:\n");
	int i, j;
	for (i = 0; i < 3; i++)
		for (j = 0; j < 3; j++)
			scanf("%d", &sh[0].xy[i][j]);
	sh[0].dir = -1;
	printf("輸入目標節點的位置:\n");
	for (i = 0; i < 3; i++)
		for (j = 0; j < 3; j++)
			scanf("%d", &sh[101].xy[i][j]);
	sh[101].dir = -1;
}

//找出0的位置
int loction(int num)
{
	int i;
	for (i = 0; i < 9; i++)
		if (sh[num].xy[i / 3][i % 3] == 0) return i;
}


//進行標記
long long sign(int num)
{
	long long  sum;
	sum = sh[num].xy[0][0]*100000000 + sh[num].xy[0][1]*10000000 + sh[num].xy[0][2]*1000000 + sh[num].xy[1][0]*100000 + sh[num].xy[1][1]*10000 + sh[num].xy[1][2]*1000 + sh[num].xy[2][0]*100 + sh[num].xy[2][1]*10 + sh[num].xy[2][2];
	return sum;
}

void mobile(int num)
{

	int temp;
	int loc;
	int up = 1, down = 1, left = 1, right = 1;
	loc = loction(num);
	int stand = sh[num].dir;
	//dir的0 1 2 3分別代表左 上 右 下
	if (loc / 3 != 0 && stand != 1)
	{
		sh[count] = sh[num];
		temp = sh[count].xy[loc / 3][loc % 3];
		sh[count].xy[loc / 3][loc % 3] = sh[count].xy[loc / 3 - 1][loc % 3];
		sh[count].xy[loc / 3 - 1][loc % 3] = temp;
		sh[count].dir = 3;
		count++;
	};
	if (loc / 3 != 2 && stand != 3)
	{
		sh[count] = sh[num];
		temp = sh[count].xy[loc / 3][loc % 3];
		sh[count].xy[loc / 3][loc % 3] = sh[count].xy[loc / 3 + 1][loc % 3];
		sh[count].xy[loc / 3 + 1][loc % 3] = temp;
		sh[count].dir = 1;
		count++;
	}
	if (loc % 3 != 0 && stand != 0)
	{
		sh[count] = sh[num];
		temp = sh[count].xy[loc / 3][loc % 3];
		sh[count].xy[loc / 3][loc % 3] = sh[count].xy[loc / 3][loc % 3 - 1];
		sh[count].xy[loc / 3][loc % 3 - 1] = temp;
		sh[count].dir = 2;
		count++;
	}
	if (loc % 3 != 2 && stand != 2)
	{
		sh[count] = sh[num];
		temp = sh[count].xy[loc / 3][loc % 3];
		sh[count].xy[loc / 3][loc % 3] = sh[count].xy[loc / 3][loc % 3 + 1];
		sh[count].xy[loc / 3][loc % 3 + 1] = temp;
		sh[count].dir = 0;
		count++;
	}

}
void display(int num)
{
	int i, j;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
			printf("%d ", sh[num].xy[i][j]);
		printf("\n");
	}
}

int search()
{
	int i = 0;
	while (1)
	{
		printf("\n");
		display(i);
		printf("\n");
		if (i == 100)
		{
			printf("超出了上限次數\n");
			return 0;
		}
		if (sign(i) == sign(101))
		{
			printf("在第%d次找到了", i);
			display(i);
			return i;
		}
		mobile(i);
		i++;
	}
}

int main()
{
	init();
	search();
	return 0;
}
//測試用例
/*
2 8 3
1 6 4
7 0 5
1 2 3
7 8 4
0 6 5
*/

參考資料:Sedgewick R, Wayne K D. Algorithms. 4th[J]. 2011.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM