内容预览

  • 零、读前说明
  • 一、概 述
  • 二、深度优先遍历(DFS)
    • 2.1、无向图的遍历过程
    • 2.2、有向图的遍历过程
    • 2.3、总结说明
    • 2.4、实现源代码
  • 三、广度优先遍历(BFS)
    • 3.1、广度优先的遍历过程
    • 3.2、总结说明
    • 3.3、实现源代码
  • 四、源码测试效果
    • 4.1、测试案例源码
    • 4.2、测试效果图
  • 五、简单的总结

零、读前说明

  • 本文中所有设计的代码均通过测试,并且在功能性方面均实现应有的功能。
  • 设计的代码并非全部公开,部分无关紧要代码并没有贴出来。
  • 如果你也对此感兴趣、也想测试源码的话,可以私聊我,非常欢迎一起探讨学习。
  • 由于时间、水平、精力有限,文中难免会出现不准确、甚至错误的地方,也很欢迎大佬看见的话批评指正。
  • 时隔三个月,我终于回来了,嘻嘻。。。。 。。。。。。。。收!

摘要

  图是一种非线性的数据结构,图的遍历指的是 从图中的某一顶点出发,沿着一些边访问图中所有的顶点,使得每个顶点都被访问且仅被访问一次。根据遍历路径的不同,通常有两种遍历图的方法:深度优先遍历(Depth First Search )和广度优先遍历(Breadth First Search )。它们对无向图和有向图都适用,图的遍历算法是求解图的连通性问题、拓扑排序和求关键路径等算法的基础。

一、概 述

  由于图结构本身的复杂性,所以图的遍历操作也较复杂,主要表现在以下四个方面:

  ① 在图结构中,没有一个 “自然” 的首结点,图中任意一个顶点都可作为第一个被访问的结点。
  ② 在非连通图中,从一个顶点出发,只能够访问它所在的连通分量上的所有顶点,因此,还需考虑如何选取下一个出发点以访问图中其余的连通分量。
  ③ 在图结构中,如果有回路存在,且图中的任意一个顶点可能与其他顶点相通,那么一个顶点被访问之后,有可能沿回路又回到该顶点。
  ④ 在图结构中,一个顶点可以和其它多个顶点相连,当这样的顶点访问过后,存在如何选取下一个要访问的顶点的问题。

  所以在进行图的遍历过程中,一般情况下,对于上述的问题:

  1 、遍历开始,一般 随机选择一个顶点作为起始顶点
  2 、对于非连通图,一般都是 进行多次调用遍历算法(nnn 个非连通图需要调用nnn 次遍历算法)
  3 、图中可能存在回路,所以 设置访问标志数组 visted[nnn](nnn 为顶点的个数),用来表示每个被访问的顶点。
  4 、一般情况下采用 深度优先遍历广度优先遍历

  图的遍历可分为四类:

    遍历完所有的边而不能有重复,即所谓“一笔画问题”或“欧拉路径”;
    遍历完所有的顶点而没有重复,即所谓“哈密尔顿问题”;
    遍历完所有的边而可以有重复,即所谓“中国邮递员问题”;
    遍历完所有的顶点而可以重复,即所谓“旅行推销员问题”。

  那么下面开始深度优先遍历(DFS)、广度优先遍历(BFS)的过程说明。

二、深度优先遍历(DFS)

2.1、无向图的遍历过程

  首先来看看 无向图遍历的过程说明,以下图为例。

图2.1 无向图遍历的过程示意图   

  根据图例,我们可以画出此图的简单的邻接矩阵,以下图为例。

