简单排序算法之插入排序、选择排序和冒泡排序

倒也不是临时起意,被问了冒泡排序,我竟然……忘了,好吧,时隔多年重见冒泡,好久不见 甚是想念;

于是乎就写了下代码,顺便重温了两个简单的排序算法:插入排序和选择排序;

作为一个开头,之后有机会就把其它相关排序也总结下,不得不说,真的好生疏/(ㄒoㄒ)/~~

 

简单排序算法之插入排序、选择排序和冒泡排序

插入排序:

插入排序是从无序区里依次拿到元素放到有序区里合适的位置;

选择排序;

选择排序是每次将无序区里的最小值放到有序区里的最后一个位置;

冒泡排序;

冒泡排序是每次循环将最大的数“冒泡”到最上边;

 

有了这几个简单的描述,接下来让我们一个一个来看下;

 

1.插入排序:

插入排序是从无序区里依次拿到元素放到有序区里合适的位置;

 

 

 

1.1插入排序的理解:

 

 可以将原来的数组看成两个区,分别用两个数组来表示,一个有序区,一个无序区;

 有序区起始包含一个元素,即原数组的位置0所对应的元素;

 无需区则包含剩余的所有元素;

 我们遍历无需区元素,然后与有序区(从末尾位置)逐个进行比较,因为有序区有序,所以当找到满足比较大小的要求时对应的位置,就是元素要插到有序区对应的位置;

 为此,我们还需要遍历有序区,好让遍历无序区的元素在有序区中找到合适的位置;

 

 遍历无序区对应算法的外循环;

 遍历有序区,比较出无序元素的位置对应内循环;

 

1.2我们看下具体实现的代码:

 

//插入排序
/*数组 数组长度 数组元素的大小 比较函数指针*/
int issort(void * data, int size, int esize, int (*compare)(const void * key1, const void * key2)){
    char * a = data;
    void * key;
    int i, j;
    if ((key = (char *)malloc(esize)) == NULL) {
        return -1;
    }
    for (j = 1; j < size; j++) {
        memcpy(key, &a[j * esize], esize);
        i = j - 1;
        while (i >= 0 && compare(&a[i * esize], key) > 0) {
            memcpy(&a[(i + 1) * esize], &a[i * esize], esize);
            i--;
        }
        memcpy(&a[(i + 1) * esize], key, esize);
        
        for (int x = 0 ; x < size; x++) {
            printf("%i",a[x*esize]);
        }
        printf("\n");
    }
    free(key);
    return 0;
}

 

 

 

 1.3这里需要注意c函数的使用:

 

 

     #include <string.h>

     void*

     memcpy(void*restrictdst, constvoid*restrict src,size_tn);

使用时可以这样理解:

 

由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的内存空间;

还要注意这一句:

memcpy(&a[(i + 1) * esize], &a[i * esize], esize);//这里之所以要乘以esize,是因为指针a的类型是char,要表示实际数组中的元素所占空间,需要乘以每个元素的大小;

 

 

1.4关于时间复杂度:

 

 总的执行次数,可以这样推算:

 原数组长度n

 无需区长度j在遍历时逐渐变小直至0,分别为:(n-1),(n-2)……1;

 对应相应的j值,在有序区需要遍历的次数i(我们只考虑最坏情况)则为:1,2,……(n-2),(n-1);

 所有可以得到总的执行次数为(n -1 + 1)*(n - 1)/2 ,时间复杂度为O(n^2);即O(n的平方);

 

 比较不同算法的时间复杂度可以通过下面这个:

 O(n!) > O(3^n) > O(2^n) > O(n^3) > O(n^2lgn) > O(n^2) > O(nlgn) > O(n) > O(lgn) > O(1)

 

 

2.选择排序:

 

选择排序是每次将无序区里的最小值放到有序区里的最后一个位置;

 

说完了插入排序,再说选择排序就好说了;

 

其实无论是那种排序,都需要现有一个分区的概念;

1,2,3,4,5,6,7,8,9,10;

对于这样一个序列,我们分成两个区,一个叫有序区,一个叫无序区;

第一个元素自然就被划分到有序区里了;

其余的元素就都在无序区里;

 

选择排序意思就是从无序区里选择出一个元素放到有序区;

    这个元素要满足:

    1)是无序区里最小的;

    2)然后和有序区的当前遍历次数的位置元素进行互换;

