常见九大内排序算法实现(C语言)

声明:部分动图是网上找的,因为前一版自己做的图太丑了,但保留了部分我认为有助于理解的静态图。

环境:

提示:附上头文件如下。

// hello.h
#include <stdio.h>
#include <malloc.h>

#define arr_len 8

void print_array(int arr[], int array_length) {
    for (int i = 0; i < array_length; ++i) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

1.直接插入排序

思想:

把待排序部分分为有序和无序两个部分,从无序部分取出元素直接插入到有序部分。通常把数组的第一个元素看作有序,后面所有看作无序。所以就有了遍历无序部分元素插入到有序部分两个过程,遍历的时间复杂度是O(n),插入时要查找正确的插入位置,根据查找位置的方式又分为直接插入排序和折半插入排序。直接插入就是遍历有序部分,直到找到正确插入位置。

实现:

// hello.c
#include "hello.h"

void insertion_sort(int arr[], int array_length) {
    for (int i = 1; i < array_length; ++i) {
        int data = arr[i];
        int j = 0;
        while (arr[j] < arr[i])
            j++;
        for (int k = i; k >= j + 1; k--)
            arr[k] = arr[k - 1];
        arr[j] = data;
    }
}

int main() {
    int arr[arr_len] = {6, 5, 3, 1, 8, 7, 2, 4};
    insertion_sort(arr, arr_len);
    print_array(arr, arr_len);
    return 0;
}

2.折半插入排序

思想:

折半插入排序是在直接插入排序的基础上改进了查找正确插入位置,即把遍历查找改成了折半查找

实现:

// hello.c
#include "hello.h"

void binary_insertion_sort(int arr[], int array_length) {
    int i, j, low = 0, high = 0, mid;
    int temp = 0;
    for (i = 1; i < array_length; i++) {
        low = 0;
        high = i - 1;
        temp = arr[i];
        while (low <= high) {
            mid = (low + high) / 2;
            if (arr[mid] > temp) {
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        for (j = i; j > low; j--) {
            arr[j] = arr[j - 1];
        }
        arr[low] = temp;
    }
}

int main() {
    int arr[arr_len] = {6, 5, 3, 1, 8, 7, 2, 4};
    binary_insertion_sort(arr, arr_len);
    print_array(arr, arr_len);
    return 0;
}

3.希尔排序

思想:

希尔排序,又称缩小增量排序。此处说的增量,即步长(step)。原始步长一般定为数组长度的一半,然后每轮遍历步长缩小为原来的一半,所以称为缩小增量排序。通过前面几轮循环,数组整体已经实现了基本有序(大多数元素序列有序),直到最终步长为1也就只需要做少量的调整,故效率高。但希尔排序的实际效率无法准确计算出来,一般认为希尔排序的时间复杂度为O(n^1.3~2)。

解释以下什么叫步长:比如一个长度为10的数组,初始步长为5,那么就把arr[0]和arr[5]看作是一组,组内进行插入排序,然后arr[1]和arr[6]又是一组,以此类推。一轮循环过后步长减半到2,arr[0]、arr[2]、arr[4]、arr[6]、arr[8]看作是一组,组内进行插入排序。

希尔排序过程推荐看旧金山大学官网的动图效果。点击Shell Sort即可

实现:

// hello.c
#include "hello.h"

void shell_sort(int arr[], int array_length) {
    int step = array_length / 2;
    while (step >= 1) {
        for (int i = 0; i < array_length; i += step) {
            int data = arr[i];
            int j = 0;
            while (arr[j] < arr[i])
                j++;
            for (int k = i; k >= j + 1; k--)
                arr[k] = arr[k - 1];
            arr[j] = data;
        }
        step = step / 2;
    }
}

int main() {
    int arr[arr_len] = {6, 5, 3, 1, 8, 7, 2, 4};
    shell_sort(arr, arr_len);
    print_array(arr, arr_len);
    return 0;
}

4.冒泡排序

思想:

两两交换。通过一遍一遍的交换把大的元素交换到后面去。“大数下沉”和“小数上浮”都是这一思想的体现。

实现:

// hello.c
#include "hello.h"

void bubble_sort(int arr[], int array_length) {
    for (int i = 0; i < array_length - 1; ++i) {
        for (int j = 0; j < array_length - i - 1; ++j) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int arr[arr_len] = {6, 5, 3, 1, 8, 7, 2, 4};
    bubble_sort(arr, arr_len);
    print_array(arr, arr_len);
    return 0;
}

5.快速排序

思想:

理解快速排序的关键在于理解flag,通过左右两个指针向中间移动,比flag小的出现在右边就交换,比flag大的出现在左边就交换,一轮过后flag出现在它最终会出现的位置。快速排序中同样也用到了分治的思想,对每个分治的部分最终实现了“左小、flag、右大”的大致排序,分治则保证了整体的有序。

实现:

// hello.c
#include "hello.h"

int getStandard(int arr[], int low, int high) {
    int flag = arr[low];
    while (low < high) {
        while (low < high && arr[high] >= flag) {
            high--;
        }
        if (low < high) {
            arr[low] = arr[high];
        }
        while (low < high && arr[low] <= flag) {
            low++;
        }
        if (low < high) {
            arr[high] = arr[low];
        }
    }
    arr[low] = flag;
    return low;
}

void quick_sort(int arr[], int low, int high) {
    if (low < high) {
        int pos = getStandard(arr, low, high);
        quick_sort(arr, low, pos - 1);
        quick_sort(arr, pos + 1, high);
    }
}

int main() {
    int arr[arr_len] = {6, 5, 3, 1, 8, 7, 2, 4};
    quick_sort(arr, 0, arr_len - 1);
    print_array(arr, arr_len);
    return 0;
}

6.直接选择排序

思想:

每轮选一个最小的元素,放到它最终的位置上。

实现:

// hello.c
#include "hello.h"

void select_sort(int arr[], int array_length) {
    for (int i = 0; i < array_length; ++i) {
        int min_pos = i;
        for (int j = i; j < array_length; ++j) {
            if (arr[min_pos] > arr[j])
                min_pos = j;
        }
        int temp = arr[min_pos];
        arr[min_pos] = arr[i];
        arr[i] = temp;
    }
}

int main() {
    int arr[arr_len] = {6, 5, 3, 1, 8, 7, 2, 4};
    select_sort(arr, arr_len);
    print_array(arr, arr_len);
    return 0;
}

7.堆排序

思想:

堆排序充分利用了完全二叉树的特点。任意一个父节点大于它的子节点,这样的完全二叉树叫做大顶堆;与之相反的,任意一个父节点小于它的子节点,这样的完全二叉树叫做小顶堆。

堆排序过程:把元素个数为n的完全二叉树转换为大顶堆,然后把堆顶和最后一个元素交换,此时产生了一个元素个数为n-1的完全二叉树,然后再转换为大顶堆,继续把堆顶和最后一个元素交换。循环往复就实现了排序。其实质还是选择排序,每次选出一个最大的,和最后一个交换,不过完全二叉树中选最大元素比遍历数组会快很多。

堆排序的思想并不难,按照下图推演一遍就懂了。

实现:

// hello.c
#include "hello.h"

void heap_adjust(int arr[], int n) {
    for (int i = n / 2; i >= 1; i--) {
        if (arr[i - 1] < arr[2 * i - 1]) {
            int temp = arr[i - 1];
            arr[i - 1] = arr[2 * i - 1];
            arr[2 * i - 1] = temp;
        }
        if (arr[i - 1] < arr[2 * i] && (2 * i) < n) {
            int temp = arr[i - 1];
            arr[i - 1] = arr[2 * i];
            arr[2 * i] = temp;
        }
    }
}

void heap_sort(int arr[], int array_length) {
    int n = array_length;
    do {
        heap_adjust(arr, n);
        int temp = arr[0];
        arr[0] = arr[n - 1];
        arr[n - 1] = temp;
    } while (n-- && n != 0);//加了个n!=0为了跳过最后一次循环中n=0时arr[0]=arr[n-1]的数组越界错误,实际上只剩一个元素不用再调整堆了
}

int main() {
    int arr[arr_len] = {6, 5, 3, 1, 8, 7, 2, 4};
    heap_sort(arr, arr_len);
    print_array(arr, arr_len);
    return 0;
}

8.归并排序

思想:

归并的意思就是把多个集合并成一个有序的集合。两个集合进行归并操作就称为二路归并,多个集合归并就称为多路归并算法。在内排序中提到的归并排序往往是指二路归并,多路归并在外排序中有使用,这里暂且不谈。

所以归并算法的核心就是归并,如何把多个元素集合合并成一个元素集合。但我们拿到的初始集合本身就是一个完整的集合,首先还是要用分治的思想把一个集合分解成多个集合,此时多个集合间保持了原始的无序性,然后再用归并思想进行合并。所以归并排序=分支+合并,如图所示。

实现:

// hello.c
#include "hello.h"

void merge(int arr[], int start, int mid, int end) {
    int *new_array = (int *) malloc(sizeof(int) * (end - start + 1));
    int i = start;
    int j = mid + 1;
    int k = 0;
    while (i <= mid && j <= end) {
        if (arr[i] < arr[j])
            new_array[k++] = arr[i++];
        else
            new_array[k++] = arr[j++];
    }
    while (i <= mid)
        new_array[k++] = arr[i++];
    while (j <= end)
        new_array[k++] = arr[j++];
    for (int l = 0; l < k; ++l)
        arr[start + l] = new_array[l];
    free(new_array);
}

void merge_sort(int arr[], int start, int end) {
    int mid = (start + end) / 2;
    if (start >= end)
        return;
    merge_sort(arr, start, mid);
    merge_sort(arr, mid + 1, end);
    merge(arr, start, mid, end);
}

int main() {
    int arr[arr_len] = {6, 5, 3, 1, 8, 7, 2, 4};
    merge_sort(arr, 0, arr_len - 1);
    print_array(arr, arr_len);
    return 0;
}

9.基数排序

思想:

基数排序是利用元素自身的构成规律进行排序的算法。比方说一个3位数的数字,有“百位”、“十位”、“个位”,先按照“百位”对所有元素进行排序,再按照“十位”对所有元素进行排序,最后按照“个位”对所有元素进行排序,最终实现整体排序。而基数排序中用到了一个“桶”的概念,有点类似Hash桶,如下图所示。先按高进制位把元素放到不同的桶中,一轮过后再收集起来,再按低进制位重复上述步骤。理解了基数排序的原理再看动图就懂了。

基数排序还可以用于例如二进制串的比较等,这类待排序元素具有可拆分的特征的数据。

实现:

// hello.c
#include "hello.h"

int get_num(int number, int pos) {
    int num = 0;
    while (pos--) {
        num = number % 10;
        number = number / 10;
    }
    return num;
}

void radix_sort(int arr[], int array_length) {
    int *bucket[10];
    for (int i = 0; i < 10; ++i) {
        bucket[i] = (int *) malloc(sizeof(int) * array_length + 1);
        bucket[i][0] = 0;//桶的第一位保存桶中元素个数
    }
    for (int b = 1; b <= 31; ++b) {
        for (int i = 0; i < array_length; ++i) {
            int num = get_num(arr[i], b);//计算每个位上的数字(个位、十位、百位...)
            int index = ++bucket[num][0];//计算下标
            bucket[num][index] = arr[i];//保存到桶中
        }
        for (int i = 0, k = 0; i < 10; i++) {
            for (int j = 1; j <= bucket[i][0]; ++j) {
                arr[k++] = bucket[i][j];//从桶里面按顺序取出来
            }
            bucket[i][0] = 0;//下标清零
        }
    }
}

int main() {
    int arr[arr_len] = {6, 5, 3, 1, 8, 7, 2, 4};
    radix_sort(arr, arr_len);
    print_array(arr, arr_len);
    return 0;
}
posted @ 2022-01-02 19:06  畏新  阅读(124)  评论(0)    收藏  举报