十大排序算法再总结

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

typedef int bool;
#define true 1
#define false 0

void bubbleSort(int arr[], int n);                  //冒泡排序
void bubbleSort2(int arr[] , int n);                //冒泡排序 改进一
void bubbleSort3(int arr[], int n);                 //冒泡排序 改进二
void insertionSort(int arr[], int n);               //直接插入排序
void shellSort(int arr[], int n);                   //希尔排序
void selectionSort(int arr[], int n);               //简单选择排序
void mergeSort_iteration(int arr[], int n);         //归并排序(迭代)
void mergeSort_recursion(int arr[], int n);         //归并排序(递归)
void heapSort(int arr[], int n);                    //堆排序
void quickSort(int arr[], int n);                   //快速排序
void countSort(int arr[], int n);                   //计数排序
void bucketSort(int arr[], int n);                  //桶排序
void radixSort_LSD(int arr[], int n);               //基数排序(LSD低位优先)

int main(int argc, const char * argv[]) {

//    int arr[] = {12,4,23,188,53,23,1,43,63,7};
    int arr[] = {20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0};
//    int arr[] = {15,28,17,6,14,29,80,30,50};
    int num = sizeof(arr)/sizeof(arr[0]);

    printf("Before Sorting:");
    for (int i = 0; i<num; i++) {
        printf("%d ",arr[i]);
    }
    printf("\n");

//    bubbleSort(arr, num);                 //冒泡排序
//    bubbleSort2(arr, num);                //冒泡排序 改进一
//    bubbleSort3(arr, num);                //冒泡排序 改进二
//    insertionSort(arr, num);              //直接插入排序
//    shellSort(arr, num);                  //希尔排序
//    selectionSort(arr, num);              //简单选择排序
//    mergeSort_iteration(arr, num);        //归并排序(迭代)
//    mergeSort_recursion(arr, num);        //归并排序(递归)
//    heapSort(arr, num);                   //堆排序
//    quickSort(arr, num);                  //快速排序
//    countSort(arr, num);                  //计数排序
//    bucketSort(arr, num);                 //桶排序
    radixSort_LSD(arr, num);                  //基数排序

    printf("After Sorting:");
    for (int i = 0; i<num; i++) {
        printf("%d ",arr[i]);
    }
    printf("\n");

    return 0;
}

//数值交换函数
void swap(int *a, int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}

/*冒泡排序 时间复杂度O(n^2) 稳定*/
void bubbleSort(int arr[], int n){
    for (int i = 0; i<n; i++) {
        for (int j = 0; j<n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                swap(&arr[j], &arr[j+1]);
            }
        }
    }
}

/*冒泡排序改进一
 思路:设置标记位标记序列是否已经有序,若一遍排序过程没有交换,说明序列已经有序,停止排序
 时间复杂度最佳为O(n) 最坏为O(n^2)*/
void bubbleSort2(int arr[], int n){
    for (int i = 0; i<n; i++) {
        bool isOrdered = true;
        for (int j = 0; j<n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                swap(&arr[j], &arr[j+1]);
                isOrdered = false;
            }
        }
        if (isOrdered) {
            break;
        }
    }
}

/*冒泡排序改进二
 思路:设置标记位标记一轮下来最后的交换位置,再此位置之后的序列已经有序,无需再遍历
 最佳时间复杂度O(n) 最差时间复杂度O(n^2)*/
void bubbleSort3(int arr[], int n){
    int k = n;
    int tag = k;
    while (tag) {
        k = tag;
        tag = 0;
        for (int i = 0; i<k-1; i++) {
            if (arr[i] > arr[i+1]) {
                swap(&arr[i], &arr[i+1]);
                tag = i+1; //标记最后交换的位置
            }
        }
    }
}

