五大数据排序及算法优化

排序算法简介

1、定义:将一组无序或部分有序的数据按照特定规则重新排列
2、分类:内部排序和外部排序
3、时间复杂度:O(n^2)、O(nlogn)、O(n)

=列表=

插入排序系列

1、直接插入排序:每次取未排序序列的第一个元素,在已排序序列中从后往前扫描,找到合适位置插入

#include <stdio.h>

void insertionSort(int arr[], int n) {
    int i, key, j;
    for (i = 1; i < n; i++) {
        key = arr[i];
        j = i - 1;

        /* 将比 key 大的元素向右移动 */
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j = j - 1;
        }
        arr[j + 1] = key;
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    insertionSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

2、希尔排序:对直接插入排序的改进,先把要排序的一组数按某个增量d分成若干组,对每组进行直接插入排序

#include <stdio.h>

void shellSort(int arr[], int n) {
    int gap, i, j, temp;
    for (gap = n / 2; gap > 0; gap /= 2) {
        for (i = gap; i < n; i++) {
            temp = arr[i];
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    shellSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

=列表=

选择排序系列

1、直接选择排序:每次选择未排序序列中最小(大)的数放在已排序序列的末尾

#include <stdio.h>

void selectionSort(int arr[], int n) {
    int i, j, min_idx;
    for (i = 0; i < n - 1; i++) {
        min_idx = i;
        for (j = i + 1; j < n; j++) {
            if (arr[j] < arr[min_idx]) {
                min_idx = j;
            }
        }
        /* 将找到的最小元素交换到已排序部分的末尾 */
        int temp = arr[min_idx];
        arr[min_idx] = arr[i];
        arr[i] = temp;
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    selectionSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

2、堆排序:利用二叉堆这种数据结构来实现选择排序

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void heapify(int arr[], int n, int i) {
    int largest = i;
    int l = 2 * i + 1;
    int r = 2 * i + 2;

    if (l < n && arr[l] > arr[largest]) {
        largest = l;
    }

    if (r < n && arr[r] > arr[largest]) {
        largest = r;
    }

    if (largest != i) {
        swap(&arr[i], &arr[largest]);
        heapify(arr, n, largest);
    }
}

void heapSort(int arr[], int n) {
    int i;
    for (i = n / 2 - 1; i >= 0; i--) {
        heapify(arr, n, i);
    }

    for (i = n - 1; i >= 0; i--) {
        swap(&arr[0], &arr[i]);
        heapify(arr, i, 0);
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    heapSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

交换排序系列

1、冒泡排序:反复交换相邻的未按次序排列的元素

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void bubbleSort(int arr[], int n) {
    int i, j;
    for (i = 0; i < n - 1; i++) {
        for (j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(&arr[j], &arr[j + 1]);
            }
        }
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    bubbleSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

2、快速排序:利用分治思想,选一个基准值,将小于它的放在左边,大于它的放在右边,在对左右两边递归进行快速排序

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int partition(int arr[], int low, int high) {
    int pivot = arr[high];
    int i = (low - 1);

    for (int j = low; j <= high - 1; j++) {
        if (arr[j] < pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}

void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    quickSort(arr, 0, n - 1);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

归并排序系列

1、二路归并排序:将待排记录序列分成若干个长度为1的子序列,两两合并形成有序序列

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

void merge(int arr[], int left[], int left_size, int right[], int right_size) {
    int i = 0, j = 0, k = 0;
    while (i < left_size && j < right_size) {
        if (left[i] <= right[j]) {
            arr[k++] = left[i++];
        } else {
            arr[k++] = right[j++];
        }
    }

    while (i < left_size) {
        arr[k++] = left[i++];
    }

    while (j < right_size) {
        arr[k++] = right[j++];
    }
}

void mergeSort(int arr[], int n) {
    if (n <= 1) {
        return;
    }

    int mid = n / 2;
    int *left = (int *) malloc(mid * sizeof(int));
    int *right = (int *) malloc((n - mid) * sizeof(int));

    for (int i = 0; i < mid; i++) {
        left[i] = arr[i];
    }

    for (int j = 0; j < n - mid; j++) {
        right[j] = arr[mid + j];
    }

    mergeSort(left, mid);
    mergeSort(right, n - mid);

    merge(arr, left, mid, right, n - mid);

    free(left);
    free(right);
}

int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    mergeSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

2、多路归并排序:将待排记录序列划分成多个子序列进行排序,再将排好序的子序列合并成最终序列

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

void merge(int *arr[], int k, int result[]) {
    int i, j, min_idx;
    for (i = 0; i < k; i++) {
        if (*arr[i] != -1) {
            min_idx = i;
            break;
        }
    }

    for (j = i + 1; j < k; j++) {
        if ((*arr[j] < *arr[min_idx]) && (*arr[j] != -1)) {
            min_idx = j;
        }
    }

    if (*arr[min_idx] == -1) {
        return;
    }

    result[0] = *arr[min_idx];
    arr[min_idx]++;
    merge(arr, k, result + 1);
}

void mergeSort(int *arr[], int low, int high, int k, int result[]) {
    if (low == high) {
        result[0] = *arr[low];
        return;
    }

    if (low > high) {
        return;
    }

    int mid = (low + high) / 2;
    int *temp1 = (int *) malloc(k * sizeof(int));
    int *temp2 = (int *) malloc(k * sizeof(int));
    mergeSort(arr, low, mid, k, temp1);
    mergeSort(arr, mid + 1, high, k, temp2);
    merge((int *[]) {temp1, temp2}, 2, result);

    free(temp1);
    free(temp2);
}

int main() {
    int k = 3; // 多路归并数
    int n = 10; // 数组长度
    int *arr[k];
    for (int i = 0; i < k; i++) {
        arr[i] = (int *) malloc(n / k * sizeof(int));
        for (int j = 0; j < n / k; j++) {
            arr[i][j] = rand() % 100; // 随机生成10以内的整数
        }
    }

    int result[n];
    mergeSort(arr, 0, k - 1, n / k, result);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", result[i]);
    }

    for (int i = 0; i < k; i++) {
        free(arr[i]);
    }

    return 0;
}

=列表=

快速排序系列

1、普通快排:以数组最后一个元素作为基准,然后一趟排序将待排序列分割成两部分

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

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int partition(int arr[], int low, int high) {
    int pivot = arr[high];
    int i = (low - 1);

    for (int j = low; j <= high - 1; j++) {
        if (arr[j] < pivot) {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}

void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);

    quickSort(arr, 0, n - 1);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

2、三路快排:将待排序列分成三个部分:小于基准值、等于基准值、大于基准值

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

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void threeWayQuickSort(int arr[], int low, int high) {
    if (low >= high) {
        return;
    }

    int lt = low, gt = high, i = low + 1;
    int pivot = arr[low];

    while (i <= gt) {
        if (arr[i] < pivot) {
            swap(&arr[lt++], &arr[i++]);
        } else if (arr[i] > pivot) {
            swap(&arr[i], &arr[gt--]);
        } else {
            i++;
        }
    }

    threeWayQuickSort(arr, low, lt - 1);
    threeWayQuickSort(arr, gt + 1, high);
}

int main() {
    int arr[] = {2, 1, 2, 5, 0, 5, 8, 7};
    int n = sizeof(arr) / sizeof(arr[0]);

    threeWayQuickSort(arr, 0, n - 1);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

=列表=

排序算法优化

1、时间复杂度上的优化
排序优化算法可以有效地提高排序算法的效率,其中比较常见的包括桶排序、计数排序和基数排序。以下是它们的详细介绍以及C语言实现示例:

  • 桶排序

桶排序算法将元素分配到不同的桶中,并在每个桶内部使用其他排序算法(如插入排序)对元素进行排序。桶的数量通常与输入元素数量相同,并且预先知道元素范围,以便确定每个元素应该属于哪个桶。
时间复杂度:O(n + k),其中k为桶的数量。
代码:

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

void bucketSort(int arr[], int n) {
    int max_val = arr[0];
    for (int i = 1; i < n; ++i) {
        if (arr[i] > max_val) {
            max_val = arr[i];
        }
    }

    int *buckets = (int *) malloc((max_val + 1) * sizeof(int));
    for (int i = 0; i <= max_val; ++i) {
        buckets[i] = 0;
    }

    for (int i = 0; i < n; ++i) {
        ++buckets[arr[i]];
    }

    int i = 0;
    for (int j = 0; j <= max_val; ++j) {
        while (buckets[j]) {
            arr[i++] = j;
            --buckets[j];
        }
    }

    free(buckets);
}

int main() {
    int arr[] = {5, 2, 9, 4, 7, 6, 1, 3, 8};
    int n = sizeof(arr) / sizeof(int);

    bucketSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; ++i) {
        printf("%d ", arr[i]);
    }
    return 0;
}

在上面的代码中,我们首先找到输入元素的最大值,并使用它来确定需要多少个桶。然后,我们初始化每个桶的计数器为0,并将每个元素分配到其对应的桶中。接着,我们遍历每个桶中的元素,并将它们放回原始数组中,即可得到已排序的数组。

  • 计数排序

计数排序算法通过统计每个元素出现的次数来进行排序,并使用前缀和技术来确定每个元素的最终位置。与桶排序不同,计数排序仅仅适用于非负整数。
时间复杂度:O(n + k),其中k为元素范围。
代码:

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

void countingSort(int arr[], int n) {
    int max_val = arr[0];
    for (int i = 1; i < n; ++i) {
        if (arr[i] > max_val) {
            max_val = arr[i];
        }
    }

    int *count = (int *) malloc((max_val + 1) * sizeof(int));
    for (int i = 0; i <= max_val; ++i) {
        count[i] = 0;
    }

    for (int i = 0; i < n; ++i) {
        ++count[arr[i]];
    }

    for (int i = 1; i <= max_val; ++i) {
        count[i] += count[i - 1];
    }

    int *result = (int *) malloc(n * sizeof(int));
    for (int i = n - 1; i >= 0; --i) {
        result[--count[arr[i]]] = arr[i];
    }

    for (int i = 0; i < n; ++i) {
        arr[i] = result[i];
    }

    free(count);
    free(result);
}

int main() {
    int arr[] = {5, 2, 9, 4, 7, 6, 1, 3, 8};
    int n = sizeof(arr) / sizeof(int);

    countingSort(arr, n);

    printf("排序后的数组:\n");
        for (int i = 0; i < n; ++i) {
        printf("%d ", arr[i]);
    }
    return 0;
}

在上面的代码中,我们首先找到输入元素的最大值,并使用它来确定需要多少个桶。然后,我们初始化每个计数器为0,并将每个元素的计数器加1。接着,我们使用前缀和技术计算出每个元素在已排序数组中的最终位置,并将每个元素放入结果数组中。最后,我们将结果数组复制回原始数组中。

  • 基数排序
    基数排序算法按照数字位(例如个位、十位、百位等)进行排序,具体而言,我们首先按照最低位进行排序,然后按照次低位进行排序,以此类推,直到所有数字位都排好序。
    时间复杂度:O(d * n),其中d为最大数字位数,n为元素数量。
    代码:
#include <stdio.h>
#include <stdlib.h>

void radixSort(int arr[], int n) {
    int max_val = arr[0];
    for (int i = 1; i < n; ++i) {
        if (arr[i] > max_val) {
            max_val = arr[i];
        }
    }

    for (int exp = 1; max_val / exp > 0; exp *= 10) {
        int *count = (int *) malloc(10 * sizeof(int));
        for (int i = 0; i < 10; ++i) {
            count[i] = 0;
        }

        for (int i = 0; i < n; ++i) {
            ++count[(arr[i] / exp) % 10];
        }

        for (int i = 1; i < 10; ++i) {
            count[i] += count[i - 1];
        }

        int *result = (int *) malloc(n * sizeof(int));
        for (int i = n - 1; i >= 0; --i) {
            result[--count[(arr[i] / exp) % 10]] = arr[i];
        }

        for (int i = 0; i < n; ++i) {
            arr[i] = result[i];
        }

        free(count);
        free(result);
    }
}

int main() {
    int arr[] = {329, 457, 657, 839, 436, 720, 355};
    int n = sizeof(arr) / sizeof(int);

    radixSort(arr, n);

    printf("排序后的数组:\n");
    for (int i = 0; i < n; ++i) {
        printf("%d ", arr[i]);
    }
    return 0;
}

在上面的代码中,我们首先找到输入元素的最大值,并使用它来确定需要遍历多少个数字位(例如个位、十位等)。然后,对于每个数字位,我们初始化一个计数器数组,并将每个元素分配到其对应的计数器中。接着,我们使用前缀和技术计算出每个元素在已排序数组中的最终位置,并将每个元素放入结果数组中。最后,我们将结果数组复制回原始数组中。

2、空间复杂度上的优化:原地排序
原地排序是指在排序过程中只使用原始输入数组所占用的空间,而不需要额外申请存储空间。这种方法在排序大数据集时非常有用,因为它可以避免占用过多的内存空间。
代码:

#include <stdio.h>

void swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int partition(int arr[], int low, int high)
{
    int pivot = arr[high];
    int i = (low - 1);
 
    for (int j = low; j <= high- 1; j++)
    {
        if (arr[j] < pivot)
        {
            i++;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i + 1], &arr[high]);
    return (i + 1);
}

void quickSort(int arr[], int low, int high)
{
    if (low < high)
    {
        int pi = partition(arr, low, high);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

int main()
{
    int arr[] = {10, 7, 8, 9, 1, 5};
    int n = sizeof(arr) / sizeof(arr[0]);
    quickSort(arr, 0, n - 1);
    printf("Sorted array: ");
    for (int i=0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");
    return 0;
}

3、常数级别的优化:插入排序优化、快速排序的三数取中

  • 插入排序优化

插入排序是一个简单而有效的排序算法,但是当待排序数组规模较大时,它的效率会降低。一种常见的优化方式是,在每次插入操作之前,使用二分查找来寻找插入位置。

以下是带有二分查找优化的插入排序的C语言实现:

void binaryInsertionSort(int arr[], int n)
{
    for (int i = 1; i < n; i++)
    {
        int key = arr[i];
        int j = i - 1;
        int pos = binarySearch(arr, key, 0, j);
        while (j >= pos)
        {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

int binarySearch(int arr[], int key, int left, int right)
{
    while (left <= right)
    {
        int mid = left + (right - left) / 2;
        if (arr[mid] == key)
            return mid + 1;
        else if (arr[mid] < key)
            left = mid + 1;
        else
            right = mid - 1;
    }
    return left;
}

在这个例子中,binaryInsertionSort() 函数使用二分查找来确定插入位置,从而减少了比较的次数。binarySearch() 函数实现了二分查找算法。

  • 快速排序的三数取中

快速排序是一种常用的排序算法,但它在某些情况下可能会出现最坏时间复杂度 O(n²)。为了避免这种情况,可以使用三数取中的方法来选择枢轴元素。

以下是使用三数取中优化的快速排序的C语言实现:

void quickSort(int arr[], int low, int high)
{
    if (low < high)
    {
        int pivotIndex = getPivotIndex(arr, low, high);
        int pi = partition(arr, low, high, pivotIndex);
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

int getPivotIndex(int arr[], int low, int high)
{
    int mid = (low + high) / 2;
    if (arr[low] > arr[mid])
        swap(&arr[low], &arr[mid]);
    if (arr[low] > arr[high])
        swap(&arr[low], &arr[high]);
    if (arr[mid] > arr[high])
        swap(&arr[mid], &arr[high]);
    return mid;
}

在这个例子中,getPivotIndex() 函数使用三数取中的方式来确定枢轴元素。它首先计算数组的中间位置,然后选取该位置、第一个位置和最后一个位置上的元素,并将它们按从小到大的顺序排列。最后,它返回中间位置上的元素作为枢轴元素。通过这种优化方式,可以使快速排序的效率更加稳定,并且避免了最坏情况的出现。

希望这篇博客对各位大佬有所帮助,有任何的不足希望大佬多多指教,小编会积极纠正哦!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值