插入排序

本篇来学习十大排序中的插入排序,学习其算法思想并尝试实现排序。

插入排序有两种:直接插入排序和折半插入排序。

直接插入排序

直接插入排序(4,3,1,2)的流程如下图:

image

对于每个未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。其过程大概是这样的:

第一个元素就认为是有序的,取第二个元素,判断是否大于第一个元素。若是大于,表示已经有序,不用移动,否则将已经有序的序列整体向后移动一个位置。依此类推,直到所有元素已经有序。

可以借助上面的图来理解。

时间复杂度

需要到两层循环来处理,外层循环用于跑多少趟,而内层循环用于移动元素位置,因此时间复杂度仍为
O ( n2 )

伪代码

void insertSort(int a[], int len) {

for i = 1; i < len; ++i {

// 后者>前者,才需要移动和插入

if a[i] < a[i - 1] {

// 记录下要移动的元素

int target = a[i];

// 将前j-1个有序的元素分别后移一个位置

int j = i;

while j > 0 && a[j - 1] > target {

a[j] = a[j - 1];

j–;

}

// 将目标元素插入对应的位置,使之有序

a[j] = target;

}

}

}

C语言版

void insertSort(int a[], int len) {

for (int i = 1; i < len; ++i) {

// 遇到不是有序的,才进入移动元素

if (a[i] < a[i - 1]) {

int target = a[i];

// 移动前j-1元素,分别向后移动一个位置

int j = i;

while (j > 0 && a[j - 1] > target) {

a[j] = a[j - 1];

j–;

}

// 将目标元素放到目标位置,使之有序

a[j] = target;

}

}

}

OjbC版

  • (void)insertSort:(int[])a len:(int)len {

for (int i = 1; i < len; ++i) {

// 遇到不是有序的,才进入移动元素

if (a[i] < a[i - 1]) {

int target = a[i];

// 移动前j-1元素,分别向后移动一个位置

int j = i;

while (j > 0 && a[j - 1] > target) {

a[j] = a[j - 1];

j–;

}

// 将目标元素放到目标位置,使之有序

a[j] = target;

}

}

}

Swift版

func insertSort(var a: [Int]) ->[Int] {

for var i = 1; i < a.count; ++i {

if a[i] < a[i - 1] {

let target = a[i]

var j = i

while j > 0 && a[j - 1] > target {

a[j] = a[j - 1]

j–

}

a[j] = target

}

}

return a

}

折半插入排序

从第二个元素开始逐个置入监视哨,使用low、high标签进行折半判断比较大小,并确认插入位置,该位置到最后一个数全部后移一位,然后腾出该位置,把监视哨里面的数置入该位置。依此类推进行排序,直到最后一个数比较完毕。

时间复杂度

折半插入排序, 即查找插入点的位置,
可以使用折半查找,这样可以减少比较的次数,
但是移动的次数不变,因此,时间复杂度仍为 O(n2) ;

伪代码

void binaryInsertSort(int a[], int len) {

for int i = 2; i < len; ++i {

// 将元素放到哨兵的位置

a[0] = a[i];

int low = 1;

int high = i - 1;

// 折半查找位置

while low <= high {

int mid = (low + high) / 2;

// 在低半区

if a[mid] > a[0] {

high = mid - 1;

} else { // 在高半区

low = mid + 1;

}

}

// 将前i - 1个元素后移

// 找到high,那么high+1就是i要插入的位置

for int j = i - 1; j >= high + 1; –j {

a[j + 1] = a[j];

}

// 将临时放在岗哨的元素放到所查找到的位置处

a[high + 1] = a[0];

}

}

C语言版

void binaryInsertSort(int a[], int len) {

for (int i = 2; i < len; ++i) {

// 第一个位置永远只是当作哨兵用

a[0] = a[i];

int low = 1;

int high = i - 1;

while (low <= high) {

int mid = (low + high) / 2;

if (a[mid] > a[0]) {

high = mid - 1;

} else {

low = mid + 1;

}

}

// 移动前i - 1个元素

for (int j = i - 1; j >= high + 1; –j) {

a[j + 1] = a[j];

}

// 放到查找到的位置处

a[high + 1] = a[0];

}

}

Swift版

func binaryInsertSort(var a: [Int]) ->[Int] {

for var i = 2; i < a.count; ++i {

a[0] = a[i]

var low = 1

var high = i - 1

while low <= high {

let mid = (low + high) / 2

if a[mid] > a[0] {

high = mid - 1

} else {

low = mid + 1

}

}

for var j = i - 1; j >= high + 1; –j {

a[j + 1] = a[j]

}

a[high + 1] = a[0]

}

return a

}

小结

对于折半插入排序算法,它需要一个哨兵,数组的0号位置是用作哨兵,而不存储数据。为什么所找到的位置是high+1呢?因为最后退出while循环一定是high= mid – 1之后,导致条件low > high而退出。所以,最后所找到的位置一定是high = mid - 1,由于减了1,所以应该插入的位置是high + 1。

0%