算法:图的常见算法的实现

图的算法都不算难,只不过coding的代价比较高

(1)先用自己最熟练的方式,实现图结构的表达

(2)在自己熟悉的结构上,实现所有常用的图算法作为模板

(3)把面试题提供的图结构转化为自己熟悉的图结构,再调用模板或改写即可

图的表示方法这么多种,并且每次给你的形式还可能不同,所以就有必要抽象一个自己的表示方法,以后对于不同的形式,写一个能转换为自己定义形式的方法即可(有种适配器的感觉),这样才能以不变应万变,把不熟悉的表示方法转换为自己熟悉的方法

下面仅做算法的实现,没有进行内存管理

图的表示

#include <vector>
#include <map>
#include <vector>
#include <memory>
#include <stack>
#include <set>


using namespace std;

using namespace std;

struct Edge;
struct Node;
struct Graph;

struct Node{
    int val;
    int in;
    int out;

    std::vector<Node *> nexts;
    std::vector<Edge *> edge;
    Node(int val, int in = 0, int out = 0) : val(val), in(in), out(out){

    }
};

struct Edge{
    int weight;
    Node *from;
    Node *to;

    Edge(Node *from, Node *to, int weight = 0) : from(from), to(to), weight(weight){
    }

};


struct Graph{
    std::map<int, Node*> nodes;
    std::set<Edge *> edges;
};

图的生成

// matrix 所有的边
// N*3 的矩阵
// [weight, from节点上面的值,to节点上面的值]
//
// [ 5 , 0 , 7]
// [ 3 , 0,  1]
Graph *createGraph(std::vector<std::vector<int>> &m){
    Graph *graph = new Graph;
    for(auto vec : m){
        int weight = vec[0], fLabel = vec[1], tLabel = vec[2];
        if(!graph->nodes.count(fLabel)){
            graph->nodes[fLabel] = new Node(fLabel);
        }
        if(!graph->nodes.count(tLabel)){
            graph->nodes[tLabel] = new Node(tLabel);
        }
        auto fNode = graph->nodes[fLabel], tNode = graph->nodes[tLabel];
        auto edge = new Edge(fNode, tNode, weight);
        fNode->out++;
        tNode->in++;
        fNode->nexts.push_back(tNode);
        fNode->edge.push_back(edge);
        graph->edges.emplace(edge);
    }
    return graph;
}
int main(){
    std::vector<std::vector<int>> matrix;
    matrix.emplace_back(std::vector<int>{5, 0, 7});
    matrix.emplace_back(std::vector<int>{3, 0, 1});
    createGraph(matrix);
}

图的遍历

广度优先遍历

(1)准备一个队列,一个Set(存放遍历过的节点,登记表),出发节点为A,把A放到队列和Set中

(2)弹出队列的顶点M,打印M的值。获取M的所有邻居节点next,查看Set中有没有这些next节点,无则放到Set和队列中,有则跳过此next节点

(3)一直执行第2步,直到队列为空

// 从node出发,进行宽度优先遍历
void bfs(Node *start){
    if(start == nullptr){
        return ;
    }

    std::queue<Node *> queue;
    std::set<Node *> set;
    queue.push(start);
    set.emplace(start);
    while (!queue.empty()){
        auto cur = queue.front(); queue.pop();
        std::cout << cur->val <<"\n";
        for(auto &next : cur->nexts){
            if(!set.count(next)){
                queue.push(next);
                set.emplace(next);
            }
        }
    }
}


怎么调用:

int main(){
    std::vector<std::vector<int>> matrix;
    matrix.emplace_back(std::vector<int>{5, 0, 7});
    matrix.emplace_back(std::vector<int>{3, 0, 1});
    matrix.emplace_back(std::vector<int>{3, 1, 2});
    matrix.emplace_back(std::vector<int>{3, 2, 7});
    auto graph = createGraph(matrix);
    bfs(graph->nodes[0]);
}


在这里插入图片描述

深度优先遍历

一条路没走完就一直走,走完了就往回走,看哪些岔路还没有走。(不能走出环路,走过的地方就不能再走了)

(1)准备一个栈(存放目前的整条路径),一个Set(存放遍历过的节点,登记表),出发节点为A,把A放到栈和Set中,同时打印A的值(入栈就打印)

