DFS和BFS笔记(一)基于C语言的广度优先搜索

主要是整理老师课堂笔记+参考博文的学习笔记一篇。


 搜索是“通用解题方法”,在算法和人工智能领域有重要的地位。但是搜索有局限性和自身灵活性,是最难学最难用的算法之一。

【学习目标】面对问题:l1.很快建立状态空间 2.提出一个合理算法 3.简单估计时空性能

搜索算法是利用计算机的高性能来有目的地穷举一个问题的部分或所有可能情况从而求得问题的解的一种方法。

状态(state):是对问题在某一时刻进展情况的数学描述。

状态转移(state-transition):问题从一种状态到另一种(或几种)状态的操作。

状态空间(state space):搜索其实是在遍历一个隐式图(节点是所有的状态,有向边对应状态转移)。搜索的一个可行解就是一条从起始节点出发到目标状态集中任一节点的路径;搜索的过程就是根据初始条件和扩展规则,构造一棵解答树并寻找符合目标状态的节点的过程。在这个过程中对隐式图遍历所形成的遍历树也成为状态空间。

搜索的分类:

1盲目搜索:

·广度优先搜索(Breadth First Search)

·深度优先搜索(Depth First Search)

·纯随机搜索、重复式搜索、迭代加深搜索、迭代加宽搜索、柱型搜索……

2启发式搜索

BFS广度优先搜索

概念

BFS追求的是广度,每次都是一层一层地遍历,每次把当前一层的元素遍历完才继续遍历下一层。

广度优先搜索是一种用于图的查找算法,可帮助回答两类问题。

  ·从节点A出发,有前往节点B的路径吗?
  ·从节点A出发,前往节点B的哪条路径最短?

搜索步骤