/*直接插入排序
 思路:将第一个元素作为一个有序数列,依次遍历后面的元素,每遍历一个元素将其置入有序数列,规则为若该元素比前一个数大,则直接置入;若该数比前一个数小,则交换两数后继续与前一个数比较,直到比前一个数大或称为第一个元素
 最佳时间复杂度O(n) 最坏时间复杂度O(n^2)
 稳定
 适用于整体递增(目标升序)/递减(目标降序)序列*/
void insertionSort(int arr[], int n){
    for (int i = 1; i<n; i++) {
        int k = i;
        while (k && arr[k]<arr[k-1]) {
            swap(&arr[k], &arr[k-1]);
            k--;
        }
    }
}

/*希尔排序
思路:对于一个序列,设置一定的增量递减规则,对于每一个增量,将序列分成若干个子序列分别进行直接插入排序。当增量递减到1时,则为整个序列的直接插入排序。
 希尔排序是对直接插入排序的改进,按照增减递减序列进行多趟排序使得序列整体逐渐向有序靠近。
 时间复杂度平均O(n^1.3)
 不稳定
 */
void shellInsertionSort(int arr[], int n, int inc){
    for (int i = 0; i<inc; i++) {
        for (int j = 0; j<n; j+=inc) {
            int k = j;
            while (k-i && arr[k] < arr[k-inc]) {
                swap(&arr[k], &arr[k-inc]);
                k -= inc;
            }
        }
    }
}

void shellSort(int arr[], int n){
    int inc = n/2;  //这里按照增量递减序列(n/2,n/4,n/8,...,1)进行排序
    while (inc) {
        shellInsertionSort(arr, n, inc);
        inc /= 2;
    }
}

/*简单选择排序 时间复杂度O(n^2) 稳定*/
void selectionSort(int arr[], int n){
    for (int i = 0; i<n; i++) {
        int minIndex = i;
        for (int j = i; j<n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex != i) {
            swap(&arr[minIndex], &arr[i]);
        }
    }
}

/*归并排序(迭代)
思路:将原始序列的每一个元素有看成是一个有序的序列,从左到右将相邻的有序序列两两合并成一个新的有序序列,此时各个有序序列的元素个数为2或1(1为原始序列个数为奇数情况下最后一个有序序列元素的个数)。重复操作,将新得到的有序序列从左到右合并成新的有序序列。最后一次合并是将元素索引为0~(n-1)/2的序列与(n-1)/2+1~n-1的序列合并。
 时间复杂度O(nlogn)
 稳定*/
void merge_iteration(int arr[], int *temArray, int n){
    int inc = 1;
    while (inc<n) {
        for (int i = 0; i<n; i+=inc*2) {
            int lBegin = i;
            int lEnd = i+inc-1;
            int rEnd = (i+inc*2-1)>n-1?n-1:(i+inc*2-1);
            int lHalfIndex = lBegin;
            int rHalfIndex = lEnd+1;
            int temIndex = lBegin;
            while (lHalfIndex<=lEnd && rHalfIndex<=rEnd) {
                if (arr[lHalfIndex]<=arr[rHalfIndex]) {
                    temArray[temIndex++] = arr[lHalfIndex++];
                }else{
                    temArray[temIndex++] = arr[rHalfIndex++];
                }
            }
            while (lHalfIndex<=lEnd) {
                temArray[temIndex++] = arr[lHalfIndex++];
            }
            while (rHalfIndex<rEnd) {
                temArray[temIndex++] = arr[rHalfIndex++];
            }
            for (int i = temIndex-1; i>=lBegin; i--) {
                arr[i] = temArray[i];
            }
        }

        inc *= 2;
    }
}

void mergeSort_iteration(int arr[], int n){
    int *temArray;
    temArray = (int *)malloc(sizeof(int)*n);
    if (temArray == NULL) {
        printf("error to alloc memory!");
        exit(1);
    }
    merge_iteration(arr, temArray, n);
    free(temArray);
}

/*归并排序(递归)
 时间复杂度O(nlogn)
 稳定*/
