图论-DFS、DFS序、欧拉序

DFS

DFS(Depth First Search),深度优先遍历,是用于遍历或者搜索树或图的算法。深度优先则指的是,其每次搜寻都会尝试往更深结点走。

DFS在搜索算法中,常常利用函数递归实现暴力枚举,而DFS在图论中,则是对图的每个结点的遍历。

DFS最显著的特征在于其 递归调用自身 ,在遍历图时,对其访问的点打上访问标记,在遍历时跳过标记过的点,以确保每个点仅访问一次

DFS大致结构如下:

DFS(v) // v 可以是图中的一个顶点,也可以是抽象的概念,如 dp 状态等。
	在v上打上访问标记
	for u in v的相邻结点
		if u没有打过标记 then
			DFS(u)
		end
	end
end

实际的DFS代码需要结合操作目的添加具体功能。

二叉树的DFS

在数据结构中二叉树是一种重要的数据结构,其概念在此不赘述。这里用二叉树的DFS来举例DFS在图的遍历应用。

二叉树的DFS遍历主要有三种顺序:
前序:根左右
中序:左根右
后序:左右根

具体代码:

/*前序遍历:根左右*/
void printPreorder(struct node* node){
    if (node == NULL){
        return;
    }
    //遍历根结点    这里对遍历到节点的操作为输出值,具体问题可以改成其它操作
    printf("%d ",node->data);  //节点操作
    //遍历左子树
    printPreorder(node->left);
    //遍历右子树
    printPreorder(node->right);
}

/*中序遍历:左根右*/
void printInorder(struct node *node) {
    if (node == NULL){
        return;
    }
    //遍历左子树
    printInorder(node->left);
    //遍历根结点
    printf("%d ",node->data);
    //遍历右子树
    printInorder(node->right);
}

/*后序遍历:左右根*/
void printPostorder(struct node* node)
{
    if (node == NULL){
        return;
    }
    //遍历左子树
    printPostorder(node->left);
    //遍历右子树
    printPostorder(node->right);
    //遍历根结点
    printf("%d ", node->data);
}

DFS序

DFS序,指的是在DFS调用过程中访问节点编号的序列。其记录了DFS遍历过程中各节点的访问顺序。这与上面二叉树的前中后序不同,前者是遍历序列,后者为遍历顺序。
图论-DFS、DFS序、欧拉序_第1张图片
该图的DFS序为: [ 1,2,3,6,4,5,7 ] 、 [ 1,5,7,4,2,6,3 ] 等。
同一颗树的 DFS序 可以不同,这取决于其遍历顺序,但并不影响DFS序的作用。

性质

我们发现,因为DFS的访问特点,每一个子树都对应着DFS序列中连续的一段(一段区间)。

	 1 , [2 , 3 , 6] , 4 , 5 , 7    子树2
	 1 , 2 , 3 , 6 , 4 , [5 , 7 ]   子树5

证明: 在dfs遍历时,当进入一个节点之后,dfs会先把当前的节点的所有子节点都遍历一遍,然后在回溯到当前节点,在这个过程中,它的所有子孙节点一定都被访问过了,而且在这之前,它的任何一个子孙节点都不可能被访问过。然后它才离开当前子树,回到父亲节点,再访问其他子树,所以同一子树的节点在dfs序中一定是连续的一段。

这个性质使得在子树上进行的修改、查询可以转换成区间修改、区间查询。dfs序的主要作用就是将一个子树变成序列上的一个连续区间

实现

在DFS遍历时,顺便记录下访问顺序,例如设dfn[x]为第x个访问的节点。

如何获得当前节点在dfs序中的范围呢?

我们只要设一个时间 time,一开始是0,每遍历到一个新的节点就+1。那么,当前节点的访问时间范围就是当前节点的访问标号范围。设in[x]为进入当前节点时的时间,out[x]为离开当前节点时的时间,那么子树x在dfs序里所对应的范围就是in[x]~out[x]。

但是,我们在具体操作的过程中,往往不记录dfs序,因为重要的往往是范围。注意,in[x]是第一次访问节点x的时间,in[x]≠dfn[x],而dfn[in[x]]=x。

DFS(v) 
	在v上打上访问标记
	dfn[cnt] = v
	in[v] = cnt++
	for u in v的相邻结点
		if u没有打过标记 then
			DFS(u)
		end
	end
	out[v] = cnt++
end

欧拉序

欧拉序和DFS序差不多,不过欧拉序记录重复遍历到的节点。
图论-DFS、DFS序、欧拉序_第2张图片
其中一种欧拉序为:
[ 1 , 2 , 3 , 2 , 6 , 2 , 1 , 4 , 1 , 5 , 7 , 5 , 1 ]

在进出节点时都将其进行记录。其作用与dfs序差不多。可在求解LCA中发挥作用。

DFS(v) 
	在v上打上访问标记
	O[cnt++] = v
	for u in v的相邻结点
		if u没有打过标记 then
			DFS(u)
			O[cnt++] = v
		end
	end
end

还有一种进入返回时加入一次:
[ 1 , 2 , 3 , 3 , 6 , 6 , 2 , 4 , 4 , 5 , 7 , 7 , 5 , 1 ]

DFS(v) 
	在v上打上访问标记
	O[cnt++] = v
	for u in v的相邻结点
		if u没有打过标记 then
			DFS(u)
		end
	end
	O[cnt++] = v
end

你可能感兴趣的:(ACM摸鱼)