六种常见排序

1.冒泡排序(Bubble Sort)是一种基础的排序算法,其核心思想是通过重复交换相邻元素的位置,使得最大的元素逐渐“冒泡”到数组的末端。尽管它的时间复杂度较高,通常为 O(n^2),但由于其简单直观,仍然是学习排序算法时常用的一个例子。

#include <stdio.h> 

// 冒泡排序函数,接收数组指针和数组大小作为参数
void bubbleSort(int *arr, int size) {
    // 外层循环:控制排序的轮数,每一轮可以将最大的元素移动到数组末尾
    for(int i = 0; i < size - 1; i++) {
        // 使用标志位来优化排序过程,提前结束排序
        _Bool flag = 1;  // 默认假设没有交换,即假设数组已排序
        // 内层循环:遍历数组进行相邻元素的比较和交换
        for(int j = 0; j < size - 1 - i; j++) {
            // 如果前一个元素大于后一个元素,则交换它们
            if(arr[j] > arr[j+1]) {
                flag = 0;  // 表示发生了交换
                // 交换 arr[j] 和 arr[j+1] 的值
                int temp = arr[j+1];
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
        // 如果这一轮没有发生交换,表示数组已经有序,可以提前结束排序
        if(flag) break;
    }
}

int main(void) {
    // 初始化一个整数数组
    int arr[] = {7, 64, 3, 5, 2, 35, 53, 54, 43};
    // 计算数组的大小
    int size = sizeof(arr) / sizeof(arr[0]);
    
    // 调用冒泡排序函数排序数组
    bubbleSort(arr, size);
    
    // 输出排序后的数组
    for(int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

2.插入排序(Insertion Sort)是一种简单且直观的排序算法。它的基本思想是通过逐步插入元素到已排序的子序列中,从而实现排序。插入排序的时间复杂度为 O(n²),在元素个数较少时,插入排序通常表现得较好,且实现非常简单。

#include <stdio.h> 

// 插入排序函数,接收数组和数组的大小作为参数
void insertsort(int arr[], int size) {
    // 从数组的第二个元素开始,因为第一个元素默认已经是“已排序”的
    for (int i = 1; i < size; i++) {
        int temp = arr[i];  // 将当前元素保存在临时变量 temp 中
        int j = i;  // j 用于找到正确的插入位置

        // 查找当前元素的插入位置
        // 当 j > 0 且前一个元素大于 temp 时,说明需要将前一个元素向右移动一位
        // 直到找到合适的插入位置
        while (j > 0 && arr[j - 1] > temp) {
            arr[j] = arr[j - 1];  // 将前一个元素移动到当前位置
            j--;  // 向左移动 j,继续比较
        }

        // 将 temp 插入到找到的位置
        arr[j] = temp;
    }
}

int main(void) {
    // 初始化一个整数数组
    int arr[] = {13, 2, 35, 4, 5, 36, 17, 855, 91, 0};
    // 计算数组的大小
    int size = sizeof(arr) / sizeof(arr[0]);
    
    // 调用插入排序函数进行排序
    insertsort(arr, size);
    
    // 输出排序后的数组
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

3.快速排序是一种经典的排序算法,通常基于“分治法”策略进行排序。在标准的快速排序中,通常使用一个基准元素来将数组分成两部分,从而递归地排序每一部分。而双轴快速排序(Dual-Pivot QuickSort)则是在标准快速排序的基础上进行优化,它通过使用两个基准元素来实现更高效的排序。

#include <stdio.h>

// 这是传统的快速排序实现
// void quicksort(int *arr, int start, int end) {
//     if (start >= end) return;  // 递归终止条件,如果区间为空或只有一个元素,则停止排序
//     int left = start, right = end;
//     int pivot = arr[left];  // 选择第一个元素作为基准
//     while (left < right) {  // 当左指针小于右指针时,继续交换
//         while (arr[right] > pivot && left < right) right--;  // 右边找到小于基准的元素
//         arr[left] = arr[right];  // 把小于基准的元素放到左边
//         while (arr[left] < pivot && left < right) left++;  // 左边找到大于基准的元素
//         arr[right] = arr[left];  // 把大于基准的元素放到右边
//     }
//     arr[left] = pivot;  // 最终将基准值放到正确的位置
//     quicksort(arr, start, left - 1);  // 递归排序基准值左侧部分
//     quicksort(arr, left + 1, end);  // 递归排序基准值右侧部分
// }

void swap(int *a, int *b) {
    int temp = *a;  // 临时存储a的值
    *a = *b;        // 将b的值赋给a
    *b = temp;      // 将原来a的值赋给b
}

void dualPivotQuickSort(int arr[], int start, int end) {
    if (start >= end) return;  // 递归终止条件

    // 确保两个基准元素是有序的,将较大的基准放到右侧
    if (arr[start] > arr[end]) {
        swap(&arr[start], &arr[end]);
    }

    int pivot1 = arr[start], pivot2 = arr[end];  // 取出两个基准元素
    int left = start, right = end, mid = left + 1; // 使用三个指针:left, mid, right

    // 对区间进行分割,直到mid指针到达right
    while (mid < right) {
        if (arr[mid] < pivot1) {  // 如果当前元素小于第一个基准值
            swap(&arr[++left], &arr[mid++]);  // 将其交换到左侧
        } else if (arr[mid] <= pivot2) {  // 如果当前元素在两个基准之间
            mid++;  // 继续向前移动
        } else {  // 如果当前元素大于第二个基准
            // 找到一个合适的位置将当前元素移到右边
            while (arr[--right] > pivot2 && right > mid);
            if (mid >= right) break;  // 如果mid和right交错,结束排序
            swap(&arr[mid], &arr[right]);  // 否则交换mid和right指向的元素
        }
    }

    // 最终将两个基准值放到合适的位置
    swap(&arr[start], &arr[left]);
    swap(&arr[end], &arr[right]);

    // 对三个部分分别递归进行双轴快速排序
    dualPivotQuickSort(arr, start, left - 1);  // 排序第一个基准值左边的部分
    dualPivotQuickSort(arr, left + 1, right - 1);  // 排序两个基准值之间的部分
    dualPivotQuickSort(arr, right + 1, end);  // 排序第二个基准值右边的部分
}

int main(void) {
    int arr[] = {133, 21, 13, 14, 35, 56, 37, 38, 954, 10};
    int n = sizeof(arr) / sizeof(arr[0]);  // 计算数组的长度
    // quicksort(arr, 0, n - 1);  // 传统快速排序代码被注释掉,使用双轴快速排序替代
    dualPivotQuickSort(arr, 0, n - 1);  // 使用双轴快速排序

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

    return 0;
}

 4.希尔排序(Shell Sort)是一种在插入排序的基础上进行优化的排序算法。希尔排序通过引入分组和逐步缩小步长来提高排序效率。

#include <stdio.h>

// 希尔排序函数
void shellSort(int arr[], int size){
    int delta = size / 2;  // 初始化步长delta为数组长度的一半
    while (delta >= 1) {  // 当步长大于等于1时,继续分组排序
        // 对于当前的delta,我们使用插入排序对分组进行排序
        for (int i = delta; i < size; ++i) {  // 从第delta个元素开始进行排序
            int j = i, tmp = arr[i];  // 暂存待插入的元素tmp
            while (j >= delta && arr[j - delta] > tmp) {  // 如果前一个分组的元素大于待插入的元素
                arr[j] = arr[j - delta];  // 将前一个元素向后移动
                j -= delta;  // 向前跳跃delta个位置
            }
            arr[j] = tmp;  // 将待插入的元素放到正确位置
        }
        delta /= 2;  // 每轮完成分组插排后,将步长减半
    }
}

// 主函数
int main(void) {
    int arr[] = {94, 45, 83, 65, 54, 37, 55, 87, 91, 150};  // 初始化数组
    int size = sizeof(arr) / sizeof(arr[0]);  // 计算数组的长度
    shellSort(arr, size);  // 调用希尔排序进行排序

    // 打印排序后的数组
    for (int i = 0; i < size; ++i) {
        printf("%d ", arr[i]);
    }
    return 0;
}

5.堆排序(Heap Sort)归并排序(Merge Sort)。我们不仅会关注排序算法本身的实现,还会涉及堆(Heap)结构的实现与使用。

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

typedef int E;  // 定义类型E为int

// 小顶堆结构体定义
typedef struct MinHeap {
    E *arr;      // 存储堆元素的数组
    int size;    // 当前堆中的元素个数
    int capacity;  // 堆的容量
} *Heap;

// 交换两个变量的值
void swap(E *a, E *b) {
    E t = *a;
    *a = *b;
    *b = t;
}

// 初始化堆
_Bool initHeap(Heap heap){
    heap->size = 0;             // 初始堆大小为0
    heap->capacity = 11;        // 设置堆的初始容量为11
    heap->arr = malloc(sizeof(E) * heap->capacity);  // 为堆的元素数组分配内存
    return heap->arr != NULL;   // 如果分配内存失败,返回false
}

// 向堆中插入一个新元素
_Bool insert(Heap heap, E element) {
    if(heap->size == heap->capacity) return 0;  // 如果堆满了,不能插入新元素
    int index = ++heap->size;   // 将元素插入到堆的末尾
    // 堆的上浮操作,保持堆的性质
    while (index > 1 && element < heap->arr[index / 2]) {
        heap->arr[index] = heap->arr[index / 2];  // 如果当前元素小于父节点,则将父节点下移
        index /= 2;  // 更新当前元素的索引,继续向上浮动
    }
    heap->arr[index] = element;  // 将元素放到合适的位置
    return 1;
}

// 删除堆中的根元素(最小元素)
E delete(Heap heap) {
    E max = heap->arr[1], e = heap->arr[heap->size--];  // 保存堆顶元素,并将堆的最后一个元素移到堆顶
    int index = 1;
    while (index * 2 <= heap->size) {  // 只要当前结点有子结点,就继续调整
        int child = index * 2;
        if(child < heap->size && heap->arr[child] > heap->arr[child + 1])  // 找到左右孩子中较小的一个
            child += 1;
        if(e <= heap->arr[child]) break;  // 如果当前元素小于等于最小孩子,则无需继续调整
        else heap->arr[index] = heap->arr[child];  // 否则交换元素
        index = child;  // 更新当前元素的位置,继续向下调整
    }
    heap->arr[index] = e;  // 将最后一个元素放到合适的位置
    return max;
}

// 该函数实现的是对一个子树进行堆化,使得该子树满足堆的性质
void makeHeap(int* arr, int start, int end) {
    while (start * 2 + 1 <= end) {  // 只要有子结点,就继续调整
        int child = start * 2 + 1;  // 左孩子结点的索引
        if(child + 1 <= end && arr[child] < arr[child + 1])  // 如果右孩子存在且右孩子较大
            child++;  // 更新孩子结点为右孩子
        if(arr[child] > arr[start])  // 如果孩子比父节点大,交换
            swap(&arr[child], &arr[start]);
        start = child;  // 更新当前结点为孩子结点,继续向下调整
    }
}

// 堆排序实现
void heapSort(int arr[], int size) {
    // 首先对所有非叶子节点进行堆化
    for(int i = size / 2 - 1; i >= 0; i--)  
        makeHeap(arr, i, size - 1);  // 从最后一个非叶子节点开始堆化
    // 然后将堆顶元素(最大元素)交换到数组的末尾,再次堆化
    for (int i = size - 1; i > 0; i--) {
        swap(&arr[i], &arr[0]);  // 交换堆顶元素与数组的最后一个元素
        makeHeap(arr, 0, i - 1);  // 调整堆,使堆顶保持堆的性质
    }
}

// 合并两个已经排序的子数组
void merge(int arr[], int tmp[], int left, int leftEnd, int right, int rightEnd) {
    int i = left, size = rightEnd - left + 1;  // 保存当前合并区间的大小
    while (left <= leftEnd && right <= rightEnd) {  // 两个子数组都未合并完成
        if(arr[left] <= arr[right])  // 如果左边的元素小于等于右边的元素
            tmp[i++] = arr[left++];  // 将左边元素放入临时数组
        else
            tmp[i++] = arr[right++];  // 否则将右边元素放入临时数组
    }
    // 如果左边还有剩余元素,直接放入临时数组
    while (left <= leftEnd)    
        tmp[i++] = arr[left++];
    // 如果右边还有剩余元素,直接放入临时数组
    while (right <= rightEnd)   
        tmp[i++] = arr[right++];
    // 将临时数组中的元素拷贝回原数组
    for (int j = 0; j < size; ++j, rightEnd--)   
        arr[rightEnd] = tmp[rightEnd];
}

// 归并排序的递归实现
void mergeSort(int arr[], int tmp[], int start, int end) {
    if(start >= end) return;  // 如果数组已经有序(只包含一个元素),则不进行操作
    int mid = (start + end) / 2;  // 计算中间位置
    mergeSort(arr, tmp, start, mid);  // 对左半部分进行归并排序
    mergeSort(arr, tmp, mid + 1, end);  // 对右半部分进行归并排序
    merge(arr, tmp, start, mid, mid + 1, end);  // 合并两个已经排序的部分
}

int main() {
    int arr[] = {3, 5, 7, 2, 9, 0, 6, 1, 8, 4};  // 初始化数组

    struct MinHeap heap;  // 创建堆
    initHeap(&heap);  // 初始化堆
    // 往堆中插入元素
    // for (int i = 0; i < 10; ++i)
    //     insert(&heap, arr[i]);
    // for (int i = 0; i < 10; ++i)
    //     arr[i] = delete(&heap);  // 从堆中删除元素,得到有序数组
    
    heapSort(arr, sizeof(arr) / sizeof(arr[0]));  // 调用堆排序函数进行排序
    for (int i = 0; i < 10; ++i)  // 打印排序后的数组
        printf("%d ", arr[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值