邻接表图+遍历+拓扑排序
Visit为全局变量,也可以不使用全局,但是必须是 通过引用(&)传递
由于不止一次的遍历,所以每一次遍历时都将全局变量还原为 false
bool Visit[max] = { false };
1 // 最大存储data[] 数组数据的最大值 2 const int max = 20; 3 // 数组链接的链表尾巴结点 4 struct TNode { 5 int index; 6 TNode * next; 7 }; 8 // 数组头结点 9 struct ArrayHNode { 10 char ch; 11 TNode * Anext; 12 }; 13 // 邻接表图 adjacency list graph 14 class AGraph { 15 public: 16 AGraph(); 17 ~AGraph(); 18 int In_degree(char); // 某结点的入度 19 int Out_degree(char); // 某结点的出度 20 int getIndex(char); // 某结点所在 data 数组的下标 21 void ResetArray(int); // 重新设置数组(将数组从 index 位置前移) 22 bool InsertVex(char); // 插入一个结点 23 bool InsertArc(char, char); // 插入一条边 24 bool DeleteVex(char); // 删除一个结点 25 bool DeleteArc(char, char); // 删除一条边 26 void DFS(); // 深度优先遍历 注:遍历过程似构建以广度优先树的森林 27 void DFS_visit(int); // 对某个结点深度遍历 28 void BFS(char); // 广度优先遍历 注:遍历过程似构建一颗广度优先树 (故此不需要 BFS_visit()辅助函数) 29 void Show_everyArray(); // 输出有效数组元素的所有结点 30 bool Topological_order(); // 拓扑排序 31 private: 32 ArrayHNode data[max]; 33 int Size; // 有效数据个数 34 };
1 bool AGraph::Topological_order() { 2 /* 3 拓扑排序----维基百科 4 在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。 5 每个顶点出现且只出现一次; 6 若A在序列中排在B的前面,则在图中不存在从B到A的路径。 7 也可以定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。 8 以上是维基百科上的 解释。 通俗的说是: (注意:上面已经说到的是有向无环的图) 9 1.有向图中选一个没有前驱的结点(即入度为0)的顶点并输出 10 2.从图中删除该顶点和所有以它为尾的弧(即删除每一个数组中以该顶点作为终点形成的边) 注:删除一个弧时,对应的 degree-1,因为少了一个出度 11 */ 12 int top = -1; 13 int Stack[max]; // 使用栈来保存 入度为 0 的数据下标(可以知道对应结点) 14 int degree[max] = { 0 }; // 初始化每一个入度为 0 15 for (int i = 0; i < Size; ++i) { 16 // 保存每一个结点的 入度值至 degree[]中 17 degree[i] = In_degree(data[i].ch); // degree存放的是 每一个对于下标数据结点入度 18 if(degree[i] == 0) { 19 // 如果入度是 0 的就将其入栈 20 Stack[++top] = degree[i]; // Stack 存放的是 每个入度为 0的结点 21 } 22 } 23 int count = 0; // 计数: 记住操作或输出了几个结点 24 int Findex, Lindex; // Fist_index 是data[]数组的下标 List_index 是data[]数组中 下标为Findex 链接的结点下标 25 while(top != -1) { // 栈空时退出 26 Findex = Stack[top--]; // 退栈,退出一个元素用于操作输出 27 cout << data[Findex].ch << " "; 28 count++; // 输出一个结点,即操作了一个结点, count++ 29 for (TNode * TN = data[Findex].Anext; TN != NULL; TN = TN->next) { 30 // 根据拓扑排序原理,输出了一个结点,就要将其对应相关联的结点入度 - 1 31 Lindex = TN->index; 32 degree[Lindex]--; // ‘删除’一个结点,将相关联的下标为Lindex的结点入度 - 1 33 if (degree[Lindex] == 0) { // 如果由于 入度减一为 0了, 根据拓扑规则,将其入队 34 Stack[++top] = Lindex; 35 } 36 } 37 } 38 if(count < Size) { 39 cout << " 图中存在回路! 非拓扑序列"; 40 return false; 41 } else { 42 cout << " 该图为一个拓扑序列!"; 43 return true; 44 } 45 }
1 void AGraph::DFS() { 2 // 默认从第一个结点 data[0]进行深度优先遍历 3 for (int index = 0; index < Size; ++index) { 4 if(Visit[index] == false) { 5 // 对没有被访问过的元素访问之 6 DFS_visit(index); 7 } 8 } 9 cout << endl; 10 // 对改变Visit 进行还原 全为false 11 for (int i = 0; i < Size; ++i) { 12 Visit[i] = false; 13 } 14 } 15 void AGraph::DFS_visit(int i) { 16 // 执行该函数表示当前 下标为 i的数组元素没有被访问过 17 Visit[i] = true; // 标志该元素被访问,下面输出该元素 18 cout << data[i].ch << " "; 19 TNode * TN = data[i].Anext; 20 while(TN != NULL) { 21 // 对当前数组元素链接的链表进行索引 22 if (Visit[TN->index] == false) // 对没有访问过的(false) 进行递归访问 23 DFS_visit(TN->index); 24 TN = TN->next; 25 } 26 }
1 void AGraph::BFS(char ch) { 2 int Queue[max]; // 使用队列操作 3 TNode * TN = NULL; 4 int front = -1; 5 int rear = -1; 6 int index; 7 Queue[++rear] = getIndex(ch); // 找到对应数组位置 8 while(front != rear) { 9 index = Queue[++front]; 10 // 取出队头元素操作它链接的结点数据 11 cout << data[index].ch << " "; 12 TN = data[index].Anext; 13 while(TN != NULL) { 14 if(Visit[TN->index] == false) { 15 Visit[TN->index] = true; 16 Queue[++rear] = TN->index; 17 } 18 TN = TN->next; 19 } 20 } 21 cout << endl; 22 // 对改变Visit 进行还原 全为false 23 for (int i = 0; i < Size; ++i) { 24 Visit[i] = false; 25 } 26 }
1 #include <iostream> 2 using namespace std; 3 // 最大存储data[] 数组数据的最大值 4 const int max = 20; 5 // 数组链接的链表尾巴结点 6 struct TNode { 7 int index; 8 TNode * next; 9 }; 10 // 数组头结点 11 struct ArrayHNode { 12 char ch; 13 TNode * Anext; 14 }; 15 // 邻接表图 adjacency list graph 16 class AGraph { 17 public: 18 AGraph(); 19 ~AGraph(); 20 int In_degree(char); // 某结点的入度 21 int Out_degree(char); // 某结点的出度 22 int getIndex(char); // 某结点所在 data 数组的下标 23 void ResetArray(int); // 重新设置数组(将数组从 index 位置前移) 24 bool InsertVex(char); // 插入一个结点 25 bool InsertArc(char, char); // 插入一条边 26 bool DeleteVex(char); // 删除一个结点 27 bool DeleteArc(char, char); // 删除一条边 28 void DFS(); // 深度优先遍历 注:遍历过程似构建以广度优先树的森林 29 void DFS_visit(int); // 对某个结点深度遍历 30 void BFS(char); // 广度优先遍历 注:遍历过程似构建一颗广度优先树 (故此不需要 BFS_visit()辅助函数) 31 void Show_everyArray(); // 输出有效数组元素的所有结点 32 bool Topological_order(); // 拓扑排序 33 private: 34 ArrayHNode data[max]; 35 int Size; // 有效数据个数 36 }; 37 38 bool Visit[max] = { false }; // 作为访问标识符,默认全为 false 39 40 int main() { 41 42 void launch(); 43 launch(); 44 45 system("pause"); 46 return 0; 47 } 48 49 void launch() { 50 cout << "最大存放数据个数" << max << endl; 51 AGraph AG; 52 int Times = 3; 53 int Order; 54 char flag = 'N'; 55 char ch; 56 char ch1, ch2; 57 while (Times > 0) { 58 cout << "┏=============================================================┓" << endl; 59 cout << "| 入度 1, 插结点 3, 插边 4, DFS遍历 7, 输出邻接表图 9 |" << endl; 60 cout << "| 出度 2, 删结点 5, 删边 6, BFS遍历 8, 输出拓扑排序 10 |" << endl; 61 cout << "┗=============================================================┛" << endl; 62 cout << endl << "剩余Times= " << Times << "次操作--输入对应数字操作:"; 63 cin >> Order; 64 switch (Order) { 65 case 1: cout << "请输入求入度的字符:"; cin >> ch; 66 cout <<" ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄"<<endl << AG.In_degree(ch); 67 break; 68 case 2: cout << "请输入求出度的字符:"; cin >> ch; 69 cout << " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄" << endl << AG.Out_degree(ch); 70 break; 71 case 3: cout << "请输入插入结点字符:"; cin >> ch; 72 cout << " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄" << endl << AG.InsertVex(ch); 73 break; 74 case 4: cout << "请输入要插入边的起点结点和终点字符(连续输入):"; cin >> ch1 >> ch2; 75 cout << " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄" << endl << AG.InsertArc(ch1, ch2); 76 break; 77 case 5: cout << "请输入删除结点字符:"; cin >> ch; 78 cout << " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄" << endl << AG.DeleteVex(ch); 79 break; 80 case 6: cout << "请输入要删除边的起点结点和终点字符(连续输入):"; cin >> ch1 >> ch2; 81 cout << " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄" << endl << AG.DeleteArc(ch1, ch2); 82 break; 83 case 7: cout << " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄" << endl ; 84 AG.DFS(); 85 break; 86 case 8: cout << "请输入 BFS 遍历的起始结点字符:"; cin >> ch; 87 cout << " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄" << endl ; 88 AG.BFS(ch); 89 break; 90 case 9: cout << " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄" << endl; 91 AG.Show_everyArray(); 92 break; 93 case 10: cout << " ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄" << endl; 94 AG.Topological_order(); 95 break; 96 } 97 cout << endl << "_______________" << endl; 98 Times--; 99 cout << endl; 100 if(Times == 0) { 101 cout << "Times为 0退出,请输入是否追加 Times值(N 否, Y 是):"; 102 cin >> flag; 103 if (flag == 'y' || flag == 'Y') 104 Times = 3; 105 } 106 } 107 } 108 109 bool AGraph::Topological_order() { 110 /* 111 拓扑排序----维基百科 112 在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。 113 每个顶点出现且只出现一次; 114 若A在序列中排在B的前面,则在图中不存在从B到A的路径。 115 也可以定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。 116 以上是维基百科上的 解释。 通俗的说是: (注意:上面已经说到的是有向无环的图) 117 1.有向图中选一个没有前驱的结点(即入度为0)的顶点并输出 118 2.从图中删除该顶点和所有以它为尾的弧(即删除每一个数组中以该顶点作为终点形成的边) 注:删除一个弧时,对应的 degree-1,因为少了一个出度 119 */ 120 int top = -1; 121 int Stack[max]; // 使用栈来保存 入度为 0 的数据下标(可以知道对应结点) 122 int degree[max] = { 0 }; // 初始化每一个入度为 0 123 for (int i = 0; i < Size; ++i) { 124 // 保存每一个结点的 入度值至 degree[]中 125 degree[i] = In_degree(data[i].ch); // degree存放的是 每一个对于下标数据结点入度 126 if(degree[i] == 0) { 127 // 如果入度是 0 的就将其入栈 128 Stack[++top] = degree[i]; // Stack 存放的是 每个入度为 0的结点 129 } 130 } 131 int count = 0; // 计数: 记住操作或输出了几个结点 132 int Findex, Lindex; // Fist_index 是data[]数组的下标 List_index 是data[]数组中 下标为Findex 链接的结点下标 133 while(top != -1) { // 栈空时退出 134 Findex = Stack[top--]; // 退栈,退出一个元素用于操作输出 135 cout << data[Findex].ch << " "; 136 count++; // 输出一个结点,即操作了一个结点, count++ 137 for (TNode * TN = data[Findex].Anext; TN != NULL; TN = TN->next) { 138 // 根据拓扑排序原理,输出了一个结点,就要将其对应相关联的结点入度 - 1 139 Lindex = TN->index; 140 degree[Lindex]--; // ‘删除’一个结点,将相关联的下标为Lindex的结点入度 - 1 141 if (degree[Lindex] == 0) { // 如果由于 入度减一为 0了, 根据拓扑规则,将其入队 142 Stack[++top] = Lindex; 143 } 144 } 145 } 146 if(count < Size) { 147 cout << " 图中存在回路! 非拓扑序列"; 148 return false; 149 } else { 150 cout << " 该图为一个拓扑序列!"; 151 return true; 152 } 153 } 154 155 void AGraph::BFS(char ch) { 156 int Queue[max]; // 使用队列操作 157 TNode * TN = NULL; 158 int front = -1; 159 int rear = -1; 160 int index; 161 Queue[++rear] = getIndex(ch); // 找到对应数组位置 162 while(front != rear) { 163 index = Queue[++front]; 164 // 取出队头元素操作它链接的结点数据 165 cout << data[index].ch << " "; 166 TN = data[index].Anext; 167 while(TN != NULL) { 168 if(Visit[TN->index] == false) { 169 Visit[TN->index] = true; 170 Queue[++rear] = TN->index; 171 } 172 TN = TN->next; 173 } 174 } 175 cout << endl; 176 // 对改变Visit 进行还原 全为false 177 for (int i = 0; i < Size; ++i) { 178 Visit[i] = false; 179 } 180 } 181 182 void AGraph::DFS() { 183 // 默认从第一个结点 data[0]进行深度优先遍历 184 for (int index = 0; index < Size; ++index) { 185 if(Visit[index] == false) { 186 // 对没有被访问过的元素访问之 187 DFS_visit(index); 188 } 189 } 190 cout << endl; 191 // 对改变Visit 进行还原 全为false 192 for (int i = 0; i < Size; ++i) { 193 Visit[i] = false; 194 } 195 } 196 197 void AGraph::DFS_visit(int i) { 198 // 执行该函数表示当前 下标为 i的数组元素没有被访问过 199 Visit[i] = true; // 标志该元素被访问,下面输出该元素 200 cout << data[i].ch << " "; 201 TNode * TN = data[i].Anext; 202 while(TN != NULL) { 203 // 对当前数组元素链接的链表进行索引 204 if (Visit[TN->index] == false) // 对没有访问过的(false) 进行递归访问 205 DFS_visit(TN->index); 206 TN = TN->next; 207 } 208 } 209 210 void AGraph::Show_everyArray() { 211 int index; 212 TNode * TN = NULL; 213 for (int i = 0; i < Size; ++i) { 214 cout << data[i].ch << " -> "; 215 TN = data[i].Anext; 216 while(TN != NULL) { 217 cout << data[TN->index].ch << " "; 218 TN = TN->next; 219 } 220 cout << endl; 221 } 222 } 223 224 int AGraph::Out_degree(char ch) { 225 TNode * TN = NULL; 226 int i = getIndex(ch); 227 int number = 0; 228 if(i != -1) { 229 // 求的结点合法 230 TN = data[i].Anext; 231 while(TN != NULL) { 232 number++; 233 TN = TN->next; 234 } 235 } 236 return number; 237 } 238 239 int AGraph::In_degree(char ch) { 240 // 入队就是要遍历 n-1个数组下标所链接的结点,来确定有哪些与之构成边 241 int index = getIndex(ch); 242 int number = 0; 243 TNode * TN = NULL; 244 for (int i = 0; i < Size; ++i) { 245 // 可能存在环 即 A->A,所以遍历全部数组 246 TN = data[i].Anext; 247 while(TN != NULL) { 248 if (TN->index == index) // 下标相等表示数据相同,记录 249 number++; 250 TN = TN->next; 251 } 252 } 253 return number; 254 } 255 256 void AGraph::ResetArray(int index) { 257 // 类似普通数组一样,将后面的数据覆盖掉前面的数据,链表保存的下标就会发生变化,从 index开始 258 TNode * TN = NULL; 259 for (int i = index; i < Size-1; ++i) { 260 data[i].ch = data[i + 1].ch; // 元素前移 261 data[i].Anext = data[i + 1].Anext; // 对于的链表也要插入过去 262 // 由于数组的 Size 发生变化了,那么就必须作出调整 263 TN = data[i].Anext; 264 while(TN != NULL) { 265 // 对 凡是保存的下标大于等于 index的都必须减一,因为数组从index-1处下降了一个高度,原来下标保存为 F=10的,可能降到了 F=9 266 if(TN->index >= index) { 267 TN->index = TN->index - 1; 268 } 269 TN = TN->next; 270 } 271 } 272 data[Size - 1].Anext = NULL; 273 // 将data[Size-1]指向那个链表断开,由于 data[Size-1]后面的链表 已经链接到了 data[Size-2]的后面了,所以不用担心该链表不会被析构,此语句可以不写,只是安全起见 274 Size--; // 每一次重置数组就是删除了一个结点才需要重置,所以 Size-1 275 } 276 277 bool AGraph::DeleteVex(char ch) { 278 // 删除一个结点的时候,就是将数组的元素重新排一下,并且把与结点相关联的边删除 279 int i = getIndex(ch); 280 if(i != -1) { 281 // 存在要删除的结点数据,要删除的结点合法 282 for (int j = 0; j < Size; ++j) { 283 // 对待删结点在数组中作为终点构成的边 全部删除,这儿不需要待删除结点所在的下标数据 284 if (j != i) 285 DeleteArc(data[j].ch, ch); 286 }// 将关联的边删除了,开始删除该结点 287 TNode * p = data[i].Anext; 288 TNode * tem; 289 while(p != NULL) { 290 // 删除该结点的出度,即作为起点关联的边 291 tem = p; 292 p = p->next; 293 delete tem; 294 } 295 data[i].Anext = NULL; // 对于要删除的结点,它的 指针域应当置为 NULL 296 ResetArray(i); // Size 在该函数里面再减 1,删除一个结点,空出一个位置,让后面的数据前移,补位 297 } 298 return true; 299 } 300 301 bool AGraph::DeleteArc(char ch1, char ch2) { 302 // 删除边时,应注意删除的结点是 数组的第一个结点和不是第一个结点是不同的代码 303 int i = getIndex(ch1); 304 int j = getIndex(ch2); 305 if(i != -1 && j != -1) { 306 // 存在该两个结点,接下来判断有两结点构成的边就删除 307 TNode * TN = data[i].Anext; 308 TNode * pre = NULL; // 作为索引前驱 309 TNode * tem = NULL; // 指向要删除的结点 对其删除操作 310 while (TN != NULL) { 311 // 索引两结点构成的边 312 if(TN->index == j && pre == NULL) { 313 // 存在索引的边 并且 为数组链接的 第一个结点 314 tem = TN; 315 data[i].Anext = tem->next; 316 delete tem; 317 return true; 318 } else if(TN->index == j && pre != NULL) { 319 // 存在索引的边 并且 不是数组链接的 第一个结点 320 tem = TN; 321 pre->next = tem->next; 322 delete tem; 323 return true; 324 } 325 pre = TN; // 保存当前指向的结点 326 TN = TN->next; // 转入下一个结点, 而 pre 则成了上一次访问的结点 327 } 328 } 329 return false; 330 } 331 332 bool AGraph::InsertVex(char ch) { 333 // 插入结点数据就是 将数据插入 数组中 334 if(Size == max) { 335 return false; 336 } else { 337 // 还有位置可以插入数据,直接录入数据不初始化相关边 338 data[Size].ch = ch; 339 data[Size].Anext = NULL; 340 Size++; 341 return true; 342 } 343 } 344 345 bool AGraph::InsertArc(char ch1, char ch2) { 346 // 插入边就是 在 起点结点对于的数组下标哪儿 插入一个结点到其后面去 347 int i = getIndex(ch1); 348 int j = getIndex(ch2); 349 TNode * p = NULL; 350 if(i != -1 && j != -1) { 351 // 当两个结点构成的边合法,通过头插法插入 data[]中 352 p = data[i].Anext; 353 while(p != NULL) { 354 // 索引一下,要构成的边是不是已经存在了,存在的话就不插入 355 if (p->index == j) 356 return false; 357 p = p->next; 358 } 359 TNode * TN = new TNode(); 360 TN->index = j; 361 TN->next = data[i].Anext; 362 data[i].Anext = TN; 363 return true; 364 } else { 365 return false; 366 } 367 } 368 369 int AGraph::getIndex(char ch) { 370 for (int i = 0; i < Size; ++i) { 371 if (ch == data[i].ch) 372 return i; 373 } 374 return -1; 375 } 376 377 AGraph::AGraph() { 378 // 初始化数组的结点 379 char ch1; 380 cout << "请输入结点数据(#作为结束符):"; 381 cin >> ch1; 382 int i = 0; 383 while(ch1 != '#') { 384 data[i].ch = ch1; 385 data[i].Anext = NULL; 386 i++; 387 cin >> ch1; 388 } 389 Size = i; // 保存实际数据个数,非最大元素的下标 390 // 初始化边,也就是数组链接的链表数据 391 cout << "以出度构建邻接表图:" << endl; 392 char ch2; 393 bool flag = true; 394 while(flag) { 395 cout << "初始化边,请输入一条边的起点(#表终止):"; 396 cin >> ch1; 397 cout << "初始化边,请输入一条边的终点(#表终止):"; 398 cin >> ch2; 399 if(ch1 != '#' || ch2 != '#') { 400 InsertArc(ch1, ch2); 401 } else { 402 flag = false; 403 } 404 } 405 } 406 407 AGraph::~AGraph() { 408 TNode * TN = NULL; 409 TNode * tem = NULL; 410 for (int i = 0; i < Size; ++i) { 411 TN = data[i].Anext; 412 cout << "析构数组下标为"<<i<<"元素为"<<data[i].ch<<"的尾链表结点:"; 413 while(TN != NULL) { 414 tem = TN; 415 // TN = TN->next; 416 cout << TN->index << " "; 417 TN = TN->next; 418 delete tem; 419 } 420 cout << endl; 421 } 422 Size = 0; 423 }