算法学习与代码实现2——插入排序

算法思路

插入排序其实就是斗地主抓拍的过程,大神级玩家除外,上学时我们寝室一大神,打牌时手中的牌从来不按套路放,我看的是云里雾里,但人家却心中有数。我不是大神,我斗地主只能按顺序放牌,右边小左边大,而且摸牌的时候必须随时排序。

插入排序就是个摸牌的过程,每摸到一张牌,就从左边开始对比,直到找到一张手中已有的牌小于或等于这张新摸到的牌,然后把这张牌插入到该牌的左边。

算法性能

插入排序是第一个涉及到的排序算法,要列出它的性能,先要知道几个指标。

稳定性

关于一个排序是否是稳定排序,标准是排序过程中两个相等的数的相对位置。假设待排序的数组中有连个5,那么排序后这两个5的相对位置如果能保证不变,就是稳定排序,否则就是不稳定排序。

时间复杂度

时间复杂度其实就是判断一个排序所用时间的数量级,一般存在最好时间复杂度,最差时间复杂度和平均时间复杂度。

空间复杂度

就是排序过程中占用的额外存储空间。

插入排序的性能

稳定性:稳定排序

时间复杂度:
平均情况:O(n^2),最好情况:O(n),最坏情况:O(n^2)

空间复杂度:
O(1)

伪代码

INSERTION-SORT(A)
    for j <- 2 to length[A]
        do key <- A[j]
        ▷ Insert A[j] into the sorted sequence A[1..j-1].
        i <- j-1
        while i > 0 and A[i] > key
            do  A[i + 1] <- A[i]
                i <- i - 1
        A[i+1] <- key

C语言实现

在准备实现的过程中发现排序这种代码还是c/c++实现着有点意义,Python人家已经有了排序了,不需要我们实现,而且想实现貌似也无从下手。

C语言实现插入排序的函数如下:

#include "insertion.h"

void insertion_sort(int * array, int numb){
    int key, i;
    for (int j = 1; j < numb; j++) {
        key = array[j];
        i = j - 1;
        while ( i >= 0 && array[i] > key) {
            array[i + 1] = array[i];
            i--;
        }
        array[i + 1] = key;
    }
}

借助上一篇介绍的生成随机数并写入文件的工具,生成一个一逗号隔开的随机数文件,用于测试,测试中读取文件中的随机数,并将排序后的数写入另一个文件,测试代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include "insertion.h"

int main(int argc, char *argv[])
{   
    if (argc != 3) {
        printf("usage:\n");
        printf("\tsort <filein> <fileout>\n");
        return -1;
    }
    FILE *fin = fopen(argv[1], "r");
    if (NULL == fin) {
        printf("open error\n");
        return -1;
    }
    char sep;
    int numb = 0;
    do {
        sep = fgetc(fin);
        if ( sep == ',')
            numb++; // get the count of the numbers in the input file
    } while (EOF != sep);
    printf("count of numbers in file \"%s\" is %d\n", argv[1], numb);
    fseek(fin, 0, SEEK_SET);
    int * iout = (int*)malloc(sizeof(int) * numb);
    int *p = iout;
    while (!feof(fin)) {
        int a;
        fscanf(fin, "%d,", &a);
        *p++ = a;
    }
    fclose(fin);

    // sort
    insertion_sort(iout, numb);
    FILE * fout = fopen(argv[2], "w");
    for (int i = 0; i < numb; i++) {
        fprintf(fout, "%d,", *(iout + i));
    }
    fclose(fout);
}

小知识,小经验总结

1. 判断文件中有多少个以逗号隔开的数

do {
    sep = fgetc(fin);
    if ( sep == ',')
        numb++; // get the count of the numbers in the input file
} while (EOF != sep);

通过fgetc()函数一个个读入文件中的字符,记录其中逗号的个数。当读到EOF时结束。

2. 移动文件指针fseek

fseek接受三个参数,第一个是文件描述符;第二个是移动的偏移量,正数表示正向偏移,复数表示反向偏移;第三参数是偏移的起始点,有三个预定义的值:SEEK_CUR、 SEEK_END 和 SEEK_SET,分别表示当前位置、文件结尾、文件开头。

3. 将文件中的字符串以数字形式读出

文件中自然只能保存字符串,想要将其按int形式读出,可使用fscanf进行格式化读入,循环使用fscanf,通过feof()函数判断是否已经读到了文件结尾。