八數碼問題

 摘要:近日來,人工智能成爲科技領域搜索熱詞,無論是從人機大戰的新聞來看,還是從新提出的深度學習理論來分析,我們可以可以清晰的預見,人工智能即將騰飛。

  
人工智能,顧名思義,就是模擬人類思考模式的超級算法系統,學習能力和推理能力是其核心內容。舉個簡單的例子,“機器學習(MachineLearning)”就是人工智能領域裏很有前途的課題,其主要內容是利用大數據訓練程序,讓它們找到一些可遵循的規律,並且讓程序本身大膽的預測結果。在這個過程中搜索策略變的尤爲關鍵。本文主要論述計算機科學與技術專業大三下專業課《人工智能》第二個實驗算法。

關鍵字:人工智能,搜索問題,啓發式搜索

Eight digital problem 

Abstract: in recent days, the artificial intelligence search words become areas of science and technology, whether from the point of man-machine war news, or the depth of the new proposed learning theory to the analysis, we can clearly foresee, artificial intelligence is about to take off.

  Artificial intelligence, as the name implies, is to simulate human thinking mode of super algorithm system, learning ability and reasoning ability is the core content. A simple example, the "machine learning (MachineLearning)" is the field of artificial intelligence is a promising subject, its main content is to use big data training program, let them find some follow rules, and make bold prediction to the program itself. In the process of the search strategy is particularly critical. This paper mainly discusses the computer science and technology under the junior in professional course "artificial intelligence".

Keywords: artificial intelligence, search problems, heuristic search

1,問題重述 
  
3×3九宮棋盤,放置數碼爲1 -8的8個棋牌,剩下一個空格,只能通過棋牌向空格的移動來改變棋盤的佈局。

要求:根據給定初始佈局(即初始狀態)和目標佈局(即目標狀態),如何移動棋牌才能從初始佈局到達目標佈局,找到合法的走步序列。
             圖片

2,問題分析
  
對於八數碼問題的解決,首先要考慮是否有答案。每一個狀態可認爲是一個1×9的矩陣,問題即通過矩陣的變換,是否可以變換爲目標狀態對應的矩陣?由數學知識可知,可計算這兩個有序數列的逆序值,如果兩者都是偶數或奇數,則可通過變換到達,否則,這兩個狀態不可達。這樣,就可以在具體解決問題之前判斷出問題是否可解,從而可以避免不必要的搜索。
  
如果初始狀態可以到達目標狀態,那麼採取什麼樣的方法呢?
  
常用的狀態空間搜索有深度優先和廣度優先。廣度優先是從初始狀態一層一層向下找,直到找到目標爲止。深度優先是按照一定的順序前查找完一個分支,再查找另一個分支,以至找到目標爲止。廣度和深度優先搜索有一個很大的缺陷就是他們都是在一個給定的狀態空間中窮舉。這在狀態空間不大的情況下是很合適的算法,可是當狀態空間十分大,且不預測的情況下就不可取了。他的效率實在太低,甚至不可完成。由於八數碼問題狀態空間共有9!個狀態,對於八數碼問題如果選定了初始狀態和目標狀態,有9!/2個狀態要搜索,考慮到時間和空間的限制,在這裏採用A*算法作爲搜索策略。在這裏就要用到啓發式搜索
  
啓發式搜索就是在狀態空間中的搜索對每一個搜索的位置進行評估,得到最好的位置,再從這個位置進行搜索直到目標。這樣可以省略大量無畏的搜索路徑,提到了效率。在啓發式搜索中,對位置的估價是十分重要的。採用了不同的估價可以有不同的效果。
  
啓發中的估價是用估價函數表示的,如:f(n) = g(n) +h(n)其中f(n) 是節點n的估價函數,g(n)是在狀態空間中從初始節點到n節點的實際代價,h(n)是從n到目標節點最佳路徑的估計代價。 在此八數碼問題中,顯然g(n)就是從初始狀態變換到當前狀態所移動的步數,估計函數f(n)我們就可採用當前狀態各個數字牌不在目標狀態未知的個數,即錯位數。 

  2.1使用寬度優先搜索方法解決該問題
   
