使用插入排序优化的自底向上的归并排序的性能问题

使用插入排序优化的自底向上的归并排序的性能问题

使用了插入排序优化后,感觉性能差了不少,大约用时增加了50%,老师看看我是不是写的有问题呀,下面是完整的C++测试代码。

#include <iostream>
#include <ctime>
#include <iomanip>
#include <cassert>

using namespace std;

template <typename T>
class MergeSort {
private:
static void merge(T arr[], int l, int mid, int r, int temp[]) {
// 创建临时数组 temp 存放 arr[l, r] 的内容
// 复制当前的arr[l, r] 到 temp 中
for (int i = l; i <= r; i++) temp[i] = arr[i];
int i = l;
int j = mid + 1;
// 每轮循环为 arr[k] 赋值
// i 的取值范围为[l, mid]
// j 的取值范围为[mid + 1, r]
for (int k = l; k <= r; k++) {
// 取出 arr[i] 和 arr[j] 的最小值
// arr[j] 对应 temp[j - l], arr[i] 对应 temp[i - l]
if (i > mid) {
// i 已经超过范围
arr[k] = temp[j];
j++;
} else if (j > r) {
// j 已经超过范围
arr[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
// 注意这里如果 T 不是基本类型,需要重载 <= 运算符
arr[k] = temp[i];
i++;
} else {
arr[k] = temp[j];
j++;
}
}
}

public:
// 自底向上的归并排序(非递归)
static void sortBU(T arr[], int length) {
// 复制 arr 数组到 temp 数组
T *temp = new T[length];
for (int i = 0; i < length; i++) temp[i] = arr[i];

// 遍历合并的区间长度
for (int sz = 1; sz < length; sz += sz) {
// 遍历合并的两个区间的起始位置 i
// 合并 [i, i + sz - 1] 和 [i + sz, min(i + sz + sz - 1, length - 1)]
for (int i = 0; i + sz < length; i += sz + sz) {
int l = i;
int mid = i + sz - 1;
int r = min(i + sz + sz - 1, length - 1);
if (arr[mid] > arr[mid + 1]) merge(arr, l, mid, r, temp);
}
}
}

// 使用插入排序优化的自底向上的归并排序(非递归)
static void sortBUI(T arr[], int length) {
// 复制 arr 数组到 temp 数组
T *temp = new T[length];
for (int i = 0; i < length; i++) temp[i] = arr[i];

// 对于长度小于等于16的区间使用插入排序
for (int k = 0; k < length; k += 16) {
for (int i = k + 1; i < min(length - 1, k + 15); i++) {
T t = arr[i];
int j;
for (j = i; j - 1 >= 0 && t < arr[j - 1]; j--) arr[j] = arr[j - 1];
arr[j] = t;
}
}

// 遍历合并的区间长度
for (int sz = 1; sz < length; sz += sz) {
// 遍历合并的两个区间的起始位置 i
// 合并 [i, i + sz - 1] 和 [i + sz, min(i + sz + sz - 1, length - 1)]
for (int i = 0; i + sz < length; i += sz + sz) {
int l = i;
int mid = i + sz - 1;
int r = min(i + sz + sz - 1, length - 1);
if (arr[mid] > arr[mid + 1]) merge(arr, l, mid, r, temp);
}
}
}
};

template <typename T>
class SortHelper {
public:
// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
static int *generateRandomArray(int n, int rangeL, int rangeR) {
// 设置断言,保证正确的输入
assert(rangeL <= rangeR);
int *arr = new int[n];
// 设置随机数种子
srand((unsigned int)time(NULL));
for (int i = 0; i < n; i++) {
// 标准的随机数生成方式
arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
}
return arr;
}

// 生成有n个元素的近乎有序的数组
static int *generateNearlyOrderedArray(int n, int swapTimes) {
int *arr = new int[n];
for (int i = 0; i < n; i++) arr[i] = i;

srand((unsigned int)time(NULL));
for (int i = 0; i < swapTimes; i++) {
int posx = rand() % n;
int posy = rand() % n;
swap(arr[posx], arr[posy]);
}
return arr;
}

// 生成有n个元素的完全有序的数组
static int *generateOrderedArray(int n) {
int *arr = new int[n];
for (int i = 0; i < n; i++) arr[i] = i;
return arr;
}

static void printArray(T arr[], int n) {
for (int i = 0; i < n; i++) cout << arr[i] << " ";
cout << endl;
}

static bool isSorted(T arr[], int n) {
for (int i = 0; i < n - 1; i++)
if (arr[i] > arr[i + 1]) return false;
return true;
}

static void testSort(string sortName, void (*sort)(T[], int), T arr[],
int n) {
clock_t startTime = clock();
sort(arr, n);
clock_t endTime = clock();

assert(isSorted(arr, n));
cout << setw(17) << sortName << ", n = " << n << " : "
<< double(endTime - startTime) / CLOCKS_PER_SEC << " s" << endl;
}

// 拷贝整形数组
static int *copyIntArray(int a[], int n) {
int *arr = new int[n];
copy(a, a + n, arr);
return arr;
}
};

int main() {
int dataSize[] = {10000, 100000, 1000000, 10000000};
for (int n : dataSize) {
int *arr1 = SortHelper<int>::generateRandomArray(n, 0, n);
int *arr2 = SortHelper<int>::copyIntArray(arr1, n);

cout << "\nFor Random Array:\n";
SortHelper<int>::testSort("Merge Sort BU", MergeSort<int>::sortBU, arr1, n);
SortHelper<int>::testSort("Merge Sort BUI", MergeSort<int>::sortBUI, arr2, n);

arr1 = SortHelper<int>::generateNearlyOrderedArray(n, 10);
arr2 = SortHelper<int>::copyIntArray(arr1, n);

cout << "\nFor Nearly Ordered Array:\n";
SortHelper<int>::testSort("Merge Sort BU", MergeSort<int>::sortBU, arr1, n);
SortHelper<int>::testSort("Merge Sort BUI", MergeSort<int>::sortBUI, arr2, n);

arr1 = SortHelper<int>::generateOrderedArray(n);
arr2 = SortHelper<int>::copyIntArray(arr1, n);
cout << "\nFor Fully Ordered Array:\n";
SortHelper<int>::testSort("Merge Sort BU", MergeSort<int>::sortBU, arr1, n);
SortHelper<int>::testSort("Merge Sort BUI", MergeSort<int>::sortBUI, arr2, n);
}
}​

正在回答

登陆购买课程后可参与讨论,去登陆

1回答

对长度为 16 的数组使用插入排序以后,下面的归并排序 sz 就不需要从 1 开始了。


P.S. 貌似你的区间插入排序的代码也是有问题的,可以在调试一下。


我之前的一个课程有 C++ 的 merge sort bu + insertion 优化的示例代码,可以参考:https://github.com/liuyubobobo/Play-with-Algorithms/blob/master/03-Sorting-Advance/Course%20Code%20(C%2B%2B)/04-Merge-Sort-Bottom-Up/main.cpp


继续加油!:)

  • 我是没有昵称 提问者 #1

    确实上课还不够仔细,做完插入排序后,sz应该从16开始,要不然插入排序部分就白做了,另外之前的插入排序算法也有一些问题。下面是修改后的代码:感觉应该ok啦,谢谢老师!

    ​#include <cassert>
    #include <ctime>
    #include <iomanip>
    #include <iostream>

    using namespace std;

    template <typename T>
    class MergeSort {
    private:
    static void merge(T arr[], int l, int mid, int r, int temp[]) {
    // 创建临时数组 temp 存放 arr[l, r] 的内容
    // 复制当前的arr[l, r] 到 temp 中
    for (int i = l; i <= r; i++) temp[i] = arr[i];
    int i = l;
    int j = mid + 1;
    // 每轮循环为 arr[k] 赋值
    // i 的取值范围为[l, mid]
    // j 的取值范围为[mid + 1, r]
    for (int k = l; k <= r; k++) {
    // 取出 arr[i] 和 arr[j] 的最小值
    // arr[j] 对应 temp[j - l], arr[i] 对应 temp[i - l]
    if (i > mid) {
    // i 已经超过范围
    arr[k] = temp[j];
    j++;
    } else if (j > r) {
    // j 已经超过范围
    arr[k] = temp[i];
    i++;
    } else if (temp[i] <= temp[j]) {
    // 注意这里如果 T 不是基本类型,需要重载 <= 运算符
    arr[k] = temp[i];
    i++;
    } else {
    arr[k] = temp[j];
    j++;
    }
    }
    }

    public:
    // 自底向上的归并排序(非递归)
    static void sortBU(T arr[], int length) {
    // 复制 arr 数组到 temp 数组
    T *temp = new T[length];
    for (int i = 0; i < length; i++) temp[i] = arr[i];

    // 遍历合并的区间长度
    for (int sz = 1; sz < length; sz += sz) {
    // 遍历合并的两个区间的起始位置 i
    // 合并 [i, i + sz - 1] 和 [i + sz, min(i + sz + sz - 1, length - 1)]
    for (int i = 0; i + sz < length; i += sz + sz) {
    int l = i;
    int mid = i + sz - 1;
    int r = min(i + sz + sz - 1, length - 1);
    if (arr[mid] > arr[mid + 1]) merge(arr, l, mid, r, temp);
    }
    }
    }

    // 使用插入排序优化的自底向上的归并排序(非递归)
    static void sortBUI(T arr[], int length) {
    // 复制 arr 数组到 temp 数组
    T *temp = new T[length];
    for (int i = 0; i < length; i++) temp[i] = arr[i];

    // 对于所有arr[i, i + 15]的区间使用插入排序
    for (int i = 0; i < length; i += 16) {
    int l = i;
    int r = min(length - 1, i + 15);
    for (int i = l + 1; i <= r; i++) {
    T e = arr[i];
    int j;
    for (j = i; j > l && e < arr[j - 1]; j--) arr[j] = arr[j - 1];
    arr[j] = e;
    }
    }

    // 遍历合并的区间长度,注意这里 sz 从 16 开始
    for (int sz = 16; sz < length; sz += sz) {
    // 遍历合并的两个区间的起始位置 i
    // 合并 [i, i + sz - 1] 和 [i + sz, min(i + sz + sz - 1, length - 1)]
    for (int i = 0; i + sz < length; i += sz + sz) {
    int l = i;
    int mid = i + sz - 1;
    int r = min(i + sz + sz - 1, length - 1);
    if (arr[mid] > arr[mid + 1]) merge(arr, l, mid, r, temp);
    }
    }
    }
    };

    template <typename T>
    class SortHelper {
    public:
    // 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
    static int *generateRandomArray(int n, int rangeL, int rangeR) {
    // 设置断言,保证正确的输入
    assert(rangeL <= rangeR);
    int *arr = new int[n];
    // 设置随机数种子
    srand((unsigned int)time(NULL));
    for (int i = 0; i < n; i++) {
    // 标准的随机数生成方式
    arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
    }
    return arr;
    }

    // 生成有n个元素的近乎有序的数组
    static int *generateNearlyOrderedArray(int n, int swapTimes) {
    int *arr = new int[n];
    for (int i = 0; i < n; i++) arr[i] = i;

    srand((unsigned int)time(NULL));
    for (int i = 0; i < swapTimes; i++) {
    int posx = rand() % n;
    int posy = rand() % n;
    swap(arr[posx], arr[posy]);
    }
    return arr;
    }

    // 生成有n个元素的完全有序的数组
    static int *generateOrderedArray(int n) {
    int *arr = new int[n];
    for (int i = 0; i < n; i++) arr[i] = i;
    return arr;
    }

    static void printArray(T arr[], int n) {
    for (int i = 0; i < n; i++) cout << arr[i] << " ";
    cout << endl;
    }

    static bool isSorted(T arr[], int n) {
    for (int i = 0; i < n - 1; i++)
    if (arr[i] > arr[i + 1]) return false;
    return true;
    }

    static void testSort(string sortName, void (*sort)(T[], int), T arr[],
    int n) {
    clock_t startTime = clock();
    sort(arr, n);
    clock_t endTime = clock();

    assert(isSorted(arr, n));
    cout << setw(17) << sortName << ", n = " << n << " : "
    << double(endTime - startTime) / CLOCKS_PER_SEC << " s" << endl;
    }

    // 拷贝整形数组
    static int *copyIntArray(int a[], int n) {
    int *arr = new int[n];
    copy(a, a + n, arr);
    return arr;
    }
    };

    int main() {
    int dataSize[] = {10000, 100000, 1000000, 10000000, 100000000};
    for (int n : dataSize) {
    int *arr1 = SortHelper<int>::generateRandomArray(n, 0, n);
    int *arr2 = SortHelper<int>::copyIntArray(arr1, n);

    cout << "\nFor Random Array:\n";
    SortHelper<int>::testSort("Merge Sort BU", MergeSort<int>::sortBU, arr1, n);
    SortHelper<int>::testSort("Merge Sort BUI", MergeSort<int>::sortBUI, arr2, n);

    arr1 = SortHelper<int>::generateNearlyOrderedArray(n, 10);
    arr2 = SortHelper<int>::copyIntArray(arr1, n);

    cout << "\nFor Nearly Ordered Array:\n";
    SortHelper<int>::testSort("Merge Sort BU", MergeSort<int>::sortBU, arr1, n);
    SortHelper<int>::testSort("Merge Sort BUI", MergeSort<int>::sortBUI, arr2, n);

    arr1 = SortHelper<int>::generateOrderedArray(n);
    arr2 = SortHelper<int>::copyIntArray(arr1, n);
    cout << "\nFor Fully Ordered Array:\n";
    SortHelper<int>::testSort("Merge Sort BU", MergeSort<int>::sortBU, arr1, n);
    SortHelper<int>::testSort("Merge Sort BUI", MergeSort<int>::sortBUI, arr2, n);
    }
    }


    2021-04-13 22:10:56
  • liuyubobobo 回复 提问者 我是没有昵称 #2

    大赞!继续加油!:)

    2021-04-13 22:41:38
问题已解决,确定采纳
还有疑问,暂不采纳

恭喜解决一个难题,获得1积分~

来为老师/同学的回答评分吧

0 星
算法与数据结构
  • 参与学习       2583    人
  • 解答问题       1082    个

慕课网算法名师Liuyubobobo,5年集大成之作 从0到工作5年,算法与数据结构系统解决方案

了解课程
请稍等 ...
意见反馈 帮助中心 APP下载
官方微信

在线咨询

领取优惠

免费试听

领取大纲

扫描二维码,添加
你的专属老师