iOS进阶

图解常用排序算法

2019-08-21  本文已影响13人  劳模007_Mars

排序(Sorting)是数据处理中一种很重要也很常用的运算。一般情况下,排序操作在数据处理过程中要花费很多时间,选对了排序算法,能够提升程序的运行效率。在面试过程中,有关排序算法的面试也是必考点。所以,作为一名开发者,排序算法是一项必备的技能。

排序(Sorting)

排序就是将一组对象按照规定的次序重新排列的过程。

排序可分为两大类:
内部排序(Internal Sorting):待排序的记录全部存放在计算机内存中进行的排序过程;
外部排序(External Sorting):待排序的记录数量很大,内存不能存储全部记录,需要对外存进行访问的排序过程。
本文只讨论内部排序

评价一个排序算法的优劣,主要从时间复杂度空间复杂度两方面考虑:
时间复杂度:指执行算法所需要的计算工作量,粗略谈就是该算法的运行时间。时间复杂度常用大O符号表示。
空间复杂度:指执行这个算法所需要的内存空间。

当然,仅用执行效率和内存消耗来衡量排序算法的好坏是不够的。针对排序算法,还有一个重要的度量指标:排序算法的稳定性
排序算法的稳定性:相同键值的两个记录在排序前后的相对位置的变化情况。比如在原序列中,r[i] = r[j],并且 r[i] 在 r[j] 前面;在排序后的序列中,r[i] 仍在 r[j] 前面,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序的方法有很多,本文主要介绍一下几种:

插入排序

交换排序

选择排序

归并排序

一、插入排序

常用的插入排序方法有直接插入排序、折半插入排序、表插入排序和希尔排序。本文只介绍直接插入排序和希尔排序。

直接插入排序

直接插入排序(Straight Insertion Sorting)的基本思想是依次将每个记录插入到一个已排序好的有序表中,从而得到一个新的、记录数加 1 的有序表。

void InsertSort(int  a[], int n) {
    int temp = 0;
    //n为表长,从第二个记录起进行插入
    for (int i = 1; i < n; i++) {
        int j = i - 1;
        if (a[i] < a[j]) {
            temp = a[i];
            a[i] = a[j];
            while (temp < a[j-1]) {
                a[j] = a[j-1];
                j--;
            }
            a[j] = temp;
        }
    }
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int array[] = {45,38,66,90,88,10,25};
        int n = sizeof(array)/sizeof(int);
        InsertSort(array, n);
        printf("排序好的数组为:");
        for (int i = 0; i < n; i++) {
            printf(" %d", array[i]);
        }
        printf("\n");
    }
    return 0;
}

直接插入排序过程如下:


直接插入排序过程示意图.png

直接插入排序算法的时间复杂度为O(n²),空间复杂度为O(1)。如果待排序记录的数量很大时,一般不选用直接插入排序。

希尔排序

希尔排序(Shell Sort)基本思想是把序列分成若干个小组,然后对每一个小组分别进行插入排序。先取n/2作为第一个增量gap,把序列分组。所有距离为gap的倍数的记录为一组,在各组内进行直接插入排序。然后增量再减半进行分组排序,直到增量为1。

void ShellSort(int a[], int n){
    int i;
    int j;
    int k;
    int gap;    //gap是分组的增量
    int temp;
    //增量gap减半
    for(gap = n / 2; gap > 0; gap = gap / 2){
        for(i = 0; i < gap; i++){
            for(j = i + gap; j < n; j = j + gap){    //单独一次的插入排序
                if(a[j] < a[j - gap]){
                    temp = a[j];
                    k = j - gap;
                    while(k >= 0 && a[k] > temp){
                        a[k + gap] = a[k];
                        k = k - gap;
                    }
                    a[k + gap] = temp;
                }
            }
        }
    }
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int array[] = { 5,12,35,11,2,9,41,26,18,4 };
        int n = sizeof(array) / sizeof(int);
        ShellSort(array, n);
        printf("排序好的数组为:");
        for (int i = 0; i < n; i++) {
            printf("%d ", array[i]);
        }
        printf("\n");
    }
    return 0;
}

