十五数码——原理及实现(C语言)

目录

1、问题描述

2、原理及方法

2.1 启发式图搜索

2.1.1 启发式图搜索

2.1.2 启发式搜索算法A

2.2 A*

3、算法设计

3.1 数据结构

3.1.1 结构体

3.1.2 数组

 3.1.3 链表

3.2 函数

3.2.1 寻找0点位置

3.2.2 比较函数

3.2.3 移动函数

3.2.4 复制函数

3.2.5 输出函数

3.2.6 计算评价函数

3.3 循环

3.4 最小状态min

4、实验结果


1、问题描述

设计一个启发函数,利用A*算法求解15数码问题。

初始状态:

十五数码——原理及实现(C语言)_第1张图片

目标状态:

十五数码——原理及实现(C语言)_第2张图片

2、原理及方法

2.1 启发式图搜索

2.1.1 启发式图搜索

1、概念

启发式搜索时利用问题拥有的启发信息来引导搜索,达到减少搜索范围,降低问题复杂度的目的。这种利用启发信息的搜索过程称为启发式搜索方式。

2、核心思想

利用所处理问题的启发信息引导搜索。

3、目的

减少搜索范围,降低问题复杂度。

2.1.2 启发式搜索算法A

1、概念

启发式搜索算法A,一般简称为A算法,是一种典型的启发式搜索算法。

2、基本思想

定义一个评价函数f,对当前的搜索状态进行评估,找出一个最有希望的结点来拓展。

3、评价函数

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

其中,n是被评价的结点。

具体函数含义如下表所示:

十五数码——原理及实现(C语言)_第3张图片

4、过程

A算法就是利用预测,来达到有效搜索的目的。它每次按照f(n)值的大小对OPEN表中的元素进行排序,f值小的结点放在前面,而f值大的结点则被放在OPEN表的后面,这样每次扩展结点时,总是选择当前f值最小的结点来优先拓展。

  • OPEN:=(s),f(s):=g(s)+h(s);
  • LOOP: IF OPEN=( ) THEN EXIT(FAIL);
  • n:=FIRST(OPEN);
  • IF GOAL (n)  THEN EXIT(SUCCESS);
  • REMOVE (n, OPEN), ADD(n, CLOSED); 
  • EXPAND(n)→ (mi),把mi作为n的后继节点添入G,并计算 f(n-mi)=g(n-mi)+h(mi);

        ADD (mj, OPEN),

        IF f(n-mk) < f(mk)  THEN f(mk):=f(n-mk),

        IF f(n-ml) < f(ml)  THEN f(ml):=f(n-ml),ADD(ml,OPEN);

  • OPEN 中的节点按 f  值从小到大排序;
  • GO LOOP;

5、算法说明

  • A 算法由一般的图搜索算法改变而成;
  • 控制策略:按照 f(n)值递增的顺序对 OPEN 中的元素进行排序,f(n)值小的节点排在前面,大的放在 OPEN 表的后面。
  • 效果:每次扩展节点时,优先选择当前f(n)值最小的节点来扩展。
  • 结论:算法 A 是一个好的优先搜索策略。

6、搜索策略

  • 扩展n后新生成的子节点m1({mj})、m2({mk})、m3({ml})分别计算其评价函数值:

       f(m1)=g(m1)+h(m1);

       f(n-m2)=g(n-m2)+h(m2);

       f(n-m3)=g(n-m3)+h(m3);

  • 按第6步比较不同路径的耗散值并进行相应的指针修正。 

2.2 A*

1、概念

在算法A中,当 h(n) ≤ h*(n) 时,称其为A*算法。

2、性质

  • 完备性:如果问题有解,则算法一定能找到解。
  • 可采纳性:如果问题有解,则算法一定能找到最佳解。即算法总能找到一条从 S 到目标节点的最佳路径。
  • 最优性:设A1和A2为某问题求解的两个A* 算法,若对所有非目标节点均有 h1(n) < h2(n) ≤ h*(n) 则算法 A1展开的节点数目至少和A2一样多。

3、结论

通过A*算法的性质的证明,可得到几个有助于理解A*算法的结论:

  • A*算法结束前,OPEN表中必存在f(n) ≤ f*(S)的节点(n是在最佳路径上的节点)。
  • OPEN表上任一具有 f(n) ≤ f*(S)的节点n,最终都将被A*选作扩展的节点。
  • A*选作扩展的任一节点,有 f(n) ≤ f*(S)。

4、不足

  • 存在的问题: 如果用扩展节点数作为评价搜索效率的准则,那么可以发现A算法第6步中,对CLOSED表中ml类节点要重新放回OPEN表中的操作,将引起多次扩展同一节点的可能。
  • 不良结果: 即使问题所包含的节点数少,但重复扩展某些节点,也将导致搜索效率下降。

5、改进

  • 对h加以限制,使得第一次扩展一个节点时,就找到了从s到该节点的最短路径。
  • 对算法加以改进,避免或减少节点的多次扩展。

