排序算法(三)直接插入排序

来源:互联网 发布:音频裁剪软件 编辑:程序博客网 时间:2024/05/17 04:42

1、前面介绍的冒泡排序算法以及鸡尾酒排序算法都称为交换排序算法,因为它们的核心都在于交换,事实证明交换不仅能够减少内存是使用,还能提高效率。本文介绍一种插入排序算法,顾名思义,核心在于“插入”,当然事实上,当中肯定包含了“交换”的步骤。


2、直接插入排序的思想大致如下:通过将一组无序的数据逐个插入到一组有序的数据中,使那组有序的数据依然有序。这样就实现了排序的效果,由此看出,这种算法更适用于两组以上数据的排序,即向有序数据中添加数据。


3、目前,我们要对一组数据进行排序,显然这组数据是无序的,根据直接插入排序算法的思想,我们需要构造一组有序的数据。这是要解决的第一个问题。


4、另一个问题就是:“插入”操作具体是如何实现的?我们知道要在数组中插入一个数据不是那么简单的(当然不用数组存储数据不在本文的探讨范围内),原因是一个数组在初始化后,大小就是固定的。所以要往数组中插入数据,必须满足一个条件:数组中还有空位。


5、举例来说,现有5个有序数据:{1, 2, 4, 5, 6},保存在一个整型数组中:

(1)如果这样定义:int a[5] = {1, 2, 4, 5, 6};数组大小为5,正好保存了5个数据,显然没有空位再放一个数了。

(2)马上想到,定义数组时,数组大小定的大一点:int a[6] = {1, 2, 4, 5, 6};这样就可以插入一个数据了,譬如插入数据3。a[5] = 3;

(3)现在这组数的顺序是:{1, 2, 4, 5, 6, 3},显然是无序的,这样这个问题不是回到了原点了吗?既然如此,一开始就把有序的一组数和无序的一组数放在一个数组中不就行了。


6、事实上,直接插入排序就是在一个数组中实现的:

(1)将一组数据(共n个)中的第1个数看成有序的,将第2个数与第1个数比较进而确定位置;

(2)现在,这组数中的前2个数都是有序的了,将第3个数插入到前2个数中;

(3)循环下去,直到将最后一个数,插入到前n-1个有序数据中,排序完成。


7、现已一组数据{1, 2, 4, 5, 6, 3}说明一次“插入”的过程:

(1)首先,前5个数是有序的,最后一个数是待插入的数据,这些数都存储在一个数组中;

(2)要实现将数据3放到数据2的后面(即当前数据4所在的位置),需要把数据4 所在的位置空出来;

(3)而要把数据4的位置空出来,同时又要保证原有数据的顺序,需要把4,5,6这三个数据全部右移一位;


8、第7条的具体实现过程是:

(1){1, 2, 4, 5, 6, 3},首先用一个变量保存待插入数据3的值:temp = 3;

(2)将数据6右移一位:{1, 2, 4, 5,6,6}

(3)将数据5右移一位:{1, 2, 4,5,5, 6}

(4)将数据4右移一位:{1, 2,4,4, 5, 6}

(5)将待插入的数据(temp中保存的值)放入第3个位置:{1, 2,3, 4, 5, 6}

注意:要将一组数据通过两两交换的方式右移,应该从右向左两两交换。


9、实现代码如下:

void StraightInsertionSort1(int* a, int n)
{
    int nTemp;
 
    for (int i = 1; i < n; i++)
    {
        nTemp = a[i];
 
        for (int j = i - 1; j >= 0; j--)
        {
            if (nTemp >= a[j])
            {
                a[j + 1] = nTemp;
                break;
            }
            else
            {
                swap(a[j], a[j + 1]);
            }
        }
 
    }
}

10、代码说明:

(1)传入参数是int型数组的首地址和数组大小;

(2)首先,将第一个数看成是有序的,由第二个数据开始插入;

(3)其次,将待插入数据与有序数据从大到小逐个比较,以确定待插入数据的位置;

(4)如果待插入数据大于等于某个有序数据,那么待插入数据的位置就是该有序数据的后一位;