(2)弹出栈顶元素M,遍历M的所有邻居节点next,查看Set中有没有这些next节点,无则将M和此时的next节点入栈、next放到Set中,打印next的值(入栈就打印),终止遍历next节点(即只入栈一个Set中不包含的节点)

(3)一直执行第2步,直到栈为空

void dfs(Node *node){
    if(node == nullptr){
        return ;
    }

    std::stack<Node *> stack;
    std::set<Node *> set;
    stack.emplace(node);
    set.emplace(node);
    printf("%d\n", node->val);
    while (!stack.empty()){
        auto cur = stack.top(); stack.pop();
        for(const auto& next : cur->nexts){
            if(!set.count(next)){
                stack.push(cur);
                stack.push(next);
                set.emplace(next);
                printf("%d\n", next->val);
                break;
            }
        }
    }
}


图的拓扑排序

std::vector<Node *> sortedTopology(Graph *graph){
    std::map<Node *, int> inMap;  // key 某个节点   value 剩余的入度
    std::queue<Node *> zeroQueue;  // 只有剩余入度为0的点,才进入这个队列
    
    //1. 先将入度为0的点加入队列
    for(auto &node : graph->nodes){
        inMap[node.second] = node.second->in;
        if(node.second->in == 0){
            zeroQueue.push(node.second);
        }
    }
    
    // 开始删边
    std::vector<Node *> result;
    while (!zeroQueue.empty()){
        auto cur = zeroQueue.front(); zeroQueue.pop();
        result.emplace_back(cur);
        for(auto &next : cur->nexts){
            inMap[next] = inMap[next] - 1;
            if(inMap[next] == 0){
                zeroQueue.emplace(next);
            }
        }
    }
    return result;
}

Dijkstra算法 (迪杰斯特拉算法)

Dijkstra算法(迪杰斯特拉算法)是很有代表性的最短路径算法,用于计算一个结点到其他结点的最短路径。该算法指定一个点(源点)到其余各个结点的最短路径,因此也叫做单源最短路径算法。该算法是由荷兰计算机科学家Edsger W.Dijkstra于1959年发表。

  • Dijkstra算法是一种用于计算带权有向图中单源最短路径算法,不存在回溯的过程,因此它还不适用于带有负权重的情况
    • 如果权值存在负数,那么被派生出来的可能是更短的路径,这就需要过程可以回溯,之前的路径需要被更短的路径替换掉
    • 而Dijkstra算法是不能回溯的,它的每一步都是以当前最优选择为前提的。
  • Dijkstra算法的思想是广度优先搜索(BFS) 贪心策略。对于计算非加权图中的最短路径,也可使用BFS算法。
    • Dijkstra算法是对BFS算法的推广,以起始点为中心向外层层扩展,并且每一次都选择最优的结点进行扩展,直到扩展到终点为止。
    • Dijkstra算法可以划归为贪心算法,下一条路径都是由当前更短的路径派生出来的更长的路径。

在这里插入图片描述

例子

  • 创建距离表。第1列是结点名称,第2列是从起点A到对应结点的已知最短距离。开始我们并不知道A到其它结点的最短距离是多少,默认初始距离是无穷大。

在这里插入图片描述

  • 遍历起点A的所有相邻结点,找到起点A的邻接结点B和C。从A到B的距离是5,从A到C的距离是2,刷新距离表中起点A到各结点的最短距离(绿色表示刷新)。在这里插入图片描述
  • 从距离表中找到从A出发距离最短的点,也就是结点C(最小距离是2)。遍历结点C的所有相邻结点,找到结点C的相邻结点D和F(A已经遍历过,不需要考虑)。从C到D的距离是1,所以A到D的距离是A-C-D=2 1=3;从C到F的距离是8;从A到F的距离是A-C-F=2 8=10。然后刷新距离表(绿色表示刷新)。

在这里插入图片描述

  • 从距离表中找到从A出发距离最短的点(红色结点C已经遍历过,不需要考虑),也就是结点D(最小距离是3)。遍历结点D的所有相邻结点,找到相邻结点B、E和F(C已遍历过,不考虑)。从A-C-D-B的距离是3+1=4;从A-C-D-E的距离是3+1=4;从A-C-D-F的距离是3+ 2=5。刷新距离表中起点A到各结点的最短距离