6、改进后的算法过程

  • OPEN:=(s),f(s):=g(s)+h(s)= h(s),fm:=0;
  • LOOP:IF OPEN=()  THEN  EXIT (FAIL);
  • NEST:= { ni |f (ni) < fm }; * NEST给出了OPEN表中满足 f < fm 的节点集合。fm为处理过的节点中最大的f值*。(划分OPEN表)

         IF NEST ≠ ()  THEN  n:= ni (min (gi))  ELSE n:=FIRST (OPEN),fm =f(n);* NEST不空时,取其中 g(n) 的最小者作为当前要扩展的节点,否则取 OPEN 的第一个为当前要扩展的节点* (保证搜索效率)

  • IF GOAL (n)  THEN EXIT(SUCCESS);
  • REMOVE (n, OPEN), ADD(n, CLOSED); 
  • EXPAND(n)→ (mi),把mi作为n的后继节点添入G,并计算 f(n-mi)=g(n-mi)+h(mi);

         ADD (mj, OPEN),

         IF f(n-mk) < f(mk)  THEN f(mk):=f(n-mk),

         IF f(n-ml) < f(ml)  THEN f(ml):=f(n-ml),ADD(ml,OPEN);

  • OPEN 中的节点按 f  值从小到大排序;
  • GO LOOP;

3、算法设计

3.1 数据结构

3.1.1 结构体

1、概念

在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate data type)的一类。结构体可以被声明为变量、指针或数组等,用以实现较复杂的数据结构。结构体同时也是一些元素的集合,这些元素称为结构体的成员(member),且这些成员可以为不同的类型,成员一般用名字访问。 

2、定义

typedef struct QNode {
	int data[N][N]; 
	int ancent;	  
	int x;          
	int y;          
	int gone;		 
	int value;		 
	int deep;       
	struct QNode* father;	
	struct QNode* next;     
}QNode, *QueuePtr;

我们定义了一个名为QNode的结构体,结构体中的变量的作用及类型如下表所示:

 十五数码——原理及实现(C语言)_第4张图片

在这里我们设置评价函数为:f(n) = d(n) + p(n)。

其中,d(n)代表结点的深度,取g(n) = d(n)表示讨论单位耗散的情况;取h(n) = p(n)表示以“不在位”的每一个将牌与其目标位置之间的距离的总和作为启发函数的度量。

我们的距离采用的是曼哈顿距离。曼哈顿距离,就是表示两个点在标准坐标系上的绝对轴距之和。

3.1.2 数组

1、概念

在C语言中,数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。

2、定义

int A[N][N] = {          
{5,1,2,4},
{9,6,3,8},
{13,15,10,11},
{14,0,7,12}
};
int B[N][N] = {          
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,0}
};

 我们定义了两个4*4的二位数组,A,B。分别用来存储初始状态和目标状态。如下表所示:

十五数码——原理及实现(C语言)_第5张图片

 3.1.3 链表

1、概念

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。相比于线性表顺序结构,操作复杂。

链表具有单链表、循环单链表、双链表、循环双链表。在这里我们使用的是双链表。

2、定义

typedef struct {
	QueuePtr head;
	QueuePtr rear; 
}LinkQueue;

 其中head是头结点,rear是尾结点。

3.2 函数

3.2.1 寻找0点位置

bool begin_opint() { 
	int i, j;
	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++) {
			if (A[i][j] == 0) {
				x = i; y = j;
				return true;
			}
		}
	}                                              
 	return false;
}

3.2.2 比较函数

bool compare(int a[N][N]) { 
	int i, j;
	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++) {
			if (a[i][j] != B[i][j])
				return false;
		}
	}
	return true;
}

传入的a矩阵,即当前状态的矩阵同目标矩阵B进行比较。若相等,返回true,否则,返回false。 

3.2.3 移动函数

针对十五数码问题,0位于不同位置的移动是不同的,如下图所示:

十五数码——原理及实现(C语言)_第6张图片

其中,1代表不能向上移,2不能向右移,3不能向下移,4不能向左移。以向左移动为例。

向左移动:

bool moveleft(int a[N][N], QueuePtr * b, int x, int y) {
	int k, i, j;
	if (y == 0)
		return false;
	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++)
			(*b)->data[i][j] = a[i][j];
	}
	k = (*b)->data[x][y];
	(*b)->data[x][y] = (*b)->data[x][y - 1];
	(*b)->data[x][y - 1] = k;
	(*b)->x = x;
	(*b)->y = y - 1;
	return true;
}

当0位于数组的第一行时,不可以向上移,即y = 0时,返回false。

3.2.4 复制函数

bool copy(QueuePtr * a) { 
	int i, j;
	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++)
			(*a)->data[i][j] = A[i][j];
	}
	return true;
}

 将初始状态A矩阵复制到当前状态a矩阵中。

3.2.5 输出函数

