数据结构与算法实验报告
第五次实验
姓名:孙瑞霜
一、实验目的
1、复习各种排序方法的算法思想;
2、掌握对存储在数组中的多个元素排序的方法;
3、比较各种排序方法的效率高低。
二、实验要求:
1. 认真阅读和掌握教材上和本实验相关的内容和算法(P265~P283);
2. 上机将排序的各种相关算法实现;
3. 实现上面实验目的要求的功能,并能进行简单的验证。
三、实验内容
编程实现排序的所有算法,放入sort.c中,并在main函数中分别调用,实现对数组中存储的多个无序数据排序,要求能够进行简单的输入输出验证,并分析各自排序算法的效率高低(可以加大待排数据规模或者重复操作多次,参照P4~P7)。
#include<stdio.h>
#include<time.h>
typedef int ElementType; //元素类型为int
#include"sort.c" //把排序算法sort.c包含到当前文件
#define N 100000 //预定义N为10,表示数组中元素个数,可调
void Init(ElementType A[],int n)
{//对A数组中的n个元素分别赋随机值
int i;
for(i=0;i<n;i++)
A[i]=rand()%n;
}
void Print(ElementType A[],int n)
{//输出a数组中的n个元素
int i;
for(i=0;i<n;i++)
printf("%6d",A[i]);
putchar('\n');
}
int main(void)
{
ElementType A[N];
clock_t startclock,endclock; //定义时钟打点类型的两个变量
Init(A,N); //初始化数组
printf("排序前:\n");
Print(A,N); //输出数组
startclock=clock(); //排序前获得一个时钟打点
/*调用排序算法对A数组中的N个数排序*/
// SelectSort(A,N); //选择排序 ,可换为其他排序方法
HeapSort(A,N); //堆排序
endclock=clock(); //排序后获得一个时钟打点
printf("排序后:\n");
Print(A,N);
printf("共耗时%.3f秒\n",(double)(endclock-startclock)/CLK_TCK); //计算运行耗时
/* CLK_TCK是机器时钟每秒所走的时钟打点数 */
return 0;
}
三、算法描述
在第七章我们学习了选择排序、堆排序、简单插入排序、希尔排序、冒泡排序、快速排序、归并排序、基数排序8种不同的内部排序方法,它们在时间复杂度、空间复杂度和稳定性上各有优劣。不存在绝对意义上最佳的排序方法,8种排序方法分别适用于不同的条件下。例如基数排序是时间复杂度最低的排序方法,借助O(N+R)(R为每个关键字不同取值的个数)浦助空间和严格限制的元素数据类型,仅仅需要O(D(N+R))的时间复杂度。基数字适用于处理数量大、关键字取值范围有限的序列,例如扑克牌排序等。同时,基数排序也是稳定的排序方法。其余7种排序方法都是建立在比较和交换操作上的,决定其性能的是比较、交换的次数和是否需要额外空间用于保存临时值。选择排序、堆排序、希尔排序、快速排序不稳定;简单插入排序、冒泡排序、归并排序稳定。此次实验就各种排序过程及耗时进行研究。
四、详细设计
五、程序代码
#include<stdio.h>
#include<time.h>
typedef int ElementType; //元素类型为int
#include"sort.c" //把排序算法sort.c包含到当前文件
#define N 100000 //预定义N为10,表示数组中元素个数,可调
void Init(ElementType A[],int n)
{//对A数组中的n个元素分别赋随机值
int i;
for(i=0;i<n;i++)
scanf("%d",&A[i]);
}
void Print(ElementType A[],int n)
{//输出a数组中的n个元素
int i;
for(i=0;i<n;i++)
printf("%6d",A[i]);
putchar('\n');
}
int main(void)
{
int n,i;
ElementType A[N];
clock_t startclock,endclock; //定义时钟打点类型的两个变量
printf("输入要排序的个数:");
scanf("%d",&n);
Init(A,n); //初始化数组
printf("排序前:\n");
Print(A,n);
printf("\n"); //输出数组
startclock=clock(); //排序前获得一个时钟打点
/*调用排序算法对A数组中的N个数排序*/
for(i=0;i<10000000;i++)
HeapSort(A,n); //堆排序
endclock=clock(); //排序后获得一个时钟打点
printf("堆排序后:\n");
Print(A,n);
printf("共耗时%.3f秒\n\n",(double)(endclock-startclock)/CLK_TCK)/10000000; //计算运行耗时
/* CLK_TCK是机器时钟每秒所走的时钟打点数 */
startclock=clock();
for(i=0;i<10000000;i++)
SimpleSelectionSort(A,n);
endclock=clock();
printf("简单选择排序后:\n");
Print(A,n);
printf("共耗时%.3f秒\n\n",(double)(endclock-startclock)/CLK_TCK)/10000000;
startclock=clock();
for(i=0;i<10000000;i++)
InsertionSort(A,n);
endclock=clock();
printf("插入排序后:\n");
Print(A,n);
printf("共耗时%.3f秒\n\n",(double)(endclock-startclock)/CLK_TCK)/10000000;
startclock=clock();
for(i=0;i<10000000;i++)
BubbleSort(A,n);
endclock=clock();
printf("冒泡排序后:\n");
Print(A,n);
printf("共耗时%.3f秒\n\n",(double)(endclock-startclock)/CLK_TCK)/10000000;
startclock=clock();
for(i=0;i<10000000;i++)
ShellSort(A,n);
endclock=clock();
printf("希尔排序后:\n");
Print(A,n);
printf("共耗时%.3f秒\n\n",(double)(endclock-startclock)/CLK_TCK)/10000000;
startclock=clock();
for(i=0;i<10000000;i++)
QuickSort(A,n);
endclock=clock();
printf("快速排序后:\n");
Print(A,n);
printf("共耗时%.3f秒\n\n",(double)(endclock-startclock)/CLK_TCK)/10000000;
startclock=clock();
for(i=0;i<10000000;i++)
MergeSort(A,n);
endclock=clock();
printf("归并排序后:\n");
Print(A,n);
printf("共耗时%.3f秒\n\n",(double)(endclock-startclock)/CLK_TCK)/10000000;
startclock=clock();
for(i=0;i<10000000;i++)
LSDRadixSort(A,n);
endclock=clock();
printf("基数排序后:\n");
Print(A,n);
printf("共耗时%.3f秒\n\n",(double)(endclock-startclock)/CLK_TCK)/10000000;
return 0;
}
sort.c文件:
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
typedef int ElementType;
//简单选择排序
void swap(ElementType *a ,ElementType *b)
{
ElementType t=*a;
*a=*b;
*b=t;
}
void SimpleSelectionSort(ElementType A[], int N)
{ //简单选择排序
int i,j,min;
for(i=0;i<N-1;i++){
min=i;
for(j=i+1;j<N;j++){
if(A[j]<A[min])
min=j;//min记录最小元素位置
}
//将第i个元素与最小元素交换
swap(&A[i],&A[min]);
}
}
void PercDown(ElementType A[],int p,int N)
{ //将N个元素的数组中以A[p]为跟的子堆调整为最大堆
int parent,child;
ElementType x;
x=A[p]; //取出根结点存放的值
for(parent=p;(parent*2+1)<N;parent=child){
child=parent*2+1;
if((child!=N-1)&&(A[child]<A[child+1]))
child++; //child指向左右子结点的较大者
if(x>=A[child]) //找到了合适位置
break;
else //下滤x
A[parent]=A[child];
}
A[parent]=x;
}
void HeapSort(ElementType A[],int N)
{ //堆排序
int i;
for(i=N/2-1;i>=0;i--)/* 建立最大堆 */
PercDown(A,i,N);
for(i=N-1;i>0;i--){
/* 删除最大堆顶 */
swap(&A[0],&A[i]);
PercDown(A,0,i);
}
}
void InsertionSort(ElementType A[],int N)
{ //插入排序
int p,i;
ElementType tmp;
for(p=1;p<N;p++){
tmp=A[p]; //取出未排序序列中的第一元素
for(i=p;i>0&&A[i-1]>tmp;i--)
A[i]=A[i-1]; //依次与已排序序列中元素比较并右移
A[i]=tmp; //放进合适的位置
}
}
void ShellSort(ElementType A[], int N)
{
int P,D,i,Si;
ElementType Tmp;
//这里只列出一小部分增量
int Sedgewick[] = {929,505,209,109,41,19,5,1, 0};
for(Si=0;Sedgewick[Si]>=N;Si++ )
; /* 初始的增量Sedgewick[Si]不能超过待排序列长度 */
for(D=Sedgewick[Si];D>0;D=Sedgewick[++Si]){
for(P=D;P<N;P++){
Tmp=A[P];
for(i=P;i>=D&&A[i-D]>Tmp;i-=D){
A[i]=A[i-D];
}
A[i]=Tmp;
}
}
}
void BubbleSort(ElementType A[],int N)
{ //冒泡排序
int p,i;
bool flag;
for(p=N-1;p>=0;p--){
flag=false; //标识该次循环中是否发生交换,若无,则说明整个序列有序
for(i=0;i<p;i++){ //一趟冒泡
//每次循环找出一个最大元素,被交换到最右端
if(A[i]>A[i+1]){
swap(&A[i],&A[i+1]);
flag=true; //标识发生了交换
}
}
if(flag==false)
break; //若全程无交换,则跳出循环
}
}
ElementType Median3(ElementType A[],int Left,int Right)
{
int Center=(Left+Right)/2;
if(A[Left]>A[Center])
swap(&A[Left],&A[Center]);
if(A[Left]>A[Right])
swap(&A[Left],&A[Right]);
if(A[Center]>A[Right])
swap(&A[Center],&A[Right]);
//此时A[Left]<=A[Center]<=A[Right]
swap(&A[Center],&A[Right-1]); //将基准pivot藏到右边
//只需要考虑A[Left+1]...A[Right-2]
return A[Right-1]; //返回基准pivot
}
void Qsort(ElementType A[],int Left,int Right)
{//核心递归函数
int pivot,Low,High;
if(Left<Right){
pivot=Median3(A,Left,Right); //选基准
Low=Left; High=Right-1;
while(Low<High){ //将序列中比基准小的移到基准左边,大的移到右边
while(A[++Low]<pivot);
while(A[--High]>pivot);
if(Low<High)
swap(&A[Low],&A[High]);
else
break;
}
swap(&A[Low],&A[Right-1]); //将基准移到正确的位置
Qsort(A,Left,Low-1); //递归解决左边
Qsort(A,Low+1,Right); //递归解决右边
}
}
void QuickSort(ElementType A[],int N)
{//统一接口
Qsort(A,0,N-1);
}
//L=左边起始位置,R=右边起始位置,RightEnd=右边终点位置
void Merge(ElementType A[], ElementType TmpA[],int L,int R,int RightEnd)
{//将有序的A[L]~A[R-1]和A[R]~A[RightEnd]归并成一个有序序列
int LeftEnd,NumElements,Tmp,i;
LeftEnd=R-1; //左边终点位置
Tmp=L; //有序序列的起始位置
NumElements=RightEnd-L+1;
while(L<=LeftEnd&&R<=RightEnd)
{
if(A[L]>A[R])
TmpA[Tmp++]=A[R++]; //将左边元素复制到TmpA
else
TmpA[Tmp++]=A[L++]; //将右边元素复制到TmpA
}
while(L<=LeftEnd)
TmpA[Tmp++]=A[L++]; //直接复制左边剩下的
while(R<=RightEnd)
TmpA[Tmp++]=A[R++]; //直接复制右边剩下的
for(i=0;i<=NumElements;i++,RightEnd--)
A[RightEnd]=TmpA[RightEnd]; //将有序的TmpA[]复制回A[]
}
void MSort(ElementType A[], ElementType TmpA[], int L, int RightEnd)
{//核心递归排序函数
int center;
if(L<RightEnd){
center=(L+RightEnd)/2;
MSort(A,TmpA,L,center); //递归解决左边
MSort(A,TmpA,center+1,RightEnd); //递归解决右边
Merge(A,TmpA,L,center+1,RightEnd); //合并两段有序序列
}
}
void MergeSort(ElementType A[],int N)
{/*归并排序 */
ElementType *TmpA;
TmpA=(ElementType *)malloc(N*sizeof(ElementType));
if (TmpA!=NULL)
{
MSort(A,TmpA,0,N-1);
free(TmpA);
}
else
printf("空间不足");
}
//假设元素最多有MaxDigit个关键字,基数全是同样的Radix
#define MaxDigit 4
#define Radix 10
//桶元素结点
typedef struct Node *PtrToNode;
struct Node {
int key;
PtrToNode next;
};
//桶头结点
struct HeadNode {
PtrToNode head,tail;
};
typedef struct HeadNode Bucket[Radix];
int GetDigit(int X,int D)
{//默认次位D=1,主位D<=MaxDigit
int d,i;
for(i=1;i<=D;i++){
d=X%Radix;
X/=Radix;
}
return d;
}
void LSDRadixSort(ElementType A[], int N)
{//基数排序 - 次位优先
int D,Di,i;
Bucket B;
PtrToNode tmp,p,List=NULL;
for(i=0;i<Radix;i++) //初始化每个桶为空链表
B[i].head=B[i].tail=NULL;
for(i=0;i<N;i++){ //将原始序列逆序存入初始链表List
tmp=(PtrToNode)malloc(sizeof(struct Node));
tmp->key=A[i];
tmp->next=List;
List=tmp;
}
//下面开始排序
for(D=1;D<=MaxDigit;D++){//对数据的每一位循环处理
//下面是分配的过程
p=List;
while(p){
Di=GetDigit(p->key,D); //获得当前元素的当前位数字
//从List中摘除
tmp=p; p=p->next;
//插入B[Di]号桶尾
tmp->next=NULL;
if(B[Di].head==NULL)
B[Di].head=B[Di].tail=tmp;
else{
B[Di].tail->next=tmp;
B[Di].tail=tmp;
}
}
//下面是收集的过程
List=NULL;
for(Di=Radix-1;Di>=0;Di--){//将每个桶的元素顺序收集入List,
if (B[Di].head){//如果桶不为空
//整桶插入List表头
B[Di].tail->next=List;
List=B[Di].head;
B[Di].head=B[Di].tail=NULL; //清空桶
}
}
}
//将List倒入A[]并释放空间
for (i = 0; i<N; i++){
tmp=List;
List=List->next;
A[i]=tmp->key;
free(tmp);
}
}
六、测试和结果
七、用户手册
打开devC++,新建一个源程序,拷贝5部分的代码进去,点击运行,在出现的界面中按照提示输入数据,一步步按下回车键即可运行该程序,最后测试完毕,关闭界面。