Leetcode数据结构&算法:栈和深度优先搜索(DFS)

一、介绍

  • LIFO 数据结构中,将首先处理添加到队列中的最新元素
  • 插入操作,在栈中被称作入栈 push ,总是在堆栈的末尾添加一个新元素
  • 删除操作,退栈 pop ,将始终在末尾删除一个元素

二、栈实现

栈的实现比队列容易。动态数组足以实现堆栈结构。Leetcode提供的参考如下:

#include <iostream>

class MyStack {
    private:
        vector<int> data;               // store elements
    public:
        /** Insert an element into the stack. */
        void push(int x) {
            data.push_back(x);
        }
        /** Checks whether the queue is empty or not. */
        bool isEmpty() {
            return data.empty();
        }
        /** Get the top item from the queue. */
        int top() {
            return data.back();
        }
        /** Delete an element from the queue. Return true if the operation is successful. */
        bool pop() {
            if (isEmpty()) {
                return false;
            }
            data.pop_back();
            return true;
        }
};

int main() {
    MyStack s;
    s.push(1);
    s.push(2);
    s.push(3);
    for (int i = 0; i < 4; ++i) {
        if (!s.isEmpty()) {
            cout << s.top() << endl;
        }
        cout << (s.pop() ? "true" : "false") << endl;
    }
}

三、深度优先搜索DFS

 深度优先搜索(DFS)也可用于查找从根结点到目标结点的路径。

1. 结点的处理顺序

从根结点 A 开始。

  • 首先,我们选择结点 B 的路径,并进行回溯,直到我们到达结点 E,我们无法更进一步深入。
  • 然后我们回溯到 A 并选择第二条路径到结点 C 。从 C 开始,我们尝试第一条路径到 E 但是 E 已被访问过。所以我们回到 C 并尝试从另一条路径到 F。最后,我们找到了 G

总的来说,在我们到达最深的结点之后,我们会回溯并尝试另一条路径

因此,在 DFS 中找到的第一条路径并不总是最短的路径。例如,在上面的例子中,我们成功找出了路径 A-> C-> F-> G  并停止了 DFS。但这不是从 AG 的最短路径。

2. 栈的入栈和退栈顺序

  • 首先将根结点推入到栈中;
  • 然后我们尝试第一个邻居 B 并将结点 B 推入到栈中;
  • 等等等等。
  • 当我们到达最深的结点 E 时,我们需要回溯。
  • 当我们回溯时,我们将从栈中弹出最深的结点,这实际上是推入到栈中的最后一个结点

结点的处理顺序是完全相反的顺序,就像它们被添加到栈中一样,它是后进先出(LIFO)。这就是我们在 DFS 中使用栈的原因。

四、两种方法

大多数情况下,我们在能使用 BFS 时也可以使用 DFS。但是有一个重要的区别:遍历顺序

与 BFS 不同,更早访问的结点可能不是更靠近根结点的结点。因此,你在 DFS 中找到的第一条路径可能不是最短路径

 两种实现 DFS 的方法。

第一种方法是递归,模板如下:

boolean DFS(Node cur, Node target, Set<Node> visited) {
    return true if cur is target;
    for (next : each neighbor of cur) {
        if (next is not in visited) {
            add next to visted;
            return true if DFS(next, target, visited) == true;
        }
    }
    return false;
}

这种方法,我们使用的是由系统提供的隐式栈,也称为调用栈(Call Stack)。

每个元素都需要固定的空间。栈的大小正好是 DFS 的深度。因此,在最坏的情况下,维护系统栈需要 O(h),其中 h 是 DFS 的最大深度。在计算空间复杂度时,永远不要忘记考虑系统栈。

第二种是使用显式栈的DFS

递归解决方案的优点是它更容易实现。 但是,存在一个很大的缺点:如果递归的深度太高,你将遭受堆栈溢出。

在这种情况下,可以使用 BFS,或使用显式栈实现 DFS。

该逻辑与递归解决方案完全相同。 但我们使用 while 循环和来模拟递归期间的系统调用栈

boolean DFS(int root, int target) {
    Set<Node> visited;
    Stack<Node> s;
    add root to s;
    while (s is not empty) {
        Node cur = the top element in s;
        return true if cur is target;
        for (Node next : the neighbors of cur) {
            if (next is not in visited) {
                add next to s;
                add next to visited;
            }
        }
        remove cur from s;
    }
    return false;
}

五、相关的Leetcode例子

Leetcode算法题:岛屿数量(BFS&DFS) 

https://blog.csdn.net/yfy1127yfy/article/details/102955020

发布了29 篇原创文章 · 获赞 2 · 访问量 8441
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览