void merge_recursion(int arr[], int temArr[], int lBegin, int lEnd, int rEnd){

    if (lBegin < rEnd) {
        merge_recursion(arr, temArr, lBegin, (lBegin+lEnd)/2, lEnd);
        merge_recursion(arr, temArr, lEnd+1, (lEnd+1+rEnd)/2, rEnd);

        int lHalfIndex = lBegin;
        int rHalfIndex = lEnd+1;
        int temIndex = lBegin;
        while (lHalfIndex <= lEnd && rHalfIndex <= rEnd) {
            if (arr[lHalfIndex] <= arr[rHalfIndex]) {
                temArr[temIndex++] = arr[lHalfIndex++];
            }else{
                temArr[temIndex++] = arr[rHalfIndex++];
            }
        }
        while (lHalfIndex <= lEnd) {
            temArr[temIndex++] = arr[lHalfIndex++];
        }
        while (rHalfIndex <= rEnd) {
            temArr[temIndex++] = arr[rHalfIndex++];
        }

        for (int i = temIndex-1; i>=lBegin; i--) {
            arr[i] = temArr[i];
        }
    }
}

void mergeSort_recursion(int arr[], int n){
    int *temArray;
    temArray = (int *)malloc(n * sizeof(int));
    if (temArray == NULL) {
        printf("error to alloc memory!");
        exit(1);
    }
    int centerIndex = (n-1)/2;
    merge_recursion(arr, temArray, 0, centerIndex, n-1);
    free(temArray);
}

/*堆排序  时间复杂度O(nlogn) 不稳定
说明:将序列数组模拟成一个堆,则堆是一个完全二叉树结构。堆分为小根堆和大根堆,小根堆所有的根节点小于其子节点,大根堆所有根节点大于其子节点。若要对一个序列进行升序排序,只要借助堆的性质,将序列构造成堆,这样堆顶元素便是序列中的最小(或最大)元素,将其输出后将剩余元素重新调整成堆,重复操作,每次便可获取到最小(最大)元素。
 思路:①将序列构造成堆,并输出堆顶元素。 ②将剩余n-1个元素重新调整成堆结构,输出堆顶元素,重复操作。
 难点:①如何构造堆 ②获取堆顶元素后,如何调整剩余n-1个元素使之成为新堆。
 讨论:
 首先讨论第②个问题,实现思路如下(这里以小根堆为例)
 1.一个有n个元素的堆,输出堆顶元素后,将堆底元素送入堆顶,这时堆结构被破坏。
 2.将根节点元素与左右子树中较小的元素交换。
 3.若与左子树交换,判断左子树堆是否被破坏(若根节点比左右节点之一大则破坏)。若左子树堆被破坏,则对左子树重复 2 操作。
 4.若与右子树交换,判断右子树堆是否被破坏。若右子树堆被破坏,则对右子树重复 2 操作。
 5.继续对交换后不满足堆性质的子树进行上述操作,直到叶子节点。
 上述这一过程暂且称之为堆的调整。

 下面讨论第①个问题,如何构建堆?思路如下:
 由于序列数组对应的堆是一个完全二叉树结构,因此最后一个叶子节点的父节点对应的索引为(n-2)/2,n为元素个数。当只有一个元素时,不存在父节点。要做的是:从索引为(n-2)/2的节点依次遍历到索引为0的节点,每遍历一个节点,以该节点为根节点,对其所在的树进行②的调整。
*/


//堆的调整 n为元素个数 nodeIndex为要调整的树的根节点索引
void adjustHeap(int arr[],int n,int nodeIndex){
    int leftIndex = 2*nodeIndex+1;
    while (leftIndex < n) {
        int minIndex;
        if ((leftIndex+1) <= (n-1)) { //存在右子树
            minIndex = arr[leftIndex]<arr[leftIndex+1]?leftIndex:leftIndex+1;
        }else{
            minIndex = leftIndex;
        }
        if (arr[nodeIndex] < arr[minIndex]) {
            break;
        }
        int temp = arr[nodeIndex];
        arr[nodeIndex] = arr[minIndex];
        arr[minIndex] = temp;
        nodeIndex = minIndex;
        leftIndex = nodeIndex*2+1;
    }
}

