O(n^2)时间复杂度的算法 学习笔记一

本文深入讲解了选择排序和插入排序的原理与实现,通过对比分析两种算法的时间复杂度和性能表现,强调了插入排序在特定场景下的优势。同时,文章提供了详细的C++代码示例,包括算法实现、测试方法及效率评估。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

刘宇波《算法与数据结构》 学习笔记

为什么要学O(n^2)时间复杂度的算法?

  • 基础, 可由简单衍生出复杂的算法
  • 简单场景下易于实现
  • 特殊情况下,可能更优
  • 作为的子过程,对复杂排序算法优化

准备工作

为了方便排序算法的测试,先创建一个随机数产生方法和一个打印方法。
新建一个头文件 SortTestHelper.h

   //
// Created by BMElab on 9/3/2019.
//

#ifndef SORT_SORTTESTHELPER_H
#define SORT_SORTTESTHELPER_H

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

using namespace std;

namespace SortTestHelper{
    int* genRandomArray(int n, int rangeL, int rangeR){
        assert(rangeL < rangeR);
        int *arr = new int[n];
        srand(time(NULL));
        for (int i=0; i<n; i++)
            arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
        return arr;
    }

    void printArray(int n, int *arr){
        for (int i=0; i<n; i++)
            std::cout << arr[i] << std::ends;
    }
}
#endif //SORT_SORTTESTHELPER_H

测试两个方法:

   #include <iostream>
#include "SortTestHelper.h"

int main() {
    int n = 10;
    int *arr = SortTestHelper::genRandomArray(n, 0, n);
    int setArr[10] = {1, 2, 3, 4, 5, 6, 6, 7, 8, 9};
    SortTestHelper::printArray(n, arr);
    SortTestHelper::printArray(n, setArr);
}

运行结果:

D:\cpp_projects\sort\cmake-build-debug\sort.exe
5 2 6 1 10 4 10 1 2 1 1 2 3 4 5 6 6 7 8 9

Process finished with exit code 0

选择排序

   void sectionSort(int n, int arr[]){
    for (int i=0; i<n; i++){
        int minInd = i;
        for (int j=i+1; j<n; j++){
            if (arr[j] < arr[minInd])
                minInd = j;
        }
        swap(arr[i], arr[minInd]);
    }
}

这里的swap是标准库中的函数。

测试:

   #include <iostream>
#include "SortTestHelper.h"

using namespace std;

void sectionSort(int arr[]int n){
    for (int i=0; i<n; i++){
        int minInd = i;
        for (int j=i+1; j<n; j++){
            if (arr[j] < arr[minInd])
                minInd = j;
        }
        swap(arr[i], arr[minInd]);
    }
}

int main() {

    int n = 10;
    int setArr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    sectionSort(setArr, n);
    SortTestHelper::printArray(setArr, n);
    return 0;
}

关于 using namespace std 的使用问题,会对命名空间造成污染,这里不考虑那么多,只为了快速方便实现算法而使用。

模板函数

使用模板使排序算法能够对多种数据类型进行排序
将排序算法声明为模板函数并修改其参数如下:

   template <typename T>
void sectionSort(T arr[]int n){
    for (int i=0; i<n; i++){
        int minInd = i;
        for (int j=i+1; j<n; j++){
            if (arr[j] < arr[minInd])
                minInd = j;
        }
        swap(arr[i], arr[minInd]);
    }
}

创建一个模板,并将形参类型改为T即可。

测试:

   #include <iostream>
#include "SortTestHelper.h"
#include <string>

using namespace std;

template <typename T>
void sectionSort(T arr[]int n){
for (int i=0; i<n; i++){
    int minInd = i;
    for (int j=i+1; j<n; j++){
        if (arr[j] < arr[minInd])
            minInd = j;
    }
    swap(arr[i], arr[minInd]);
}
}