void output(QueuePtr * p) { 
	int i, j;
	long int n = 0;
	for (; (*p)->father != NULL; (*p) = (*p)->father, n++) {
		for (i = 0; i < N; i++) {
			for (j = 0; j < N; j++) {
				printf(" %d", (*p)->data[i][j]);
			}printf("\n");
		}printf("\n");
	}
	printf("step is %d\n", n - 1);
}

 若当前状态和目标状态相同,则输出。

3.2.6 计算评价函数

int getvalue(QueuePtr * p) { 
	int count = 0;
	bool test = true;  
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			test = true;
			for (int k = 0; k < N; k++) {
				for (int l = 0; l < N; l++) {
					if ((i != (*p)->x || j != (*p)->y) && (*p)->data[i][j] == B[k][l]) {
						count = count + abs(i - k) + abs(j - l);
						test = false;
					}
					if (test == false) break;
				}
				if (test == false) break;
			}
		}
	}
	count = count + (*p)->deep;
	return count;
}

评价函数为:f(n) = d(n) + p(n)。

其中,d(n)代表结点的深度,(*p)->deep;取h(n) = p(n)表示以“不在位”的每一个将牌与其目标位置之间的距离的总和作为启发函数的度量,count = count + abs(i - k) + abs(j - l)最后求其和:count = count + (*p)->deep

3.3 循环

1、switch格式

switch(值){

          case 值1:

                  代码

                  break;

          case 值2:

                  代码

                  break;

           default:

                  代码

                  break;

2、switch原理

(1) 找case的值 匹配到进入case执行代码,找break;

(2) 若没找到,找下一个case,如果都没找到就找default,进行default代码,找break;

(3) 若没break,进入下一个case或default找break,直到最后。

3、代码

switch (min->ancent) {
		case 1:p = (QueuePtr)malloc(sizeof(QNode));	
			if (moveleft(min->data, &p, min->x, min->y)) {
...
}
case 2:p = (QueuePtr)malloc(sizeof(QNode));			
			if (moveleft(min->data, &p, min->x, min->y)) {
...
}
case 3:p = (QueuePtr)malloc(sizeof(QNode));			
			if (moveleft(min->data, &p, min->x, min->y)) {
...
}
case 4:p = (QueuePtr)malloc(sizeof(QNode));
			if (moveup(min->data, &p, min->x, min->y)) {
...
}
default:p = (QueuePtr)malloc(sizeof(QNode));
			if (moveleft(min->data, &p, min->x, min->y)) { 
...
}

min->ancent表示最小状态上一次移动的方向,即当前状态不可移动上一次移动方向的反方向。

当min->ancent = 1时,表示祖先结点从右边来,即不可再向右移动;当min->ancent = 2时,祖先结点从下边来,即不可再向下移动;当min->ancent = 3时,表示祖先结点从左边来,即不可再向左移动;当min->ancent = 4时,表示祖先结点从上面来,即不可再向上移动;当min->ancent ≠ 1,2,3,4时,表示当前状态为初始状态,祖先为空,因此任意方向均可。当前状态p插入到OPEN表的尾部。

3.4 最小状态min

for (min = q = open.head->next; q != NULL; q = q->next) {
			if (q->value <= min->value && q->gone == 0) {
				min = q;
				break;
			}
		}
}

min为最小状态,通过遍历OPEN表寻找最小状态。

min->father->next = min->next;  

min->next = closed->next;

closed->next = min;

在open表中删除找到的最小态并将最小态min插入到CLOSED表头。

4、实验结果

所有结果均为逆序,因为每次输出的均是当前状态的父节点。

1、目标状态

 1 2 3 4

 5 6 7 8

 9 10 11 12

 13 14 15 0

2、中间状态 

1 2 3 4

5 6 7 8

9 10 11 0

13 14 15 12

 

 1 2 3 4

 5 6 7 8

 9 10 0 11

 13 14 15 12

 

 1 2 3 4

 5 6 0 8

 9 10 7 11

 13 14 15 12

 

 1 2 0 4

 5 6 3 8

 9 10 7 11

 13 14 15 12

 

 1 0 2 4

 5 6 3 8

 9 10 7 11

 13 14 15 12

 

 0 1 2 4

 5 6 3 8

 9 10 7 11

 13 14 15 12

 

 5 1 2 4

 0 6 3 8

 9 10 7 11

 13 14 15 12

 

 5 1 2 4

 9 6 3 8

 0 10 7 11

 13 14 15 12

 

 5 1 2 4

 9 6 3 8

 13 10 7 11

 0 14 15 12

 

 5 1 2 4

 9 6 3 8

 13 10 7 11

 14 0 15 12

 

 5 1 2 4

 9 6 3 8

 13 10 7 11

 14 15 0 12

 

 5 1 2 4

 9 6 3 8

 13 10 0 11

 14 15 7 12

 

 5 1 2 4

 9 6 3 8

 13 0 10 11

 14 15 7 12

3、初始状态

 5 1 2 4

 9 6 3 8

 13 15 10 11

 14 0 7 12

4、代价

step is 14

你可能感兴趣的:(C,Artificial,Intelligence,人工智能,启发式算法,c语言)