(5)而如果待插入数据小于某个有序数据,就将该数据后移移位;

(6)最后,如果待插入数据一直小于有序数据,待插入数据自然被移动到了有序数据的开头。


11、上述代码用了一个nTemp保存待插入数据,但是实际上待插入数据的值数组中一直有,其实上述代码中的a[j + 1]本来就等于nTemp,故修改代码如下:

void StraightInsertionSort2(int* a, int n)
{
    for (int i = 1; i < n; i++)
    {
        for (int j = i - 1; j >= 0; j--)
        {
            if (a[j + 1] >= a[j])
            {
                break;
            }
            else
            {
                swap(a[j], a[j + 1]);
            }
        }
    }
}

12、二次修改如下:

void StraightInsertionSort3(int* a, int n)
{
    for (int i = 1; i < n; i++)
    {
        for (int j = i - 1; j >= 0 && a[j + 1] < a[j]; j--)
        {
            swap(a[j], a[j + 1]);
        }
    }
}

13、如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,所以插入排序是稳定的。


14、经过二次修改后的代码简单明了,但是还可以优化(主要是大数据量下的速度优化):

(1)我们知道,在不使用其它变量的情况下,要交换两个变量的值需要做一次加法操作、两次减法操作以及三次赋值操作,通过这些操作以换取不使用临时变量,达到节省存储空间的目的。但是,仔细想一想,当需要交换很多次,并且交换的数据是顺序相连的时候,这种移动数据的方法是否合理。

(2)修改代码如下:

void StraightInsertionSort4(int* a, int n)
{
    int nTemp;
 
    for (int i = 1; i < n; i++)
    {
        nTemp = a[i];
 
        for (int j = i - 1; j >= 0; j--)
        {
            if (nTemp >= a[j])
            {
                a[j + 1] = nTemp;
                break;
            }
            else
            {
                a[j + 1] = a[j];
            }
        }
 
        if (nTemp < a[0])
        {
            a[0] = nTemp;
        }
    }
}

(3)上述代码可能比较乱,因为是根据StraightInsertionSort1改写而来的(这样逻辑比较清除),当然也可以根据StraightInsertionSort3改写,如下:

void StraightInsertionSort5(int* a, int n)
{
    int nTemp;
 
    for (int i = 1; i < n; i++)
    {
        nTemp = a[i];
        for (int j = i - 1; j >= 0 && a[j + 1] < a[j]; j--)
        {
            a[j + 1] = a[j];
            a[j] = nTemp;
        }
    }
}

(4)此处的nTemp已经可以看做一个哨兵了,但是上述代码还可以修改如下:

void StraightInsertionSort6(int* a, int n)
{
    for (int i = 1; i < n; i++)
    {
        a[0] = a[i];
        for (int j = i - 1; a[j + 1] < a[j]; j--)  // 当j减小到0时便会跳出循环,不会出现j<0的情况
        {
            a[j + 1] = a[j];
            a[j] = a[0];
        }
    }
}

(5)注意:上述实现(即StraightInsertionSort6)有一个问题,那就是传入数组的第一个元素a[0]是没有存储有效数据的,a[0]相当于一个临时变量;也就是说,在传入数组首地址时,如果第一个元素是有效数据,则需要把所有数据右移一位,再调用该函数;但是,我在前面的文章也提到过,要向数组中插入数据是不容易的,应为数组大小是固定的,因此只有重新定义一个数组的方法了,并且为了使数组第一个元素无效,该数组的大小应该比原数组大一,然后逐个赋值,最后调用该函数。

(6)显然,当数据量很大时,这种方式得不偿失;因为减少一个循环判断条件,实际的提速并不大(相对于交换移动的方法,即StraightInsertionSort3)。

(7)顺带一提,为了检验上述内容,我用同一组随机生成的10000个数据,分别调用StraightInsertionSort3和StraightInsertionSort5,结果是后者比前者快110ms左右,而调用StraightInsertionSort5和StraightInsertionSort6比较,后者比前者快20ms左右;

StraightInsertionSort6快于StraightInsertionSort5快于StraightInsertionSort3








0 0
原创粉丝点击