在这里插入图片描述

  • 从距离表中找到从A出发距离最短的点(红色结点C、D已经遍历过,不需要考虑),也就是结点B和E(最小距离是4)。遍历结点B的所有相邻结点,找到相邻结点E(D遍历过,不考虑),从A-C-D-B-E的距离为10,比当前A到E的最小距离4要大,不考虑。遍历结点E的所有相邻结点,找到相邻结点G、B(D遍历过,不考虑),从A-C-D-E-G的距离为4 7=11<∞, 刷新距离表;A-C-D-E-B的距离4+6=10>4,不考虑

在这里插入图片描述

  • 从距离表中找到从A出发距离最短的点(红色结点B、C、D、E已经遍历过,不需要考虑),也就是结点F(最小距离是5)。从A-C-D-F-G的距离为8, 比当前最小距离11要小,刷新距离表。

在这里插入图片描述
就这样,除终点以外的全部结点都已经遍历完毕,距离表中存储的是从起点A到所有结点的最短距离。

实现一

Node *getMinDistanceAndUnselectedNode(
        std::map<Node *, int > &distanceMap,
        std::set<Node *> &selectNodes){
    Node *minNode = nullptr;
    int minDistance = INT_MAX;
    for(auto &entry : distanceMap){
        auto  node = entry.first;
        auto distance = entry.second;
        if(!selectNodes.count(node) && distance < minDistance){
            minNode = node;
            minDistance = distance;
        }
    }
    return minNode;
}

std::map<Node *, int> dijkstra(Node *from){
    std::map<Node *, int > distanceMap;
    std::set<Node *> selectNodes;

    distanceMap[from] = 0;
    auto minNode = getMinDistanceAndUnselectedNode(distanceMap, selectNodes);
    while (minNode != nullptr){
        //  原始点  ->  minNode(跳转点)   最小距离distance
        int dis = distanceMap[minNode];
        for(auto &edge : minNode->edge){
            auto toNode = edge->to;
            if(!distanceMap.count(toNode)){
                distanceMap[toNode] = dis + edge->weight;
            }else{
                distanceMap[toNode] = std::min(distanceMap[toNode], dis + edge->weight);
            }
        }
        
        selectNodes.insert(minNode);
        minNode = getMinDistanceAndUnselectedNode(distanceMap, selectNodes);
    }
    return distanceMap;
}

实现二(加强堆优化)

struct NodeRecord{
    std::shared_ptr<Node> node;
    int distance;
    
    NodeRecord(std::shared_ptr<Node>& node, int distance) : node(node), distance(distance){
        
    }
};

class NodeHeap{
    std::vector<std::shared_ptr<Node>> nodes; // 实际的堆结构
    std::map<std::shared_ptr<Node>, int> heapIndexMap; // key 某一个node, value 上面堆中的位置
    std::map<std::shared_ptr<Node>, int> distanceMap; // key 某一个节点, value 从源节点出发到该节点的目前最小距离
    int size;   // 堆上有多少个点
    
    void swap(int index1, int index2){
        heapIndexMap[nodes[index1]] = index2;
        heapIndexMap[nodes[index2]] = index1;
        std::swap(nodes[index1], nodes[index2]);
    }
    
    bool isEntered(std::shared_ptr<Node> &node){
        return heapIndexMap.count(node);
    }
    
    bool inHeap(std::shared_ptr<Node> & node){
        return isEntered(node) && heapIndexMap[node] != -1;
    }