图2.2 无向图邻接矩阵示意图   

  1、假设访问起始遍历的顶点为顶点 A(图 2.1中 1 所指),同时标记为已访问(下同,不再赘述);
  2、然后开始访问顶点 A 的邻接点(分别为顶点 B 、顶点 C 、顶点 D(对应于上图 2.2中的第 0 行,即顶点 A的邻接点) ,(遍历规则设定为 按照顶点的编号小的优先,即先小后大,其实邻接矩阵的遍历顺序也是按照编号进行从小到大的依次遍历))。所以首先进行遍历顶点 B(图 2.1中 2 所指,对应于上图 2.2中的第 1 行), B 为第一次访问,然后再继续遍历顶点 C(图 2.1中 3 所指对应于上图 2.2中的第 3 行)且顶点 C为第一次访问,然后遍历顶点 C 的邻接点;
  3、从图 2.1中可以看出,顶点 C 的邻接点为顶点 A 和顶点 B ,经判断顶点 A 已经被访问,所以需要回退到顶点 B (图 2.1中 4 所指),然后继续遍历顶点 B 的的邻接点(顶点 E ,顶点 F );
  4、按照编号小的优先原则,继续遍历顶点 E (图 2.1中 5 所指,对应于上图 2.2中的第 5 行),然后再继续遍历顶点 D(图 2.1中 6 所指,对应于上图 2.2中的第 4 行),同理 D 的邻接点(顶点 A 、顶点 D )均已经被访问,再回退到顶点 B (图 2.1中 7、8 所指)。继续遍历顶点 B 的邻接点顶点 F(图 2.1中 9 所指,对应于上图 2.2中的第 6 行);
  5、顶点 F 没有后续邻接点,遍历完成,按照图 2.1中 10、11 所指回退到起始位置,至此遍历完成。

  所以遍历的顺序为: A -> B -> C -> E -> D -> F

2.2、有向图的遍历过程

  来看看 有向图遍历的过程说明,以下图为例。

图2.3 有向图遍历的过程示意图   

  根据图例,我们可以画出此图的简单的邻接矩阵,以下图为例。

图2.4 有向图邻接矩阵示意图   

  有向图遍历的过程说明:

  1、假设访问起始遍历的顶点为顶点 A(图中 1 所指),同时标记为已访问(下同,不再赘述);
  2、然后开始访问顶点 A 的邻接点(分别为顶点 B 、顶点 C 、顶点 D 。(遍历规则设定为 按照顶点的编号小的优先,即先小后大,其实邻接矩阵的遍历顺序也是按照编号进行从小到大的依次遍历))。所以首先进行遍历顶点 B(图中 2 所指), B 为第一次访问,然后再继续遍历顶点 B 的邻接点(顶点 E ,顶点 F ),按照编号小的优先原则,遍历顶点 E (图中 3 所指)且顶点 E为第一次访问,然后遍历顶点 E 的邻接点。顶点 E 没有邻接点,所以进行回退操作,回退到顶点 B ,然后遍历顶点 F (图中 5 所指);
  3、然后继续遍历顶点 F 的邻接点,没有邻接点则开始回退(图中 6、7 所指)到顶点 A ,继续遍历顶点 A编号较小的没有被访问的邻接点 C ,然后遍历 C 的邻接点(顶点 B ),经判断顶点 B 已经被访问过,所以开始回退(图中 9 所指)到顶点 A
  4、继续遍历顶点 A 的编号较小的没有被访问的邻接点顶点 D (图中 10 所指),然后遍历顶点 D 的邻接点(顶点 E ),经判断顶点 E 已经被访问,然后继续回退到顶点 A(图中 11 所指);
  5、至此则所有顶点遍历完成。

  所以遍历的顺序为: A -> B -> E -> F -> C -> D

2.3、总结说明

  综合上述的过程说明,可以总结出来 深度优先遍历 的算法描述(类似于二叉树的先序遍历):

  1、访问起始顶点 vvv;
  2、当 vvv 存在邻接点并且此邻接点未被访问过时,继续深度遍历其未被访问过的邻接顶点 www;
  3、当 vvv 的所有邻接顶点都被访问过:
    1)如果图中所有顶点均已被访问,则算法结束;
    2)如果图中还有未被访问的顶点(非连通图)、以未访问顶点作为起始顶点继续深度遍历。

2.4、实现源代码

  实现代码中已经有了比较详细的注释,所以便不再进行说明,代码如下所示。

