トポロジカルソート
関連概念
最初のBaiduの百科事典は、概念を与えました:
有向非巡回グラフ(有向非巡回グラフがDAGと称す)Gを位相的にソートするためにエッジ(U、V)の場合、Gのすべての頂点は∈、例えば頂点図UおよびVの任意のペアことが、線形配列に配置されていますE(G)、uはVの前に現れる線状配列。典型的には、そのような配列は、線状配列と呼ばれるトポロジカル順(トポロジカル順)、頭字語トポロジシーケンスを満たします。簡単に言うと、セット上の半順序集合に全順序を与えるために、この操作は、トポロジカルソートと呼ばれています。
例えば人気 - スケジューリング方式。(人工知能など)上級コースを学ぶために、(このような行の世代の数が多いと)のみ対応する基本コース終了後学ぶこと、そしてそれゆえトポロジカル順序を発生さ - トポロジーシーケンスの前にこのイベントに配置された特定のイベントの発生条件を満たし。たとえば、次のようにコースや飛び級コースがあります。
コース | 前提条件 |
---|---|
データの構造 | 離散数学 |
離散数学 | 高い数学 |
高い数学 | ノー |
もちろん、あなたがデータ構造を勉強したい、私たちは第1の離散数学を学ばなければならない、離散数学を勉強したい、我々は最初の基本的なコースとして高い知識ベースの数、および大学の数が多いを知っている必要があり、あなたは前提条件を必要としません。
さて、このカリキュラムのトポロジカル順序放電に応じて、それは次のようになります。
得られたトポロジカルトポロジカルソート配列は、例えば、もちろん「プログラミングの基礎」のトッププレ知識に参加するだけ必要がない必ずしもないが、このレベルは高く、サイド関係でコース側の番号、その後、両方とも前提条件でありますそれは同じです。このようにユニークな配列のトポロジは存在しません。
アルゴリズムの設計
私たちはもちろんにあまり依存クラスから始めるべきであるという考えを持つ前提条件に従っては、徐々に学習します。DNFは、スキルのアップグレードの役割を果たしているように、異なる役割は、あなたが最初に低レベルのスキルへの依存を学ばなければならない、目覚めた後に異なるスキルを持っており、高度なスキルを取得したいです。
そのため、あなたはもちろんの依存性を学ぶことがないことが徐々にアンロック、始まる「スキルツリーを。」次に、1つのアプローチは、(スタックまたはキューができる保存された情報を使用して)トポロジシーケンスを生成することです。
- すべてのコースを通過しても事前依存(000度)であろうスタックにすべての点。
- 空でない場合は、スタックが空であるかどうかを決定する、ポップアッププログラムは、トポロジとシーケンスに追加しました。
- このコースのロックが解除されているので、それがこれに依存していなかったのコースに依存することです。したがって、それは当然のことながら - 1-1-1ある度に依存しています。
- 勿論に依存して決定される - 1-1-1侵入した後、000度に変更し、そうであれば、コースをプッシュ。
- 繰り返します 、スタックがこれまでに空になるまで。
また、図ソートトポロジーは、ループに存在しないことができ、実際にはよく理解前提条件として、独自の手段を有するループのコースがある想像する- -明らかにばかげています。
コードの実装
次のトピックに沿って、私は問題のトポロジカルソートについて選択した「データ構造」コースのセット、
特定のコース番号、コース名、クレジット、前提条件と制約:学期のクレジットの数、各学期や天井には、行を与えますレッスンプラン。
1.論理構造設計:
隣接テーブルセクションは、私は、トピックを行うコースの依存性を示します。ここで私は頭の下にもたらしたその隣接する模範となった回数を減らすために、ハッシュテーブルのように使用して、検索プログラムでインデックス化
時間複雑。
2。 ネットワーク図。
3.コード
#include<iostream>
#include<map>
#include<fstream>
#include<stack>
#include<vector>
#include<string>
#include<sstream>
#include<windows.h>
using namespace std;
typedef int ScoreType;
typedef string Number;
struct CourseInfo { // 单个课程的信息
string courseName; // 课程名称
ScoreType credit; // 课程学分
int inDgree; // 每个点的入度
CourseInfo(string courseName = "NULL", ScoreType credit = 0, int inDgree = 0) {
this->courseName = courseName;
this->credit = credit;
this->inDgree = inDgree;
}
};
struct CourseNode { // 单个课程节点
Number courseNumber; // 课程编号
struct CourseNode *next;
CourseNode(Number courseNumber = "NULL") {
this->courseNumber = courseNumber;
next = NULL;
}
};
struct CourseList {
vector<CourseNode*> courseNet; // 课程邻接表
vector<CourseNode*> tailPoint; // 记录每行的尾指针
vector<CourseInfo> courseInfo; // 课程信息
map<Number, int> hm; // 哈希表,用来记录编号与下标的关系,如201706020115对应下标 1号
int vertexNum; // 当前课程数量
CourseList() {
vertexNum = 0;
}
void CreateHashMap(stringstream &ss); // 建立索引哈希表
void CreateAdjList(string buf, int index); // 建立邻接表
};
struct UserInterface {
CourseList course;
vector<string> topoOrder; // 拓扑序列:C1->C2->C3...
bool JudgeLogicError(int count); // 判断课程逻辑是否有误,拓扑图不能有环
void LoadingCourseInfo();
void ReadFromFile();
void GetTopologicalOrder();
void ShowTopoOrder(); // 显示拓扑序列方案
void ShowCourseInfo();
void UserOption();
char ShowOption();
};
int main()
{
UserInterface user;
user.UserOption();
system("pause");
return 0;
}
void CourseList::CreateHashMap(stringstream &ss) { // 记录编号与下标的映射、课程名称、课程学分
string cName;
Number cNumber;
CourseInfo cInfo;
ss >> cNumber >> cInfo.courseName >> cInfo.credit;
courseInfo.push_back(cInfo);
hm[cNumber] = vertexNum++; // 将课程号与下标建立哈希映射,减少顺序检索带来的时间复杂度
CourseNode *cn = new CourseNode(cNumber);
courseNet.push_back(cn);
tailPoint.push_back(cn);
}
void CourseList::CreateAdjList(string buf, int index) {
vector<string> mark;
string str;
// 解析字符串
int len = buf.size(), c = 0, flag = 1;
if (buf[0] != 'C') {
flag = 0;
}
for (int i = 0;flag && i < len;i++) {
if (buf[i] != ',') {
str += buf[i];
}
else {
mark.push_back(str);
str.clear();
}
}
if (flag) {
mark.push_back(str);
}
// 根据 hm[courseNumber] 进行下标索引
int size = mark.size(), cnt = 0;
for (int i = 0;flag && i < size;i++) {
cnt++;
CourseNode * newNode = new CourseNode(courseNet[index]->courseNumber);
int n = hm[mark[i]];
tailPoint[n]->next = newNode;
tailPoint[n] = tailPoint[n]->next;
}
courseInfo[index].inDgree += cnt;
}
bool UserInterface::JudgeLogicError(int count) { // 判断课程是否存在回路
return bool(count != course.vertexNum);
}
void UserInterface::GetTopologicalOrder() {
stack<CourseNode *> st;
for (int i = 0;i < course.vertexNum;i++) { // 将入度为 0 的点压入栈中
if (!course.courseInfo[i].inDgree)
st.push(course.courseNet[i]);
}
int cnt = 0;
while (!st.empty()) { // 拓扑的核心,也可用队列存储
CourseNode *p, *temp = st.top(); // 若优化方案,考虑到先修课程并行学习,可以做多个辅助栈以达到模拟并行的目的
st.pop();
topoOrder.push_back(temp->courseNumber);
p = temp->next;
while (p) {
int index = course.hm[p->courseNumber];
if (!(--course.courseInfo[index].inDgree)) {
st.push(course.courseNet[index]);
}
p = p->next;
}
cnt++;
}
if (JudgeLogicError(cnt)) {
cout << "There are logical errors in course information!";
system("pause");
exit(0);
}
}
char UserInterface::ShowOption() {
system("cls");
char ch;
cout << " 请选择操作: " << endl;
cout << "1.显示全部课程信息" << endl;
cout << "2.显示学期课程安排" << endl;
cout << "3.退出自动排课系统" << endl;
cin >> ch;
return ch;
}
void UserInterface::UserOption() {
char op;
int flag = 1;
LoadingCourseInfo();
while (op = ShowOption()) {
switch (op)
{
case '1':
ShowCourseInfo();Sleep(5000);break;
case '2':
if (flag) {
GetTopologicalOrder();
flag = 0;
}
ShowTopoOrder();Sleep(10000);break;
case '3':
cout << "谢谢使用,再见!" << endl;
system("pause");exit(0);break;
default:
break;
}
}
}
void UserInterface::LoadingCourseInfo() {
ReadFromFile();
}
void UserInterface::ReadFromFile() {
const string fileName = "courseInfo.txt";
fstream fin;
try {
fin.open(fileName, ios::in | ios::out);
if (!fin)
throw "File Open Error!";
}
catch (const char *Warning)
{
cout << Warning << endl;
system("pause");
exit(0);
}
//成功打开文件
string line;
int flag = 0;
while (getline(fin, line))
{
if (!flag) { // 跳过第一行(标题属性)
flag = 1;
continue;
}
stringstream ss(line);
course.CreateHashMap(ss);
}
fin.clear();
fin.seekg(0);
// 再读一遍,此时根据课程依据的先序,创建邻接表
flag = 0;
int st = 0;
while (getline(fin, line))
{
if (!flag) { // 跳过第一行(标题属性)
flag = 1;
continue;
}
string buf;
stringstream ss(line);
for (int j = 1;j <= 4;j++) ss >> buf; // 吞掉前面3个字符串,只留下先修课程做解析
course.CreateAdjList(buf, st);
st++;
}
fin.close();
}
void UserInterface::ShowCourseInfo() {
//显示全部信息
for (int i = 0;i < course.vertexNum;i++) {
string name = course.courseNet[i]->courseNumber;
cout << course.hm[name] << ": " << name << " " << course.courseInfo[i].courseName << " ";
cout << course.courseInfo[i].credit << endl;;
}
}
void UserInterface::ShowTopoOrder() {
int verNum = course.vertexNum, size; // verNum 和 Size 均为总共课程数量
int flag = 0;
size = verNum;
cout << "课程学习序列为:" << endl;
for (int i = 0;i < size;i++) {
int index = course.hm[topoOrder[i]];
cout << course.courseInfo[index].courseName << "(" << topoOrder[i] << ") ";
if (!((i + 1) % 5))
cout << endl;
if (--verNum)
cout << "→ ";
}
cout << endl;
// 增加学期数和每学期学分上限的约束
int semester = 7, hasLearned = 0, ceiling = 9;
int curScore = 0, index = 0, t = 1;
for (int i = 0;i < size;i++) {
if (t) {
cout << "第 " << ++index << " 学期:" << endl;
t = 0;
}
int temp = course.courseInfo[course.hm[topoOrder[i]]].credit;
if ((hasLearned <= size - semester)) {
if (curScore <= ceiling - temp) { // 预判
cout << course.courseInfo[course.hm[topoOrder[i]]].courseName << " ";
hasLearned++;
curScore += temp;
}
else { // 如果不满足上面的约束条件,回退到上一次并清空状态,开始下一学期的排课
cout << endl;
hasLearned--;
i--;
curScore = 0;
t = 1;
}
}
else {
cout << endl;
hasLearned--;
i--;
curScore = 0;
t = 1;
}
}
cout << endl;
}