比如:

    第0次,则交换完之后,有序区的第一个元素就是刚刚插入的最小值;

    第1次,则交换完之后,有序区的第二个元素就是刚刚插入的最小值(比第0次的要大);

    依次进行选择,最后,有序区就是有序的了;

这个过程需要记录遍历次数,一次遍历过程中还要遍历整个无序区;

    次数是n-1 无序区遍历次数总共是(n-1) +(n-2)+……+((0)即(n-1)*n/2;

    对应时间复杂度就是n的平方;

 

2.1我们来看下代码;

//选择排序
/*数组 数组长度 数组元素的大小 比较函数指针*/
int chooseSort(void * data, int size, int esize, int (*compare)(const void * key1, const void * key2)){
    char * a = data;
    void * key;
    int i, j, k;
    if ((key = (char *)malloc(esize)) == NULL) {
        return -1;
    }
    for (j = 0; j < size; j++) {
        i = j + 1;
        k = j;
        while (i < size ) {
            if (compare(&a[k * esize],&a[i * esize]) > 0) {
                k = i;
            }
            i++;
        }
        if (j != k) {
            memcpy(key, &a[j * esize], esize);
            memcpy(&a[j * esize],&a[k * esize] , esize);
            memcpy(&a[k * esize],key , esize);
        }
        for (int x = 0 ; x < size; x++) {
            printf("%i",a[x*esize]);
        }
        printf("\n");
    }
    
    free(key);
    return 0;
}

 

 

3.冒泡排序:

冒泡排序是每次循环将最大的数“冒泡”到最上边;

 

3.1直接看代码吧;

 

//冒泡排序
/*数组 数组长度 数组元素的大小 比较函数指针*/
int maopaoSort(void * data,int size, int esize,int (*compare)(const void * key1, const void * key2)){
    char * a = data;
    void * key;
    int i ,j;
    if ((key = (char *)malloc(esize)) == NULL){
        return -1;
    }
    for(i = 0; i < size;i++){
        for(j = 0;j < (size - i -1);j++){
            if(compare(&a[(j)*esize],&a[(j+1)*esize]) > 0){
                 memcpy(key, &a[(j) * esize], esize);
                 memcpy(&a[(j) * esize], &a[(j+1) * esize], esize);
                 memcpy(&a[(j+1) * esize], key, esize);
            }
        }
        for (int x = 0 ; x < size; x++) {
            printf("%i",a[x*esize]);
        }
        printf("\n");
    }
    free(key);
    return 0;
}

 

 

 

 

 

4.运行排序分析:

最后,我们来通过运行结果来分析和理解一下这几个算法的思想;

首先提供两个可用的比较函数:

 

//字符升序比较函数
int compare(const void * key1, const void * key2){
    char * keychar1 = (char * )key1;
    char * keychar2 = (char * )key2;
    
    return *keychar1 - *keychar2;
}
//数字升序比较函数
int compare1(const void * key1, const void * key2){
    int * keychar1 = (int * )key1;
    int * keychar2 = (int * )key2;
    
    return *keychar1 - *keychar2;
}


之后,我们编写测试代码:

 

 

    int ints1[] = {7,8,5,6,4,3};
    for (int i = 0 ; i < 6; i++) {
        printf("%i",ints1[i]);
    }
    printf("\n");
    printf("插入排序%i\n",issort(ints1,6,4,compare1));
    printf("\n");

    int ints2[] = {7,8,5,6,4,3};
    for (int i = 0 ; i < 6; i++) {
        printf("%i",ints2[i]);
    }
    printf("\n");
    printf("冒泡排序%i\n",maopaoSort(ints2,6,4,compare1));
    printf("\n");

    int ints3[] = {7,8,5,6,4,3};
    for (int i = 0 ; i < 6; i++) {
        printf("%i",ints3[i]);
    }
    printf("\n");
    printf("选择排序%i\n",chooseSort(ints3,6,4,compare1));

    return 0;


如果是比较字符串,你也可以这样写:

 

 

   char chars[] = "flower";
    printf("%lu\n",sizeof(chars));//7 有一个\0空间

    printf("%i\n",chooseSort(chars,6,1,compare));//传6 是因为不需要比较\0
    printf("%s\n",chars);

 

 

 

 

 

我们看看这几个数字排序的log:

 

我们起始排序的数组都是{785643};

箭头表示的是每次排序之后的结果,以及数字位置的变化;(还是图说的比较清楚,文字就不再赘述了)

还需要说明的是,三个算法的时间复杂度都是O(n^2);

so,这几个排序算法就ok了,个人理解,希望对大家有帮助。

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值