数据结构实验报告
实验名称:实验四——排序
学生姓名:
班级:
班内序号:
学号:
日期:
1.实验要求
使用简单数组实现下面各种排序算法,并进行比较。
排序算法:
1、插入排序
2、希尔排序
3、冒泡排序
4、快速排序
5、简单选择排序
6、堆排序(选作)
7、归并排序(选作)
8、基数排序(选作)
9、其他
要求:
1、测试数据分成三类:正序、逆序、随机数据
2、对于这三类数据,比较上述排序算法中关键字的比较次数和移动次数(其
中关键字交换计为3次移动)。
3、对于这三类数据,比较上述排序算法中不同算法的执行时间,精确到微秒
(选作)
4、对2和3的结果进行分析,验证上述各种算法的时间复杂度
编写测试main()函数测试线性表的正确性。
2. 程序分析
插入排序类似于玩纸牌时整理手中纸牌的过程,它的基本方法是寻找一个指定元素在待排序元素中的位置,然后插入。
直接插入排序的基本思想可以这样描述:每次讲一个待排序的元素按其关键码的大小插入到一个已经排序好的有序序列中,直到全部元素排序好。元素个数为1时必然有序,这就是初始有序序列;可以采用顺序查找的方法查找插入点,为了提高时间效率,在顺序查找过程中边查找边后移的策略完成一趟插入。
希尔排序又称“缩小增量排序”,是对直接插入排序的一种改进,它利用了直接插入的两个特点:1.基本有序的序列,直接插入最快;2.记录个数很少的无序序列,直接插入也很快。希尔排序的基本思想为:将待排序的元素集分成多个子集,分别对这些子集进行直接插入排序,待整个序列基本有序时,再对元素进行一次直接插入排序。
冒泡排序的基本思想是:两两比较相邻的元素,如果反序,则交换位置,直到没有反序的元素为止。具体的排序过程是:将整个待排序元素划分成有序区和无序区,初始状态有序区为空,无序区包括所有待排序的元素;对无序区从前向后依次将相邻元素的关键码进行比较,若反序则交换,从而使得关键码小的元素向前移,关键码大的元素向后移;重复执行前一个步骤,直到无序区中没有反序的元素。
快速排序元素的比较和移动是从两端向中间进行的。快速排序的基本思想是:在分区中选择一个元素作为轴值,将待排序元素划分成两个分区,使得左侧元素的关键码均小于或等于轴值,右侧元素的关键码均大于或等于轴值,然后分别对这两个分区重复上述过程,直到整个序列有序。
简单选择排序的基本思想是:第1趟,在待排序记录r[1…n]中选出最小的记录,将它与r[1]交换;第2趟,在待排序记录r[2…n]中选出最小的记录,将它与r[2]交换;以此类推,第i趟,在待排序记录r[i…n]中选出最小的记录,将它与r[i]交换,使有序序列不断增长直到全部排序完毕。
2.1 存储结构
2.2 关键算法分析
1、直接插入排序
自然语言描述:
(1)将整个待排序的记录划分成有序区和无序区。有序区为待排序记录的第一个记录,无序区为所有剩余带待排序记录。
(2)从第二个数据开始依次插入到有序区中,直到所有记录插入完毕。
(3)在r[0]处设置“哨兵”,记为要插入的记录r[i],在自i-1起往前查找的过程中,同时后移记录。
(4)找到插入位置后,将待插入记录插入到有序表中。
(5)重复执行(3)、(4),直到无序区中没有记录。
伪代码描述:
1.初始化比较次数Cnum=0,移动次数Mnum=0
2.for(int i=2;i 2.1比较次数++ 2.2 if(r[i] 2.2.1设置哨兵r[0]=r[i],移动次数++ 2.2.2 for(j=i-1;r[0] 2.2.2.1 r[j+1]=r[j] 2.2.2.2比较次数++;移动次数++; 2.2.3 r[j+1]=r[0],移动次数++ 时间复杂度: 最好情况下,待排序序列为正序,每趟秩序与有序序列的最后一个纪录的关键码比较一次,移动两次记录。总的比较次数为n-1,记录移动的次数为2(n-1)。因此,时间复杂度为O(n)。 最坏情况下,待排序序列为逆序,比较次数为(n+2)(n+1)/2,移动次数为(n+4)(n-1)/2因此,时间复杂度为O(n2). 平均情况下,总的比较次数为n(n-1)/4,移动次数为(n+4)(n-1)/4,因此,时间复杂度为O(n2) 空间复杂度:S(1) 直接插入排序只需要一个纪录的辅助空间,用来作为待插入记录的暂存单元和记录的插入位置的过程中的“哨兵”以及交换记录时的暂存单元。 2、希尔排序 自然语言描述: (1)假设待排序记录为n个,先取整数d (2)对每个子序列分别进行直接插入排序。在每个子序列中,待插入记录和同一子序列中的前一个记录比较,在插入记录r[i]时,自r[i-d]往前查找待插入位置,在查找过程中,记录后移也是移动d个位置。 (3)当搜索位置j<=0或者r[0]>=r[j],表示插入位置已经找到。在整个序列中,前d个记录分别是d个子序列的第一个记录,所以从第d+1个记录开始插入。 (4)再缩小d,重复以上步骤,再对每个子序列进行直接插入排序,直到d=1,即将所有记录放在一组进行一次直接插入排序,最终将所有记录重新排序得到有序序列。 伪代码描述: 1.初始化比较次数Cnum=0,移动次数Mnum=0 2.for(int d=n/2;d>=1;d=d/2) 2.1 for(int i=d+1;i 2.1.1比较次数++ 2.1.2 if(r[i] 2.1.2.1 r[0]=r[i],移动次数++ 2.1.2.2 for(j=i-d;i>0&&r[0] 2.1.2.2.1 r[j+d]=r[j] 2.1.2.2.2比较次数++,移动次数++ 2.1.2.3 r[j+d]=r[0],移动次数++ 时间复杂度: 希尔排序的时间性能分析是一个复杂的问题,因为它是所取增量的函数。有人在大量实验的基础上指出,希尔排序的时间性能在O(n2)和O(nlog2n)之间,当n在某个特定范围时,希尔排序的时间性能约为O(n1.3) 空间复杂度:S(1) 希尔排序只需要一个纪录的辅助空间,用于暂存当前待插入的记录。 3、冒泡排序 自然语言描述: (1)将整个待排序的记录划分成有序区和无序区,初始有序区为空,无序区包括所有待排序记录。设置变量pos,此位置之后的所有记录均已经有序。 (2)对无序区从前向后一次将相邻记录的关键码进行比较,若反序则交换,从而使得关键码小的记录前移,关键码大的记录后移。 (3)每趟冒泡排序开始之前,设pos初值为0,在该趟排序过程中,只要有记录的交换,pos就大于0。通过pos是否为0判断是否有记录的交换,从而判断冒泡排序是否结束。 伪代码描述: 1.初始化比较次数Cnum=0,移动次数Mnum=0,int pos=n-1 2.while(pos!=0) 2.1int bound=pos,pos=0 2.2for(int j=1;j 2.2.1比较次数++ 2.2.2 if(r[j]>r[j+1]) 2.2.2.1 r[0]=r[j],r[j]=r[j+1],r[j+1]=r[0] 2.2.2.2 pos=j,Mnum+=3 时间复杂度: 最好情况下,待排序记录序列为正序,算法只执行了一趟,进行了n-1次关键妈的比较,不需要移动记录,时间复杂度为O(n) 最坏情况下,待排序记录序列为反序,每趟排序在无序序列中只有一个最大的纪录被交换到最终位置,故算法执行n-1趟,第i趟排序执行了n-1次关键码的比较和n-i次记录的交换,这样,关键码的比较次数为n(n-1)/2,记录的移动次数为3n(n-1)/2。 平均情况下,起泡排序的时间复杂度与最坏情况同数量级。 空间复杂度:S(1) 起泡排序只需要一个纪录的辅助空间,用来作为记录交换的暂存单元。 4、快速排序 自然语言描述: (1)取第一个记录作为基准,设置两个参数i,j分别用来指示将要与基准记录进行比较的左侧记录位置和右侧记录位置,也就是本次划分的区间。 (2)右侧扫描过程:将基准记录与j指向的记录进行比较,如果j指向记录的关键码大,则j--,重复右侧扫描过程,直到右侧的记录小,即反序,执行r[i]=r[j] (3)左侧扫描过程:将基准记录与i指向的记录进行比较,如果i指向记录的关键码大,则i--,重复右侧扫描过程,直到右侧的记录小,即反序,执行r[j]=r[i] (4)重复执行(2)、(3),直到i、j指向同一位置。 (5)r[i]=pivot (6)返回i的位置 伪代码描述: 1.int i=first; int j=end; int pivot=r[i] 2.while(i 2.1while((i 2.1.1j--;比较次数++ 2.2r[i]=r[j] 2.3if(i 2.3.1移动次数++ 2.4while((i 2.4.1 i++;比较次数++ 2.5r[j]=r[i] 2.6if(i 2.6.1移动次数++ 3.r[i]=pivot 4.return i 时间复杂度: 最好情况下,每次划分对一个记录定位后,该记录的左侧子序列和右侧子序列的长度相同。在具有n个记录的序列中,对一个记录定位要对整个待划分序列扫描一遍,则所需时间为O(n)。时间复杂度为O(nlog2n)。 最坏情况下,待排序记录序列正序或逆序,每次划分只得到一个比上一次划分少一个纪录的子序列(另一个子序列为空)。此时,必须经过n-1次递归调用才能把所有记录定位,而且第i趟划分需要经过n-i次关键码的比较才能找到第i个记录的基准记录,因此总的比较次数为n(n-1)/2,记录的移动次数小于等于比较次数。因此时间复杂度为O(n2). 平均情况下,可以用归纳法证明,其数量级也为O(nlog2n)。 空间复杂度: 由于快速排序是递归的,需要一个栈来存放每一层递归调用的必要信息,其最大容量应与递归调用的深度一致,最好情况下为S(nlog2n),最坏情况下,因为要进行n-1次递归调用,所以,栈的深度为S(n),平均情况下,栈的深度为S(log2n)。 5、简单选择排序 自然语言描述: (1)将整个记录划分为有序区和无序区,初始有序区为空,无序区包括所有待排序记录。 设置变量index,记录一次比较中关键码最小的位置。 (2)第i趟简单选择排序的待排序区间是r[i]-r[n],首先将index设定为当前无序区的第一个位置,然后用r[index]与无序区中其他记录进行比较,若有比r[index]小的记录,就将index改为这个新的最小的记录的位置。 (3)如果index不等于i,则将它与无序区中第一个记录交换,有序区增加一个记录,无序区减少一个记录。 (4)重复(2)(3),直到无序区只剩下一个记录为止。此时所有记录已经按照从小到大的顺序排好。 伪代码描述: 1.初始化比较次数Cnum=0,移动次数Mnum=0 2.for(int i=1;i 2.1int index=i 2.2for(int j=i+1;j 2.2.1Cnum++ 2.2.2if(r[j] 2.2.2.1 index=j 2.2.3if(index!=i) 2.2. 3.1r[0]=r[i];r[i]=r[index];r[index]=r[0] 2.2. 3.2M num+=3 时间复杂度: 待排序序列为正序时,记录的移动次数最少,为0次。在待排序序列为逆序时,记录的移动次数最多为3(n-1)次。 无论记录的初始排列如何,关键码的比较次数相同,第i趟排序需进行n-1次关键码的比较,而简单选择排序需进行n-1趟排序,则总的比较次数为n(n-1)/2=O(n2)。所以,总的时间复杂度为O(n2),这是简单选择排序最好、最坏、和平均的时间性能。 空间复杂度:S(1) 简单选择排序过程中,只需要一个用来作为记录交换的暂存单元。 3. 程序运行结果 测试条件: 问题规模的数量级是10,正序、逆序和随机顺序数组分别为 int r1[10]={3,14,23,36,47,59,63,71,82,90}; int r2[10]={90,82,71,63,59,47,36,23,14,3}; int r3[10]={47,36,82,3,14,90,23,71,59,63}; 测试结论: 程序对不同序列的数组运用各种不同方法进行排序时均能得到正确的排序结果,同时能够给出正确的比较次数和移动次数。 4. 总结 通过本次实验,我熟悉了各种排序方法的思路和优缺点,并根据时间复杂度和空间复杂度对几个方法进行了对比,用C++程序实现了对数据的排序,对每种排序方法实现的过程也都了然于胸。 插入排序:n*n的时间复杂度,稳定排序,原地排序。在数据大部分都排好了,用插入排序会给你带来很大的方便。数据的移动和交换都很少。 希尔排序:n*log(n)的时间复杂度,非稳定排序,原地排序。主要思想是分治,不过他的分治和合并排序的分治不一样,他是按步长来分组的,而不是想合并那样左一半右一半。 开始步长为整个的长度的一半,然后每组排序。接个步长减为原来的一半在分组排序,直到步长为1,排序之后希尔排序就完成了。这个思路很好,是插入排序的升级版,我觉得他是一个特别好的排序方法了。他的缺点就是两个数可能比较多次,但效率也是很高的。 冒泡排序,n*n的时间复杂度,稳定排序,原地排序。冒泡排序的思想很不错,一个一个比较,把小的上移,依次确定当前最小元素。因为他简单,稳定排序,而且好实现,所以用处也是比较多的。 选择排序,n*n的时间复杂度,稳定排序,原地排序。选择排序就是冒泡的基本思想,从小的定位,一个一个选择,直到选择结束。和插入排序是一个相反的过程,插入是确定一个元素的位置,而选择是确定这个位置的元素。它的好处就是每次只选择确定的元素,不会对很多数据进行交换。所以在数据交换量上应该比冒泡小。 数据结构实验报告 八种排序算法实验报告 一、实验内容 编写关于八种排序算法的C语言程序,要求包含直接插入排序、希尔排序、简单项选择择排序、堆排序、冒泡排序、快速排序、归并排序和基数排序。 二、实验步骤 各种内部排序算法的比较: 1.八种排序算法的复杂度分析〔时间与空间〕。 2.八种排序算法的C语言编程实现。 3.八种排序算法的比较,包括比较次数、移动次数。 三、稳定性,时间复杂度和空间复杂度分析 比较时间复杂度函数的情况: 时间复杂度函数O(n)的增长情况 所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。 时间复杂度来说: (1)平方阶(O(n2))排序 各类简单排序:直接插入、直接选择和冒泡排序; (2)线性对数阶(O(nlog2n))排序 快速排序、堆排序和归并排序; (3)O(n1+§))排序,§是介于0和1之间的常数。 希尔排序 (4)线性阶(O(n))排序 基数排序,此外还有桶、箱排序。 说明: 当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O〔n〕; 而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为O〔n2〕; 原表是否有序,对简单项选择择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。 稳定性: 排序算法的稳定性:假设待排序的序列中,存在多个具有相同关键字的记录,经过排序,这些记录的相对次序保持不变,则称该算法是稳定的;假设经排序后,记录的相对次序发生了改变,则称该算法是不稳定的。 稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以防止多余的比较; 稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序 不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序 c语言实验报告及建议 实验6 数组 一、实验目的 (1)掌握一维数组和二维数组的定义、赋值和输入输出方法。 (2)掌握与数组有关的算法(特别是排序算法)。 (3)掌握字符数组和字符串的使用方法。 (4)能正确定义数组的指针,熟练使用指针访问数组元素。 (5)学会使用字符串的指针和指向字符串的指针变量。 (6)学会使用指针数组处理多个字符串数据。 二、实验预备知识: 三、实验容 1.观察下面给一维数组赋值有什么错误,怎样修改,写出正确运行后的结果。 #include "stdio.h" main() { int i,a[3],sum=0; scanf(“%d,%d,%d”,a); for(i=0;i<=3;i++) sum=sum+a[i]; printf("sum=%d",sum); } 错误语句:scanf(“%d,%d,%d”,a);改正: scanf("%d,%d,%d",&a[0],&a[1],&a[2]); 运行结果: 1,1,1,sum=3 2.(1)补全下面的程序,程序的功能是求一位数组的中最小元素的值及其所在的下标号。#include "stdio.h" main() { int i,a[10],min,index; (1) for(i=0;i<10;i++) /*利用一充循环给数组a赋值*/ scanf(“%d”, (2)&a[i] ); for(i=0, (3)min-a[0] ;i<10;i++) /*求数组a中的最小值min及其对应的下标index*/ if(min>a[i]){ (4) min=a[i];index=i; } printf("MIN=%d,index=%d\n",min,index); } 实验2 顺序结构程序设计 一、实验目的 1. 学会使用自然语言或伪代码描述算法 2. 掌握变量、运算符、表达式的使用 3. 熟悉顺序结构程序中语句的执行过程 4. 掌握标准输入流对象cin及标准输出流对象 二、实验内容 编写程序在屏幕上显示如图2-1所示的菜单。 图2-1 学生选课管理系统主界面 实验步骤: 范例: 1)在VS2008中新建项目,在项目中新建C++源文件,输入下面程序,并且编译、连接。 //*************************************************************** //* 程序名:实训2_1.cp * //* 主要功能: * //* 显示主菜单,并获取用户输入的模块编号 * //*************************************************************** #include cout<<"\t\t|\t 0. 退出 |\n"; cout<<"\t\t|\t 1. 学生信息管理 |\n"; cout<<"\t\t|\t 2. 教师信息管理 |\n"; cout<<"\t\t|\t 3. 课程信息管理 |\n"; cout<<"\t\t|\t 4. 学生选课管理 |\n"; cout<<"\t\t|\t 5. 学生成绩管理 |\n"; cout<<"\t\t|\t 6. 信息统计模块 |\n"; cout<<"\t\t|-----------------------------------------------|\n\n"; cout<<"\t\t\t请输入模块编号(0-6):"; } 2)运行范例所示程序,观察输出结果 实训1要求编写程序在屏幕上显示如图2-2所示的菜单。 图2-2 学生信息管理菜单 实验步骤: 1) 修改范例的源程序,将各条输出语句按上图所示进行修改即可。注意增加或删除一些空格或-,使右边的|对齐。排序算法实验报告
c语言实验报告
C语言实验报告-实验2 顺序结构程序设计
C程序设计综合实验报告