    void insertHeapify(int index) {
        while (distanceMap[nodes[index]] < distanceMap[nodes[(index - 1) / 2]]) {
            swap(index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }
    
    
    void heapify(int index){
        int left = index * 2 + 1;
        while (left < size){
            int smallest =  left + 1 < size && distanceMap[nodes[left + 1]] < distanceMap[nodes[left]]
                            ? left + 1
                            : left;
            smallest = distanceMap[nodes[smallest]] < distanceMap[nodes[index]] ? smallest : index;
            if (smallest == index) {
                break;
            }
            swap(smallest, index);
            index = smallest;
            left = index * 2 + 1;
        }
    }
    
public:
    explicit NodeHeap(int max_size){
        nodes.resize(max_size);
        size = 0;
    }
    
    bool isEmpty() const{
        return size == 0;
    }
    
    

    // 有一个点叫node,现在发现了一个从源节点出发到达node的距离为distance
    // 判断要不要更新,如果需要的话,就更新
    void addOrUpdateIgnore(std::shared_ptr<Node> &node, int distance){
        if (inHeap(node)) {
            distanceMap[node] = std::min(distanceMap[node], distance);
            insertHeapify(heapIndexMap[node]);
        }
        if (!isEntered(node)) {
            nodes[size] = node;
            heapIndexMap[node] = size;
            distanceMap[node] = distance;
            insertHeapify(size++);
        }
    }
    
    
    std::shared_ptr<NodeRecord> pop(){
        auto nodeRecode = std::make_shared<NodeRecord>(nodes[0], distanceMap[nodes[0]]);
        swap(0, size - 1);
        heapIndexMap[nodes[size - 1]] = -1;
        distanceMap.erase(nodes[size - 1]);
        nodes[size - 1] = nullptr;
        size--;
        heapify(0);
        return nodeRecode;
    }
};

// 改进后的dijkstra算法
// 从head出发,所有head能到达的节点,生成到达每个节点的最小路径记录并返回
std::map<std::shared_ptr<Node>, int> dijkstra(std::shared_ptr<Node> from, int size){
    std::shared_ptr<NodeHeap> nodeHeap = std::make_shared<NodeHeap>(size);
    nodeHeap->addOrUpdateIgnore(from, 0);
    std::map<std::shared_ptr<Node>, int> result;
    while (!nodeHeap->isEmpty()){
        auto recode = nodeHeap->pop();
        auto cur = recode->node;
        int distance = recode->distance;
        for(auto &edge : cur->edges){
            nodeHeap->addOrUpdateIgnore(edge->to, edge->weight);
        }
        result[cur] = distance;
    }
    return result;
}

最小生成树

根据所有顶点之间是否存在通路,图存储结构可以细分为连通图和非连通图。举个例子:
在这里插入图片描述

图 2 a) 是一个非连通图,比如图中找不到一条从 a 到 c 的路径。图 2 b) 是一个连通图,因为从一个顶点到另一个顶点都至少存在一条通路,比如从 a 到 c 的通路可以为 a-f-c、a-b-c 等。

所谓生成树,指的是具备以下条件的连通图:

  • 包含图中所有的顶点;
  • 任意顶点之间有且仅有一条通路。

图 2 b) 是一个连通图,其对应的生成树有很多种,例如:

在这里插入图片描述
一个连通图可能对应着多种不同的生成树。

在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树

在这里插入图片描述

kruskal算法

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。

  • 将所有的边按照他们的边权由小到大排序
  • 把图中的n个顶点看成独立的n棵树组成的森林;
  • 按权值从小到大选择边,所选的边连接的两个顶点ui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
  • 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。

在这里插入图片描述

其实kruskal就是并查集和贪心的结合,仔细看看图的话并不难理解。

注意:

  • 总是从权值最小的边开始考虑,依次考察权值依次变大的边
  • 当前的边要么进入最小生成树的集合,要么丢弃
  • 如果当前的边进入最小生成树的集合中不会形成环,就要当前边
  • 如果当前的边进入最小生成树的集合中会形成环,就不要当前边
  • 考察完所有边之后,最小生成树的集合也得到了

实现:

在这里插入图片描述

#include <vector>
#include <map>
#include <vector>
#include <memory>
#include <stack>
#include <set>
#include <queue>


using namespace std;


// 点结构的描述
struct Node;
struct Edge;
struct Node{
    int value;
    int in;
    int out;

    std::vector<std::shared_ptr<Node>> nexts;
    std::vector<std::shared_ptr<Edge>> edges;

    Node(int value) : value(value), in(0), out(0){

    }
};

struct Edge{
    int weight;
    std::shared_ptr<Node> from;
    std::shared_ptr<Node> to;

    Edge(int weight, std::shared_ptr<Node> &from, std::shared_ptr<Node> &to) : weight(weight), from(from), to(to) {

    }
};

