數據結構與算法實驗報告
第五次實驗
姓名:孫瑞霜
一、實驗目的
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部分的代碼進去,點擊運行,在出現的界面中按照提示輸入數據,一步步按下回車鍵即可運行該程序,最后測試完畢,關閉界面。