一、实验目的
- 掌握选择排序、冒泡排序、合并排序、快速排序、插入排序算法原理
- 掌握不同排序算法时间效率的经验分析方法,验证理论分析与经验分析的一致性。
二、实验概述
排序问题要求我们按照升序排列给定列表中的数据项,目前为止,已有多种排序算法提出。本实验要求掌握选择排序、冒泡排序、合并排序、快速排序、插入排序算法原理,并进行代码实现。通过对大量样本的测试结果,统计不同排序算法的时间效率与输入规模的关系,通过经验分析方法,展示不同排序算法的时间复杂度,并与理论分析的基本运算次数做比较,验证理论分析结论的正确性。
三、实验步骤与结果
1.五个排序算法的设计思路,部分伪代码,演示图,时间复杂度分析,实际运行和理论值图表及图表分析。
插入排序
设计思路
从第二个位置开始将其作为插入数字,将插入数字之前已经排序好的数字从后到前与其进行比较并后移,直到达到数组首位置或出现一个数字不大于插入数字。
伪代码:
时间复杂度分析
在数据长度为n的数组上。假设每个语句的代价为c1-c7,每个语句的运行次数如上图所示,其中tj表示外层循环为j时内层while循环执行的次数。
求得运行的总时间为:
在最好情况下,整个数组已经排好序,在第4行的while循环只需要执行1次便可退出,因为循环条件不会满足,第5,6行的代码不会运行,直接跳到第7行代码,因此最好情况的运行时间为:
在最坏情况下,整个数组按逆序排序,每次第4行的while循环都需要执行到i=1时才会停止,所需时间为
而第5,6行的赋值也需要运行到i=2才会停止,运行时间为 。因此运行时间为
将上式简化成
即T是n的二次函数。平均情况和最坏情况一样差,因此平均情况下的运行时间仍然为n的二次函数。
插入排序演示
我们先思考简单的问题:如果现在有一个已经排序完毕的数组A,现在给定一个新的数b,要求将b插到原来已经排序好的数组A中,使得新数组仍然有序。最简单的想法就是对A从后到前一个位置一个位置往后边挪,给b腾出位置,直到找到一个位置pos的数字不大于新插入的b,将b放在位置pos之后,这样插入b之后的数组仍然是有序的。
A数字从后往前和b比较并后挪,直到A中数字不大于b:
插入b后得到新数组:
而实际的问题是,我们并没有一个排完序的数组,而只有一个完全乱序的长度为n的数组要进行排序。
类似上面的思想进行以下操作:
用下图对伪代码进行演示。数组第一个数字只有一个,必然是有序的,因此第二个数字作为插入数。插入数9比第一个数字4小,那就把4挪到第二个位置,把9插到数组起始;于是获得了长度为2的有序数组[4,9];再将第三个数字8作为插入数,从后到前将9与8比较,9比8大则后移,4比8小则停止比较,将8插入。最后将第7个数字作为插入数,整个数组排序完毕。
插入排序示意图:
运行结果
插入排序不同数据规模下理论值和实验值及误差:
图表解释与分析:可以看出实验值略小于理论值,且运行规模越大,实验值比理论值小的误差越大,可能因为实验规模大,while循环执行次数相比于小规模达到n的二次方的发生次数减少。但误差仍然控制在-10%-2%内,可见理论计算的复杂度
O
(
n
2
)
O(n^2)
O(n2)较有可信度。
冒泡排序
设计思路
更大的数字像水里的大泡泡一样先冒出来。把数组中未排序的大的数字往后移动,移动的过程就是和他相邻的元素交换,直到到达已排序数字的最前端。
时间复杂度分析
为了简化问题,我们不考虑在有序的情况下设置fl以退出循环,伪代码简化为上图。冒泡排序运行时间如下,其中ti表示第i趟排序交换次数。
在最坏的情况下即序列逆序,第i趟排序需要交换的次数为n-i,此时
可简化为
因此冒泡排序的运行时间为数据规模n的二次函数。
冒泡排序演示
先进行第一趟排序,即从数组首位置开始,逐渐将更大的数交换到数组的末位置。第1个数字恰好是最大的数字,因此它每次都会和相邻数字进行交换,最后到达末位置;接下来第二趟排序将第二大的数字8交换到倒数第二个位置;接下来第三趟排序发现并没有任何一个数字发生交换,这意味着数组中不存在逆序对了,即整个数组都有序了,退出程序。
图表 5 冒泡排序演示图
运行结果
图表解释和分析:由图表可以看出,实验值高于理论值,且规模越大误差越大,可能是因为冒泡排序的常数较大,且第一个作为基准的规模为10000的运行时间偏低。