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