排序算法实验比较(实验报告)
一、源码编写与调试
| 时间:2021.1.2 | 实验完成度:40% |
实验目的:
基于教材内容,选择直接插入排序和归并排序两种排序算法,实现并比较其性能。
实验过程:
1.先随机生成数据规模分别为100,1K,10K,100K和1M的5个TXT文件,用于排序的测试。
2.对源码进行编写,熟悉文件流操作和C++的微秒定时操作。
3.对源码进行编译,对于不同数据规模的数据,使用直接插入排序算法和归并排序算法各进行处理,将排序结果输出到文件中,且在屏幕上输出排序过程所花费时间。
排序算法选择:
1.直接插入排序:
基本思想:
输入一个记录数组A,存放着n条记录 先将数组中第1个记录看成是一个有序的子序列, 然后从第2个记录开始,依次逐个进行处理(插入) 将第i个记录X,依次与前面的第i-1个、第i-2 个,…,第1个记录进行比较,每次比较时,如果X的值小,则交换,直至遇到一个小于或等于 X的关键码,或者记录X已经被交换到数组的第 一个位置,本次插入才完成。 继续处理,直至最后一个记录插入完毕,整个数 组中的元素将按关键码非递减有序排列。
伪代码描述:
template <class Elem, class Comp>
void inssort(Elem A[], int n) {
for (int i=1; i<n; i++)
for (int j=i; (j>0) && (Comp::lt(A[j], A[j-1])); j--)
swap(A, j, j-1);
}
2.归并排序:
基本思想:
归并排序(划分过程)用递归实现:
若当前(未排序)序列的长度不大于1
返回当前序列
否则
将当前未排序序列分割为大小相等的两个子序列
分别用归并排序对两个子序列进行排序
将返回的两个有序子序列合并成一个有序序列
伪代码描述:
template <class Elem, class Comp>
void mergesort(Elem A[], Elem temp[],int left, int right) {
int mid = (left+right)/2;
if (left == right) return;
mergesort<Elem,Comp>(A, temp, left, mid);
mergesort<Elem,Comp>(A, temp, mid+1, right);
for (int i=left; i<=right; i++) // Copy
temp[i] = A[i];
int i1 = left; int i2 = mid + 1;
for (int curr=left; curr<=right; curr++) {
if (i1 == mid+1) // Left exhausted
A[curr] = temp[i2++];
else if (i2 > right) // Right exhausted
A[curr] = temp[i1++];
else if (Comp::lt(temp[i1], temp[i2]))
A[curr] = temp[i1++];
else A[curr] = temp[i2++];
}
}
实验源码:
#include <iostream>
#include <time.h>
#include <algorithm>
#include <fstream>
#include <windows.h>
using namespace std;
LARGE_INTEGER frequency; //时钟频率
const int maxn=1e6; //1M
int A[maxn+5]={0}; //存储产测试文件中输入的数据
int B[maxn+5]={0}; //存储产测试文件中输入的数据
double begin_,_end,dft,dff,dfm;
void Init(fstream &infile,int A[],int n) //从测试文件中输入测试数据到数组中
{
int a,i=0;
while(!infile.eof()&&i<n)
{
infile>>a;
A[i]=a;
i++;
}
}
void Printf(fstream &outfile,int A[],int n) //输出排序后的结果到文件
{
for(int i=0;i<n;i++)
{
outfile<<A[i]<<" ";
}
outfile<<"\n";
}
void swap(int A[],int a,int b)
{
int temp=A[a];
A[a]=A[b];
A[b]=temp;
}
void inssort(int A[], int n) { //直接插入排序算法
for(int i=1; i<n; i++)
{
for (int j=i; j>0 && (A[j]<A[j-1]); j--)
{
swap(A, j, j-1);
}
}
}
void inssort_find(fstream &outfile,int A[],int n) //直接插入排序时间
{
QueryPerformanceCounter(&frequency);
begin_=frequency.QuadPart;//获得初始值
inssort(A,n); //进行直接插入排序
QueryPerformanceCounter(&frequency);
_end=frequency.QuadPart;//获得终止值
dfm=(double)(_end-begin_);//差值
dft=(dfm/dff)*1000;//差值除以频率得到时间,并转换为毫秒级别
outfile<<dft<<"ms"<<endl;
cout<<dft<<"ms"<<endl;
}
void mergesort(int A[], int temp[],int left, int right) { //归并排序算法
int mid = (left+right)/2;
if (left == right) return;
mergesort(A, temp, left, mid);
mergesort(A, temp, mid+1, right);
for (int i=left; i<=right; i++)
{
temp[i] = A[i];
}
int i1 = left; int i2 = mid + 1;
for (int curr=left; curr<=right; curr++)
{
if (i1 == mid+1) A[curr] = temp[i2++];
else if (i2 > right) A[curr] = temp[i1++];
else if (temp[i1]< temp[i2]) A[curr] = temp[i1++];
else A[curr] = temp[i2++];
}
}
void mergesort_find(fstream &outfile,int A[],int n) //归并排序时间
{
for(int i=0;i<n;i++)
{
B[i]=A[i];
}
QueryPerformanceCounter(&frequency);
begin_=frequency.QuadPart;//获得初始值
mergesort(A,B,0,n-1); //进行归并排序
QueryPerformanceCounter(&frequency);
_end=frequency.QuadPart;//获得终止值
dfm=(double)(_end-begin_);//差值
dft=(dfm/dff)*1000;//差值除以频率得到时间,并转换为毫秒级别
outfile<<dft<<"ms"<<endl;
cout<<dft<<"ms"<<endl;
}
int main()
{
srand((int)time(NULL));//time seed
string files[10]={"100.txt","1K.txt","10K.txt","100K.txt","1M.txt","out_100.txt","out_1K.txt","out_10K.txt","out_100K.txt","out_1M.txt"};
fstream file; //产生测试文件结束,开始排序
QueryPerformanceFrequency(&frequency);//获得时钟频率
dff=(double)frequency.QuadPart; //取得频率
for(int i=0;i<5;i++) //对五个测试文件进行直接插入排序和归并排序
{
int n=100*pow(10,i);
cout<<n<<endl;
file.open(files[i],ios::in); //打开测试文件
Init(file,A,n); //初始化文件将数据填入数组中
file.close(); //关闭文件
file.open(files[i+5],ios::out); //打开输出文件
//对随机序列,正序序列,逆序序列进行直接插入排序:
file<<"对数据规模为"<<n<<"的数据进行直接插入排序,时间结果如下:"<<endl;
cout<<"对数据规模为"<<n<<"的数据进行直接插入排序,时间结果如下:"<<endl;
file<<" 对于随机序列,排序所需时间为: ";
cout<<" 对于随机序列,排序所需时间为: ";
inssort_find(file,A,n);
file<<" 对于正序序列,排序所需时间为: ";
cout<<" 对于正序序列,排序所需时间为: ";
inssort_find(file,A,n);
for(int i=0;i<n/2;i++)
{
int temp=A[i];
A[i]=A[n-1-i];
A[n-1-i]=temp;
}
file<<" 对于逆序序列,排序所需时间为: ";
cout<<" 对于逆序序列,排序所需时间为: ";
inssort_find(file,A,n);
file<<"序列进行直接插入排序结果如下:"<<endl;
Printf(file,A,n);
file.close();
file.open(files[i],ios::in); //打开测试文件
Init(file,A,n); //初始化文件将数据填入数组中
file.close(); //关闭文件
file.open(files[i+5],ios::app); //打开输出文件
//对随机序列,正序序列,逆序序列进行归并排序:
file<<"对数据规模为"<<n<<"的数据进行归并排序,时间结果如下:"<<endl;
cout<<"对数据规模为"<<n<<"的数据进行归并排序,时间结果如下:"<<endl;
file<<" 对于随机序列,排序所需时间为: ";
cout<<" 对于随机序列,排序所需时间为: ";
mergesort_find(file,A,n);
file<<" 对于正序序列,排序所需时间为: ";
cout<<" 对于正序序列,排序所需时间为: ";
mergesort_find(file,A,n);
for(int i=0;i<n/2;i++)
{
int temp=A[i];
A[i]=A[n-1-i];
A[n-1-i]=temp;
}
file<<" 对于逆序序列,排序所需时间为: ";
cout<<" 对于逆序序列,排序所需时间为: ";
mergesort_find(file,A,n);
file<<"序列进行直接插入归并排序结果如下:"<<endl;
Printf(file,A,n);
file.close(); //关闭文件
}
return 0;
}
编译调试结果:
二、记录实验结果
| 时间:2021.1.3 | 实验完成度:70% |
以表格形式记录在不同数据规模下,对随机排列、正序(已排序)和逆序(逆排序)的数据排序的时间(不同数据规模下排序后输出的数据结果可在输出TXT文件中查看):
直接插入排序时间 | 归并排序 | |||||
---|---|---|---|---|---|---|
数据规模 | 随机序列 | 正序序列 | 逆序序列 | 随机序列 | 正序序列 | 逆序序列 |
100 | 0.0154ms | 0.0003ms | 0.0284ms | 0.0085ms | 0.006ms | 0.0056ms |
1K | 1.4605ms | 0.0029ms | 3.2308ms | 0.1202ms | 0.0807ms | 0.0767ms |
10K | 167.161ms | 0.0341ms | 314.627ms | 1.3299ms | 0.7537ms | 0.7548ms |
100K | 17847.4ms | 0.2778ms | 33189.7ms | 19.0946ms | 10.5278ms | 9.8408ms |
1M | 459314ms | 10.5278ms | 6.71763e+006ms | 438.183ms | 270.226ms | 258.001ms |
三、实验结果分析
| 时间:2021.1.3 | 实验完成度:100% |
1.根据表格,对实验结果进行分析:
A. 随着数据规模的增大,直接插入排序和归并排序的排序时间均增大,但归并排序的增长幅度较直接插入排序相比明显小的更多,而且从数据规模为1M时,直接插入排序排序时间已经突增到459314ms 可以看出,随着数据规模的进一步加大,直接插入排序排序时间将更大幅度增长。但对于不同数据规模,对正序序列排序的时间,直接插入排序所用时间小于归并排序。
B. 对于不同的数据规模的数据,直接插入排序对逆序序列的排序时间相比较随机序列和正序序列要长,而归并排序对逆序序列的排序时间相比较随机序列和正序序列要短。
C. 在1M的数据规模下,等待直接插入排序对逆序序列的排序时间的计算超过1小时,由此可见,排序算法如果时间复杂度为θ(n *n ),将不适合做大规模数据的排序操作。
2.性能分析:
A.对于直接插入排序:
每执行一次内层for循环就要比较一次并交换一次,每一轮的最后一次比较找到应该插入的位置,此时没有发生交换,总排序次数是总比较次数减去n-1。
时间复杂度最佳情况下为0,在最差及平均情况下为θ(n *n )
新增中间变量进行交换,空间复杂度为θ(1)
B.对于归并排序:
在最佳、平均、最差情况下,时间复杂度为 θ (n log n)。
归并排序需要两倍的空间代价。
3.特点分析:
A.对于直接插入排序:
• 算法简单
• 时间复杂度为θ(n *n ),空间复杂度为θ(1)
• 初始序列基本(正向)有序时,时间复杂度为θ(n)
• 稳定的排序方法
B.对于归并排序:
• 需要辅助空间: θ(n)
• 整个归并需要 [log n/ log 2] 趟
• 时间复杂度: θ(n*log n/ log 2)
• 稳定的排序方法
• 思想可以推广到"多-路归并"
• 当输入的待排序数据存储在链表中时,归并排序是一个很好的选择
归并需要 [log n/ log 2] 趟
• 时间复杂度: θ(n*log n/ log 2)
• 稳定的排序方法
• 思想可以推广到"多-路归并"
• 当输入的待排序数据存储在链表中时,归并排序是一个很好的选择