算法系列博客之DFS
DFS(深度优先搜索)在图论的范畴里可以通俗的解释为
总是优先向相连接未访问的下一节点搜索,直到没有这样的节点即返回上一节点
DFS标准格式:
Input: G = (V, E) is a Graph
procedure dfs(G):
for all v∈V:
visited(v) = false
for all v∈V:
if not visited(v): explore(G,v)
procedure explore(G,v):
visited(v) = true
previsit(v)
for each edge (v,u)∈E:
if not visited(v): explore(G,u)
postvisit(v)
本篇博客将运用这种思想来解决leetcode上133号问题
问题描述:
Clone an undirected graph. Each node in the graph contains a label and a list of its neighbors.
The definition for undirected graph in c++:
struct UndirectedGraphNode {
int label;
vector < UndirectedGraphNode *> neighbors;
UndirectedGraphNode(int x) : label(x) {};
};
The input is a pointer that points to a UndirectedGraphNode, your output type should be the same with input.
假设传进的节点为node,声明保存结果的节点为newNode,并将其作为两个参数值
算法设计应当确保在整个过程中node对应原图节点和newNode对应新图节点是对应的
采取DFS的思想,算法步骤如下:
1.检查第一个节点是否为空,不为空则创建一个新节点来作为输出,并将其label赋为第一个节点的label值;为空则返回NULL
2.对于每个节点,遍历其邻接表,并检查下一节点是否访问过
如果访问过,则直接将该节点的地址push到对应新节点的邻接表中去
如果没有,则声明一个新的节点,并且对这个节点进行递归DFS
可以看出,需要额外保存已经访问过的节点以及这些节点的地址,为了查询和保存简单,我们采取STL中map形式的数据结构
此外注意到label是唯一的,因此我们只需要用label即可标识某个节点是否已经被访问过。c++实现如下:
class Solution {
public:
UndirectedGraphNode* cloneGraph(UndirectedGraphNode *node) {
UndirectedGraphNode *newNode = NULL;
if (node != NULL) {
newNode = new UndirectedGraphNode(node->label);
dfs(node, newNode);
}
return newNode;
}
private:
map<int, UndirectedGraphNode*> visited;
void dfs(UndirectedGraphNode *node, UndirectedGraphNode *newNode) {
visited[node->label] = newNode;
for(int i = 0; i < node->neighbors.size(); i++) {
if(visited.find(node->neighbors[i]->label) == visited.end()) {
newNode->neighbors.push_back(new UndirectedGraphNode(node->neighbors[i]->label));
dfs(node->neighbors[i], newNode->neighbors[i]);
} else
newNode->neighbors.push_back(visited[node->neighbors[i]->label]);
}
}
};
算法复杂度的分析,时间上来说每个节点会且只会被访问一次,复杂度为O(n);
空间上来说,运行下来之后会保存每个节点label和地址,并且只保存一次,也为O(n)
思考一下,时间上无法再优化,因为深拷贝必然会访问到每个节点,而空间上的优化必然会带来时间上的消耗
况且空间的使用也并不是很大,所以看起来算法无需在进行优化。
但是仔细一看,就会发现写出的代码里面似乎是做了很多类似重复的语句,并且指针使用时候链构太长也是一种消耗,还是有改进的空间
class Solution {
public:
UndirectedGraphNode* cloneGraph(UndirectedGraphNode *node) {
if(node == NULL) return NULL;
return dfs(node);
}
private:
map<int, UndirectedGraphNode*> visited;
UndirectedGraphNode* dfs(UndirectedGraphNode *node) {
if(visited.find(node->label) == visited.end()) {
visited[node->label] = new UndirectedGraphNode(node->label);
for(int i = 0; i < node->neighbors.size(); i++)
visited[node->label]->neighbors.push_back(dfs(node->neighbors[i]));
}
return visited[node->label];
}
};
虽然这貌似是改变了DFS判定的思想,即已经访问过的无需在进行DFS;但实质上并没有变,即使仍然向下DFS了一层,但也就止于了这一层
因而复杂度并没有发生任何改变,代码却变的优美、可读性更高了,目的达到。