/*** 功 能:*      深度优先遍历图的辅助函数 -- 递归实现* 参 数:*      graph:要遍历的图*      v    : 起始遍历的节点(在节点组中的编号/下标)*      visited :是否被访问的标识* 返回值:*      无 **/
static void recursive_dfs(TMGraph *graph, int v, int visited[])
{int i = 0;if (graph == NULL || v < 0 || visited == NULL)goto END;// 打印输出节点信息,也可以其它操作printf("%s ", (char *)(graph->vertex[v]));// 设置访问的标志visited[v] = TRUE;for (i = 0; i < graph->count; i++){// 判断与v相邻的节点i之间是否存在边(等于0不存在,存在的变的话既可以是1,// 也可使权值(1也有可能为权值)),并且是否已经被访问if ((graph->matrix[v][i] != 0) && visited[i] == FALSE){// 继续递归遍历recursive_dfs(graph, i, visited);}}
END:return;
}/*** 功 能:*      深度优先遍历图的函数* 参 数:*      graph:要遍历的图*      v    : 起始遍历的节点(在节点组中的编号/下标) * 返回值:*      无 **/
int MGraph_DFS(MGraph *graph, int v)
{TMGraph *tGraph = (TMGraph *)graph;int *visited = NULL;int ret = -1;if (tGraph == NULL || v < 0 || v > tGraph->count)goto ERROR;// 为访问的标志申请空间并初始化visited = (int *)calloc(tGraph->count, sizeof(int));if (visited == NULL)goto ERROR;recursive_dfs(tGraph, v, visited); // 开始递归遍历// 再次遍历所有节点,如果在上次访问中有没有被访问的节点(没有边连接),非连通图,那么以此节点开始继续访问for (int i = 0; i < tGraph->count; i++){if (visited[i] == FALSE) // 判断是否被访问{recursive_dfs(tGraph, i, visited); // 对未访问的邻接顶点递归调用}}printf("\n");ret = 0;
ERROR:if (visited)free(visited);return ret;
}

三、广度优先遍历(BFS)

3.1、广度优先的遍历过程

  图中的形式有向图和无向图的顺序正好重复且一模一样,所以就在一起描述。以下图为例。

图3.1 无向图遍历的过程示意图   

  1、假设访问起始遍历的顶点为顶点 A,将顶点 A 入队列(图中 1 所指),此时队列中为节点 A;(图中 1 所指),同时标记为已访问(下同,不再赘述);
  2、然后将顶点A出队列,顶点 A 的遍历完成,然后开始访问顶点 A 的邻接点(分别为顶点 B 、顶点 C 、顶点 D 。(遍历规则设定为 按照顶点的编号小的优先,即先小后大,其实邻接矩阵的遍历顺序也是按照编号进行从小到大的依次遍历));
  3、然后将顶点 A 的所有的没有被访问的邻接点入队列(入队列顺序从编号小到大依次为顶点 B、顶点 C、顶点 D,图中 2、3、4 所指),此时队列中为顶点 B、C、D;
  4、然后将顶点 B 出队列(图中 5 所指),顶点 B 的遍历完成,然后将顶点 B 的所有的没有被访问的邻接点入队列(入队列的顺序为 E、F ,图中 6、7 所指),此时队列中的节顶点为 C、D、E、F ;
  5、然后将顶点 C 出队列(图中 8 所指),顶点 C 的遍历完成,然后将顶点 C 的所有的没有被访问的邻接点入队列(其邻接点为顶点 B 和顶点 A (图中有向图没有顶点 A ),但是顶点 B 经判断已经遍历),则不再入队列,此时队列中的顶点为 D、E、F ;
  6、然后将顶点 D 出队列(图中 9 所指),顶点 D 的遍历完成,此时顶点 D 的邻接点为顶点 E 和顶点 A (图中有向图没有顶点 A ),且经判断顶点 E 已经被访问所以不再入队列,此时队列中的顶点为 E、F;
  7、继续出队列为顶点 E (图中 10 所指),有向图中顶点 E 没有邻接点,无向图中邻接点为 B 和 D ,经判断均已经被访问,所以也不再入队列;此时队列中顶点为 F ;
  8、出队列顶点 F (图中 11 所指),经判断顶点 F 的所有邻接点均已经被访问,没有可入队列的顶点,此时队列为空,遍历完成。

  所以,遍历的顺序为: A -> B -> C -> D -> E -> F

3.2、总结说明

  综合上述的过程说明,可以总结出来 广度优先遍历 的算法描述(二叉树的层序遍历):

  1、访问起始顶点 v0v_0v0​;
  2、依次访问 v0v_0v0​ 的各个邻接点 v0v_0v0​1_11​, v0v_0v0​2_22​ ,…, v0v_0v0​x_xx​;
  3、假设最近一次访问的顶点依次为 viv_ivi​1_11​, viv_ivi​2_22​, …, viv_ivi​y_yy​,则依次访问viv_ivi​1_11​, viv_ivi​2_22​, …, viv_ivi​y_yy​ 的未被访问的邻接点;
  4、重复3,直到所有顶点均被访问。