如图:(图片来源https://www.cnblogs.com/tianqizhi/p/9914539.html

你想知道有没有人认识芒果营销商。

搜素步骤:你→一级关系朋友是否认识→二级关系朋友是否认识→三级……

每一次搜查时候:如果有人认识,搜索终止;反之,添加到“已经搜索”名单,继续进行。

总的来说:先在一度关系中搜索,确定其中没有后,在二度关系中搜索。然后“扩大搜索圈”。

终止条件:1-找到目标2-遍历完全部图。

工作原理

(图片来源https://www.cnblogs.com/tianqizhi/p/9914539.html

 

  • 首先将根节点放入队列中。
  • 从队列中取出第一个节点,并检验它是否为目标。
  • 如果找到目标,则结束搜索并回传结果。
  • 否则将它所有尚未检验过的直接子节点加入队列中。
  • 若队列为空,表示整张图都检查过了——亦即图中没有欲搜索的目标。结束搜索并回传“找不到目标”。

定义一个队列;

起始点加入队列;

while(队列不空)

{

    取出队头结点;

    若它是所求的目标状态,跳出循环;

    否则,从它扩展出子结点,全都添到队尾;

}

若循环中找到目标,输出结果;

否则输出无解;

需要的工具1.队列2.邻接表或邻接矩阵3.标记数组visited

·实际上是在图里面搜索,但是可以不去建立图(“隐式图”),搜索会得到一颗搜索树

·l搜索树的结点个数、分枝数、深度,决定着搜索的效率。

·搜索过程没有回溯,牺牲空间换取时间。时间复杂度:O(V+E)

上面的问题(代码改成python3了)

# 来源:https://www.cnblogs.com/tianqizhi/p/9914539.html
graph = {}
graph["you"] = ["alice", "bob", "claire"]
graph["bob"] = ["anuj", "peggy"]
graph["alice"] = ["peggy"]
graph["claire"] = ["thom", "jonny"]
graph["anuj"] = []
graph["peggy"] = []
graph["thom"] = []
graph["jonny"] = []

def person_is_seller(name):
    return name[-1] == 'm'

from collections import deque
search_queue = deque()#创建一个队列
search_queue += graph["you"]#将你的邻居都加入到这个搜索队列中

def search(name):
    search_queue = deque()
    search_queue += graph[name]
    searched = [] #这个数组用于记录检查过的人
    while search_queue:
        person = search_queue.popleft()
        if not person in searched:#仅当这个人没检查过时才检查
             if person_is_seller(person):
                print(person + " is a mango seller!")
                return True
             else:
                search_queue += graph[person]
                searched.append(person)#将这个人标记为检查过
    return False

search("you")

实例:Knight Moves

(这道题在洛谷上面是:UVA439 骑士的移动 Knight Moves(所以这道题可以去洛谷找题解)

Q象棋棋盘上有一个马,要从起点跳到指定目标,最少跳几步?

(补充·马(N):每步棋先横走或直走一格,然后再往外斜走一格;或者先斜走一格,最后再往外横走或竖走一格(即走“日”字)。可以越子,没有中国象棋中的“蹩马腿”限制。

实现步骤

获取查询起点和终点。

1.生成图G。

python用字典来实现:

·哈希表来表示图结构,键表示某个结点,值表示该节点对应的一度关系结点。

·键-值对的添加顺序不是很重要,因为散列表是无序的。

2.定义查找函数。

3.创建已经查询的列表。

4.定义判断函数。

eg:a1-->e4

完整代码: (为什么我不用C++呢因为现在的我还不会)

#include<stdio.h>
#include<string.h>
#include <stdlib.h>
typedef struct QNode{
    int x;
    int y;
    struct QNode * next;
}QNode;
//创建链式队列的函数
QNode * initQueue(){
    //创建一个头节点
    QNode * queue=(QNode*)malloc(sizeof(QNode));
    //对头节点进行初始化
    queue->next=NULL;
    return queue;
}
//入队 
QNode* enQueue(QNode * rear,int x,int y){  
    QNode * enElem=(QNode*)malloc(sizeof(QNode));
    enElem->x=x;enElem->y=y;
    enElem->next=NULL;//1、用节点包裹入队元素
    rear->next=enElem;//2、新节点与rear节点建立逻辑关系
    rear=enElem;//3、rear指向新节点
    //返回新的rear,为后续新元素入队做准备
    return rear;
}
//出队
void DeQueue(QNode * top){
    if (top->next==NULL) {
        printf("队列为空\n");
        return ;
    }
    QNode * p=top->next;
    //printf("%d %d\n",p->x,p->y);
    top->next=p->next;
    free(p);
} 
//每一次进队列 
QNode* input(QNode *rear,int x,int y,int chess[9][9],int count)
{
	//8个入队列 
    if(x-2>0&&y-1>0&&chess[x-2][y-1]==0)
    {
    	rear=enQueue(rear,x-2,y-1);//1	
    	chess[x-2][y-1]=count;//(标记数组 )
	}		
	if(x-2>0&&y+1<9&&chess[x-2][y+1]==0)
	{
		rear=enQueue(rear,x-2,y+1);	//2
		chess[x-2][y+1]=count; 
	}	
	if(x+2<9&&y-1>0&&chess[x+2][y-1]==0)
	{
		rear=enQueue(rear,x+2,y-1);//3
		chess[x+2][y-1]=count;
	}
	if(x+2<9&&y+1<9&&chess[x+2][y+1]==0)
	{
		rear=enQueue(rear,x+2,y+1);	//4
		chess[x+2][y+1]=count;
	}		
	if(x-1>0&&y-2>0&&chess[x-1][y-2]==0)
	{
		chess[x-1][y-2]=count;
		rear=enQueue(rear,x-1,y-2);//5
	}	
	if(x-1>0&&y+2<9&&chess[x-1][y+2]==0)
	{
		chess[x-1][y+2]=count;
		rear=enQueue(rear,x-1,y+2);	//6
	}			
	if(x+1<9&&y-2>0&&chess[x+1][y-2]==0)
	{
		chess[x+1][y-2]=count;
		rear=enQueue(rear,x+1,y-2);//7
	}			
	if(x+1<9&&y+2<9&&chess[x+1][y+2]==0)
	{
		chess[x+1][y+2]=count;
		rear=enQueue(rear,x+1,y+2);	//8
	}	
	return rear;
}

int main()
{
	//1创建图表:数组
	int chess[9][9]={0};
	//2.获取起始和终止条件。
	char order1[2]={0},order2[2]={0};
	scanf("%s%s",order1,order2);
	
	if(strcmp(order1,order2)==0)
	{
		printf("To get from %s to %s takes 0 knight moves.",order1,order2);
		return 0;
	}
	
	int x,y,m,n;
	int endx=order2[0]-'a'+1,endy=order2[1]-'0';
	//printf("end%d %d\n",endx,endy);
	x=order1[0]-'a'+1;y=order1[1]-'0';
	chess[x][y]=-1;//仅仅是标记,不要再走 
	//3.BFS搜寻
	//因为这道题8*8总是能够走到的 
	QNode * queue,*top,*rear;//创建头结点
	//向链队列中添加结点,使用尾插法添加的同时,队尾指针需要指向链表的最后一个元素
    queue=initQueue();
    top=queue;rear=queue;
    rear=input(rear,x,y,chess,1);
	int count=1;
	while(1)
	 {//队列先进先出,后进后出 
	 	QNode * p=top->next;//查看此时队列第一个元素 
	 	m=p->x;n=p->y;
	 	//printf(" %d: %d %d\n",chess[m][n],m,n);
	 	if(chess[m][n]!=count)
	 		count++;//更新轮数 
	 	if(m==endx&&n==endy)
	 	{//终止 
	 		printf("To get from %s to %s takes %d knight moves.",order1,order2,chess[m][n]);
			 return 0;
		 }	
		 rear=input(rear,m,n,chess,count+1);
		 DeQueue(top);
		
	 }
	return 0;
} 

就是这样。晚安。

PLUS:网上一些相关算法练习

l zju 1091《Knight Moves》
l zju 1047《Image Perimeters》
l zju 1103《Hike on a Graph》
l zju 1649《Rescue 》
l zju 1310《Robot》
l zju 1136《Multiple》
l zju 1530《Find The Multiple》
l zju 1301《The New Villa》

https://blog.csdn.net/t11383/article/details/91129812

https://blog.csdn.net/weixin_44585839/article/details/96478163

https://www.cnblogs.com/tianqizhi/p/9914539.html

https://blog.csdn.net/akyna/article/details/113786003

https://blog.csdn.net/weixin_44441131/article/details/106629327


搜索算法需要在指数量级的繁杂的可选对象中搜索某个具有特定性质的解,而且,求解这些问题似乎没有什么捷径可行。——《算法概论》

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值