zoukankan      html  css  js  c++  java
  • 插入排序

    -------------------siwuxie095

       

       

       

       

       

       

       

       

    插入排序法

       

       

    它的工作原理如下:

       

    通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入

       

       

    参考链接:

    参考链接1参考链接2参考链接3

       

       

       

       

       

    程序 1:插入排序法的实现

       

    SortTestHelper.h:

       

    #ifndef SORTTESTHELPER_H

    #define SORTTESTHELPER_H

       

    #include <iostream>

    #include <string>

    #include <ctime>

    #include <cassert>

    using namespace std;

       

       

    //辅助排序测试

    namespace SortTestHelper

    {

       

    //生成测试数据(测试用例),返回一个随机生成的数组:

    //生成有n个元素的随机数组,每个元素的随机范围为[rangeL,rangeR]

    int *generateRandomArray(int n, int rangeL, int rangeR)

    {

    //默认rangeL要小于等于rangeR

    assert(rangeL <= rangeR);

       

    int *arr = new int[n];

       

    //对于数组中的每一个元素,将之随机成为rangeLrangeR之间的随机数

    //先设置随机种子:这里将当前的时间作为种子来进行随机数的设置

    srand(time(NULL));

       

    for (int i = 0; i < n; i++)

    {

    //rand()函数+百分号+数的范围,即 取中间的一个随机整数,再加上rangeL即可

    arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;

    }

    return arr;

    }

       

       

    //生成一个近乎有序的数组

    int *generateNearlyOrderedArray(int n, int swapTimes)

    {

    //先生成完全有序的数组

    int *arr = new int[n];

    for (int i = 0; i < n; i++)

    {

    arr[i] = i;

    }

       

    //以当前时间为随机种子

    srand(time(NULL));

       

    //再随机挑选几对元素进行交换,就是一个近乎有序的数组了

    for (int i = 0; i < swapTimes; i++)

    {

    int posx = rand() % n;

    int posy = rand() % n;

    swap(arr[posx], arr[posy]);

    }

       

    return arr;

    }

       

       

    template<typename T>

    void printArray(T arr[], int n)

    {

    for (int i = 0; i < n; i++)

    {

    cout << arr[i] << " ";

    }

    cout << endl;

    }

       

       

    //经过排序算法排序后,再次确认是否已经完全排序

    template<typename T>

    bool isSorted(T arr[], int n)

    {

    for (int i = 0; i < n - 1; i++)

    {

    if (arr[i]>arr[i + 1])

    {

    return false;

    }

    }

    return true;

    }

       

       

    //衡量一个算法的性能如何,最简单的方式就是看这个算法在特定数据集上的执行时间

    //1)传入排序算法的名字,方便打印输出

    //2)传入排序算法本身,即函数指针

    //3)传入测试用例:数组和元素个数

    template<typename T>

    void testSort(string sortName, void(*sort)(T[], int), T arr[], int n)

    {

    //在排序前后分别调用clock()函数

    //时间差就是该排序算法执行的时钟周期的个数

    clock_t startTime = clock();

    sort(arr, n);

    clock_t endTime = clock();

       

    assert(isSorted(arr, n));

       

    //endTime 减去 startTime 转为double类型,除以 CLOCKS_PER_SEC,其中:

    //

    //CLOCKS_PER_SEC 是标准库中定义的一个宏,表示每一秒钟所运行的时钟周期

    //的个数,而(endTime-startTime)返回的是运行了几个时钟周期

    //

    //这样,最终的结果就是在这段时间中程序执行了多少秒

    cout << sortName << "" << double(endTime - startTime) / CLOCKS_PER_SEC

    << "s" << endl;

    }

       

       

    //复制数组

    int *copyIntArray(int a[], int n)

    {

    int *arr = new int[n];

    //copy()函数在std中:

    //第一个参数是原数组的头指针,

    //第二个参数是原数组的尾指针,

    //第三个参数是目的数组的头指针

    //

    //注意:copy()函数运行时会报错,需要在:

    //项目->属性->配置属性->C/C++->预处理器->预处理器定义

    //在其中添加:_SCL_SECURE_NO_WARNINGS

    copy(a, a + n, arr);

    return arr;

    }

    }

       

    #endif

       

       

       

    InsertionSort.h:

       

    #ifndef INSERTIONSORT_H

    #define INSERTIONSORT_H

       

    //插入排序是 O(n^2) 级别算法复杂度的排序算法,它的思想就类似于

    //在玩扑克牌时插入牌的思想,即 将拿到的一张牌插入到合适的位置,

    //当最后一张牌也插入后,手中的整副扑克牌也就排序完成了

       

    //插入排序:从小到大进行排序

    template<typename T>

    void insertionSort(T arr[], int n)

    {

    //对于插入排序来说,第一个元素(索引为0)根本不用考虑

    //

    //因为在插入排序的初始,第一个元素放在那里,它本身就已经

    //有序了,不需要再把它插入到前面的任何位置,所以从第二个

    //元素(索引为1)开始考察

    for (int i = 1; i < n; i++)

    {

    //寻找arr[i]合适的插入位置:

    //对已经排好序的部分,插入时从后向前比较,

    //最多只会考察到j=1时,因为是从后向前比较

    //

    //写法1

    for (int j = i; j > 0; j--)

    {

    if (arr[j] < arr[j - 1])

    {

    swap(arr[j], arr[j - 1]);

    }

    else

    {

    //arr[i]插入到合适位置后,就跳出当前循环

    //继续对下一个元素进行考察

    break;

    }

    }

       

    ////for循环中的 if 条件加入到for循环中

    //(内层循环的两种写法,可替代上面的写法1

    //写法2

    //for (int j = i; j > 0 && arr[j] > arr[j - 1]; j--)

    //{

    // swap(arr[j], arr[j - 1]);

    //}

    }

    }

       

       

    #endif

       

       

       

    main.cpp:

       

    #include "SortTestHelper.h"

    #include "InsertionSort.h"

       

       

    int main()

    {

    int n = 10000;

       

    int *arr = SortTestHelper::generateRandomArray(n, 0, n);

       

    SortTestHelper::testSort("Insertion Sort", insertionSort, arr, n);

       

    //SortTestHelper::printArray(arr, n);

       

    delete []arr;

       

    system("pause");

    return 0;

    }

       

       

    运行一览:

       

       

       

       

       

       

       

       

    程序 2:插入排序法和选择排序法的比较

       

    SortTestHelper.h:

       

    #ifndef SORTTESTHELPER_H

    #define SORTTESTHELPER_H

       

    #include <iostream>

    #include <string>

    #include <ctime>

    #include <cassert>

    using namespace std;

       

       

    //辅助排序测试

    namespace SortTestHelper

    {

       

    //生成测试数据(测试用例),返回一个随机生成的数组:

    //生成有n个元素的随机数组,每个元素的随机范围为[rangeL,rangeR]

    int *generateRandomArray(int n, int rangeL, int rangeR)

    {

    //默认rangeL要小于等于rangeR

    assert(rangeL <= rangeR);

       

    int *arr = new int[n];

       

    //对于数组中的每一个元素,将之随机成为rangeLrangeR之间的随机数

    //先设置随机种子:这里将当前的时间作为种子来进行随机数的设置

    srand(time(NULL));

       

    for (int i = 0; i < n; i++)

    {

    //rand()函数+百分号+数的范围,即 取中间的一个随机整数,再加上rangeL即可

    arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;

    }

    return arr;

    }

       

       

    //生成一个近乎有序的数组

    int *generateNearlyOrderedArray(int n, int swapTimes)

    {

    //先生成完全有序的数组

    int *arr = new int[n];

    for (int i = 0; i < n; i++)

    {

    arr[i] = i;

    }

       

    //以当前时间为随机种子

    srand(time(NULL));

       

    //再随机挑选几对元素进行交换,就是一个近乎有序的数组了

    for (int i = 0; i < swapTimes; i++)

    {

    int posx = rand() % n;

    int posy = rand() % n;

    swap(arr[posx], arr[posy]);

    }

       

    return arr;

    }

       

       

    template<typename T>

    void printArray(T arr[], int n)

    {

    for (int i = 0; i < n; i++)

    {

    cout << arr[i] << " ";

    }

    cout << endl;

    }

       

       

    //经过排序算法排序后,再次确认是否已经完全排序

    template<typename T>

    bool isSorted(T arr[], int n)

    {

    for (int i = 0; i < n - 1; i++)

    {

    if (arr[i]>arr[i + 1])

    {

    return false;

    }

    }

    return true;

    }

       

       

    //衡量一个算法的性能如何,最简单的方式就是看这个算法在特定数据集上的执行时间

    //1)传入排序算法的名字,方便打印输出

    //2)传入排序算法本身,即函数指针

    //3)传入测试用例:数组和元素个数

    template<typename T>

    void testSort(string sortName, void(*sort)(T[], int), T arr[], int n)

    {

    //在排序前后分别调用clock()函数

    //时间差就是该排序算法执行的时钟周期的个数

    clock_t startTime = clock();

    sort(arr, n);

    clock_t endTime = clock();

       

    assert(isSorted(arr, n));

       

    //endTime 减去 startTime 转为double类型,除以 CLOCKS_PER_SEC,其中:

    //

    //CLOCKS_PER_SEC 是标准库中定义的一个宏,表示每一秒钟所运行的时钟周期

    //的个数,而(endTime-startTime)返回的是运行了几个时钟周期

    //

    //这样,最终的结果就是在这段时间中程序执行了多少秒

    cout << sortName << "" << double(endTime - startTime) / CLOCKS_PER_SEC

    << "s" << endl;

    }

       

       

    //复制数组

    int *copyIntArray(int a[], int n)

    {

    int *arr = new int[n];

    //copy()函数在std中:

    //第一个参数是原数组的头指针,

    //第二个参数是原数组的尾指针,

    //第三个参数是目的数组的头指针

    //

    //注意:copy()函数运行时会报错,需要在:

    //项目->属性->配置属性->C/C++->预处理器->预处理器定义

    //在其中添加:_SCL_SECURE_NO_WARNINGS

    copy(a, a + n, arr);

    return arr;

    }

    }

       

    #endif

       

       

       

    InsertionSort.h:

       

    #ifndef INSERTIONSORT_H

    #define INSERTIONSORT_H

       

    //插入排序是 O(n^2) 级别算法复杂度的排序算法,它的思想就类似于

    //在玩扑克牌时插入牌的思想,即 将拿到的一张牌插入到合适的位置,

    //当最后一张牌也插入后,手中的整副扑克牌也就排序完成了

       

    //插入排序:从小到大进行排序

    template<typename T>

    void insertionSort(T arr[], int n)

    {

    //对于插入排序来说,第一个元素(索引为0)根本不用考虑

    //

    //因为在插入排序的初始,第一个元素放在那里,它本身就已经

    //有序了,不需要再把它插入到前面的任何位置,所以从第二个

    //元素(索引为1)开始考察

    for (int i = 1; i < n; i++)

    {

    //寻找arr[i]合适的插入位置:

    //对已经排好序的部分,插入时从后向前比较,

    //最多只会考察到j=1时,因为是从后向前比较

    //

    //写法1

    for (int j = i; j > 0; j--)

    {

    if (arr[j] < arr[j - 1])

    {

    swap(arr[j], arr[j - 1]);

    }

    else

    {

    //arr[i]插入到合适位置后,就跳出当前循环

    //继续对下一个元素进行考察

    break;

    }

    }

       

    ////for循环中的 if 条件加入到for循环中

    //(内层循环的两种写法,可替代上面的写法1

    //写法2

    //for (int j = i; j > 0 && arr[j] > arr[j - 1]; j--)

    //{

    // swap(arr[j], arr[j - 1]);

    //}

    }

    }

       

       

    #endif

       

       

       

    SelectionSort.h:

       

    #ifndef SELECTIONSORT_H

    #define SELECTIONSORT_H

       

       

    //选择排序:从小到大进行排序

    template<typename T>

    void selectionSort(T arr[], int n)

    {

    for (int i = 0; i < n; i++)

    {

    //寻找[i,n]区间里的最小值

    int minIndex = i;

       

    for (int j = i + 1; j < n; j++)

    {

    if (arr[j] < arr[minIndex])

    {

    minIndex = j;

    }

    }

    //swap()函数:交换两个值,对于swap()函数来说:

    //C++ 11标准中,它就在std标准命名空间中,即 标准库中,不用包含其它

    //如果是比较老的标准,它在algorithm库中,需要include <algorithm>

    swap(arr[i], arr[minIndex]);

    }

    }

       

       

    #endif

       

       

       

    main.cpp:

       

    #include "SortTestHelper.h"

    #include "InsertionSort.h"

    #include "SelectionSort.h"

       

       

    int main()

    {

    int n = 10000;

       

    int *arr1 = SortTestHelper::generateRandomArray(n, 0, n);

    int *arr2 = SortTestHelper::copyIntArray(arr1, n);

       

    SortTestHelper::testSort("Insertion Sort", insertionSort, arr1, n);

    SortTestHelper::testSort("Selection Sort", selectionSort, arr2, n);

     

    delete []arr1;

    delete []arr2;

       

    system("pause");

    return 0;

    }

       

       

       

    //插入排序和选择排序的最大区别就是:

    //

    //对于 第二重循环,也就是 内层循环,插入排序可以提前结束,而

    //选择排序则没有提前中止的机会

    //

    //

    //所以理论上,插入排序应该比选择排序更快一些,但实际比较时

    //却发现插入排序的性能比选择排序还要差,也就是用时更多

    //

    //这是因为:此时的插入排序在遍历的同时,也在不停的交换,而

    //交换的操作比简单的比较操作更耗时,每一次交换,背后就有三

    //次赋值的操作

       

       

    运行一览:

       

       

       

       

       

       

       

       

    程序 3:插入排序法的优化

       

    SortTestHelper.h:

       

    #ifndef SORTTESTHELPER_H

    #define SORTTESTHELPER_H

       

    #include <iostream>

    #include <string>

    #include <ctime>

    #include <cassert>

    using namespace std;

       

       

    //辅助排序测试

    namespace SortTestHelper

    {

       

    //生成测试数据(测试用例),返回一个随机生成的数组:

    //生成有n个元素的随机数组,每个元素的随机范围为[rangeL,rangeR]

    int *generateRandomArray(int n, int rangeL, int rangeR)

    {

    //默认rangeL要小于等于rangeR

    assert(rangeL <= rangeR);

       

    int *arr = new int[n];

       

    //对于数组中的每一个元素,将之随机成为rangeLrangeR之间的随机数

    //先设置随机种子:这里将当前的时间作为种子来进行随机数的设置

    srand(time(NULL));

       

    for (int i = 0; i < n; i++)

    {

    //rand()函数+百分号+数的范围,即 取中间的一个随机整数,再加上rangeL即可

    arr[i] = rand() % (rangeR - rangeL + 1) + rangeL;

    }

    return arr;

    }

       

       

    //生成一个近乎有序的数组

    int *generateNearlyOrderedArray(int n, int swapTimes)

    {

    //先生成完全有序的数组

    int *arr = new int[n];

    for (int i = 0; i < n; i++)

    {

    arr[i] = i;

    }

       

    //以当前时间为随机种子

    srand(time(NULL));

       

    //再随机挑选几对元素进行交换,就是一个近乎有序的数组了

    for (int i = 0; i < swapTimes; i++)

    {

    int posx = rand() % n;

    int posy = rand() % n;

    swap(arr[posx], arr[posy]);

    }

       

    return arr;

    }

       

       

    template<typename T>

    void printArray(T arr[], int n)

    {

    for (int i = 0; i < n; i++)

    {

    cout << arr[i] << " ";

    }

    cout << endl;

    }

       

       

    //经过排序算法排序后,再次确认是否已经完全排序

    template<typename T>

    bool isSorted(T arr[], int n)

    {

    for (int i = 0; i < n - 1; i++)

    {

    if (arr[i]>arr[i + 1])

    {

    return false;

    }

    }

    return true;

    }

       

       

    //衡量一个算法的性能如何,最简单的方式就是看这个算法在特定数据集上的执行时间

    //1)传入排序算法的名字,方便打印输出

    //2)传入排序算法本身,即函数指针

    //3)传入测试用例:数组和元素个数

    template<typename T>

    void testSort(string sortName, void(*sort)(T[], int), T arr[], int n)

    {

    //在排序前后分别调用clock()函数

    //时间差就是该排序算法执行的时钟周期的个数

    clock_t startTime = clock();

    sort(arr, n);

    clock_t endTime = clock();

       

    assert(isSorted(arr, n));

       

    //endTime 减去 startTime 转为double类型,除以 CLOCKS_PER_SEC,其中:

    //

    //CLOCKS_PER_SEC 是标准库中定义的一个宏,表示每一秒钟所运行的时钟周期

    //的个数,而(endTime-startTime)返回的是运行了几个时钟周期

    //

    //这样,最终的结果就是在这段时间中程序执行了多少秒

    cout << sortName << "" << double(endTime - startTime) / CLOCKS_PER_SEC

    << "s" << endl;

    }

       

       

    //复制数组

    int *copyIntArray(int a[], int n)

    {

    int *arr = new int[n];

    //copy()函数在std中:

    //第一个参数是原数组的头指针,

    //第二个参数是原数组的尾指针,

    //第三个参数是目的数组的头指针

    //

    //注意:copy()函数运行时会报错,需要在:

    //项目->属性->配置属性->C/C++->预处理器->预处理器定义

    //在其中添加:_SCL_SECURE_NO_WARNINGS

    copy(a, a + n, arr);

    return arr;

    }

    }

       

    #endif

       

       

       

    InsertionSort.h:

       

    #ifndef INSERTIONSORT_H

    #define INSERTIONSORT_H

       

    //插入排序是 O(n^2) 级别算法复杂度的排序算法,它的思想就类似于

    //在玩扑克牌时插入牌的思想,即 将拿到的一张牌插入到合适的位置,

    //当最后一张牌也插入后,手中的整副扑克牌也就排序完成了

       

    //插入排序:从小到大进行排序

    template<typename T>

    void insertionSort(T arr[], int n)

    {

    //对于插入排序来说,第一个元素(索引为0)根本不用考虑

    //

    //因为在插入排序的初始,第一个元素放在那里,它本身就已经

    //有序了,不需要再把它插入到前面的任何位置,所以从第二个

    //元素(索引为1)开始考察

    for (int i = 1; i < n; i++)

    {

    //寻找arr[i]合适的插入位置:

    //对已经排好序的部分,插入时从后向前比较,

    //最多只会考察到j=1时,因为是从后向前比较

    //

    //先将要插入的元素arr[i]保存到e中,在和已经排好序的部分进行比较时:

    //1)如果小于当前被比较的元素arr[j-1],则将当前被比较的元素向后移一位

    //2)如果大于当前被比较的元素arr[j-1],则当前位置j就是要插入的位置

    T e = arr[i];

    int j;//保存元素e应该插入的位置

    for (j = i; j > 0 && arr[j - 1] > e; j--)

    {

    arr[j] = arr[j - 1];

    }

    //将之前保存在e中的元素插入到位置j

    arr[j] = e;

       

    }

    }

       

       

       

    #endif

       

       

       

    main.cpp:

       

    #include "SortTestHelper.h"

    #include "InsertionSort.h"

       

       

    int main()

    {

    int n = 10000;

       

    //随机数组arr

    int *arr = SortTestHelper::generateRandomArray(n, 0, n);

    //近乎有序的数组arrx

    int *arrx = SortTestHelper::generateNearlyOrderedArray(n, 100);

     

    SortTestHelper::testSort("Insertion Sort", insertionSort, arr, n);

    SortTestHelper::testSort("Insertion Sort", insertionSort, arrx, n);

       

    delete []arr;

    delete []arrx;

       

    system("pause");

    return 0;

    }

       

       

    //此时的插入排序,不但不再使用交换操作,更重要的是插入排序可以提前中止内层循环

    //

    //对于插入排序来说,提前中止内层循环是非常重要的一个性质

    //

    //提前中止的条件是:一旦找到合适的位置就中止

    //

    //假设这个数组本身就基本上是有序的,这种情况下,内层循环可以很快的找到它应该

    //插入的位置,此时,插入排序的效率会非常的高

    //

    //可以简单模拟如下:

    //1)在生成这个数组时,让这个随机数的范围变小一些

    //2)在生成这个数组时,先生成一个完全有序的数组,再将该数组中的若干对元素交换

    //

    //对于一个近乎有序的数组来说,插入排序的性能要远远的优于选择排序

    //

    //事实上,插入排序对于近乎于有序的数组来说,它的性能比O(n*lgn)的级别的排序算法还

    //要快,这也是为什么插入排序是有非常重要的实际意义

    //

    //因为很多时候,我们处理的真实的数据,是近乎有序的

    //

    //比如说:一套系统的日志,它的生成,就是按照时间去生成的,但又可能在中间

    //产生了一些错误,或者是某一些任务时间过长,所以存在几个无序的元素,对于

    //这样的一个系统日志,使用插入排序进行排序,性能会更好

    //

    //通过代码不难发现,在最优的情况下,即当要排序的内容是一个完全有序的数组时,

    //插入排序将变成一个O(n)级别的算法,也就是说在内层循环中,每一次都执行一下,

    //发现当前的位置就是合适的位置,直接结束内层循环,进入下一次循环,这是插入

    //排序非常重要的一个性质

    //

    //也正是这个原因,插入排序也会在更加复杂的排序算法中作为一个子过程来进行优化

    //

    //

    //

    //选择排序的思想非常简单,它的缺点也非常明显:

    //对于任何一个数组,选择排序,两层循环,每一层循环都必须完全的执行完成,

    //正是因为如此,选择排序的效率在任何情况下都是

    //非常慢的

    //

    //而插入排序,虽然它的最差的时间复杂度也是O(n^2)级别的,但是在数组近乎

    //有序的情况下,插入排序的性能非常的高,甚至比O(n*lgn)级别的排序算法性能

    //还要高,这使得插入排序有着非常重要的实际意义

    //

    //对插入排序的深入理解也告诉我们:O(n^2)级别的排序算法并非一无是处,

    //对于近乎有序的数组,插入排序的效率相对比较高

       

       

    运行一览:

       

       

       

       

       

       

       

       

    【made by siwuxie095】

  • 相关阅读:
    Hibernate的游离态与持久态转换
    怎样区分直连串口线和交叉串口线?
    JAVA 内存泄露的理解
    leetcode第一刷_Validate Binary Search Tree
    Java程序猿的JavaScript学习笔记(8——jQuery选择器)
    Android二维码开源项目zxing用例简化和生成二维码、条形码
    Android Service 服务(一)—— Service
    一个简单的文本编辑器。(是在DEV C++下写的)
    我的Hook学习笔记
    ios网络学习------8 xml格式数据的请求处理 用代码块封装
  • 原文地址:https://www.cnblogs.com/siwuxie095/p/6908168.html
Copyright © 2011-2022 走看看