3.3、实现源代码

  实现代码中已经有了比较详细的注释,所以便不再进行说明,代码如下所示。

/*** 功 能:*      广度优先遍历图的辅助函数 -- 队列实现* 参 数:*      graph:要遍历的图*      v    : 起始遍历的节点(在节点组中的编号/下标)*      visited :是否被访问的标识* 返回值:*      无 **/
static void bfs(TMGraph *graph, int v, int visited[])
{LinkQueue *queue = NULL;if (graph == NULL || v < 0 || visited == NULL)goto ERROR;// 创建一个队列queue = fLinkQueue.create();if (queue == NULL)goto ERROR;// 入队列fLinkQueue.enqueue(queue, graph->vertex + v);visited[v] = 1;while (fLinkQueue.length(queue) > 0){v = (MVertex **)fLinkQueue.dequeue(queue) - graph->vertex;printf("%s ", (char *)(graph->vertex[v]));for (int i = 0; i < graph->count; i++) // 对每一个顶点做循环{// 判断与v相邻的节点i之间是否存在边,并且是否已经被访问if ((graph->matrix[v][i] != 0) && visited[i] == FALSE){// 入队列fLinkQueue.enqueue(queue, graph->vertex + i);visited[i] = TRUE; // 将找到的此顶点标记为已访问}}}ERROR:if (queue)fLinkQueue.destroy(queue); // 销毁队列return;
}/*** 功 能:*      广度优先遍历图的函数* 参 数:*      graph:要遍历的图*      v    : 起始遍历的节点(在节点组中的编号/下标) * 返回值:*      无 **/
int MGraph_BFS(MGraph *graph, int v)
{TMGraph *tGraph = (TMGraph *)graph;int *visited = NULL;int ret = -1;if (tGraph == NULL || v < 0 || v > tGraph->count)goto ERROR;// 为访问的标志申请空间并初始化visited = (int *)calloc(tGraph->count, sizeof(int));if (visited == NULL)goto ERROR;bfs(tGraph, v, visited);// 再次遍历所有节点,如果在上次访问中有没有被访问的节点(没有边连接),非连通图,那么以此节点开始继续访问for (int i = 0; i < tGraph->count; i++){// 判断顶点是否曾经访问过,没有访问过则继续访问if (visited[i] == FALSE){bfs(tGraph, i, visited); // 递归遍历}}printf("\n");ret = 0;
ERROR:if (visited)free(visited);return ret;
}

四、源码测试效果

4.1、测试案例源码

  根据上述深度优先遍历以及广度优先遍历的过程说明等,编写出相关的测试案例的实现代码,源码如下所示。