bool operator < (std::shared_ptr<Edge> & a, std::shared_ptr<Edge> & b){ //返回true时,说明a的优先级低于b
    return  a->weight < b->weight;
}

// 当priority_queue的元素类型为指针的时候,重载< 的方法不能有效的给指针元素排序。这时候可以考虑以下的解决方案,定义cmp结构体类型,在内部重载`()`
struct cmp {
    bool operator ()(std::shared_ptr<Edge> &a, std::shared_ptr<Edge> &b){
        return  a->weight > b->weight;
    }
};


struct Graph{
    std::map<int, std::shared_ptr<Node>> nodes;
    std::set<std::shared_ptr<Edge>> edges;

    Graph(){

    }
};



class Solution {
    class UnionFind{
        // key 某一个节点, value key节点往上的节点
        std::map<std::shared_ptr<Node>, std::shared_ptr<Node>> fatherMap;
        // key 某一个集合的代表节点, value key所在集合的节点个数
        std::map<std::shared_ptr<Node>, int> sizeMap;

        std::shared_ptr<Node> findRoot(std::shared_ptr<Node>& node){
            std::stack<std::shared_ptr<Node>> path;
            while (node != fatherMap[node]){
                path.emplace(node);
                node = fatherMap[node];
            }

            while (!path.empty()){
                fatherMap[path.top()] = node;
                path.pop();
            }

            return node;
        }

    public:
        UnionFind()= default;
        // 初始化集合
        void makeSets(const std::map<int, std::shared_ptr<Node>>& nodes){
            fatherMap.clear();
            sizeMap.clear();
            for(const auto& it : nodes){
                fatherMap[it.second] = it.second;
                sizeMap[it.second] = 1;
            }
        }

        bool  isSameSet(std::shared_ptr<Node> &a, std::shared_ptr<Node> &b){
            return findRoot(a) == findRoot(b);
        }


        void merge(std::shared_ptr<Node> &a, std::shared_ptr<Node> &b){
            if(a == nullptr || b == nullptr){
                return;
            }

            auto aRoot = findRoot(a);
            auto bRoot = findRoot(b);
            if(aRoot != bRoot){
                int aSetSize = sizeMap[aRoot], bSetSize = sizeMap[bRoot];
                if(aSetSize <= bSetSize){
                    fatherMap[aRoot] = bRoot;
                    sizeMap[bRoot] = aSetSize + bSetSize;
                    sizeMap.erase(aRoot);
                }else{
                    fatherMap[bRoot] = aRoot;
                    sizeMap[aRoot] = aSetSize + bSetSize;
                    sizeMap.erase(bRoot);
                }
            }
        }

    };

public:
   std::set<std::shared_ptr<Edge>> kruskalMST(std::shared_ptr<Graph> graph){
       auto unionFind = std::make_shared<UnionFind>();
       unionFind->makeSets(graph->nodes);

       // 从小的边到大的边,依次弹出,小根堆!
       std::priority_queue<std::shared_ptr<Edge>, std::vector<std::shared_ptr<Edge>>, cmp> priorityQueue;
     //  std::priority_queue<std::shared_ptr<Edge>, std::vector<std::shared_ptr<Edge>>, std::greater<>> priorityQueue;
       // 小根堆放入所有的边
       for(auto &edge : graph->edges){
           priorityQueue.emplace(edge);
       }



       std::set<std::shared_ptr<Edge>> result;
       while (!priorityQueue.empty()){
           auto edge = priorityQueue.top(); priorityQueue.pop();
           printf("%d\t", edge->weight);
             // 小根堆堆顶的边对应的节点不会形成环(即两个点不在同一个集合中),才往结果集中添加,否则舍弃
           if(!unionFind->isSameSet(edge->from, edge->to)){
               result.emplace(edge);
               unionFind->merge(edge->from, edge->to);
           }
       }
       printf("\n");
       return result;
   }
};

