排序算法

插入排序

Posted by Liangjf on April 9, 2018

排序算法之插入排序

1.插入排序介绍

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

插入排序是基于比较的排序。这类排序,有两种基本的操作:①比较操作; ②交换操作

  • ①比较操作:元素之前的大小比较
  • ②交换操作:一般的交换操作需要三次赋值,但可以优化为移动+替换操作,因为其只需要一次赋值。

2.插入排序算法分析

  • 1.从第一个元素开始,该元素可以被认为已经被排序
  • 2.取出下一个元素,在已经排序的元素序列中从后向前扫描
  • 3.如果该元素(已排序)大于(或小于,看升序还是降序)新元素,降该元素移到下一个位置(下标后移位置)
  • 4.重复步骤3,直到找到已排序的元素小于或等于新元素的位置
  • 5.将新元素插入到该位置
  • 6.重复步骤2~5

3.插入排序算法从实现到优化

  • V1.0
    template <typename T>
    void InsertSort(T a[], int len) {
      for(int i = 1; i < len; i++) {
          for(int j = i; j > 0; j--) {
              if(a[j] < a[j-1])
                  swap(a[j], a[j-1]);
              else
                  break;
          }
      }
    }
    

    使用一般的交换操作,有三次赋值,并且看起来比较代码冗余,可以修改为V2.0。

  • V2.0
    template <typename T>
    void InsertSort(T a[], int len) {
      for(int i = 1; i < len; i++) {
          for(int j = i; j > 0 && a[j] < a[j-1]; j--) {
              swap(a[j-1], a[j]);
          }
      }
    }
    

    此版本直接把比较操作集合在for语句中,简化了代码的编写,但是也是使用了一般的交换操作,有三次赋值,不够高效。可优化为V3.0。

  • V3.0
    template <typename T>
    void InsertSort(T a[], int len) {
      for(int i = 1; i < len; i++) {
          T tmp = a[i];
          int j;
          for(j = i; j > 0 && a[j-1] > tmp; j--) {
              a[j] = a[j-1];
          }
          a[j] = tmp;
      }
    }
    

    此版本把交换操作优化为移动操作,就是不直接进行两个元素的交换,还是用一个枢轴元素(tmp)将当前元素先保存起来,然后执行移动操作,待确定了最终位置后,再将当前元素放入合适的位置。

4.插入排序多种场景下的测试

先编写一个随机生成int型数组的函数,用于方便我们的测试。

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

再编写一个函数用于测试我们的排序函数。

int main() {
	int N = 20000;

	//稀疏数组,随机性大
	int *arr = SortTestHelper::CreateRandomArray(N, 0, 1000000);
	SortTestHelper::testSort("InsertSort", InsertSortFunc::InsertSort, arr, N);

	//近乎有序
	int *arr = SortTestHelper::CreateRandomArray(N, 0, 100);
	SortTestHelper::testSort("InsertSort", InsertSortFunc::InsertSort, arr, N);

	//基本有序
	int *arr = SortTestHelper::CreateRandomArray(N, 0, 10);
	SortTestHelper::testSort("InsertSort", InsertSortFunc::InsertSort, arr, N);

	delete[] arr;
	return 0;
}

测试结果:

InsertSort : 0.266528s InsertSort : 0.2574s InsertSort : 0.232527s

测试证明,插入排序在越有序的情况下速度更快,所以一般高级排序算法结合插入算法使用,在排序到基本排好序时使用插入排序对剩下的元素排序将会加快整个排序的效率。比如快排,堆排等,后面会分析到。

插入排序的比较次数与数组的逆序数相关,因为插入排序在将某个元素插入到合适位置时,其实就是消除这个元素的逆序数。由定理:N个互异数的数组的平均逆序数是 N(N-1)/4,可知:基于相邻元素之间的比较和交换的算法的时间复杂度的一个下界为O(N^2)。因此在有序时比一般的冒泡法和选择排序快。

选择排序与插入排序的对比

int main() {
	int N = 20000;

	//稀疏数组,随机性大
	int *arr1 = SortTestHelper::CreateRandomArray(N, 0, 1000000);
	int *arr2 = SortTestHelper::copyIntArray(arr1, N);
	SortTestHelper::testSort("SelectionSort", SelectSortFunc::SelectionSort2, arr1, N);
	SortTestHelper::testSort("InsertSort", InsertSortFunc::InsertSort, arr2, N);

	//近乎有序
	int *arr3 = SortTestHelper::CreateRandomArray(N, 0, 100);
	int *arr4 = SortTestHelper::copyIntArray(arr3, N);
	SortTestHelper::testSort("SelectionSort", SelectSortFunc::SelectionSort2, arr2, N);
	SortTestHelper::testSort("InsertSort", InsertSortFunc::InsertSort, arr4, N);

	//基本有序
	int *arr5 = SortTestHelper::CreateRandomArray(N, 0, 10);
	int *arr6 = SortTestHelper::copyIntArray(arr5, N);
	SortTestHelper::testSort("SelectionSort", SelectSortFunc::SelectionSort2, arr3, N);
	SortTestHelper::testSort("InsertSort", InsertSortFunc::InsertSort, arr6, N);

	delete[] arr1;
	delete[] arr2;
	delete[] arr3;
	delete[] arr4;
	delete[] arr5;
	delete[] arr6;
	return 0;
}

测试结果:

SelectionSort : 0.412033s InsertSort : 0.251158s SelectionSort : 0.433542s InsertSort : 0.25107s SelectionSort : 0.410418s InsertSort : 0.229272s

实验表明,总体上,虽然二者都是n * n时间复杂度。但是插入排序比直接选择排序快,不管在随机性很大,近乎有序或基本有序的情况下。 但是插入排序不是一般稳定的排序算法,而直接选择排序是一种稳定的算法,二者的应用场景不太一样。所以算法是没有好坏之分,只有最适合之分。