玩转算法与数据结构 C++描述 插入排序

2-5 插入排序 Insertion Sort

整理扑克牌的思想

看后面的牌,插入到前面合适的位置

//main.cpp
#inlcude <iostream>
#inlcude <algorithm>
#include "SortTestHelper.h"
#include "SelectionSort.h"

using namespace std;

template<typename T>
void insertSort(T arr[], int n){
    //从1号开始,用j比较1号和0号是否要调换
    for(int i=1; i<n; i++){
        //寻找元素arr[i]合适的插入位置
        for(int j=i; j>0; j--){
            if(arr[j] < arr[j-1]){
                swap(arr[j],arr[j-1]);
        }else{
            break;
        }
    }
    
    //可以看到,插入排序可以break出去,减少步骤。
    //这段for也可以简化为:
    for(int i=1; i<n; i++){
        for(int j=1; j>0 && arr[j] < arr[j-1]; j--){
            swap(arr[j],arr[j-1]);
        }
    }
}

int main(){
    int n = 10000;
    int *arr = SortTestHelper::generateRandomArray(n,0,n);
    int *arr2 = SortTestHelper::copyIntArray(arr,n);
    //我们的排序改变了arr[],所以需要两份,再复制一份
    
    SortTestHelper::testSort("Insertion Sort", insertionSort, arr, n);
    SortTestHelper::testSort("Selection Sort", selectionSort, arr2, n);
    
    delete[] arr;
    delete[] arr2;
    return 0;
}



//SortTestHelper.h

namespace SortTestHelper{
    //可以改成模板函数,但如果数组中的元素是类,就涉及深拷贝的问题,超出算法范畴
    int* copyIntArray(int a[], int n){
        int *arr = new int[n];
        //使用std的copy,(原数组头指针,原数组尾指针,目的地址头指针)
        copy(a, a+n, arr);
        return arr;
    }
}

 

得到:

插入排序0.3S,选择排序0.19S

为什么插入排序还慢了?下面学一种优化,

还学一种性质,它会使插入排序的性能不比nlogn的排序效果差

 

=========

2-6 插入排序发的改进

现在实现的版本,遍历的同时也在不停地交换,交换非常耗时,

因为交换要3次操作,数组还需要访问index所在位置的时间,

 

试试能不能让内圈for,只做一次swap?

 

先把元素复制一个副本,然后不断看它与前面元素比较,是否适合放在某个位置,直到真正确定它的位置。不必每次都去swap

 

改进:
 

template<typename T>
void insertionSort(T arr[], int n){
    for(int i=1; i<n; I++){
        T e = arr[i];
        int j; //保存元素e应该插入的位置
        for(int j=i; j>0 && arr[j-1] > e; j--){
            arr[j] = arr[j-1]; //向后挪一下
        }
        arr[j] = e; //找到合适位置,放进去
    }
}

这回,消耗时变成了0.15s

这个效果还是不够明显,

现在在SortTestHelper中添加一个generatenearlyOrderedArray()

先生存一个完全有序的数组,然后随机挑出几个进行交换。

//SortTestHelper.h

namespace SortTestHelper{
    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;
}

这样,

int *arr = SortTestHelper::generateNearlyOrderArray(n,100);

只有100个是无序的,

 

此时插入排序的时间是0.004s,远远超过选择排序

这个速度甚至比nlogn还要快,所以它是有实际意义的,因为很多时候要排序的元素就是近似有序的,

比如日志,只存在某几个出问题的是无序的,就可以用插入排序,

当完全有序时,插入排序的时间复杂度是O(n)级别,只有外层循坏,

所以插入排序会在更复杂额排序算法中作为子模块,

 

========

2-7 O(n^2)排序算法的思考

selection sort,任何情况下都是很慢的,

insertion sort,在近乎有序时,插入排序性能比nlogn还高

 

Bulle Sort,

多数学校的语言学习,1st排序算法就是冒泡排序,

这节深入的理解下冒泡排序,

 

它整体没有插入排序好,后面应该不会用它,github上有它的代码和对他的改进

 

插入排序是重点,它可以引申出非常重要的排序:Shell Sort 希尔排序

它尝试每次和前面第H个元素进行比较,通过将很大的H逐渐变成1,。。。。。。

让它的时间复杂度有了质变

 

github有改进的实现

希尔排序是非常使用的方法,虽然比nlogn高一些

 

我们的学习不是简单的实现算法,而是优化它,深入理解它,有更深刻的认识

 

===============================

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值