Bootstrap

数据结构实验四

哈尔滨工业大学计算学部

实验报告

课程名称:数据结构与算法

实验项目:排序算法及其应用

实验题目:内存排序算法

  • 实验目的

排序是计算机科学中的常见任务,它将一组无序的数据元素按照某种规则重新排列,以使得数据呈现有序的状态,便于后续的查找、统计和分析等操作。当数据量较小时,将数据全部读入内存并进行排序的算法称为内存排序算法,常见的内存排序算法有:插入排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。本实验要求设计并实现上述内存排序算法并比较其运行速度。

二、实验要求及实验环境

实验要求:

1. 从文本文件中将两行数据读入内存,其中第一行有一个整数 n(n≤100000),表示待排序序列的长度,第二行有 n 个整数,用空格隔开,表示待排序序列。

2. 实现归并排序、快速排序算法,输出排序好的序列,并记录算法运行时间。

3. 实现选择排序算法或插入排序算法,并将其运行时间与归并排序、快速排序算法比较,随机生成多个适当规模的数据进行实验并绘制折线图,反映不同算法运行时间随着输入规模的变化趋势,并与理论分析结果进行比较。

实验环境:

我本次使用Visual Studio Code来编写C++程序。

三、设计思想(本程序中的用到的所有数据类型的定义,主程序的流程图及各程序模块之间的调用关系、核心算法的主要步骤)

1.所用到的数据结构

全局变量data数组及temp数组,因为在函数内部声明的数组最大只能达到100000,而测试不同算法性能需要更大的数组,故将数组声明为全局变量,以便声明更大的数组。

  1. 算法设计逻辑及程序运行效果:

(1)int ReadData()

该函数用于从文件中读取待排序数据,文件中第一行是总数据个数,下面是数据,故先读取总个数,再依次读取待排序数据到数组。下图为文件部分内容。

13100

1 7 5 8 15 65 21 14 20 6 9 12 11 42 30 10 254 214 31 200 478 364 457 145 324 8745 1478 1254 89 654 561 56 516 15 165 165 165 64 98 49 84 98 165 31 51 65 64 6 84 984 4 651 513 13 2165 684 984 56 1 516 548 4 48 4 5665 561 561 5 165 684 894 98  984 856 165 131 213 2 165 6548 998 3156 165 65 48 4 984 123 32 123 11 561 56 1 51 65 651  31 1 3 3 2 22 3 2 2 6516 5 61 68 98 498 49 9 8 123 32 0 565 165 8498 44 988 4 656 56 1 84 98 4 4

1 7 5 8 15 65 21 14 20 6 9 12 11 42 30 10 254 214 31 200 478 364 457 145 324 8745 1478 1254 89 654 561 56 516 15 165 165 165 64 98 49 84 98 165 31 51 65 64 6 84 984 4 651 513 13 2165 684 984 56 1 516 548 4 48 4 5665 561 561 5 165 684 894 98  984 856 165 131 213 2 165 6548 998 3156 165 65 48 4 984 123 32 123 11 561 56 1 51 65 651  31 1 3 3 2 22 3 2 2 6516 5 61 68 98 498 49 9 8 123 32 0 565 165 8498 44 988 4 656 56 1 84 98 4 4

2void ShowData(int sum)

该函数用于输出从文件中读取的待排序数据,由于在ReadData中获取了总数据个数,使用一个for循环即可实现。

3void Swap(int *i, int *j)

该函数用于交换i和j,引入中间变量易于实现。

4void MergeSort(int left, int right)

该函数用于归并排序,参数left、right分别为待排序区间的端点,如果left小于right,则求出中间下标mid后,分别递归划分左右两侧数组,划分后需要将数据再合并为按照从小到大的顺序存储,合并的实现使用函数Merge。下图为运行时间。

5void Merge(int left, int mid, int right)

该函数用于将两个已排好序的数组合并,一个是left到mid,另一个是mid+1到right,在i小于等于mid并且j小于等于right时候,取两个数组对应i与j中较大的那个值,直到有一个数组的元素全都遍历过一遍之后,则将另一个数组后面的值依次赋值给结果数组Temp,并最终把Temp中的值赋值给data。

(6)void QuickSort(int left, int right)

这个函数实现了快速排序算法,通过选定数组的第一个元素作为基准,然后利用双指针技术将数组重新排列,使得小于或等于基准的元素在其左侧,大于基准的元素在其右侧,接着递归地对基准左右两侧的子数组执行相同的操作,直到整个数组完全有序。下图为运行时间。

