排序算法学习整理三(插入)
三、插入排序:
插入排序是一种非常简单的排序,它的实现难度低于冒泡和选择。(我第一个for循环写出的排序就是插入排序)插入排序类似整理扑克牌,将每一张牌插到其他已经有序的牌中适当的位置。
基本思想:
插入排序由N-1趟排序组成,对于P=1到N-1趟,插入排序保证从位置0到位置P上的元素为已排序状态。
简单的说,就是插入排序总共需要排序N-1趟,从Index为1开始,讲该位置上的元素与之前的元素比较,放入合适的位置,这样循环下来之后,即为有序数组。
代码实现
1 void insertionSort(int *array, int len) 2 { 3 for (int i = 1; i < len; i++) 4 { //即使i从开始0开始,也不会进入第二个循环。 5 for (int j = 0; j < i; j++) 6 { /*从0~i都是已经排好序的,将array[i]与array[0]~array[i-1]一一进行比较, 7 找到插入点*/ 8 if (array[i] < array[j]) 9 { //找到大于array[i]的元素就立即交换(这里存在优化的可能) 10 array[i] = array[i] ^ array[j]; 11 array[j] = array[i] ^ array[j]; 12 array[i] = array[i] ^ array[j]; //交换 13 } 14 } 15 } 16 }
代码的问题很明显,就像选择排序为什么会比冒泡快一样,我们可以减少交换次数来优化代码。
但是不交换怎么排序呢?用赋值语句进行覆盖实现,
其核心的代码如下:
for(j = i; j > 0 && temp < array[j-1]; j--) { array[j] = array[j-1]; }
array[j] = temp;
打个比方:
原数组元素为 : 7 1 2 3 第一轮: 1 < 7;执行array[j] = array[j-1] 1 1 2 3; 再执行array[j] = temp; 1 7 2 3; 第二轮: 2 < 7;执行array[j] = array[j-1] 1 2 2 3 再执行array[j] = temp; 1 2 7 3 2 !< 1;退出循环 第三轮: 3 < 7;执行array[j] = array[j-1] 1 2 3 3 再执行array[j] = temp; 1 2 3 7 3 !< 2;退出循环;
代码实现:
1 void insertionSort(int *array, int len) 2 { 3 int i, j; 4 int temp = 0; 5 for (i = 1; i < len; i++) 6 { 7 temp = array[i]; //记录需要插入的元素 8 for(j = i; j > 0 && temp < array[j-1]; j--) 9 { //从array[i]开始和自己的前一个相比较(存在优化的可能) 10 //如果array[i] < array[j-1],就代表array[i]插入的位置不对, 11 //如果temp < array[j-1],不成立,就代表到了array[i]该插入的位置了 12 array[j] = array[j-1]; 13 } 14 array[j] = temp; //找到正确位置后立即插入 15 } 16 }
现在让我来想一想一个问题,一个数组其元素为 5 1 2 3 4 6 7 8,
根据插入排序的代码可得到相应的过程
- 第一轮:1 5 2 3 4 6 7 8 一次比较,两次赋值
- 第二轮:1 2 5 3 4 6 7 8 两次比较,三次赋值
- 第三轮:1 2 3 5 4 6 7 8 三次比较,四次赋值
- 第四轮:1 2 3 4 5 6 7 8 四次比较,五次赋值
这个数组的元素除了5原本都是有序的,但是,为了找到5正确的插入位置,总共进行了10次比较,14次赋值。
明眼人都看得出来5的正确位置应该是整个数组的最中间的位置,可是计算机偏偏就是个“瞎子”,那我们该怎么样让这个“瞎子”,知道这个元素是在数组的最中间呢?
这就涉及到到上篇拓展的内容——运用二分查找来这个元素的正确位置。
这里先贴上二分的核心代码(建议先看懂二分查找再来看二分排序)
while (left <= right) //如果走到头都没有找到就退出 { mid = (left+right) / 2; //记录中间数 if (arr[mid] > temp) //和中间数相比较 { right = mid - 1; //比中间数小就向前走 } else { left = mid + 1; //比中间数大就向后走 } }
这里是用循环来实现二分查找,当然,我们也可以用递归来实现。这里为了节省时间,我就不再多做解释。
从所贴的代码可看出通过二分查找我们确定元素5的正确位置只需要1次比较和5次赋值,大大减少了比较次数和赋值次数。
当然对于二分排序来说采用折半查而减少,为O(nlogn),但是元素交换的次数仍为O(n2),二分排序算法是稳定的。
下面我们来看一下完整的代码:
1 void insertsort(int *arr, int n) 2 { 3 int i, j, temp, left, right, mid; 4 5 for (i = 1; i < n; i++) 6 { 7 left = 0; 8 temp = arr[i]; 9 right = i-1; 10 11 while (left <= right) 12 { 13 14 mid = (left+right) / 2; 15 if (arr[mid] > temp) 16 { 17 right = mid - 1; 18 } 19 else 20 { 21 left = mid + 1; 22 } 23 } 24 25 for (j = i-1; j > right; j--) 26 { 27 arr[j+1] = arr[j]; 28 } 29 arr[right+1] = temp; 30 } 31 }
大家可以自行尝试写一下递归版的二分排序,我在这里就贴代码了。