目录 第九章算法2 9.12算法是计算机的灵魂. 2 9.1.3运行时间. 3 9.1.4算法的表示 9.2穷举算法 92.1百元百鸡. 9.2.2旅行商问题 9.3查找算法 9.3.1顺序查找 9.3.2二分查找 9.4排序算法 9.4.1直接插入排序」 9.4.2选择排序 9.4.3冒泡法排序. 9.4.4快速排序 9.45分而治之. 11 9.5贪婪算法 12 9.5.1教室调度问题 .12 9.5.2背包问题 .13 9.5.3旅行商问题. .13 9.6动态规划. 14 9.61背包问题. 15 9.6.2旅行商问题 .18 9.7回溯法. 9.8趣味算法举例. .21 9.8.1兔子产仔问题. .21 9.8.2谁在说谎 .22 9.8.3有趣的数字 .23
目录 第九章 算法. 2 9.1.2 算法是计算机的灵魂. 2 9.1.3 运行时间 . 3 9.1.4 算法的表示. 3 9.2 穷举算法. 4 9.2.1 百元百鸡 . 4 9.2.2 旅行商问题. 5 9.3 查找算法. 6 9.3.1 顺序查找 . 6 9.3.2 二分查找 . 6 9.4 排序算法. 7 9.4.1 直接插入排序. 7 9.4.2 选择排序 . 8 9.4.3 冒泡法排序. 9 9.4.4 快速排序 . 9 9.4.5 分而治之 . 11 9.5 贪婪算法. 12 9.5.1 教室调度问题. 12 9.5.2 背包问题 . 13 9.5.3 旅行商问题. 13 9.6 动态规划. 14 9.6.1 背包问题 . 15 9.6.2 旅行商问题. 18 9.7 回溯法 . 19 9.8 趣味算法举例. 21 9.8.1 兔子产仔问题. 21 9.8.2 谁在说谎 . 22 9.8.3 有趣的数字. 23
第九章算法 9.1算法简介 9.1.1引言 算法这个名词听上去很抽象,让人联想不到任何具体的物体。甚至你会觉得算法与自己的生活并无太多关联, 它只是为计算机专业人品成者科学家服各的。这样的相法直是大错特错了。 事实上,算法无处不在。比如你去食堂买饭会选择一个较短的队列,有的人则可能选择一个推进速度快的队列 再比如每天早上起床,你可能先读一会儿书再去吃早餐,而别的人可能先去吃早餐然后再看书。所有这些行为都是 算法的体现。运行这些算法并不一定是刻意的出现在你的意识中,也通常不会经过精心设计,但它确实存在。 1962年春天,宝洁公司发起了一场广告宣传活动,在应用数学家中引起了不小的反响.活动的重头戏是项竞赛, 奖金高达1万美元,在当时足以买下一座房子。参赛规则如下: 假设Tooy和Muldoon打算开车环游美国,地图上用点标出的33个地点都要游览,并且要走满足条件的路线 中最短的一条。请你为他们规划一条旅行路线,以伊利诺伊州的芝加哥市为旅途的起点和终点,依次用线连接各地 点,并使得总里程最短。 这就是著名的旅行商问题。这个问题容易求解吗?你可能会首先想到一个办法:检验每一条可能的路线,将最 短的一条记录下来寄给宝洁公司,然后坐等1万美元寄回家!这个策略简单又完美,但是,有一个潜在的困难:路 线总数是我们能够在有限的时间内检验完成的吗? 我们用A 21 7 为这33个城市的编号,即A代表芝加哥市。由于旅行的起点和终点相同,每个排列对应 的路线实际都是一条环形路线,城市A为每条路线的起点,则第二座城市有32种可能选择,第三座城市有31种可 能选择,以此类推。最后,可以得到环形路线的总数为32×31×30××3×2×1。这个数字记作32,读作32的阶 乘。这个数字是多少呢? 263130836933693530167218012160000000 这是无法完成的任务: 这里列出的只是解决旅行商问愿的其中一种方法。在后面的章节中,将会学习到更快捷的解决策略。可以这样 说,在算法的世界里,没有最快,只有更快! 9.1.2算法是计算机的灵魂 说到这里,我们来了解本章的第一个概念,什么是算法? 最常用的一个说法是:算法是解决间题的有穷步骤的描述 从这个定义中我们公现7洗是用类解决同题的也藏是说宜法发现总是由相的问要配动的,就拿推序米说 我们的生活中处处可 见次序 工作评优等 ,大家首先想到 方法是 概就是插入法了。换一个通俗易懂的说法,就是人们打牌时整理手中扑克牌的算法。这个算法的效率会随着数据量 的增加而大幅度降低,于是人们又发明了其他更快速的排序方法,这部分内容将在9.4节中详细介绍。 一个个新的算法都是为了解决前面算法遗留的问题而产生,新的算法提高了效率,同时也会有意或无意的引入 了新的问题,这应该就是算法永远不会停止发展的一个原因吧」 回到算法的定 我们还可以看出算法是求解问恩的步骤。由于计算机的计算操作完全是一步一步进行的,因 此算法的特征用于计算机是再合适不过了。计算机无法独立于算法而存在,如果说操作系统是计算机的心智,那么 算法就是计算机的灵魂。 举个例子,要计算1到100的累加和。有人先计算1+2,再将结果加上3,然后再将结果加上4,以此类推, 一直加到100。而有的人会将1+100,然后乘以50即可同样得出答案。这两种方法的时间复杂度是不一样的,很 显然第二种方法更快的得到答案。由于现代计算机运行速度都非常快,对于运行这样一点小运算来说不会有明显的 时间差异。但是在大型软件项目中,程序的执行效率高低就显得至关重要,会直接影响到用户体验。因此,合理的
第九章 算法 9.1 算法简介 9.1.1 引言 算法这个名词听上去很抽象,让人联想不到任何具体的物体。甚至你会觉得算法与自己的生活并无太多关联, 它只是为计算机专业人员或者科学家服务的。这样的想法真是大错特错了。 事实上,算法无处不在。比如你去食堂买饭会选择一个较短的队列,有的人则可能选择一个推进速度快的队列。 再比如每天早上起床,你可能先读一会儿书再去吃早餐,而别的人可能先去吃早餐然后再看书。所有这些行为都是 算法的体现。运行这些算法并不一定是刻意的出现在你的意识中,也通常不会经过精心设计,但它确实存在。 1962 年春天,宝洁公司发起了一场广告宣传活动,在应用数学家中引起了不小的反响。活动的重头戏是项竞赛, 奖金高达 1 万美元,在当时足以买下一座房子。参赛规则如下: 假设 Toody 和 Muldoon 打算开车环游美国,地图上用点标出的 33 个地点都要游览,并且要走满足条件的路线 中最短的一条。请你为他们规划一条旅行路线,以伊利诺伊州的芝加哥市为旅途的起点和终点,依次用线连接各地 点,并使得总里程最短。 这就是著名的旅行商问题。这个问题容易求解吗?你可能会首先想到一个办法:检验每一条可能的路线,将最 短的一条记录下来寄给宝洁公司,然后坐等 1 万美元寄回家!这个策略简单又完美,但是,有一个潜在的困难:路 线总数是我们能够在有限的时间内检验完成的吗? 我们用 A~Z 及 1~7 作为这 33 个城市的编号,即 A 代表芝加哥市。由于旅行的起点和终点相同,每个排列对应 的路线实际都是一条环形路线,城市 A 为每条路线的起点,则第二座城市有 32 种可能选择,第三座城市有 31 种可 能选择,以此类推。最后,可以得到环形路线的总数为 32 x 31 x 30 x . x 3 x 2 x 1。这个数字记作 32!,读作 32 的阶 乘。这个数字是多少呢? 263 130 836 933 693 530 167 218 012 160 000 000 这是无法完成的任务! 这里列出的只是解决旅行商问题的其中一种方法。在后面的章节中,将会学习到更快捷的解决策略。可以这样 说,在算法的世界里,没有最快,只有更快! 9.1.2 算法是计算机的灵魂 说到这里,我们来了解本章的第一个概念,什么是算法? 最常用的一个说法是:算法是解决问题的有穷步骤的描述。 从这个定义中我们发现算法是用来解决问题的,也就是说算法的发现总是由相关的问题驱动的。就拿排序来说, 我们的生活中处处可见次序,比如考试排名,工作评优等。大家首先想到的排序方法是什么呢?这个问题的答案大 概就是插入法了。换一个通俗易懂的说法,就是人们打牌时整理手中扑克牌的算法。这个算法的效率会随着数据量 的增加而大幅度降低,于是人们又发明了其他更快速的排序方法,这部分内容将在 9.4 节中详细介绍。 一个个新的算法都是为了解决前面算法遗留的问题而产生,新的算法提高了效率,同时也会有意或无意的引入 了新的问题,这应该就是算法永远不会停止发展的一个原因吧。 回到算法的定义,我们还可以看出算法是求解问题的步骤。由于计算机的计算操作完全是一步一步进行的,因 此算法的特征用于计算机是再合适不过了。计算机无法独立于算法而存在,如果说操作系统是计算机的心智,那么 算法就是计算机的灵魂。 举个例子,要计算 1 到 100 的累加和。有人先计算 1+ 2 ,再将结果加上 3,然后再将结果加上 4,以此类推, 一直加到 100。而有的人会将 1 + 100,然后乘以 50 即可同样得出答案。这两种方法的时间复杂度是不一样的,很 显然第二种方法更快的得到答案。由于现代计算机运行速度都非常快,对于运行这样一点小运算来说不会有明显的 时间差异。但是在大型软件项目中,程序的执行效率高低就显得至关重要,会直接影响到用户体验。因此,合理的
算法设计是非常必要的。 9.1.3运行时间 上面讲到的1累加到100的例子中.我们提到了“时间复杂度”这个概今,这一节我们就详细的了解一下有关 算法的运行时间问题。 假设在字典中查找一个以M开头的单词,你可以从头开始翻页,直到进入以M开头的部分。但是我可以肯定 你不会这样做的。你会从中间开始,因为你知道M开头的单词在字典的中间位置附近。这两种查找的方法就存在 一个运行时间上的差异。一般而言,效率越高的算法就越能在最大限度上减少运行时间或占用的空间。 再比如有100个数字,己经按照从小到大的顺序排列好了。当我们需要找出其中某一个数字时,可以从头开始 比对,最差的情况是需要比对100次。那么我们也试着从中间开始,也就是说,先比对这组数列中间的那个数字是 不是我们需要的,如果不是,就判 一下这个中间的数字比我们需要的数字大了还是小了?如果大,就从中间数气 的右侧列表中继续查找,如果小,就从中间数字的左侧列表中继续查找。对100个数字而言,这种方法最多需要比 对7次。如果列表中包含40亿个数字,最多也只需要比对32次。厉害吧? 对长度为n的列表,使用逐个比对的方法就需要执行n次操作,这个运行时间我们记做0(),也叫线性时间。 如果使用从中间折半的方法就仅仅需要执行1ogn次操作,记做0(gn),也叫对数时间。这种表示法不是以秒为单 位的,它告诉我们算 到底有多快,也就是指出了算法运行时间的增速。Og)此O)快,当需要搜索的元素越多 时,前者比后者快的越多 9.1.4算法的表示 不管用什么形式描述一个解决问题的过程,只要它逻辑清晰,结果正确,哪怕只是在脑子里构思的,也都是好 算法。对于复杂问题的解决,单靠脑子构思可能就不够了,这就需要借助一些工具来表示解决过程。算法的表示形 式很多,下面列出几种常用的方式以供参考。 1.自然语言 知道把大象放冰箱一共分几步吗?一,把冰箱门打开。二,把大象塞进去。三,把冰箱门关上。这就是用自然 语言描述的算法。 下面我们来看一个真正的算法:欧几里得算法。古希腊数学家欧几里得(公元前3世纪)曾在他的著作中描述 过求两个数的最大公因子的过程。这里用到了一个定理,即两个整数的最大公约数等于其中较小的那个数和两数相 除余数的最大公约数。 算法9.1 ()输入任意两个整数m和n,使得m>n: 2)计算:m除以n得余数r: (3)若r=0,则n为求得的最大公约数,算法结束:否则执行(4: 4)将m的值修改为 将n的值修政为r,再重复执行(2) 这就是用自然语言描述的算法。当然,除了这种很简单的问题,一般是不使用自然语言来描述计算机算法的, 因为自然语言存在二义性,表达起来不精准。 2.流程图 流程图是一种使用最普遍的算法表示方法,它用一组标准图形符号来描述算法。它的主要优点是简单直观, 便于理解。图9.1给出了流程图的图元表示方法。前面介绍的欧几里得算法也可以用流程图来表示,如图9.2所示
算法设计是非常必要的。 9.1.3 运行时间 上面讲到的 1 累加到 100 的例子中,我们提到了“时间复杂度”这个概念,这一节我们就详细的了解一下有关 算法的运行时间问题。 假设在字典中查找一个以 M 开头的单词,你可以从头开始翻页,直到进入以 M 开头的部分。但是我可以肯定, 你不会这样做的。你会从中间开始,因为你知道 M 开头的单词在字典的中间位置附近。这两种查找的方法就存在 一个运行时间上的差异。一般而言,效率越高的算法就越能在最大限度上减少运行时间或占用的空间。 再比如有 100 个数字,已经按照从小到大的顺序排列好了。当我们需要找出其中某一个数字时,可以从头开始 比对,最差的情况是需要比对 100 次。那么我们也试着从中间开始,也就是说,先比对这组数列中间的那个数字是 不是我们需要的,如果不是,就判断一下这个中间的数字比我们需要的数字大了还是小了?如果大,就从中间数字 的右侧列表中继续查找,如果小,就从中间数字的左侧列表中继续查找。对 100 个数字而言,这种方法最多需要比 对 7 次。如果列表中包含 40 亿个数字,最多也只需要比对 32 次。厉害吧? 对长度为 n 的列表,使用逐个比对的方法就需要执行 n 次操作,这个运行时间我们记做 O(n),也叫线性时间。 如果使用从中间折半的方法就仅仅需要执行 log n 次操作,记做 O(log n),也叫对数时间。这种表示法不是以秒为单 位的,它告诉我们算法到底有多快,也就是指出了算法运行时间的增速。O(log n)比 O(n)快,当需要搜索的元素越多 时,前者比后者快的越多。 9.1.4 算法的表示 不管用什么形式描述一个解决问题的过程,只要它逻辑清晰,结果正确,哪怕只是在脑子里构思的,也都是好 算法。对于复杂问题的解决,单靠脑子构思可能就不够了,这就需要借助一些工具来表示解决过程。算法的表示形 式很多,下面列出几种常用的方式以供参考。 1. 自然语言 知道把大象放冰箱一共分几步吗?一,把冰箱门打开。二,把大象塞进去。三,把冰箱门关上。这就是用自然 语言描述的算法。 下面我们来看一个真正的算法:欧几里得算法。古希腊数学家欧几里得(公元前 3 世纪)曾在他的著作中描述 过求两个数的最大公因子的过程。这里用到了一个定理,即两个整数的最大公约数等于其中较小的那个数和两数相 除余数的最大公约数。 算法 9.1: (1) 输入任意两个整数 m 和 n,使得 m>n; (2) 计算:m 除以 n 得余数 r; (3) 若 r=0,则 n 为求得的最大公约数,算法结束;否则执行(4); (4) 将 m 的值修改为 n,将 n 的值修改为 r,再重复执行(2)。 这就是用自然语言描述的算法。当然,除了这种很简单的问题,一般是不使用自然语言来描述计算机算法的, 因为自然语言存在二义性,表达起来不精准。 2. 流程图 流程图是一种使用最普遍的算法表示方法,它用一组标准图形符号来描述算法。它的主要优点是简单直观, 便于理解。图 9.1 给出了流程图的图元表示方法。前面介绍的欧几里得算法也可以用流程图来表示,如图 9.2 所示
开始 起止框 输入m和m rm%n 输入输出框 条件判断 处理语句框 流程线 结束门 图9.1流程图的图元 图9.2欧几里得算法流程图 9.2穷举算法 穷举算法是一种最为直接、实现最简单、最耗时的一种算法思想。它的基本思想是:在可能的解空间中穷举出 每一种可能的解,并对每一个可能解进行判断,从中筛选出问题的答案。 9.2.1百元百鸡 公元5世纪末,我国古代数学家张丘建在他编写的《算经》中提出这样一个问题:“鸡 一值钱五:鸡母一值 钱三:鸡雏三值钱一。百钱买百鸡,问鸡翁、母、雏各几何?”意思是说,公鸡每只5元,母鸡每只3元,3只小 鸡1元,用100元钱买100只鸡,求公鸡、母鸡和小鸡各多少只。这里设每种鸡至少一只。 算法分析: 我们假设公鸡、母鸡、小鸡的只数分别为x、y2。我们以三种鸡总数(x+y+z)和买鸡的总钱数(5x+3y+z3) 都等于100为判定条件,穷举出各种鸡的只数 算法9.2: 公鸡数x=1to100 /公鸡数从1枚举到100 母鸡数y1to100 /母鸡数从1枚举到100 小鸡数=1to100 ∥小鸡数从1枚举到100 如果ky4z10并且5x43y+z/3100那么 输出×、y、z的但 这个算法对×、yz都从1开始枚举到100,计算机需要做100*100*100次比较操作,虽然这点“规模”对计 算机来说不算什么,可以很快完成计算,但是,这个算法仍然后很大的改进空间。 因为公鸡5元钱一只,所以100元最多买20只公鸡。同理,100元最多买33只母鸡,而小鸡的数目是100-xy, 因此没必要枚举。改进后的算法如下: 算法93: 公鸡数x=1to20 公鸡数从1枚举到20 母鸡数y1to33/∥母鸡数从1枚举到33 小鸡数2=100-xy 如果(5*x+3y+z/3=100那么 出 、z的值 改进后的算法让计算机执行操作的次数减少到大约660次。这个程度的优化在实际运行中是感觉不到的。但是 算法优化的思想还请读者记在心里
图 9.1 流程图的图元 图 9.2 欧几里得算法流程图 9.2 穷举算法 穷举算法是一种最为直接、实现最简单、最耗时的一种算法思想。它的基本思想是:在可能的解空间中穷举出 每一种可能的解,并对每一个可能解进行判断,从中筛选出问题的答案。 9.2.1 百元百鸡 公元 5 世纪末,我国古代数学家张丘建在他编写的《算经》中提出这样一个问题:“鸡翁一值钱五;鸡母一值 钱三;鸡雏三值钱一。百钱买百鸡,问鸡翁、母、雏各几何?”意思是说,公鸡每只 5 元,母鸡每只 3 元,3 只小 鸡 1 元,用 100 元钱买 100 只鸡,求公鸡、母鸡和小鸡各多少只。这里设每种鸡至少一只。 算法分析: 我们假设公鸡、母鸡、小鸡的只数分别为 x、y、z。我们以三种鸡总数(x+y+z)和买鸡的总钱数(5*x+3*y+z/3) 都等于 100 为判定条件,穷举出各种鸡的只数。 算法 9.2: 公鸡数 x=1 to 100 //公鸡数从 1 枚举到 100 母鸡数 y=1 to 100 //母鸡数从 1 枚举到 100 小鸡数 z=1 to 100 //小鸡数从 1 枚举到 100 如果(x+y+z)=100 并且 (5*x+3*y+z/3)=100 那么 输出 x、y、z 的值 这个算法对 x、y、z 都从 1 开始枚举到 100,计算机需要做 100*100*100 次比较操作,虽然这点“规模”对计 算机来说不算什么,可以很快完成计算,但是,这个算法仍然后很大的改进空间。 因为公鸡 5 元钱一只,所以 100 元最多买 20 只公鸡。同理,100 元最多买 33 只母鸡,而小鸡的数目是 100-x-y, 因此没必要枚举。改进后的算法如下: 算法 9.3: 公鸡数 x=1 to 20 //公鸡数从 1 枚举到 20 母鸡数 y=1 to 33 //母鸡数从 1 枚举到 33 小鸡数 z=100-x-y 如果 (5*x+3*y+z/3)=100 那么 输出 x、y、z 的值 改进后的算法让计算机执行操作的次数减少到大约 660 次。这个程度的优化在实际运行中是感觉不到的。但是 算法优化的思想还请读者记在心里
9.2.2旅行商问题 旅行商问愿是一个经典问题。商人要到若干个城市销售商品,己知各城市之间的距离,要求商人选择出发的城 市及旅行线路,使每一个城市仅经过一次,最后回到出发城市 ,且总路程最短 采用穷举法逐一计算出每一条线路的距离,从中找到距离最短的路线就是问题的解。我们以图9.3所示的5个 城市为例来讲解这个算法,并要求商人从A城市出发,来求得最短路径。这时只需考虑用A开头的排列即可,一共 有4!种可能。 10 B 10 A 10 D 图9.3旅行商问题 算法9.4: 城市数目为5,从A城市出发,列出所有可能的路线和距离 ABCED ADBEC 45 ABDCE 名 ADCBE 45 ABDEC ADCEB 41 ABECD ADECB 3 ABEDC ADEBC ACBDE AEBCD 45 ACBED AEBDC 44 ACDEB AECBD 49 ACDBE ACEBD 1445 14 ACEDB 38 AEDCB 38 在上述路线中找出最小值34,距离为34的路线有两条:ABEDCA和ACDEBA。 这是仅考虑从A城市出发的情况,如果从任意城市出发,寻找一条经过5个城市的最短路径,就会有5!=120 种不同的排列方式。如果涉及到6个城市,就需要执行6:720次操作,推而广之,涉及n个城市时,就需要执行 n!次操作。假如城市数=19,则有19:条路线,如果每秒钟列出1亿条路线,还需要超过114年才能全部列出。 如果涉及到超过100个城市,这根本就是无法完成的任务,等计算出结果,太阳都没了! 所以穷举法在解决旅行商问题时,适合涉及城市较少的情况。可以说,针对这个问题目前还没有更巧妙的算法。 我们在后面章节中介绍的贪婪法也只是找出近似的答案罢了。 综上所述,穷举法也被称为童力法,它可能是唯一一种几乎什么问题都能解决的一般性方法。它的优点是思路 简单,程序编写和调试方便。在解决 些规模不是很大的问题时 它也是 种很好的选择。但是, 穷举法是牺牲 时间来保证解的全面性,因而运行效率低下是它的缺点。随着计算机硬件性能的不断提高,CU的运算速度越来越 快,穷举法已经不再是最低等、最原始的无奈之选,而是越来越被人们所重视
9.2.2 旅行商问题 旅行商问题是一个经典问题。商人要到若干个城市销售商品,已知各城市之间的距离,要求商人选择出发的城 市及旅行线路,使每一个城市仅经过一次,最后回到出发城市,且总路程最短。 采用穷举法逐一计算出每一条线路的距离,从中找到距离最短的路线就是问题的解。我们以图 9.3 所示的 5 个 城市为例来讲解这个算法,并要求商人从 A 城市出发,来求得最短路径。这时只需考虑用 A 开头的排列即可,一共 有 4!种可能。 图 9.3 旅行商问题 算法 9.4: 城市数目为 5,从 A 城市出发,列出所有可能的路线和距离 路线 距离 路线 距离 ABCDE 38 ADBCE 49 ABCED 40 ADBEC 45 ABDCE 44 ADCBE 45 ABDEC 38 ADCEB 41 ABECD 41 ADECB 39 ABEDC 34 ADEBC 39 ACBDE 42 AEBCD 45 ACBED 39 AEBDC 44 ACDEB 34 AECBD 49 ACDBE 44 AECDB 44 ACEBD 45 AEDBC 42 ACEDB 38 AEDCB 38 在上述路线中找出最小值 34,距离为 34 的路线有两条:ABEDCA 和 ACDEBA。 这是仅考虑从 A 城市出发的情况,如果从任意城市出发,寻找一条经过 5 个城市的最短路径,就会有 5!=120 种不同的排列方式。如果涉及到 6 个城市,就需要执行 6!=720 次操作,推而广之,涉及 n 个城市时,就需要执行 n!次操作。假如城市数 n=19,则有 19!条路线,如果每秒钟列出 1 亿条路线,还需要超过 114 年才能全部列出。 如果涉及到超过 100 个城市,这根本就是无法完成的任务,等计算出结果,太阳都没了! 所以穷举法在解决旅行商问题时,适合涉及城市较少的情况。可以说,针对这个问题目前还没有更巧妙的算法。 我们在后面章节中介绍的贪婪法也只是找出近似的答案罢了。 综上所述,穷举法也被称为蛮力法,它可能是唯一一种几乎什么问题都能解决的一般性方法。它的优点是思路 简单,程序编写和调试方便。在解决一些规模不是很大的问题时,它也是一种很好的选择。但是,穷举法是牺牲了 时间来保证解的全面性,因而运行效率低下是它的缺点。随着计算机硬件性能的不断提高,CPU 的运算速度越来越 快,穷举法已经不再是最低等、最原始的无奈之选,而是越来越被人们所重视