(7)void SelectSort(int n)

该函数用于选择排序,每次都选择未排序数据中最小的和排好序的下一个位置的数据交换,实现排序。下图为运行时间。

四、测试结果

选择排序,快速排序,归并排序运行时间比较,初始生成随机数为10000,以10000的步长递增来记录运行时间,由于当数据量达到420000时,选择排序运行时间达到接近三分钟,故下面运行结果只展示到数据量达到420000,但已经足以可见,选择排序算法随数据量增大运行时间增长速度远高于快速排序和归并排序,是因为选择排序的时间复杂度为o(n^2),快速排序和归并为o(nlogn)。

其中第一列为数据量,后面三列分别是快排、归并、选择所用时间(s)。

10000 0.000000 0.000000 0.080000

20000 0.011000 0.000000 0.328000

30000 0.013000 0.001000 0.734000

40000 0.016000 0.000000 1.313000

50000 0.020000 0.010000 2.031000

60000 0.038000 0.000000 2.981000

70000 0.057000 0.000000 4.008000

80000 0.057000 0.010000 6.339000

90000 0.103000 0.010000 7.701000

100000 0.128000 0.000000 9.598000

110000 0.132000 0.005000 11.604000

120000 0.189000 0.009000 14.255000

130000 0.223000 0.013000 16.577000

140000 0.245000 0.012000 18.748000

150000 0.281000 0.008000 21.756000

160000 0.318000 0.009000 24.257000

170000 0.323000 0.011000 27.378000

180000 0.364000 0.010000 32.778000

190000 0.466000 0.011000 34.219000

200000 0.444000 0.011000 37.553000

210000 0.468000 0.012000 41.922000

220000 0.588000 0.012000 45.674000

230000 0.577000 0.012000 51.131000

240000 0.635000 0.013000 55.224000

250000 0.678000 0.016000 61.573000

260000 0.893000 0.015000 67.721000

270000 0.959000 0.017000 72.889000

280000 0.949000 0.017000 74.903000

290000 0.949000 0.016000 80.710000

300000 1.038000 0.022000 90.186000

310000 1.049000 0.026000 81.069000

320000 1.010000 0.019000 84.255000

330000 1.069000 0.019000 90.972000

340000 1.139000 0.019000 102.600000

350000 1.423000 0.020000 116.699000

360000 1.483000 0.021000 112.862000

370000 1.321000 0.022000 126.777000

380000 1.650000 0.021000 137.824000

390000 1.860000 0.028000 126.511000

400000 1.567000 0.025000 137.291000

410000 1.643000 0.025000 139.803000

420000 1.696000 0.019000 161.591000

下图为使用python程序及上面截图中的数据绘制出的三种算法的运行时间随数据量变化图。可以看到快速排序和归并排序都远优于选择排序,选择排序所需时间随数据量增大增长较快,因为选择排序的时间复杂度为o(n^2),快速排序和归并为o(nlogn)。

五、经验体会与不足

通过此次实验的练习我更加熟悉排序算法,并且发现了之前学习排序算法上的漏洞,并且由于程序代码较多,存在“牵一发而动全身”的情况,我认识到先有一个整体架构再填充细节的重要性,每修改一个地方都应注意是否会影响程序的其它部分。

#include <iostream>
#include <ctime>

using namespace std;
#define MAX 10000000
#define N 1000

int Data[MAX];
int Temp[MAX];

int ReadData();
void ShowData(int sum);
void Swap(int *i, int *j);
void QuickSort(int left, int right);
void MergeSort(int left, int right);
void SelectSort(int n);
void Merge(int left, int mid, int right);

