排序算法学习整理三(插入)

三、插入排序:

  插入排序是一种非常简单的排序,它的实现难度低于冒泡和选择。(我第一个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 }
insertionSort

  代码的问题很明显,就像选择排序为什么会比冒泡快一样,我们可以减少交换次数来优化代码。

  但是不交换怎么排序呢?用赋值语句进行覆盖实现,

    其核心的代码如下:    

    

    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 }
insertionSor

  现在让我来想一想一个问题,一个数组其元素为  5 1 2 3 4 6 7 8,

     根据插入排序的代码可得到相应的过程

  1. 第一轮:1 5 2 3 4 6 7 8       一次比较,两次赋值
  2. 第二轮:1 2 5 3 4 6 7 8                 两次比较,三次赋值
  3. 第三轮:1 2 3 5 4 6 7 8                 三次比较,四次赋值
  4. 第四轮: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 }
insertsort

  大家可以自行尝试写一下递归版的二分排序,我在这里就贴代码了。

猜你喜欢

转载自www.cnblogs.com/daker-code/p/10322437.html