爲問題狀態的表示建立數據結構:
 (1)
3×3的一個矩陣矩陣元素S ij∈{0,1,,8};其中1≤i,j≤3,
 (2)
數字0指示空格,
 (3)
數字1 - 8指示相應棋牌。
 (4)
制定操作算子集:

   直觀方法——爲每個棋牌制定一套可能的走步:左、上、右、下四種移動。這樣就需32個操作算子。

   簡易方法——僅爲空格制定這4種走步,因爲只有緊靠空格的棋牌才能移動。

   空格移動的唯一約束是不能移出棋盤。  
   圖片 
  
  2.2使用深度優先搜索解決該問題
  
首先擴展最新產生的(即最深的)節點防止搜索過程沿着無益的路徑擴展下去,往往給出一個節點擴展的最大深度——深度界限與寬度優先搜索算法最根本的不同在於:將擴展的後繼節點放在OPEN表的前端。 
  
  圖片

  圖片

2.3使用啓發式搜索
  
特點:重排OPEN表,選擇最有希望的節點加以擴展

  種類:有序搜索(A算法)A*算法

用來加速搜索過程的有關問題領域的
特徵信息。包括:
用於決定要擴展的下一個節點的信息;
在擴展一個節點時,用於決定要生成哪一個或哪幾個後繼節點的信息;
用於決定某些應該從搜索樹中拋棄或修剪的節點的信息;
使用啓發式信息指導的搜索過程稱爲啓發式搜索.
用來估算節點處於最佳求解路徑上的希望程度的函數

f(n) = g(n) + h(n)

n ——搜索圖中的某個當前被擴展的節點;

f(n) ——從初始狀態節點s, 經由節點n到達目標節點ng估計的最小路徑代價;

g(n) ——s的實際路徑代價;

 h(n)——nng,估計的最小路徑代價。
 
  

   估價函數:f(n)=d(n)+w(n) 
        
其中:d(n)n的深度 w(n)爲不在位的棋子數
3,求解過程
  
不管哪種搜索,都統一用這樣的形式表示:搜索的對象是一個圖,它面向一個問題,不一定有明確的存儲形式,但它裏面的一個結點都有可能是一個解(可行解),搜索的目的有兩個方面,或者求可行解,或者從可行解集中求最優解。
  
搜索算法可分爲兩大類:無信息的搜索算法和有信息的搜索算法。無信息的搜索又稱盲目搜索,其特點是隻要問題狀態可以形式化表示,原則上就可用使用無信息的搜索,無信息搜索有如下常見的幾種搜索策略:廣度優先搜索、代價一致搜索、深度優先搜索、深度有限搜索、迭代深入優先搜索、雙向搜索。我們說DFSBFS都是蠻力搜索,因爲它們在搜索到一個結點時,在展開它的後續結點時,是對它們沒有任何認識的,它認爲它的孩子們都是一樣的優秀,但事實並非如此,後續結點是有好有壞的。好,就是說它離目標結點,如果優先處理它,就會更快的找到目標結點,從而整體上提高搜索性能。
  
爲了改善上面的算法,我們需要對展開後續結點時對子結點有所瞭解,這裏需要一個估值函數,估值函數就是評價函數,它用來評價子結點的好壞,因爲準確評價是不可能的,所以稱爲估值。這就是我們所謂的有信息搜索。如果估值函數只考慮結點的某種性能上的價值,而不考慮深度,比較有名的就是有序搜索(Ordered-Search),它着重看好能否找出解,而不看解離起始結點的距離(深度)。如果估值函數考慮了深度,或者是帶權距離(從起始結點到目標結點的距離加權和),那就是A*如果不考慮深度,就是說不要求最少步數,移動一步就相當於向後多展開一層結點,深度多算一層,如果要求最少步數,那就需要用A*。簡單的來說A*就是將估值函數分成兩個部分,一個部分是路徑價值,另一個部分是一般性啓發價值,合在一起算估整個結點的價值,