//堆的构建
void constructHeap(int arr[], int n){
    if (n>1) {
        int nodeIndex = (n-1-1)/2;
        for (int i = nodeIndex; i>=0; i--) {
            adjustHeap(arr,n,i);
        }
    }
}

void heapSort(int arr[], int n){
    constructHeap(arr, n); //构造小根堆
    int *tempArray = (int *)malloc(sizeof(int)*n);
    tempArray[0] = arr[0]; //输出堆顶元素
    arr[0] = arr[n-1];     //将堆底元素送入堆顶
    for (int i = n-2; i>=0; i--) { //堆的调整,重复操作,每次调整后都将堆顶元素输出,并将堆底元素送入堆顶
        adjustHeap(arr,i+1,0);
        tempArray[n-i-1] = arr[0];
        arr[0] = arr[i];
    }
    for (int i = 0; i<n; i++) {
        arr[i] = tempArray[i];
    }
    free(tempArray);
}

/*快速排序 时间复杂度O(nlogn) 不稳定
 思路:
 ①初始时将原始序列第一个元素看成锚点,进行一趟排序,结果使得该锚点左侧的元素均比锚点小,右侧的元素均比锚点大。
 ②分别将锚点左侧的元素和右侧的元素重新看成一个新的序列,重复①的操作,直到子序列只有一个元素。

 难点:如何调整序列使得一趟排序之后锚点左侧元素均比其小,右侧元素均比其打?
 步骤:
 ①对于一个序列,设置两个标记left和righ分别标记首元素和末尾元素,初始将left所指的元素看成锚点。
 ②若锚点为left标记的元素,将其与right标记的元素比较。若锚点小于等于right标记的元素,right左移;若锚点大于right标记的元素,交换两元素,并将left右移,这时候锚点由right标记。
 ③若锚点为right标记的元素,将其与left标记的元素比较。若锚点大于等于left标记的元素,left右移;若锚点小于left标记的元素,交换两元素,并将right左移,这时候锚点由left标记。
 ④重复②③操作,直到left与right重合。
 */

//调整left与right包围的序列,使锚点左侧元素均比其小,右侧元素均比其大。
int adjustSequence(int arr[], int n, int left, int right){
    int sign = 0; //标记 0表示锚点在左,1表示锚点在右
    while (left != right) {
        if (sign == 0) {
            if (arr[left] <= arr[right]) {
                right--;
            }else{
                swap(&arr[left], &arr[right]);
                left++;
                sign = 1;
            }
        }else{
            if (arr[right] >= arr[left]) {
                left++;
            }else{
                swap(&arr[left], &arr[right]);
                right--;
                sign = 0;
            }
        }
    }
    return left;
}

void qSort(int arr[], int n, int left, int right){
    if (left < right) {
        int loc = adjustSequence(arr, n, left, right);
        qSort(arr, n, left, loc-1);
        qSort(arr, n, loc+1, right);
    }
}

void quickSort(int arr[], int n){
    qSort(arr, n, 0, n-1);
}


/*计数排序 时间复杂度O(n+k)
思路:计数排数借助一个用于计数的辅助数组,该数组的容量为序列最大元素max与最小元素min的差值+1,第i个元素值为序列中值为(i+min)的元素个数。之后遍历整个辅助数组,若值为x(x不为0),说明原始序列中有x个值为辅助数组当前索引值的元素,输出x次索引值即可。
缺点:执行过程中需要动态分配一个用于计数的辅助数组,数组容量取决于原始序列中数值的范围,对于范围很大的序列,需要消耗大量的时间和内存。
 */
