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高一些
我们的学习不是简单的实现算法,而是优化它,深入理解它,有更深刻的认识
===============================