std::shared_ptr<Graph> createGraph(std::vector<std::vector<int>> matrix){
    auto graph = std::make_shared<Graph>();
    for (int i = 0; i < matrix.size(); ++i) {
        int weight = matrix[i][0], from = matrix[i][1], to = matrix[i][2];
        if(!graph->nodes.count(from)){
            graph->nodes[from] = std::make_shared<Node>(from);
        }
        if(!graph->nodes.count(to)){
            graph->nodes[to] = std::make_shared<Node>(to);
        }

        std::shared_ptr<Node>  fromNode = graph->nodes[from], toNode = graph->nodes[to];
        auto newEdge = std::make_shared<Edge>(weight, fromNode, toNode);
        fromNode->nexts.emplace_back(toNode);
        fromNode->out++;
        toNode->in++;
        fromNode->edges.emplace_back(newEdge);
        graph->edges.emplace(newEdge);
    }
    return graph;
}
int main(){
    Solution a;
    set<shared_ptr<Edge>> i ;
    std::vector<std::vector<int>> matrix;
    matrix.clear();
    matrix.emplace_back(std::vector<int>{3, 1, 3});
    matrix.emplace_back(std::vector<int>{1, 2, 3});
    matrix.emplace_back(std::vector<int>{9, 3, 4});
    matrix.emplace_back(std::vector<int>{7, 4, 5});
    matrix.emplace_back(std::vector<int>{0, 5, 1});
    auto graph = createGraph(matrix);
    i = a.kruskalMST(graph);

    std::shared_ptr<Node> n = std::make_shared<Node>(1);
    std::priority_queue<std::shared_ptr<Edge>, vector<std::shared_ptr<Edge>>, cmp> priorityQueue;
    priorityQueue.emplace(std::make_shared<Edge>(3, n, n));
    priorityQueue.emplace(std::make_shared<Edge>(1, n, n));
    priorityQueue.emplace(std::make_shared<Edge>(7, n, n));
    priorityQueue.emplace(std::make_shared<Edge>(9, n, n));



    while (!priorityQueue.empty()){
       auto top = priorityQueue.top(); priorityQueue.pop();
       printf("%d\t", top->weight);
    }
}


Prim算法

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

int dist[n],state[n],pre[n];
dist[1] = 0;
for(i : 1 ~ n)
{
    t <- 没有连通起来,但是距离连通部分最近的点;
    state[t] = 1;
    更新 dist 和 pre;
}

在这里插入图片描述
在这里插入图片描述
注意:

  • 可以从任意节点出发来寻找最小生成树
  • 某个点加入到被选取的点中后,解锁这个点出发的所有新的边
  • 在所有解锁的边中选最小的边,然后看看这个边会不会形成环
  • 如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3)
  • 如果不会,要当前边,将该边的指向点加入到被选取的点中,重复2)
  • 当所有点都被选取,最小生成树就得到了

实现:

在这里插入图片描述

class Solution {
public:
   static std::set<std::shared_ptr<Edge>> primMST(Graph * graph){
       std::priority_queue<std::shared_ptr<Edge>> priorityQueue;  // 解锁的边按照权重值标准放到小根堆中
       std::set<std::shared_ptr<Node>> nodeSet;        // 已经解锁的点
       std::set<std::shared_ptr<Edge>> result;      // 依次挑选的的边在result里
       
       for(const auto& it : graph->nodes){  // 1.从任意节点出发来寻找最小生成树
           // node 是开始点
           std::shared_ptr<Node> node = it.second;
           if(!nodeSet.count(node)){
               nodeSet.emplace(node);
               for(auto &edge : node->edges){  // 2.此点连接的所有边解锁
                   priorityQueue.emplace(edge);
               }
               while (!priorityQueue.empty()){
               		// 3.在所有解锁的边中选最小的边,然后看这个边加入到被选取的点中后会不会形成环
                   auto edge = priorityQueue.top(); priorityQueue.top();   
                   auto toNode = edge->to; // 可能的一个新的点
                   if (!nodeSet.count(toNode)) { // 该边的指向点未解锁,则解锁
                       nodeSet.emplace(toNode);
                       result.emplace(edge);
                       for (const auto& nextEdge : toNode->edges) {  // 指向点连接的所有边解锁
                           priorityQueue.emplace(nextEdge);
                       }
                   }
                    // 该边的指向点已经解锁,直接舍弃此边
               }
            // 为了防止森林,所以不break
            // 如果明确知道不会出现森林(或不需要防止森林),可以break
            // break;
           }
       }
       return result;
   }
};
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值