void countSort(int arr[], int n){

    int max = arr[0];
    int min = arr[0];
    for (int i = 1; i<n; i++) { //n
        if (max < arr[i]) {
            max = arr[i];
        }
        if (min > arr[i]) {
            min = arr[i];
        }
    }

    int k = max-min+1;
    int *tempArray = (int *)malloc(sizeof(int)*k);
    if (tempArray == NULL) {
        printf("fail to alloc memory!");
        exit(1);
    }
    memset(tempArray, 0, sizeof(int)*k);

    for (int i = 0; i<n; i++) {
        tempArray[arr[i]-min] += 1;
    }

    int index = 0;
    for (int i = 0; i<k; i++) {
        if (tempArray[i] != 0) {
            for (int j = 0; j<tempArray[i]; j++) {
                arr[index] = i+min;
                index++;
            }
        }
    }

    free(tempArray);
}

/*桶排序
 桶排序采用的是分治的思想,将序列元素按照某一规则分成若干组,每个组便是一个“桶”,将各个桶里的元素按照任意一种排序方法排好序之后,最终将所有元素重新合并成一个有序序列。
 例如对一组数值为0~100的均匀分布的元素序列进行桶排序,步骤如下:
 ①设计好元素的映射规则,可以设置“0号~9号”10个“桶”,分别存放“0~9、10~19、...、90~100”范围内的元素,这样元素就能根据“n/10”映射到对应的桶。
 ②创建10个“空桶”,可以用链表的形式表示每一个桶,一个“空桶”即为一个链表的头结点。
 ③遍历序列元素,将元素映射到“桶”里。
 ④遍历每一个“桶”,对其中的元素排序
 ⑤合并每一个“桶”里的元素,得到新的排好序的序列。

 时间复杂度:对于桶排序而言,分的“桶”越多,消耗的时间就越少,但是空间开销就越大,这也就是时间空间不两立。假设有n个元素,m个桶,元素大小是均匀分布的,那么平均每一桶里有n/m个元素。若桶内元素采用的是快速排序的方法,那么时间复杂度将是O(n+m*n/m*log(n/m))即O(n+n(logn-logm)),可见当m越接近n,时间复杂度越靠近O(n)。
 稳定性无法确定,取决于分桶后的排序方法
 */
#define bucketNum 10 //分10个桶

typedef struct node{
    int data;
    struct node *next;
}*Node;

void bucketSort(int arr[], int n){

    Node bucket[bucketNum];
    for (int i = 0; i<bucketNum; i++) {
        bucket[i] = (Node)malloc(sizeof(Node));
        bucket[i]->data = 0; //链头节点data标记当前链表中节点数(即当前桶中的元素个数)
        bucket[i]->next = NULL;
    }

    int max = arr[0];
    int min = arr[0];
    for (int i = 1; i<n; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
        if (arr[i] < min) {
            min = arr[i];
        }
    }
    int interval = max-min+1;
    int bucketSpace = (int)ceil(interval/(float)bucketNum); //计算每个桶的容量

    printf("元素映射:\n");
    for (int i = 0; i<n; i++) {
        int key = (arr[i]-min)/bucketSpace; //获取元素映射桶的下标
        printf("%d:%d\n",arr[i],key);

        Node item = (Node)malloc(sizeof(Node));
        item->data = arr[i];

        Node temp = bucket[key];
        if (!temp->next) { //空桶
            temp->next = item;
            item->next = NULL;
            bucket[key]->data++;
        }else{  //非空桶 插入链表排序排序
            while (temp->next && temp->next->data<item->data) {
                temp = temp->next;
            }
            item->next = temp->next;
            temp->next = item;
            bucket[key]->data++;
        }
    }

    printf("映射完毕:\n");

    //合并桶中元素
    int index = 0;
    Node temp;
    for (int i = 0; i<bucketNum; i++) {
        printf("%d:",i);
        if (bucket[i]->data) {
            temp = bucket[i];
            while (temp->next) {
                arr[index] = temp->next->data;
                printf("%d ",temp->next->data);
                temp = temp->next;
                index++;
            }
        }
        printf("\n");
    }

    //释放动态分配的内存
    for (int i = 0; i<bucketNum; i++) {
        temp = bucket[i];
        while (temp->next) {
            temp = temp->next;
            bucket[i]->next = temp->next;
            free(temp);
        }
        free(bucket[i]);
    }
}