int main() {

    int n = 10;
    int setArrA[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    sectionSort(setArrA, n);
    for (int i=0; i<n; i++)
        cout<<setArrA[i]<<ends;
    cout << endl;

    float setArrB[5] = {10.1, 9.1, 8.1, 7.1, 6.1};
    sectionSort(setArrB, 5);
    for (int i=0; i<5; i++)
        cout<<setArrB[i]<<ends;
    cout << endl;

    string setArrC[5] = {"E", "D", "C", "B", "A"};
    sectionSort(setArrC, 5);
    for (int i=0; i<5; i++)
        cout<<setArrC[i]<<ends;
    cout << endl;
return 0;
}

结果:

D:\cpp_projects\sort\cmake-build-debug\sort.exe
1 2 3 4 5 6 7 8 9 10 
6.1 7.1 8.1 9.1 10.1
A B C D E  

建立测试模板

使用测试模板更为简便地测试每个算法的效率和正确性:
修改前面的SortTestHelper.h头文件,添加两个函数isTrue(), testSort().
isTrue()为了确定排序是否正确。
testSort(string sortName, void(*sort)(int, T[]), T arr[], int n)
用于测试排序算法的效率,第一个参数是要测试的函数名,第二个参数为一个指针,指向被测试函数,第三个参数为测试的数组,第四个参数为被测数据个数。

   //
// Created by BMElab on 9/3/2019.
//

#ifndef SORT_SORTTESTHELPER_H
#define SORT_SORTTESTHELPER_H

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

using namespace std;
namespace SortTestHelper{
    int* genRandomArray(int n, int rangeL, int rangeR){
        assert(rangeL < rangeR);
        int *arr = new int[n];
        srand(time(NULL));
        for (int i=0; i<n; i++)
            arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;
        return arr;
    }

    template <typename T>
    void printArray(T arr[], int n){
        for (int i=0; i<n; i++)
            std::cout << arr[i] << std::ends;
        cout<<endl;
        return;
    }

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

    // build a test sort function for all kinds of sort methods
    template <typename T>
    void testSort(string sortName, void(*sort)(T[], int), T arr[], int n){
        clock_t startTime = clock();
        sort(arr, n);
        clock_t endTime = clock();

        assert(isTrue(arr, n));
        cout << sortName<<" " << double(endTime - startTime) / CLOCKS_PER_SEC << " s" << endl;
        return;
    }	
}
#endif //SORT_SORTTESTHELPER_H

测试:

#include <iostream>
#include "SortTestHelper.h"

using namespace std;

template <typename T>
void sectionSort(T arr[], int n){
    for (int i=0; i<n; i++){
        int minInd = i;
        for (int j=i+1; j<n; j++){
            if (arr[j] < arr[minInd])
                minInd = j;
        }
        swap(arr[i], arr[minInd]);
    }
}

int main(){
    int n = 100000;
    int *arr = SortTestHelper::genRandomArray(n, 0, n);
    sectionSort(arr, n);
    SortTestHelper::testSort("sectionSort", sectionSort, arr, n);
    delete[] arr;

    return 0;

}

结果:

sectionSort 12.007 s

插入排序

遍历数组,依次比较当前位置 j 上的值与其j-1的值大小, 如果当前值小于前一值,则交换两个数的位置,继续比较 j-1与 j-2 位置上的值,直到 j-n=0。

template <typename T>
void insertSort(T arr[], int n){
    for (int i=1; i<n; i++){
        for (int j=i; j>0 && arr[j-1] > arr[j]; j--)
             swap(arr[j-1], arr[j]);
    }
}

性能比较和分析

#include <iostream>
#include "SortTestHelper.h"
//#include <string>

using namespace std;

template <typename T>
void sectionSort(T arr[], int n){
    for (int i=0; i<n; i++){
        int minInd = i;
        for (int j=i+1; j<n; j++){
            if (arr[j] < arr[minInd])
                minInd = j;
        }
        swap(arr[i], arr[minInd]);
    }
}

template <typename T>
void insertSort(T arr[], int n){
    for (int i=1; i<n; i++){
        for (int j=i; j>0 && arr[j-1] > arr[j]; j--)
		     swap(arr[j-1], arr[j]);
    }
}

int main(){
    int n = 10000;
    int *arr = SortTestHelper::genRandomArray(n, 0, n);
    int *copyArr = SortTestHelper::copyArray(arr, n);

    SortTestHelper::testSort("sectionSort", sectionSort, arr, n);
    SortTestHelper::testSort("insertSort", insertSort, copyArr, n);
    delete[] arr;
    delete[] copyArr;

    return 0;
}
D:\cpp_projects\sort\cmake-build-debug\sort.exe
sectionSort 0.135 s
insertSort 0.351 s

上面的SortTestHelper::copyArray(arr, n)函数定义在头文件SortTestHelper中

    template <typename T>
    T* copyArray(T arr[], int n){
        T* new_arr = new int[n];
        copy(arr, arr+n, new_arr);
        return new_arr;
    }

对于万级的数据量,插入排序的性能比选择排序差。
理论上插入排序应该不选择排序性能好,因为选择排序第二轮循环中每次都要对剩下的所有数进行比较,而插入排序在不满足arr[j-1] > arr[j]时能够提前停止。
为什么还插入排序的性能比选择排序差?
分析具体的实现过程,我们发现在插入排序中,第二轮循环会发生多次的交换操作。 虽然比较的次数相对于选择排序少了,但是交换次数远远多于选择排序。
交换操作设计到数组的索引,3次复制,耗时远大于比较操作。
所以改进的方法就是减少交换操作的次数,使用赋值操作代替交换操作。

插入算法的改进

template <typename T>
void insertSort1(T arr[], int n){
    for (int i=1; i<n; i++){
        T cur = arr[i];
        int j;
        for (j=i; j>0 && arr[j - 1]>cur; j--)
            arr[j] = arr[j - 1];
        arr[j] = cur;
    }
}

先复制当前的值T cur = arr[i], 当在第二轮循环中arr[j-1] > cur ,将前一个值后移arr[j] = arr[j - 1]。进行下轮比较,每次比较都是与复制的cur比较,若不满足arr[j - 1] > cur,说明当前的值cur处在正确的位置上,arr[j] = cur。

测试结果:

D:\cpp_projects\sort\cmake-build-debug\sort.exe
sectionSort 0.133 s
insertSort 0.303 s
insertSort1 0.122 s

插入排序优于选择排序。

插入排序的重要特性

第二轮循环提前终止是插入排序的重要特性。
在近乎有序的数组中,插入排序性能远远优于选择排序。
在小规模的数列中,数据就近乎有序,因此在复杂的排序中,常使用插入排序作为最后排序的优化算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值