插入排序与优化
算法简介
插入排序对于需要排序少量数据来说,是一个有效率的算法。其时间复杂度为cn^2,其中n为需要排序的数据,c是一个与n无关的常数因子。
算法原理
就像打牌时摸牌的过程一样,我们从牌堆顶(未排序的数组)中取出一张牌(一个元素),然后把它与手中的牌(已经排序好的数组)逐个比较,把它插入到正确的位置,所以该算法称为插入排序。
算法实现
下面以从小到大排序数组为例给出插入排序的示例代码:
//从小到大排序
void insertSort(int *arr, int size)
{
int key, i, j;
for (i = 1; i < size; i++)
{
key = arr[i];
j = i - 1;//想象一下我们把牌从手中以排序好的牌的最右边开始比较
while (j >= 0 && key < arr[j])
{
arr[j+1] = arr[j];
j = j-1;
}
arr[j+1]=key;
}
}
for循环很好理解,从1开始迭代数组。为什么从1开始迭代,因为当子数组中只有一个元素的时候,子数组一定是有序的,所以我们直接让子数组从arr[0]
开始增长,这样就减少了一次for循环。
从代码中不难看出,子数组是一直保持有序的,所以我们在while循环的执行条件中直接判断当前待插入的元素是否小于子数组的最大元素,如果小于则执行移动数组元素的代码(也就是while循环中的语句),直到把所有大于key的元素都向后移动了一位,while循环结束,执行插入代码。此时一趟插入排序就完成了。
如此反复,直到所有的元素都被排序好。退出for循环,结束排序,并得出有序的数组。
稳定性分析
从while循环的判断语句不难看出,插入排序是稳定的,因为我们拿key和有序子数组的最后一个元素进行比较,如果key等于最后一个元素,我们是不会执行while中的代码的。所以插入排序是稳定的。
但是,有细心得读者也许发现了,在while循环中得判断是使用了小于号的,如果把小于改为小于等于,这就破坏了插入排序的稳定性。
算法优化
对于插入排序这样简单的算法,存在许多优化之处。下面列举一下插入排序的优化方案
使用二分查找
在将key于子数组中的元素进行比较时,我们是从最后一个开始一个一个进行比较的,我们可以使用二分查找来寻找第一个小于key的元素,这样就避免了逐个比较,在一定程度上优化了算法
使用哨兵元素
我们可以在子数组的第一个位置插入一个哨兵元素,该元素是我们规定的或待排序数组的最小元素。这样就避免了每次while循环都要判断j是否大于等于0。这在一定程度上也优化了算法。
块插入排序
插入排序对于数据规模较小的数组,是较为高效的。因此我们可以将一个大数组划分为若干个小数组,对每个小数组进行插入排序,最后将排序好的小数组进行合并,得到有序的大数组。
使用链表
用链表实现插入排序,每次插入只需要改变指针,不需要移动大量元素。这样做的缺点是比较慢,因为链表不支持随机访问,除非明确需要链式结构,一般不如数组实现高效。