/*基数排序
 计数排序和桶排序都只是研究一个关键字的排序,基数排序则是针对多个关键字的排序。

 对于扑克牌而言,每一张牌牌值由两个要素构成:
 花色:梅花<方块<红心<黑心 ♣ < ♦ < ♥ < ♠
 面值:2<3<4<5<6<7<8<9<10<J<Q<K<A
 那么牌值的大小顺序为 ♣2<♣3<♣4<...<♣A<♦2<♦3<♦4<...<♦A<♥2<♥3<♥4<...<♥A<♠2<♠3<♠4<...<♠A

对于一副牌的排序,按照基数排序,有这样两种方案:
 ①先将牌按花色排序,将其分成4个组,即梅花组,方块组,红心组,黑心组。再对每个组的牌按面值排序,最后将4个组按顺序合并起来即可。
 ②先将牌按面值排序,将其分成13个组。再对每个组的牌按花色排序,最后将13个组按顺序合并起来即可。

 上面两种方案,对应基数排序的两种方案:最高位优先(Most Significant Digit first简称MSD)和最低位优先(Least Significant Digit first简称LSD)。顾名思义,前者以权重大的关键字优先排序,后者以权重小的关键字优先排序。

 对于多关键字的元素排序而言,只需要按照各个关键字逐个分组排序,最终合并所有分组即可,因此基数排序本质上是分解成了多个桶排序。至于基数排序的“基数”,是指关键字的范围,例如扑克牌花色的基数值为4,面值的基数值为13。而对于数值或字符类型的序列,若将其看成单关键字序列进行基数排序,那么它就是桶排序。其实我们可以把数值看成是多关键字的元素,关键字是它的数位值(个位、十位...),每个关键字的基数值均为10。
 */

void radixSort_LSD(int arr[], int n){

    Node bucket[10];  //由于对于数值而言,各个关键字的基数值都为10,因此只需借助一个“桶数组”即可
    Node tail[10];    //用于保存各个桶链表链尾结点的指针,这样插入结点的时候可以避免遍历整个链表
    memset(bucket, 0, sizeof(bucket));
    memset(tail, 0, sizeof(tail));

    int max = arr[0];
    for (int i = 0; i<n; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    //计算最大位数
    int maxDigit = 1;
    while (max >= (int)pow(10, maxDigit)) {
        maxDigit++;
    }
    for (int digit = 0; digit<maxDigit; digit++) {  //对每一位(每个关键字)做桶排序

        //关键字为第digit位的桶排序
        for (int i = 0; i<n; i++) {
            Node node = (Node)malloc(sizeof(Node));
            node->data = arr[i];
            int key = arr[i]/(int)pow(10, digit)%10; //获取映射桶的索引

            if (bucket[key] == NULL) {
                bucket[key] = node;
                tail[key] = node;
            }else{
                tail[key]->next = node;
                tail[key] = node;
            }
        }

        //桶中数值送回数组同时回收内存
        int index = 0;
        Node temp;
        for (int key = 0; key<10; key++) {
            temp = bucket[key];
            while (temp != NULL) {
                arr[index] = temp->data;
                bucket[key] = bucket[key]->next;
                free(temp);
                temp = bucket[key];
                index++;
            }
        }

        printf("digit %d:",digit);
        for (int i = 0; i<n; i++) {
            printf("%d ",arr[i]);
        }
        printf("\n");
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值