考慮到八數碼問題的特點,在本實驗中使用A*算法求解。A*搜索是一種效的搜索算法,它把到達節點的耗散g(n)和從該節點到目標節點的消耗h(n)結合起來對節點進行評價:f(n)=g(n)+h(n)。當h(n)是可採納時,使用Tree-Search的A*算法將是最優的。

      圖片     

      圖片

  A*算法,
GRAPHSEARCH過程中,如果第8步的重排OPEN表是依據f(n)=g(n)+h(n)進行的,則稱該過程爲A算法A算法中,如果對所有的n存在h(n)≤h*(n),則稱h(n)h*(n)的下界,它表示某種偏於保守的估計。採用h*(n)的下界h(n)爲啓發函數的A算法,稱爲A*算法。當h=0時,A*算法就變爲有序搜索算法。
  
A算法中,如果滿足條件:

  (1) g(n)是對g*(n)的估計,且g(n)>0;

  (2) h(n)h*(n)的下界,即對任意節點n均有0≤h(n)≤h*(n)A算法稱爲A*算法
  
A*算法的可納性,對任一個圖,存在從S到目標的路徑,如果一個搜索算法總是結束在一條從S到目標的最佳路徑上,則稱此算法是可採納的。算法A*保證只要最短路徑存在,就一定能找出這條路徑,所以算法A*是可納的。
  
估價函數:f(n)=d(n)+w(n) 

  其中:d(n)n的深度 w(n)不在位的棋子數

      取h(n)=w(n),則有w(n)≤h*(n),h(n)滿足A*算法的限制條件。 
  
在八數碼難題中, 令估價函數

    f(n)=d(n)+p(n)

  啓發函數h(n)=p(n),p(n)爲不在位的棋子與其目標位置的距離之和,則有p(n)≤h*(n),滿足A*算法的限制條件。  
  
w(n)——不在位的棋子數,不夠貼切,錯誤選用節點加以擴展。
  
更接近於h*(n)h(n),其值是節點n與目標狀態節點相比較,每個錯位棋子在假設不受阻攔的情況下,移動到目標狀態相應位置所需走步的總和。(n)w(n)更接近於h*(n),因爲p(n)不僅考慮了錯位因素,還考慮了錯位的距離(移動次數)。
  
說明h值越大,啓發功能越強, 搜索效率越高.特別地

  (1)h(n)=h*(n)

    搜索僅沿最佳路徑進行, 效率最高.
 
(2)h(n)=0

    無啓發信息, 盲目搜索, 效率低.
 
(3)h(n)>h*(n)

    是一般的A算法,效率高, 但不能保證找到最佳路徑. 有時爲求解難題取h(n)>h*(n), 以提高效率. 
  
             圖片

      圖片
4,程序設計
 

#include<iostream>
#include<cstdlib>
#include<ctime>
using namespace std;

class EightPuzzle
{
private:
	int num[9];
	int malposition;
	int depth;
	int evaluation;
public:
	EightPuzzle *parent;
	EightPuzzle *leaf_last;
	EightPuzzle *leaf_next;
public:
	EightPuzzle(int *num_input);
	void init(int *target);
	void setNum(int num[]);
	int *getNum();
	void getNum(int *num);
	int getMalposition()
	{
		return this->malposition;
	}
	int getDepth()
	{
		return this->depth;
	}
	int getEvaluation()
	{
		return this->evaluation;
	}
	void print();
	bool solvable(int *target);
	bool find_target(int *target);
	EightPuzzle& operator=(EightPuzzle& eightPuzzle);
	EightPuzzle& operator=(int other_num[9]);
	bool operator==(EightPuzzle& eigthPuzzle);
	bool operator==(int other_num[9]);
};

EightPuzzle::EightPuzzle(int *num_input)
{
	int ii;
	for (ii = 0; ii<9; ii++)
	{
		num[ii] = num_input[ii];
	}
	this->leaf_last = NULL;
	this->leaf_next = NULL;
	this->parent = NULL;
}