希尔排序过程如下:
第一步:gap = 10 / 2 = 5,将原序列分组排序:


希尔排序一.png

第二步:gap减半 = 2,将序列继续分组排序:


希尔排序二.png
第三步:gap再减半 = 1,将序列排序得出最后结果:
希尔排序三.png
希尔排序的时间复杂度为O(n*logn),空间复杂度为O(1)。希尔排序的时间复杂度与选取的增量有关,选取合适的增量可减少时间复杂度。

二、交换排序

交换排序的基本思想是比较两个记录的大小,如果这两个记录的大小出现逆序,就交换这两个记录。这样就把较小的记录往前移动,较大的记录往后移动。

冒泡排序

冒泡排序(Bubble Sorting)是一种交换排序算法,起过程是将第一个记录和第二个记录进行比较,若为逆序则将两个记录交换,然后继续比较第二个和第三个记录。依次比较,直到完成第 n - 1 个记录和第 n 个记录的比较为止。上述过程为第一趟起泡,结果就是将最大的记录移到了第 n 个位置上。然后再进行第二趟起泡排序。重复以上过程,当在一趟起泡过程中没有进行记录交换的操作时,则整个排序过程完成。

//冒泡排序算法
void bubbleSort(int a[], int n) {
   for (int i = 0; i<n - 1; i++)
       for (int j = 0; j < n - i - 1; j++){
           //如果前面的数比后面大,进行交换
           if (a[j] > a[j + 1]) {
               int temp = a[j];
               a[j] = a[j + 1];
               a[j + 1] = temp;
           }
       }
}
int main(int argc, const char * argv[]) {
   @autoreleasepool {
       int array[] = { 5,12,35,11,2,9,41 };
       int n = sizeof(array) / sizeof(int);
       bubbleSort(array, n);
       printf("排序好的数组为:");
       for (int i = 0; i < n; i++) {
           printf("%d ", array[i]);
       }
       printf("\n");
   }
   return 0;
}

冒泡排序过程如下图所示:


冒泡排序过程示意图.png

冒泡排序的时间复杂度为O(n²),空间复杂度为O(1)。

快速排序

快速排序(Quick Sorting)是交换排序的一种,实质上是对冒泡排序的一种改进。它的基本思想是:在序列中选取一个记录作为基准(通常选取第一个记录作为基准),通过一趟排序将序列分为小于等于基准和大于基准的两部分,然后分别对这两部分记录继续进行快速排序,直到达到整个序列变成有序的序列为止。

//一趟快速排序
int quickPatition(int a[], int left,int right) {

    int key = a[left];    //基准元素
    while (left < right) {
        //从尾端进行比较,将比key小的记录移到左边
        while ((left < right) && a[right] >= key) {
            right--;
        }
        a[left] = a[right];
        //从首端进行比较,将比key大的记录移到右边
        while ((left < right) && a[left] <= key) {
            left++;
        }
        a[right] = a[left];
        
    }
    //一趟快速排序结束,将key移到其最终位置
    a[left] =key;
    return left;
}
//完整快速排序,递归进行快速排序
void quickSort(int *a,int left,int right) {
    if (left>=right)
        return;
    int mid = quickPatition(a,left,right);
    quickSort(a, left, mid - 1);
    quickSort(a, mid + 1, right);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int array[] = { 5,12,35,11,2,9,41,26,18,4 };
        int n = sizeof(array) / sizeof(int);
        quickSort(array, 0, n - 1);
        printf("排序好的数组为:");
        for (int i = 0; i < n; i++) {
            printf("%d ", array[i]);
        }
        printf("\n");
    }
    return 0;
}

快速排序过程如下图所示:


快速排序示意图.png

