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 }