实验五内部排序.docx
- 文档编号:30655849
- 上传时间:2023-08-19
- 格式:DOCX
- 页数:11
- 大小:21.90KB
实验五内部排序.docx
《实验五内部排序.docx》由会员分享,可在线阅读,更多相关《实验五内部排序.docx(11页珍藏版)》请在冰豆网上搜索。
实验五内部排序
实验五各种内部排序算法的实现及性能比较
5.1背景知识
排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个按关键字有序的序列。
学习和研究各种排序方法是计算机领域的重要课题之一。
从第6章的搜索(查找)算法中可以看出,顺序表的查找时间复杂度为O(n),而建立在有序表基础上的折半查找的时间复杂度为O(log2n)。
内部排序是指待排序列完全存放在内存中所进行的排序过程,适合不太大的元素序列。
1.排序的定义
参见教科书P18710.1节基本概念。
2.排序方法
内部排序的方法很多,但就其全面性而言,很难提出一种被认为是最好的方法,每一种方法都有其各自的优缺点,适合在不同的环境(如记录的初始排列状态等)下使用。
如果按排序过程中依据的不同原则对内部排序方法进行分类,则排序大致可分为插入排序、交换排序、选择排序、归并排序、基数排序等5类;如果按内部排序过程中所需要的工作量(时间复杂度)来区分,则可分为以下3类:
1)简单的排序方法,时间复杂度为O(n2)的排序方法,例如直接插入、直接选择和冒泡排序;这些排序方法非常容易理解和实现,但是它们的效率并不高,这在数据集比较大时尤其明显。
2)先进的排序方法,时间复杂度为O(nlogn)的排序方法,例如快速排序、堆排序和归并排序。
3)基数排序:
这是一种并非基于比较的排序,它借助多关键字的排序思想,通过“分配”和“收集”的方法来完成排序,时间复杂度为O(n)。
通常(除了上面的基数排序),在排序过程中需要进行下列两种基本操作:
1)比较两个记录关键字的大小
2)将记录从一个位置移动至另一个位置。
前一个操作对大多数排序方法来说是必要的,而后一个操作可以通过改变记录的存储方式来予以避免。
可以从不同的角度描述排序算法的性能,主要有如下几个:
1)排序算法的时间复杂度和空间复杂度
2)排序算法的稳定性
3)排序算法的简单性(代码编写的难易程度)
3.排序表数据结构
排序表是待排序记录的集合。
在本次实验中,待排序的一组记录存放在地址连续的一组存储单元上。
类似于线性表的顺序存储结构。
每个排序算法的实现都采用模板函数的方式,把实验中所有的排序算法都放在SortAlgorithm.h文件中。
5.2实验目的
1)掌握常见的排序方法和实现排序的算法。
2)能根据待排序记录的关键字的初始状态选择合适的排序方法。
3)学会分析算法的基本方法,并计算算法的时间复杂度。
4)学会比较排序算法的性能。
5.3工具/准备工作
在开始实验前,请回顾教科书中有关排序的定义及常见的内部排序算法。
需要一台计算机,其中装有MicrosoftVisualC++6.0开发环境或DevCpp集成开发环境。
5.4实验内容与步骤
5.4.1基础知识
1)三种计算时间为O(n2)的排序方法是:
()、()和()。
2)对于快速排序是使用()算法设计策略开发的一种高效的交换排序。
3)()排序方法不是基于比较的排序方法。
4)目前以比较为基础的内部排序的时间复杂度T(n)的范围是( ),其比较次数与待排序的记录的初始状态无关的是( )。
5)对于部分有序列表,简单选择排序算法的性能比对于随机列表的性能高,这句话是否正确?
6)对于部分有序列表,直接插入排序和冒泡排序算法的性能比对于随机列表的性能高,这句话是否正确?
7)如果排序处理的是对象列表,这些对象表示包含着多个不同类型的信息项的记录,例如学生对象。
这时,排序是基于这些记录中的某个关键域进行的。
在排序过程中如果移动整个数据对象,所需要的时间将长得令人无法忍受。
对于这些由大记录或对象组成的列表,可以使用间接排序,即使用索引表来存放对象的位置,并移动对象的索引(即下标或位置)而非对象本身。
8)数组(顺序存储结构)能够用来有效地存储堆,因为堆是一种()二叉树。
9)C++标准库
10)根据数据项存放在内存还是外存,排序算法能够分为()和()两种。
5.4.2各种排序算法的实现
1、选择排序
选择排序的基本思想是对列表或者列表的一部分进行多次扫描,每次选出一个元素将其放到正确位置。
1)简单选择排序
每次选择总是从未排序序列中选出最小元素并把其放在未排序序列的起始位置上。
每选择一次会使未排序序列元素个数减一,已排序序列元素个数增一。
共需要n-1趟的选择。
初始时,未排序序列元素个数为n。
简单选择排序算法描述请参考教科书P188程序10.1。
该算法的最好、最坏和平均情况的时间复杂度为O(n2)。
且为不稳定的排序方法。
2)堆排序
教科书5.6节介绍了堆,它是实现优先权队列的有效的数据结构。
堆还可用于排序,这就是堆排序。
可以直接使用优先权队列实现排序,先将序列中的每个元素入优先权队列,之后再依次出队列,得到的结果即是有序序列。
(教科书上的优先权队列PrioQueue是小顶堆,STL的适配器类priority_queue是大顶堆。
)
同学们可以自己编程序进行测试。
有关STL中的栈和队列的相关内容请参考:
堆排序的基本思想如下:
第一步:
为了得到非递减的排序序列,首先构造一个大顶堆。
第二步:
for(i=n-1;i>0;i--)//每循环一次进行一个根叶交换
1)交换a[i]和a[0]
2)使用向下调整算法,把a[0]~a[i-1]重新调整成大顶堆
具体的算法实现请参考教科书P196的程序10.7和程序10.8。
该算法的最好、最坏和平均情况的时间复杂度为O(nlog2n)。
且为不稳定的排序方法。
3)树排序
我们可以利用二叉查找树完成排序,把这种排序方法称为树排序。
只需要把列表元素插入到初始为空的二叉查找树BSTree中,然后使用中序遍历来把元素复制到列表中。
算法描述如下:
template
voidTreeSort(TA[],intn)
{
BSTree
for(inti=0;i tree.Insert(A[i]); inti=0; tree.InOrder(int&i)//对中序遍历进行适当修改, //对某个结点访问也就是把结点的值赋给A[i]后再i++ } 同学们可以自己编程序进行测试。 2、插入排序 1)直接插入排序 直接插入排序的思想非常简单,将序列中第一个元素作为一个有序序列,然后将剩下的n-1个元素按关键字大小依次插入该有序序列,每插入一个元素后依然保持该序列有序,经过n-1趟排序后即成为有序序列。 直接插入排序算法请参考P189的程序10.2。 直接插入排序算法的最坏情况出现在当列表按相反方向排列的时候。 时间复杂为O(n2)。 最好情况为序列已经有序,时间复杂度为O(n)。 该排序算法是稳定的排序方法。 对于短列表(包含20个左右的元素)和部分有序列表,在我们讨论的所有排序算法中,实验结果表明直接插入排序的性能最佳。 2)折半插入排序 如果我们使用折半查找而不是顺序查找来在排好序的子列表a[0],a[1],…,a[i-1]中找出下一项a[i]应该插入的位置。 那么该算法称为折半插入排序。 请编写折半插入排序算法并编程实现: template voidBInsertSort(intA[],intn) { } 3)Shell排序 对于短列表和部分有序列表,它使用插入排序的性能较好。 希尔排序(取名于DonaldShell)是一种插入排序,它使用直接插入排序对短列表排序,并产生更长的部分有序表。 具体地,它使用直接插入排序来对相互之间“间隔”为d的元素进行排序,首先处理a[0],a[0+d],a[0+2d],…,然后处理a[1],a[1+d],a[1+2d],…,然后处理a[2],a[2+d],a[2+2d],…,以此类推。 然后减少间隔d的值重复以上处理过程,直到间隔d变成1为止,此时执行直接插入排序之后列表就被排好了。 请编写Shell排序算法并编程实现,其中d的初值为 ,且每回d的值减3,这里k是某个整数。 template voidShellSort(intA[],intn) { } 3、交换排序 交换排序的基本思想是系统化地交换那些不符合次序的元素对,直到列表中不存在这种元素对为止,此时列表就排好了。 冒泡排序和快速排序是典型的交换排序。 1)冒泡排序 冒泡排序通过交换相邻的两个元素来实现排序。 算法请参考教科书P191的程序10.3。 注意程序中last的使用,它记录每趟过程中最后一次交换元素的位置,如果一趟起泡过程中,last仍为0,则排序算法结束。 冒泡排序最好情况(已有序)下只需要进行一趟排序,(n-1)次比较,因此最好情况下时间复杂度为O(n);最坏情况(倒序有序),第i趟比较(n-i)次,因此最坏情况下时间复杂度为O(n2)。 冒泡排序是稳定的排序方法。 2)双向冒泡排序 双向冒泡排序是冒泡排序的一个变种,它交替地从左到右和从右到左扫描未排好序列的子列表。 在从左到右扫描的时候,如果ai>ai+1,则交换ai和ai+1,使得较大的元素移向子列表的右端。 在从右到左地扫描的时候,如果ai 扫描进行到没有元素被交换为止。 编写一个算法来实现双向冒泡排序,算法写在下面,并实现该算法。 可在程序10.3的基础上修改。 template voidBiBubbleSort(intA[],intn)//双向冒泡排序 { } 3)快速排序 在冒泡排序中,每轮都比较并在需要时交换相邻的元素,这意味着有可能需要很多的交换才能把一个元素移到其正确的位置。 由C.A.R.Hoare提出并由RobertSedgewick作了明显改进的一种交换算法,就是快速排序算法,比冒泡排序的效率高很多,原因在于进行交换的元素相隔很远,使得我们需要较少的交换次数来把一个元素移到其正确位置。 快速排序采用分治策略(divide-and-conquer)。 在快速排序中,分治策略选取某个称为基准(pivot)的元素,然后进行一系列交换,使得小于或等于这个基准的所有元素都放在基准的前面,而所有大于基准的元素都放在其后边。 这就正确地定好了基准的位置,且把(子)列表分成两个更小的子列表,每个子列表都可以使用同样的方法来进行排序。 反复运用这个方法,最终得到易于排序的短列表。 容易写出快速排序算法的递归版本,算法的描述和实现请参考教科书P192的程序10.4。 我们也可以写出快速排序的非递归算法。 template voidIterQuickSort(intA[],intn)//非递归的快速排序算法 { } 快速排序的最坏情况发生在列表已经排序或者元素的排列符合反向次序的时候,时间复杂度为O(n2)。 而其平均情况下的时间复杂度为O(nlog2n)。 4)对快速排序的改进 ●基准的选择 一种选择基准的通用方法是采用三数取中规则,即选取子列表的第一个、中间一个和最后一个元素的中间一个来作为基准。 事实上,许多列表都是部分有序的,此时三数取中规则很有可能选出一个与子列表的中位数相近的基准数字,而不再是“首元素”规则。 ●短子列表 对于短列表(n≤20),快速排序的性能低于插入排序的性能;而递归过程中许多短列表会出现。 当元素个数n≤20时用直接插入排序,作为快速排序递归版本算法的递归出口。 在C++标准库 大部分实现都使用了这里提到的改进方法来提高快速排序算法的性能。 请写出你自己的经过改进的快速排序递归版本的算法。 (实验必做内容) template voidQuickSort(intA[],intn)//非递归的快速排序算法 { QSort(A,0,n-1); } template voidQSort(TA[],intleft,intright) { } 4、归并排序 1)折半归并排序 2)自然归并排序 折半归并排序要求子文件的长度为 5)基数排序 5.4.3编程测试各种排序算法 要求: 1)使用随机数发生器产生大数据集合,运行上述各排序算法,使用系统时钟测量各算法所需要的时间,并进行比较。 2)请研究下面的程序代码,理解随机数的产生和时间函数的使用。 #include #include #include //有关时间函数的内容请参考 #include"SortAlgorithm.h"//实验的各个排序算法的实现都放入此头文件中 #defineMAX_SIZE30000//排序列表规模 usingnamespacestd; intmain(intargc,char*argv[]) { clock_tstart,finish; doubleduration; inta[MAX_SIZE]; srand((unsigned)time(NULL));//随机数种子,这样每次运行序列内容不同 for(inti=0;i a[i]=rand(); start=clock(); QuickSort(a,MAX_SIZE);//快速排序 finish=clock(); duration=(double)(finish-start)/CLOCKS_PER_SEC; cout<<"Theduringtimeis: "< system("PAUSE"); return0; }
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 实验 内部 排序