数据结构--排序之插入排序

插入排序分类:

一般有直接插入、折半插入、希尔排序三种。

三种插入排序的基本思想大致相同

给定一个序列a[1...n]

前两种将一个序列分成有序部分(sorted)和无需部分(unsorted), 循环遍历序列a, 当遍历到第r个下标时, 区间 [1,r-1] 是有序部分,区间[r, n]是无序的,当前任务就是讲下标为r的数插入到有序部分,将区间[1,r]变为有序,这样有序区间长度加一,无序区间长度减一,循环结束后区间[1,n]内的数就是有序的,排序完成。

一、直接插入排序:(Straight Insertion Sort)

代码如下:

#include <bits/stdc++.h>
using namespace std;

#define MAXSIZE 100
typedef int KeyType;
typedef struct///定义每个节点信息
{
    KeyType key;
//    InfoType otherinfo;
}RType;
typedef struct///定义顺序表
{
    RType r[MAXSIZE + 1];
    int length;
}SqList;
void InsertSort(SqList &L)
{
    for(int i = 2; i <= L.length; i ++)
    {
        /***
        r[1...i-1]有序,r[i+1...n]无序
        对于当前循环需要将r[i]插入到有序段中,使r[1...r]有序,
        即找到r[i] 在 r[1...i-1]中的插入位置即可
        ***/
        if(L.r[i-1].key > L.r[i].key)
        {
             ///当r[i] < r[i-1] 时需要将r[i]插入到区间r[1...i-1]中
            L.r[0] = L.r[i];
            int j;
            for(j = i - 1; L.r[0].key < L.r[j].key; j --)
            {
                ///寻找插入位置,当r[0] > r[j]则跳出循环,j+1就是r[i]要插入的位置
                ///这里体会r[0]的妙用
                L.r[j+1] = L.r[j];
            }
            L.r[j+1] = L.r[0];
        }
    }
}
int main()
{
    SqList L;
    printf("请输入序列的个数:");
    scanf("%d", &L.length);
    printf("\n\n请输入序列:");
    for(int i = 1; i <= L.length; i ++)
    {
        scanf("%d", &L.r[i].key);
    }
    InsertSort(L);
    printf("\n\n");
    for(int i = 1; i <= L.length; i ++)
    {
        printf("%5d", L.r[i].key);
    }
    printf("\n");
    return 0;
}
/**
7
49 38 65 97 76 13 27

10
18   73   40   84   71   59   64   85   41   98
**/

二、折半插入排序(Binary Insertion Sort)

和直接插入排序差不多,不同的是使用折半查找来寻找第i个数在区间r[1...i-1]中的插入位置,减少了关键字的比较

代码如下:

#include <bits/stdc++.h>
using namespace std;

#define MAXSIZE 100
typedef int KeyType;
typedef struct///定义每个节点信息
{
    KeyType key;
//    InfoType otherinfo;
}RType;
typedef struct///定义顺序表
{
    RType r[MAXSIZE + 1];
    int length;
}SqList;
void BInsertSort(SqList &L)
{
    for(int i = 2; i <= L.length; i ++)
    {
        L.r[0] = L.r[i];
        int left = 1, right = i - 1, mid;
        while(left <= right)
        {
            mid = (left + right) >> 1;
            if(L.r[0].key < L.r[mid].key)right = mid - 1;
            else left = mid + 1;
        }
        ///插入位置是 right + 1,思考为什么?
        for(int j = i - 1; j >= right + 1; j --)
        {
            L.r[j+1] = L.r[j];
        }
        L.r[right+1] = L.r[0];
    }
}
int main()
{
    SqList L;
    printf("请输入序列的个数:");
    scanf("%d", &L.length);
    printf("\n\n请输入序列:");
    for(int i = 1; i <= L.length; i ++)
    {
        scanf("%d", &L.r[i].key);
    }
    BInsertSort(L);
    printf("\n\n");
    for(int i = 1; i <= L.length; i ++)
    {
        printf("%5d", L.r[i].key);
    }
    printf("\n");
    return 0;
}
/**
7
49 38 65 97 76 13 27

10
18   73   40   84   71   59   64   85   41   98
**/

思考插入位置是 right + 1

注意插入的位置满足大于等于 r[i] 的第一位

 

以上是循环里面的两种条件:

r[0] < r[mid],此时right = mid - 1, 如果r[0] >= r[right],则插入位置是就是right+1;

r[0] >= r[mid],则left = mid +1, 如果r[0] <= r[left], 所以left就是插入位置,然后接下来的循环中right始终都是mid-1,最终循环结束时

            right  = left - 1;

随着循环的进行,其他情况都能得到以上两种情况

同时循环结束条件是left = right + 1,所以程序中插入位置right+1也能用left代替

 

直接插入排序与折板插入排序的时间复杂度都是O(n^2),移动次数也一样,不过后者的关键字比较次数较少,空间复杂度均是O(1)。

三、希尔排序(Shell's Sort)

将序列分成若干组,对每一组进行插入排序

代码如下:

#include <bits/stdc++.h>
using namespace std;

#define MAXSIZE 100
typedef int KeyType;
typedef struct///定义每个节点信息
{
    KeyType key;
//    InfoType otherinfo;
}RType;
typedef struct///定义顺序表
{
    RType r[MAXSIZE + 1];
    int length;
}SqList;

///相当于对每一个分组进行直接插入排序
void ShellInsert(SqList &L, int dk)
{
    for(int i = dk + 1; i <= L.length; i ++)
    {
        if(L.r[i-dk].key > L.r[i].key)
        {
            L.r[0] = L.r[i];
            int j;
            for(j = i - dk; j > 0 && L.r[0].key < L.r[j].key; j -= dk)
            {
                L.r[j+dk] = L.r[j];
            }
            L.r[j+dk] = L.r[0];
        }
    }
}
void ShellSort(SqList &L, int d[], int t)
{
    for(int i = 0; i < t; i ++)
    {
        ShellInsert(L, d[i]);
    }
}
int main()
{
    SqList L;
    printf("请输入序列的个数:");
    scanf("%d", &L.length);
    printf("\n\n请输入序列:");
    for(int i = 1; i <= L.length; i ++)
    {
        scanf("%d", &L.r[i].key);
    }
    int d[3] = {5, 3, 1};
    int t = 3;
    ShellSort(L, d, t);
    printf("\n\n");
    for(int i = 1; i <= L.length; i ++)
    {
        printf("%5d", L.r[i].key);
    }
    printf("\n");
    return 0;
}
/**

以d[0] = 5, d[1] = 3, d[2] = 1 为例
7
49 38 65 97 76 13 27

10
18   73   40   84   71   59   64   85   41   98
**/

对以上的算法用到一个分组函数d[n], 不过目前还任未找到一个较好的函数。

注意:最后一个分组必定是1,来确保最终的序列有序。

此时算法就退化到直接插入排序,不过经过前面的对每个分组排序后,一般序列基本有序,此时移动次数较少。

总体来说,希尔排序的时间复杂度可减小到n(log n)^2, 空间复杂度O(1)。

 

 

猜你喜欢

转载自blog.csdn.net/CS171_chengl/article/details/84786839