1、需求分析
(1)输入数据的形式为:伪随机数产生程序产生,且每次输入数不少于100个,至少要用5组不同的输入数据
(2)输出的形式为:输出关键字参加的比较次数和关键字的移动次数(关键字交换计为3次移动)的数据
(3)程序能达到的功能:对起泡排序,直接插入排序,简单选择排序,快速排序,希尔排序,堆排序这6种常用的内部排序算法进行比较,比较的指标为有关键字参加的比较次数和关键字的移动次数(关键字交换计为3次移动)
(4)测试数据:正确输入为由伪随机数产生程序产生100个随机数,然后输出比较结果,错误输入为输入极少量数据,此时输出结果不能比较
(5)C语言编写
2、概要设计
本程序设计了一个顺序表结构来存储需要排序的数据,主程序运行之后,先初始化6个顺序表,然后就进入一个需要循环5次的大循环里,在循环里面有一个小循环需要执行100次产生100个随机数给6个顺序表,此时这6个顺序表的数据相同且是随机数,然后分别调用void InsertSort(SqList *L)冒泡排序,void InsertSort(SqList *L)直接插入排序,void SelectSort(SqList *L)简单选择排序,void QuickSort(SqList *L) 快速排序,void ShellSort(SqList *L)希尔排序,void HeapSort(SqList *L)堆排序,来对待排序列排序。其中void InsertSort(SqList *L),void SelectSort(SqList *L),void HeapSort(SqList *L)这几个模块中,当两个数据需要交换位置时调用了void swap(SqList *L,int i,int j)模块。void HeapSort(SqList *L) 模块中调用了void HeapAdjust(SqList *L,int s,int m,int &a,int &b),使得将L->r[1...i-1]重新调整为大顶堆 。void QuickSort(SqList *L) 模块中调用了void QSort(SqList *L,int low,int high,int &k,int &l)来对对顺序表L中的子序列L->r[low..high]作快速排序,void QSort(SqList *L,int low,int high,int &k,int &l)中又调用了int Partition(SqList *L,int low,int high,int &k,int &l)来将L->r[low..high]一分为二,算出枢轴值。
3、详细设计
顺序表结构里有用于int类型的存储排序数据的数组,r[0]用作哨兵或临时变量,以及int类型的用于记录顺序表的长度变量。
typedef struct{
int r[MAXSIZE+1];
int length;
}SqList;
4、调试分析
(1)调试过程中遇到的问题:
1、一开始产生随机数时,对顺序表的数组里都赋了值,结果出来时,第一个数会没有排序,其它的数都正常,原因是r[0]是用作哨兵或者存放临时变量,所以一开始赋值时,r[0]不应该赋值。
2、计算关键字交换的次数时,定义的变量为int l;计算结果出来之后,数字非常大。是因为对于局部变量,不赋初值的话,其实它里面存的是一个随机的值,而并不是0,所以定义时应定义为int l = 0;
3、在计算关键字的比较和交换时,由于模块之间要相互调用,所以计算的值要当做参数传输,但返回时不能返回两个返回值,困扰了较久,然后用了int &k,int &l,即可以改变实参的方法就解决了问题
(2)算法的时空分析:
排序法 平均时间 最差情形 稳定度 额外空间 备注
冒泡 O(n2) O(n2) 稳定 O(1) n小时较好
选择 O(n2) O(n2) 不稳定 O(1) n小时较好
插入 O(nlogn) O(n2) 稳定 O(1) 大部分已排序时较好
希尔 O(n^1.5) 不详 不稳定 O(1)
快速 O(nlogn) O(n2) 不稳定 O(nlogn) n大时较好
堆 O(nlogn) O(nlogn) 不稳定 O(1) n大时较好
冒泡排序优化:当序列还没比较但已经有序时,其实已经不要再继续后面的循环判断工作了,所以增加一个标量flag来实现这算法的改进
void BubbleSort2(SqList *L){
int i,j;
Status flag = TRUE;
for(i = 1;i<L->length && flag;i++){
flag = FALSE;
for(j = L->length;j>=i;j--){
if(L->r[j] > L->r[j+1]){
swap(L,j,j+1);
flag = TRUE;
}
}
}
}
(3)经验与体会:开始打代码前要理一下思想,找到最佳入口来写代码,模块之间的调用也要先构思好,不然后面代码之间过于混乱,当出现bug时,会很难找到,要充分利用调试的功能
5、用户使用说明
本程序中要用户输入100个数是不太可能的。所以本程序执行之后就会自动产生随机数。直接比较结果就会自动显示。使用简单,直接运行就行。
6、测试结果
由测试的数据可以看到冒泡排序和简单排序中关键字的比较次数相同且不会波动,这是由于无论数据的数的变化,只要总数不变,冒泡排序和简单排序都要执行循环中的比较语句。简单排序中关键字的移动自述最少且波动幅度不大,是由于直接插入排序是将记录从无序区直接插入到有序区,所以没有数据之间的交换,所以移动次数较少,但是比较次数较多。堆排序中比较次数和移动次数两者相差不大,是由于堆排序中将是频繁将最大值与末尾比较然后交换。希尔排序和快排中关键字的移动次数波动较大。是由于这两种排序进行数据之间交换位置的动作较大,因此当数据较混乱和较整齐时,移动次数的结果会相差较大。
5、附录
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
//排序用的顺序表结构
typedef struct{
int r[MAXSIZE+1];//用于存储要排序数组,r[0]用做哨兵或临时变量
int length;
}SqList;
//交换两个值
void swap(SqList *L,int i,int j){
int temp = L->r[i];
L->r[i] = L->r[j];
L->r[j] = temp;
}
//冒泡排序
void BubbleSort(SqList *L){
int i,j,k=0,l=0;
for (i = 1;i<L->length;i++){ //外层循环,确定所有数都与其它数比较
//k++;
for(j = i+1;j<=L->length;j++){ //内层循环,用一个数跟其它数比较大小
k++;
if(L->r[i] > L->r[j]){
swap(L,i,j);
l = l+3;
}
}
}
printf("冒泡排序中关键字的比较次数为%d:",k);
printf("\n冒泡排序中关键字的移动次数为%d:",l);
printf("\n");
}
//直接排序
void InsertSort(SqList *L) {
int i,j,k=0,l=0;
for(i = 2;i<=L->length;i++){
k++;
if(L->r[i] < L->r[i-1]){
L->r[0] = L->r[i];//设置哨兵
l++;
for(j = i-1;L->r[j] > L->r[0];j--){
L->r[j+1] = L->r[j];//记录后移
l++;
k++;
}
k++;//这一步容易忽略,跳出循环的时候,是比较了一次,不符合条件才跳出的
L->r[j+1] = L->r[0];//插入到正确位置
l++;
}
}
printf("直接排序中关键字的比较次数为%d:",k);
printf("\n直接排序中关键字的移动次数为%d:",l);
printf("\n");
}
//简单选择排序
void SelectSort(SqList *L){
int i,j,min;
int k=0,l=0;
for(i = 1;i<L->length;i++){
//k++;
min = i;
for(j = i+1;j<=L->length;j++){
k++;
if(L->r[min] > L->r[j]){
min = j;
}
}
if(i != min){ //判断 i!min,则证明有数据比 r[min]还要小,则需交换
swap(L,i,min);
l = l+3;
}
}
printf("简单排序中关键字的比较次数为:%d",k);
printf("\n简单排序中关键字的移动次数为:%d",l);
printf("\n");
}
//希尔排序
void ShellSort(SqList *L) {
int i,j;
int k = 0,l = 0;
int increment = L->length;
do{
increment = increment/5+1;//增量序列
for(i = increment+1;i<=L->length;i++){
k++;
if(L->r[i] < L->r[i-increment]){ // 需要将L->r[i]插入有序增量子表
L->r[0] = L->r[i];
l++;
for(j = i-increment;L->r[0]<L->r[j] && j>0;j = j-increment){
k++;
L->r[j+increment] = L->r[j];
l++;
}
k++;//这一步容易忽略,跳出循环的时候,是比较了一次,不符合条件才跳出的
L->r[j+increment] = L->r[0];
l++;
}
}
}while(increment > 1);
printf("希尔排序中关键字的比较次数为:%d",k);
printf("\n希尔排序中关键字的移动次数为:%d",l);
printf("\n");
}
//已知L->r[s..m]中记录的关键字除L->r[s]之外均满足堆的定义
//本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆
void HeapAdjust(SqList *L,int s,int m,int &a,int &b){
int temp,j;
temp = L->r[s];
b++;
for(j = 2*s;j<=m;j = j*2){
a++;
if( L->r[j] < L->r[j+1] && j<m)
++j;//j为关键字中较大的记录的下标
a++;
if(temp >= L->r[j])
break;
L->r[s] = L->r[j];
b++;
s = j;
}
L->r[s] = temp;
b++;
}
//堆排序
void HeapSort(SqList *L) {
int i ;
int k = 0,l = 0;
for(i = L->length/2;i>0;i--){ //把L中的r构建成一个大顶堆
HeapAdjust(L,i,L->length,k,l);
}
for(i = L->length;i>1;i--){
swap(L,1,i);//将堆顶记录和当前未经排序子序列的最后一个记录交换
l = l+3;
HeapAdjust(L,1,i-1,k,l);//将L->r[1...i-1]重新调整为大顶堆
}
printf("堆排序中关键字的比较次数为:%d",k);
printf("\n堆排序中关键字的移动次数为:%d",l);
printf("\n");
}
//交换顺序表L中子表的记录,使枢轴记录到位,并返回其所在位置
//此时在它之前(后)的记录均不大(小)于它
int Partition(SqList *L,int low,int high,int &k,int &l){
int pivotkey;
pivotkey = L->r[low];
while(low<high){
while(L->r[high] >= pivotkey && low<high ){
k++;
high--;
}
k++;//这一步容易忽略,跳出循环的时候,是比较了一次,不符合条件才跳出的
swap(L,low,high);
l = l+3;
while(L->r[low] <= pivotkey && low<high ){
k++;
low++;
}
k++;//这一步容易忽略,跳出循环的时候,是比较了一次,不符合条件才跳出的
swap(L,low,high);
l = l+3;
}
return low;
}
//对顺序表L中的子序列L->r[low..high]作快速排序
void QSort(SqList *L,int low,int high,int &k,int &l){
int pivot;//枢轴
if(low<high){
pivot = Partition(L,low,high,k,l);//将L->r[low..high]一分为二,算出枢轴值
QSort(L,low,pivot-1,k,l);//对低子表递归排序
QSort(L,pivot+1,high,k,l);//对高子表递归排序
}
}
//快速排序
void QuickSort(SqList *L) {
int k=0,l=0;
QSort(L,1,L->length,k,l);
printf("快速排序中关键字的比较次数为:%d",k);
printf("\n快速排序中关键字的移动次数为:%d",l);
printf("\n");
}
int main(){
int x,y;
SqList L,L1,L2,L3,L4,L5;
L.length = 100;
for(int i = 0;i<5;i++){
printf("第%d次待排序列为:\n",i+1);
for(x=1; x<101; x++) {
y = rand()% 100;
L.r[x] = y;
printf("%3d",y);
}
L1=L2=L3=L4=L5=L;
//fflush(stdin);
printf("\n排序后的结果\n");
BubbleSort(&L);
printf("直接排序后的结果\n");
InsertSort(&L1);
printf("简单排序后的结果\n");
SelectSort(&L2);
printf("希尔排序后的结果\n");
ShellSort(&L3);
printf("堆排序后的结果\n");
HeapSort(&L4);
printf("快速排序后的结果\n");
QuickSort(&L5);
for(x=1; x<101; x++) {
printf("%3d",L.r[x]);
}
printf("\n");
}
while(1){ //设置一个死循环,为了不让程序结束而关闭窗口
}
return 0;
}