#include "../src/graph/graph.h"
#include <stdio.h>
#include <stdlib.h>int main(int argc, char *argv[])
{int ret = system("color "); // 颜色设置,用于在windows下终端显示的时候不明原因换行的问题// 顶点MVertex const *v[] = {"A", "B", "C", "D", "E", "F"};/** 下面开始无向图的测试 **/printf("\n\e[1;31mLet's start with an example of an undirected graph:\e[0m\n");// 创建一个图MGraph *ungraph = funMGraph.create((MVertex **)v, 6);// 添加边ret = funMGraph.edge.add(ungraph, 0, 1, 1, UNDIRECTED_GRAPH); // (A, B)ret = funMGraph.edge.add(ungraph, 0, 2, 1, UNDIRECTED_GRAPH); // (A, C)ret = funMGraph.edge.add(ungraph, 0, 3, 1, UNDIRECTED_GRAPH); // (A, D)ret = funMGraph.edge.add(ungraph, 1, 5, 1, UNDIRECTED_GRAPH); // (B, F)ret = funMGraph.edge.add(ungraph, 1, 4, 1, UNDIRECTED_GRAPH); // (B, E)ret = funMGraph.edge.add(ungraph, 2, 1, 1, UNDIRECTED_GRAPH); // (C, B)ret = funMGraph.edge.add(ungraph, 3, 4, 8, UNDIRECTED_GRAPH); // (D, E)// 显示funMGraph.display(ungraph);// 遍历printf("\nDFS Starting with vertex 'A': ");ret = funMGraph.traverse.dfs(ungraph, 0);printf("BFS Starting with vertex 'A': ");ret = funMGraph.traverse.bfs(ungraph, 0);printf("DFS Starting with vertex 'C': ");ret = funMGraph.traverse.dfs(ungraph, 2);printf("BFS Starting with vertex 'D': ");ret = funMGraph.traverse.bfs(ungraph, 3);printf("\n\e[1;31mLet's start with an example of directed graph \e[0m\n");// 创建一个图MGraph *graph = funMGraph.create((MVertex **)v, 6);// 添加边ret = funMGraph.edge.add(graph, 0, 1, 1, DIRECTED_GRAPH); // <A, B>ret = funMGraph.edge.add(graph, 0, 2, 1, DIRECTED_GRAPH); // <A, C>ret = funMGraph.edge.add(graph, 0, 3, 1, DIRECTED_GRAPH); // <A, D>ret = funMGraph.edge.add(graph, 1, 5, 1, DIRECTED_GRAPH); // <B, F>ret = funMGraph.edge.add(graph, 1, 4, 1, DIRECTED_GRAPH); // <B, E>ret = funMGraph.edge.add(graph, 2, 1, 1, DIRECTED_GRAPH); // <C, B>ret = funMGraph.edge.add(graph, 3, 4, 8, DIRECTED_GRAPH); // <D, E>// 显示funMGraph.display(graph);// 遍历printf("\nDFS Starting with vertex 'A': ");ret = funMGraph.traverse.dfs(graph, 0);printf("BFS Starting with vertex 'A': ");ret = funMGraph.traverse.bfs(graph, 0);printf("DFS Starting with vertex 'C': ");ret = funMGraph.traverse.dfs(ungraph, 2);printf("BFS Starting with vertex 'D': ");ret = funMGraph.traverse.bfs(ungraph, 3);// 销毁图funMGraph.destroy(graph);funMGraph.destroy(ungraph);printf("\n\e[1;32msystem exited with return code %d\e[0m\n\n", 0);return ret;
}

4.2、测试效果图

  根据上述测试代码,进行编译并测试效果以如下图所示。

图4.1 无向图遍历的过程示意图   

五、简单的总结

  遍历的实质:找到每个顶点的邻接点的过程

  深度优先遍历 :一条道路走到黑的故事,是一个递归的定义,也是一个俄罗斯套娃的过程。

    对于深度优先遍历,在遍历到有多个邻接点的位置的时候,不同的选择将会出现不同的遍历顺序。

    邻接矩阵:如果设定了存储结构为邻接矩阵,那么基于邻接矩阵的遍历的顺序是固定的

  广度优先遍历 :wifi信号式的遍历。

    对于 广度优先遍历,甚至于所有的层序的遍历,都需要借助队列的辅助实现

  
  好啦,废话不多说,总结写作不易,如果你喜欢这篇文章或者对你有用,请动动你发财的小手手帮忙点个赞,当然 关注一波 那就更好了,有任何问题或者想法,欢迎 留言评论。就到这儿了,么么哒(*  ̄3)(ε ̄ *)。

上一篇:数据结构(廿四) – C语言版 – 图 - 图的存储结构 – 十字链表、邻接多重表、 边集数组
下一篇:数据结构(廿六) – C语言版 – 图 - 图的遍历 – 邻接表 - 深度/广度优先遍历/搜索(DFS、BFS)