EightPuzzle& EightPuzzle::operator=(EightPuzzle& eightPuzzle)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		this->num[ii] = eightPuzzle.getNum()[ii];
	}
	this->malposition = eightPuzzle.getMalposition();
	this->depth = eightPuzzle.getDepth() + 1;
	this->evaluation = this->malposition + this->depth;
	return *this;
}
EightPuzzle& EightPuzzle::operator=(int other_num[9])
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		num[ii] = other_num[ii];
	}
	return *this;
}
bool EightPuzzle::operator==(EightPuzzle& eightPuzzle)
{
	int match = 1;
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (this->num[ii] != eightPuzzle.getNum()[ii])
		{
			match = 0;
			break;
		}
	}
	if (match == 0)
		return false;
	else
		return true;
}
bool EightPuzzle::operator==(int other_num[9])
{
	int match = 1;
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (this->num[ii] != other_num[ii])
		{
			match = 0;
			break;
		}
	}
	if (match == 0)
		return false;
	else
		return true;
}

void EightPuzzle::init(int *target)
{
	int ii;
	int temp = 0;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] != target[ii])
		{
			temp++;
		}
	}
	this->malposition = temp;
	if (this->parent == NULL)
	{
		this->depth = 0;
	}
	else
	{
		this->depth = this->parent->depth + 1;
	}
	this->evaluation = this->malposition + this->depth;
}

void EightPuzzle::setNum(int num[])
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		this->num[ii] = num[ii];
	}
}

int *EightPuzzle::getNum()
{
	return this->num;
}

void EightPuzzle::getNum(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		num[ii] = this->num[ii];
	}
}

bool EightPuzzle::solvable(int *target)
{
	int ii, ij;
	int count_num=0, count_target=0;
	for (ii = 0; ii < 9; ii++)
	{
		for (ij = 0; ij < ii; ij++)
		{
			if ((this->num[ij] < this->num[ii]) && (this->num[ij] != 0))
			{
				count_num++;
			}
			if (target[ij] < target[ii] && target[ij] != 0)
			{
				count_target++;
			}
		}
	}
	if ((count_num + count_target) % 2 == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}

bool EightPuzzle::find_target(int *target)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (this->num[ii] != target[ii])
		{
			break;
		}
	}
	if (ii == 9)
	{
		return true;
	}
	else
	{
		return false;
	}
}

bool move_up(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] == 0)
		{
			break;
		}
	}
	if (ii < 3)
	{
		return false;
	}
	else
	{
		num[ii] = num[ii - 3];
		num[ii - 3] = 0;
	}
	return true;
}

bool move_down(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] == 0)
		{
			break;
		}
	}
	if (ii > 5)
	{
		return 0;
	}
	else
	{
		num[ii] = num[ii + 3];
		num[ii + 3] = 0;
	}
	return true;
}

bool move_left(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] == 0)
		{
			break;
		}
	}
	if (ii == 0 || ii == 3 || ii == 6)
	{
		return false;
	}
	else
	{
		num[ii] = num[ii - 1];
		num[ii - 1] = 0;
	}
	return true;
}

bool move_right(int *num)
{
	int ii;
	for (ii = 0; ii < 9; ii++)
	{
		if (num[ii] == 0)
		{
			break;
		}
	}
	if (ii == 2 || ii == 5 || ii == 8)
	{
		return false;
	}
	else
	{
		num[ii] = num[ii + 1];
		num[ii + 1] = 0;
	}
	return true;
}

void EightPuzzle::print()
{
	int ii;
	for (ii = 0; ii<9; ii++)
	{
		if ((ii + 1) % 3 != 0)
		{
			cout << num[ii] << ",";
		}
		else
		{
			cout << num[ii] << endl;
		}
	}
}

bool existed(int *num, EightPuzzle *start)
{
	EightPuzzle *temp;
	for (temp = start; temp != NULL; temp = temp->parent)
	{
		if (*temp == num)
		{
			return true;
		}
	}
	return false;
}

EightPuzzle *best_route(EightPuzzle *start,EightPuzzle *target)
{
	EightPuzzle *temp, *best;
	temp = best = start;
	start->init(target->getNum());
	int min = start->getEvaluation();
	for (temp = start; temp != NULL; temp = temp->leaf_next)
	{
		if (min > temp->getEvaluation())
		{
			best = temp;
			min = temp->getEvaluation();
		}
	}
	return best;
}