快速排序时间复杂度为 O(n logn),空间复杂度为 O(n logn)。

三、选择排序

选择排序(Selection Sorting)基本思想为:每一次在 n - i + 1 个记录中(i = 1,2,...,n - 1)选取最小的记录作为有序序列的第 i 个记录。
本文只介绍直接选择排序。

直接选择排序

直接选择排序基本思想是:在第 i 次选择操作中,通过 n - i 次比较,从 n - i + 1 个记录中选出最小的记录,并和第 i 个记录交换(1 ≤ i ≤ n - 1)。

void SelectSort(int *a, int n) {
    //每次循环,选出一个最小的值
    for (int i = 0; i < n; i++){
        //临时变量用于存放数组最小值的位置
        int key = i;
        for (int j = i + 1; j < n; j++) {
            if (a[j] < a[key]){
                //记录数组最小值位置
                key = j;
            }
        }
        if (key != i){
            //交换最小值,可以用c++的swap()函数替代
            int tmp = a[key];
            a[key] = a[i];
            a[i] = tmp;
        }
        
    }
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int array[] = {45,38,66,90,88,10,25};
        int n = sizeof(array) / sizeof(int);
        SelectSort(array, n);
        printf("排序好的数组为:");
        for (int i = 0; i < n; i++) {
            printf("%d ", array[i]);
        }
        printf("\n");
    }
    return 0;
}

直接选择排序过程如下图所示:


直接选择排序示意图.png

直接选择排序时间复杂度为O(n²),空间复杂度为O(1)。

四、归并排序

归并排序(Merge Sorting)是将两个或两个以上的有序序列合并成一个新的有序序列。合并的方法是比较各子序列的第一个记录,最小的一个就是排序后新序列的第一个记录。取出这个记录继续比较各子序列现有的第一个记录,便可找出排序后新序列的第二个记录。如此类推,最终可以得出新的序列。
与上文所讲述的插入排序、交换排序、选择排序不同的是,归并排序要求待排序的序列是有若干个有序的子序列组成的。

有序序列的合并

void mergeList(int a[], int aLen, int b[], int bLen, int result[]) {
    //p、q为起始位置
    int p = 0;
    int q = 0;
    int i = 0;
    
    while (p < aLen && q < bLen) {
        if (a[p] <= b[q]) {
            result[i] = a[p];
            p++;
        }else {
            result[i] = b[q];
            q++;
        }
        i++;
    }
    //a数组有剩余
    while (p < aLen) {
        result[i] = a[p++];
        i++;
    }
    //b数组有剩余
    while (q < bLen) {
        result[i] = b[q++];
        i++;
    }
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int aArray[] = {5,38,66,80,88,100,125};
        int aLength = sizeof(aArray) / sizeof(int);
        int bArray[] = {4,6,15,32,44,47,55,73};
        int bLength = sizeof(bArray) / sizeof(int);
        int newArray[15];
        mergeList(aArray, aLength, bArray, bLength, newArray);
        printf("排序好的数组为:");
        for (int i = 0; i < 15; i++) {
            printf("%d ", newArray[i]);
        }
        printf("\n");
    }
    return 0;
}

过程示意图如下:
分别取出两个序列第一条记录比较,得出新序列第一条记录为 4 :


有序序列排序过程示意图1.png

然后 q 往后移动,比较得出,新序列第二条为 5 ,p往后移动。如此类推。


有序序列排序过程示意图2.png

总结

以上几种算法的时间复杂度、空间复杂度、稳定性总结如下图所示。排序算法有很多种,本文只简单介绍了其中几种。后续会为大家持续更新,共同学习更多算法。每种算法的复杂程度不同,针对的数据情况也不同,算法本身没有好坏之分,在一定的数据支持下,选择合适的算法,才能提高程序的运行效率。


算法总结.png

更多技术知识请关注微信公众号
iOS进阶


iOS进阶.jpg
上一篇下一篇

猜你喜欢

热点阅读