上一篇文章对二叉树做了一个简单的小练习,也是对二叉树有了一个大概的了解,接下来这一篇便是二叉树中即为关键的算法——二叉树的层次遍历,即BFS。
这个算法的重要性呢不再多说,主要用于求单源最短路径问题(无权值,即单权值的树)。
BFS——宽度优先遍历
顾名思义:先遍历完与初始状态最近的状态,然后再遍历与遍历完状态的最近的状态。从开始状态–>只需一次转移就到达的状态–>只需两次就到达的状态–>……..
如上一篇文章的二叉树
1
2 3
4 5 6 7
8 9 10 11 12 13 14 15
如果按照BFS如何遍历呢?
(1)从1开始遍历;(2)遍历与1最近的结点,即2和3;(3)遍历与2和3最近的结点,即4,5,6,7;(4)遍历与4,5,6,7最近的结点,即8,9,10,11,12,13,14,15。完成。
如何实现BFS?
答:队列。试想一下队列的特点——FIFO(先进先出),ok,那么我们模拟一下结点入队出队的过程。
(1)创建一个空队列q;
(2)将结点1入队;队列状态为:1
(3)循环(下面的步骤都在这个循环里完成),条件是判断队列q是否为空,因为如果所有结点都不再入队即遍历完成;
(4)取队头结点赋给新结点V,然后出队,即结点1出队,目前队列为空,结点V就是结点1;队列状态为:NULL
(5)找到与V相邻的所有结点,即结点2和3,然后入队;队列状态为:2 3
(6)取队头元素,即结点2,赋给V,然后出队;队列状态为:3
(7)找到与V相邻的所有结点,即结点4和5,然后入队:队列状态为:3 4 5
(8)。。。。。不再赘述过程
基本过程就是这样,求最短路(无权)的思路也是如此,每个结点有个dis值,表示从头结点开始到其中一个结点的最短距离。过程和上面基本一致,就是需要将所有结点的dis值先初始化为0,然后遍历到一个结点,就将此结点的dis等于它的父节点的dis+1。
-例题.树的层次遍历-UVA122
算法入门经典上给出一个例题,我们来分析一下。
问题描述:输入一个二叉树,然后从上到下从左到右的顺序输出各个结点的值。每个结点都按照从根结点到它的移动序列给出(L表示左,R表示右)。
样例输入:(11,LL) (7,LLL) (8,R) (5,) (4,L) (13,RL) (2,LLR) (1,RRR) (4,RR) ()
样例输出:5 4 8 11 13 4 7 2 1
这个题目的bfs就是按照我刚才整理的思路用队列来实现,其实我觉得难度在于如何按照题目要求进行输入,将结点保存起来。按照刘汝佳老师的代码进行分析。
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn = 256+10;
struct Node{
bool have_value;
int v;//用来保存结点值
Node *left,*right;
Node():have_value(false),left(NULL),right(NULL){}
};//定义一个结点结构体
Node* newnode()
{
return new Node();
}//创建新结点函数
char s[maxn];
Node* root;
bool failed;
void addnode(int v,char* s)
{
int n = strlen(s);
Node* u = root;
for(int i=0;i<n;i++)
{
if(s[i]=='L')
{
if(u->left==NULL) u->left = newnode();//如果结点不存在,建立新结点
u = u->left;//往左走到达新建立的结点
}
else if(s[i]=='R')
{
if(u->right==NULL) u->right = newnode();
u = u->right;
}
}
if(u->have_value) failed = true;//已经赋值过,表明输入有误
u->v = v;
u->have_value = true;//做标记
}
bool read_input()
{
failed = false;
root = newnode();
for(;;)
{
if(scanf("%s",s)!=1) return false;
if(!strcmp(s,"()")) break;//如果输入为"()",即输入结束,直接退出循环。
int v;
sscanf(&s[1],"%d",&v);//将字符串中的结点值读取出来赋给V
addnode(v,strchr(s,',')+1);//strchr函数是返回,首次出现的位置,然后插入结点
}
return true;
}
bool bfs(vector<int>& ans)
{
queue<Node*> q;
ans.clear();//每次遍历之前要先重置vector,即清空
q.push(root);//将根结点先放入队列中
while(!q.empty())//判断是否为空
{
Node* u = q.front();q.pop();//取队头结点赋给u,并出队
if(!u->have_value) return false;//如果有结点没有赋值,则输入有误
ans.push_back(u->v);//将当期结点的值插入到向量中
if(u->left!=NULL)
{
q.push(u->left);//如果左子结点不为空,则将左子结点入队
}
if(u->right!=NULL)
{
q.push(u->right);
}
}
return true;
}
int main()
{
vector<int> v;
while(read_input())
{
if(!bfs(v)) failed = 1;
if(failed) printf("not complete\n");
else{
for(int i=0;i<v.size();i++)
{
if(i != 0) printf(" ");
printf("%d", v[i]);
}
printf("\n");
}
}
return 0;
}
这题价值还是非常大的,通过vector来保存遍历的结点值,一边遍历一边保存。