void print_route(EightPuzzle *best,int list_length)
{
	int step = 0;
	EightPuzzle *temp;
	for (temp = best->parent; temp != NULL; temp = temp->parent)
	{
		cout << endl;
		temp->print();
		step++;
	}
	cout << endl << "The total steps is " << step << "." << endl;
	cout << endl << "The memory cost is " << list_length << "." << endl;
	return;
}

void proceeding(EightPuzzle &start, EightPuzzle &target)
{
	if (!start.solvable(target.getNum()))
	{
		cout <<endl<< "The serious number you input can't be solvable!" << endl;
		return;
	}
	EightPuzzle *best = &start;
	EightPuzzle *list = &start;
	EightPuzzle *apply,*temp;
	int num[9],list_length=0;
	while (best != NULL)
	{
		best = best_route(list,&target);
		if (best->find_target(target.getNum()))
		{
			print_route(best,list_length);
			return;
		}
		temp = best->leaf_last;
		best->getNum(num);
		if (move_up(num) && !existed(num, best))
		{
			apply = new EightPuzzle(num);
			apply->parent = best;
			apply->init(target.getNum());
			apply->leaf_last = temp;
			if (temp == NULL)
			{
				list = apply;
			}
			else
			{
				temp->leaf_next = apply;
			}
			temp = apply;
			list_length++;
		}
		best->getNum(num);
		if (move_down(num) && !existed(num, best))
		{
			apply = new EightPuzzle(num);
			apply->parent = best;
			apply->init(target.getNum());
			apply->leaf_last = temp;
			if (temp == NULL)
			{
				list = apply;
			}
			else
			{
				temp->leaf_next = apply;
			}
			temp = apply;
			list_length++;
		}
		best->getNum(num);
		if (move_left(num) && !existed(num, best))
		{
			apply = new EightPuzzle(num);
			apply->parent = best;
			apply->init(target.getNum());
			apply->leaf_last = temp;
			if (temp == NULL)
			{
				list = apply;
			}
			else
			{
				temp->leaf_next = apply;
			}
			temp = apply;
			list_length++;
		}
		best->getNum(num);
		if (move_right(num) && !existed(num, best))
		{
			apply = new EightPuzzle(num);
			apply->parent = best;
			apply->init(target.getNum());
			apply->leaf_last = temp;
			if (temp == NULL)
			{
				list = apply;
			}
			else
			{
				temp->leaf_next = apply;
			}
			temp = apply;
			list_length++;
		}
		temp->leaf_next = best->leaf_next;
		if (best->leaf_next != NULL)
		{
			best->leaf_next->leaf_last = temp;
		}
		best->leaf_next = best->leaf_last = NULL;
	}
}

void input(int num_init[])
{
	int ii, ij;
	cout << "Please input the initial state of the eight puzzle:" << endl;
	cout << "(0 for the blank)" << endl << endl;
	for (ii = 0; ii<9; ii++)
	{
		cin >> num_init[ii];
		if (num_init[ii]<0 || num_init[ii]>8)
		{
			cout << "Wrong number! Please input again:(0-8)" << endl;
			ii--;
		}
		for (ij = 0; ij<ii; ij++)
		{
			if (num_init[ii] == num_init[ij])
			{
				cout << "The number you inputed is duplicated! Try again:" << endl;
				ii--;
			}
		}
	}
}

int main(int argc, char **argv)
{
	double time;
	clock_t START, FINISH;
	int num_init[9];
	input(num_init);
	EightPuzzle *start = new EightPuzzle(num_init);
	int num_target[9] = { 1,2,3,8,0,4,7,6,5 };
	EightPuzzle *target = new EightPuzzle(num_target);
	cout << "The initial serial number is:" << endl;
	start->print();
	cout << "The target serial number is:" << endl;
	target->print();
	START = clock();
	proceeding(*start, *target);
	FINISH = clock();
	time = (double)(FINISH - START) * 1000 / CLOCKS_PER_SEC;
	cout << endl << "The total time cost to solve the puzzle is: ";
	cout<< time <<" millisecond."<< endl << endl;
	system("pause");
	return 0;
}

//216408753->18steps

發佈了21 篇原創文章 · 獲贊 14 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章