拓扑排序

判断拓扑排序有入度表方式和深度优先(锁路径)无回路方式
其中入度表的能通过栈完成所有无前驱节点访问,也能通过队列广度优先完成访问,本质上只是存储无前驱节点的容器
构建对应拓扑序列序列只能用入度表的方式,按顺序解锁无前驱节点

    bool TopologicalSort(Graph G){
        stack<int> S;//新建一个栈存储无前驱节点(队列效果一样)
        for(int i = 0; i < G.vexnum; i++)
            if(indegree[i]==0) S.push(i);//入度为0顶点入栈
        int count = 0;//对已遍历节点计数
        while(!S.empty()){//开始处理栈中元素,相当于深度优先遍历图,逐渐减少入度
            int point = S.front();S.pop();//出栈
            cout<<point<<endl;//输出该节点
            count++;//计数加一
            for(int p = G.vertices[point].firstarc;p;p->nextarc){//遍历该节点邻接边
                int v = p ->adjvex;//得到邻接节点
                if(!(--dindegree[v])) S.push(v);//减少其入度,若等于0则入栈,即已经无前驱
            }
        }
        if(count<G.vexnum) return false;//排序失败有回路
        return true;//排序成功
    }

1. 课程表

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false

深度优先锁路径判断有无环
class Solution {
private:
    vector<vector<int>> edges;//邻接表存储图
    vector<int> visited;//记录已经遍历节点,防止重复遍历,同时锁住当前路径
    bool valid = true;

public:
    void dfs(int u) {
        visited[u] = 1;//访问后锁住
        for (int v: edges[u]) {//遍历顶点后继
            if (visited[v] == 0) {
                dfs(v);//继续递归遍历其后继
                if (!valid) return;//剪枝
            }
            else if (visited[v] == 1) {//发现是锁住节点,说有存在闭环
                valid = false;
                return;
            }
        }//无后继节点
        visited[u] = 2;//访问结束解锁
    }

    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        edges.resize(numCourses);
        visited.resize(numCourses);
        for (const auto& info: prerequisites) 
            edges[info[1]].push_back(info[0]);//重构图,存放每个顶点对应后继

        for (int i = 0; i < numCourses && valid; ++i) {
            if (!visited[i]) dfs(i);//深度优先遍历每一个未访问节点
            if(!valid) return valid;//剪枝
        }
        return valid;
    }
};

入度表
class Solution {
private:
    vector<vector<int>> edges;//重构图(邻接表)
    vector<int> indeg;//记录顶点入度t

public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        edges.resize(numCourses);//分配内存
        indeg.resize(numCourses);//分配内存
        for (const auto& info: prerequisites) {
            edges[info[1]].push_back(info[0]);//重构图(邻接表)
            ++indeg[info[0]];//构图时记录入度
        }

        queue<int> q;//栈和队列在这里没有区别,都是用来存储无前驱节点的容器
        for (int i = 0; i < numCourses; ++i) 
            if (indeg[i] == 0) q.push(i); //先存储所有初始化无前驱节点

        int visited = 0;//记录访问数,后面就不用判断入度表有没有清0
        while (!q.empty()) {//当队列不为空
            ++visited;//访问一次加一
            int u = q.front();q.pop();//无前驱节点出队列,该节点移除
            for (int v: edges[u]) {//遍历该节点后继
                --indeg[v];//出队列节点移除,其后继入度都减一
                if (indeg[v] == 0) q.push(v);//削减至无前驱则入队
            }
        }
        return visited == numCourses;//判断是否访问完所有节点
    }
};

2. 外星文字典

请你根据该词典还原出此语言中已知的字母顺序,并按字母递增顺序排列。若不存在合法字母顺序,返回 ""
若存在多种可能的合法字母顺序,返回其中任意一种顺序即可

构建领接矩阵和入度表
class Solution {
public:
    vector<vector<bool>> edges =  vector<vector<bool>>(26,vector<bool>(26));//矩阵
    vector<int> indeg = vector<int>(26);//记录顶点入度,直接从字符转换到下标
    unordered_set<int> s;//记录出现的字符,用于判断最后结果正确性
    bool flag = false;//居然还要判断给的数据是否正确
    string alienOrder(vector<string>& words) {
        //先按逻辑构件图(拓扑序列)
        int count = 0;//记录访问节点数量
        constructgraph(words);//构件好了邻接矩阵图,入度数据和所有节点
        if(flag) return "";
        //准备开始拓扑排序算法
        stack<int> stack_;
        string res;//存储结果
        for (int i = 0; i < 26; ++i) 
            if (s.count(i)!=0&&indeg[i] == 0) //字符出现过且无前驱
                stack_.push(i); //先存储所有初始化无前驱节点
        while(!stack_.empty()){
            count++;//记录一次访问
            int u = stack_.top(); stack_.pop();
            res.push_back(u+'a');
            for(int v = 0; v < 26;++v)
                if(edges[u][v]==1){-
                    --indeg[v];
                if(indeg[v]==0) stack_.push(v);
            }
        }
        if(count==s.size()) return res;
        return "";
    }

    void constructgraph(vector<string>& words){
        //先比较各单词内的,看错了其实不需要,但可以用来记录出现的字符
        for(int i=0;i<words.size();i++){
            s.insert(words[i][0]-'a');
            for(int j=1;j<words[i].size();j++){
                if(words[i][j]==words[i][j-1]) continue;
                s.insert(words[i][j]-'a');}}//记录已有字符
        //比较各单词间的
        for(int i=1;i<words.size();i++){
            int j = 0;
            while(words[i][j]==words[i-1][j]){ 
                j++;
                if(j==words[i].size()||j==words[i-1].size()) break;
            }
            if(j==words[i].size()||j==words[i-1].size()){
                if(words[i].size()<words[i-1].size()) flag=true;  
                continue;}//没从组间得到顺序
            //否则不等的话
            int pre = words[i-1][j]-'a';
            int tail = words[i][j]-'a';
            if(edges[pre][tail]==0){//如果不在则加入
                edges[pre][tail]=1;//加入邻接表
                indeg[tail]++;//入度加一
        }
    }
}
};

3. 重建序列

构件邻接表和入度表
//无前驱节点队列至多一个节点,保证拓扑排序路径只有一条
class Solution {
public:
    bool sequenceReconstruction(vector<int>& nums, vector<vector<int>>& sequences) {
        int n = nums.size();
        vector<int> indegrees(n + 1);//入度表
        vector<unordered_set<int>> graph(n + 1);
        //邻接表构件图
        for (auto &sequence : sequences) {
            for (int i = 1; i < sequence.size(); i++) {
                int prev = sequence[i - 1], next = sequence[i];
                if (!graph[prev].count(next)) {
                    graph[prev].emplace(next);
                    indegrees[next]++;//记录入度数
                }
            }
        }
        //队列记录无前驱节点
        queue<int> qu;
        for (int i = 1; i <= n; i++) {
            if (indegrees[i] == 0) {
                qu.emplace(i);
            }
        }
        while (!qu.empty()) {
            if (qu.size() > 1) {//队列大于1说明存在多条路径
                return false;
            }
            int num = qu.front();//删除当前节点
            qu.pop();
            for (int next : graph[num]) {//对删除节点后继进行减一
                indegrees[next]--;
                if (indegrees[next] == 0) {//获得新的无前驱节点
                    qu.emplace(next);
                }
            }
        }
        return true;
    }
};
posted @ 2022-06-01 00:30  失控D大白兔  阅读(28)  评论(0编辑  收藏  举报