邻接表图+遍历+拓扑排序

  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 };
AGraph类的定义
 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 }
DFS遍历
 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 }
BFS遍历
  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 }
邻接表图较为完整的程序

 

posted @ 2016-12-02 13:38  enjoy_Lify  阅读(341)  评论(0编辑  收藏  举报