int main()
{
    srand((unsigned)time(NULL));
    clock_t start, end;
    double time_used;
    //初始
    int sum = ReadData();
    // printf("the unsorted list is:\n");
    // ShowData(sum);

    // //快速排序
    // start = clock();
    // QuickSort(1, sum);
    // end = clock();
    // printf("\nthe sorted list use quick sort\n");
    // // ShowData(sum);
    // time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
    // printf("\nthe time quick sort use: %f seconds\n", time_used);

    // //归并排序
    // sum = ReadData();
    // start = clock();
    // MergeSort(1, sum);
    // end = clock();
    // printf("\nthe sorted list use merge sort\n");
    // // ShowData(sum);
    // time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
    // printf("\nthe time merge sort use: %f seconds\n", time_used);

    // //选择排序
    // sum = ReadData();
    // start = clock();
    // SelectSort(sum);
    // end = clock();
    // printf("\nthe sorted list use select sort\n");
    // // ShowData(sum);
    // time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
    // printf("\nthe time select sort use: %f seconds\n", time_used);

    //随机数据对比
    FILE *fp = fopen("result.txt", "w");
    for (int j = 10000; j <= 420000; j = j + 10000)
    {
        for (int i = 1; i <= j; i++)
        {
            Temp[i] = rand() % 100;
            Data[i] = Temp[i];
        }
        fprintf(fp, "%d\t", j);
        printf("%d\n", j);

        start = clock();
        QuickSort(1, j);
        end = clock();
        time_used = ((double)(end - start)) / CLOCKS_PER_SEC;

        fprintf(fp, "%f\t", time_used);
        for (int i = 1; i <= j; i++)
        {
            Data[i] = Temp[i];
        }
        start = clock();
        MergeSort(1, j);
        end = clock();
        time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
        fprintf(fp, "%f\t", time_used);

        for (int i = 1; i <= j; i++)
        {
            Data[i] = Temp[i];
        }
        start = clock();
        SelectSort(j);
        end = clock();
        time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
        fprintf(fp, "%f\t", time_used);
        fprintf(fp, "\n");
    }
    fclose(fp);

    return 0;
}

int ReadData()
{
    FILE *fp = fopen("input.txt", "r");
    int sum;
    int i;
    fscanf(fp, "%d", &sum);
    for (int i = 1; i <= sum; i++)
        fscanf(fp, "%d", &Data[i]);
    fclose(fp);
    return sum;
}

void ShowData(int sum)
{
    for (int i = 1; i <= sum; i++)
        printf("%d ", Data[i]);
    printf("\n");
}

void Swap(int *i, int *j)
{
    int temp;
    temp = *i;
    *i = *j;
    *j = temp;
}

void QuickSort(int left, int right)
{
    int i, j, pivot;
    if (left < right)
    {
        pivot = left;
        i = left;
        j = right;
        while (i < j)
        {
            while (Data[i] <= Data[pivot] && i < right)
                i++;
            while (Data[j] > Data[pivot])
                j--;
            if (i < j)
                Swap(&Data[i], &Data[j]);
        }
        Swap(&Data[pivot], &Data[j]);
        QuickSort(left, j - 1);
        QuickSort(j + 1, right);
    }
}

void SelectSort(int n)
{
    int lowkey;
    int lowindex;
    for (int i = 1; i <= n; i++)
    {
        lowindex = i;
        lowkey = Data[i];
        for (int j = i + 1; j <= n; j++)
        {
            if (Data[j] < lowkey)
            {
                lowkey = Data[j];
                lowindex = j;
            }
        }
        if (i != lowindex)
            Swap(&Data[i], &Data[lowindex]);
    }
}

void MergeSort(int left, int right)
{
    int mid;
    if (left < right)
    {
        mid = (left + right) / 2;
        MergeSort(left, mid);
        MergeSort(mid + 1, right);
        Merge(left, mid, right);
    }
}

void Merge(int left, int mid, int right)
{
    int i = left;
    int j = mid + 1;
    int k = left;
    while (i <= mid && j <= right)
        Temp[k++] = (Data[i] <= Data[j]) ? Data[i++] : Data[j++];
    while (i <= mid)
        Temp[k++] = Data[i++];
    while (j <= right)
        Temp[k++] = Data[j++];
    while (left <= right)
    {
        Data[left] = Temp[left];
        left++;
    }
}
import matplotlib.pyplot as plt

data_sizes = []
quick_sort_times = []
merge_sort_times = []
selection_sort_times = []

with open("result.txt", "r") as file:
    for line in file:
        parts = line.strip().split("\t")
        if len(parts) == 4:
            data_sizes.append(int(parts[0]))
            quick_sort_times.append(float(parts[1]))
            merge_sort_times.append(float(parts[2]))
            selection_sort_times.append(float(parts[3]))

plt.figure(figsize=(10, 6))

plt.plot(data_sizes, quick_sort_times, label="Quick Sort", marker="o")
plt.plot(data_sizes, merge_sort_times, label="Merge Sort", marker="s")
plt.plot(data_sizes, selection_sort_times, label="Selection Sort", marker="^")

plt.xlabel("Data Size")
plt.ylabel("Time (seconds)")
plt.title("Comparison of Sorting Algorithms Performance")

plt.legend()

plt.grid(True)

plt.show()

;