数据结构(廿五) -- C语言版 -- 图 - 图的遍历 -- 邻接矩阵 - 深度/广度优先遍历/搜索(DFS、BFS)相关推荐

  1. 数据结构(廿六) -- C语言版 -- 图 - 图的遍历 -- 邻接表 - 深度/广度优先遍历/搜索(DFS、BFS)

    内容预览 零.读前说明 一.深度优先遍历 1.1.深度优先的遍历过程 1.2.深度优先的遍历实现代码 二.广度优先遍历 2.1.广度优先的遍历过程 2.2.广度优先的遍历实现代码 三.源码测试效果 3 ...

  2. 数据结构c语言版题库编程,数据结构习题库(c语言版)

    <数据结构习题库(c语言版)>由会员分享,可在线阅读,更多相关<数据结构习题库(c语言版)(104页珍藏版)>请在人人文库网上搜索. 1.wages in arrears. 2 ...

  3. 构建线性表的c语言代码,数据结构严蔚敏C语言版—线性表顺序存储结构(顺序表)C语言实现相关代码...

    1.运行环境 这里说明一下这里所有的C语言代码都是基于code::blocks 20.03编译运行的.当然一些其他集成开发环境应该也是可以的,个人不太喜欢功能太过强大的IDE,因为那同样意味着相关设置 ...

  4. 数据结构严蔚敏C语言版—线性表顺序存储结构(顺序表)C语言实现相关代码

    数据结构严蔚敏C语言版-线性表顺序存储结构(顺序表)C语言实现相关代码 1.运行环境 2.准备工作 1)项目构建 1>新建一个SeqList项目 2>新建两个文件Sources和Heade ...

  5. 《数据结构与算法 C语言版》—— 3.8习题

    本节书摘来自华章出版社<数据结构与算法 C语言版>一 书中的第3章,第3.8节,作者:徐凤生,更多章节内容可以访问云栖社区"华章计算机"公众号查看. 3.8习题 1名 ...

  6. 《数据结构与算法 C语言版》—— 2.5上机实验

    本节书摘来自华章出版社<数据结构与算法 C语言版>一 书中的第2章,第2.5节,作者:徐凤生,更多章节内容可以访问云栖社区"华章计算机"公众号查看. 2.5上机实验 实 ...

  7. 《数据结构与算法 C语言版》—— 2.7习题

    本节书摘来自华章出版社<数据结构与算法 C语言版>一 书中的第2章,第2.7节,作者:徐凤生,更多章节内容可以访问云栖社区"华章计算机"公众号查看. 2.7习题 1描 ...

  8. 数据结构题及c语言版严第七章答案,数据结构第七章习题答案.doc

    数据结构第七章习题答案 PAGE PAGE 9 第7章 <图>习题参考答案 一.单选题(每题1分,共16分) ( C )1. 在一个图中,所有顶点的度数之和等于图的边数的 倍. A.1/2 ...

  9. c语言数据结构算法设计题,数据结构题集(C语言版)算法设计题答案[].doc

    数据结构题集(C语言版)算法设计题答案[].doc 第一章 绪论 1.16 void print_descending(int x,int y,int z)// 按从大到小顺序输出三个数 { scan ...

最新文章

  1. 大数据之hadoop伪集群搭建与MapReduce编程入门
  2. VMWare假造机上装配Ubuntu Linux体例-1
  3. Mac~Terminal终端操作命令、vim操作命令、mac系统快捷键
  4. PMCAFF | 用户体验中4个你不曾知晓的秘密
  5. 第四篇 Python循环
  6. FreeMaker+Xml导出word(含图片)
  7. POJ 3154 Graveyard【多解,数论,贪心】
  8. DSOFramer原有的接口说明
  9. 修改了DNS服务器网速慢,网络速度缓慢怎么办?轻松一键修改DNS设置让网速提升五倍...
  10. 约瑟夫环(简单理解版)
  11. 离散小波变换wavedec matlab,MATLAB小波变换指令及其功能介绍(超级有用)
  12. 正余弦信号的DFT频谱分析
  13. MIRACL大数运算库使用手册
  14. ps.execute()不能当作执行结果反馈,需要用getResultSet或getUpdateCount
  15. 数字示波器CAN节点标定
  16. spring cloud 的getway路由配置(网关配置)
  17. 在线直播系统源码,多图加载成动画的形式如何实现
  18. 计算机中二进制的加法
  19. php抓取天眼查,python用xpath采集天眼查内容,有反爬,zip拼数据
  20. 2015移动安全挑战赛(阿里看雪主办)全程回顾

热门文章

  1. Linux Shell编程语句case
  2. 设计模式(11)代理模式The Proxy Pattern - 1 - 远程代理rmi
  3. pinyin4j汉语拼音库的使用
  4. quartus频率计 时钟设置_Verilog频率计设计
  5. 如果你到了20岁,还没有到25岁
  6. iOS - 发送邮件(E-mail)
  7. DAYU200最新烧录OpenHarmony系统教程
  8. CSS:改变 icon 图片的颜色
  9. linux串口驱动(1)
  10. python计算机程序设计基础篇答案_计算机程序设计(Python)-中国大学mooc-试题题目及答案...