用C语言实现堆排序及相关操作

堆排序是一种高效的排序算法,它利用堆这种数据结构来实现排序。本文将详细介绍堆的概念、堆的创建、堆排序的实现以及堆的插入和删除操作,并提供完整的C语言代码实现。

堆的基本概念

堆是一种特殊的完全二叉树,它满足以下性质:

  1. 对于最大堆,每个节点的值都大于或等于其子节点的值

  2. 对于最小堆,每个节点的值都小于或等于其子节点的值

在本文中,我们将以最大堆为例进行讲解。

堆的存储结构

堆通常用数组来表示,因为堆是完全二叉树,数组可以很好地反映其结构:

  • 对于下标为i的节点:(从零开始,下标从1开始的类似)

    • 父节点下标:(i-1)/2

    • 左子节点下标:2*i+1

    • 右子节点下标:2*i+2

堆的基本操作实现(大根堆,小根堆类似)

1. 堆的调整(Heapify)

堆调整是堆操作的核心,它确保以某个节点为根的子树满足堆的性质。

// 调整堆,使其满足堆的性质
void heapify(int arr[], int n, int i) {
    int largest = i;        // 初始化最大元素为当前节点
    int left = 2 * i + 1;   // 左子节点
    int right = 2 * i + 2;  // 右子节点

    // 如果左子节点大于当前最大节点
    if (left < n && arr[left] > arr[largest])
        largest = left;

    // 如果右子节点大于当前最大节点
    if (right < n && arr[right] > arr[largest])
        largest = right;

    // 如果最大节点不是当前节点,交换并继续调整
    if (largest != i) {
        int temp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = temp;
        
        // 递归调整受影响的子树
        heapify(arr, n, largest);
    }
}

2. 创建堆(Build Heap)

从无序数组创建堆的过程是从最后一个非叶子节点开始,自底向上进行调整。

// 构建最大堆
void buildHeap(int arr[], int n) {
    // 从最后一个非叶子节点开始调整
    for (int i = n / 2 - 1; i >= 0; i--)
        heapify(arr, n, i);
}

3. 堆排序(Heap Sort)

堆排序的基本思想是利用堆的性质,每次取出堆顶元素(最大值),然后调整剩余元素为堆。

// 堆排序函数
void heapSort(int arr[], int n) {
    // 首先构建最大堆
    buildHeap(arr, n);

    // 逐个从堆顶取出元素
    for (int i = n - 1; i > 0; i--) {
        // 将当前堆顶元素(最大值)与末尾元素交换
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;

        // 调整剩余元素为堆
        heapify(arr, i, 0);
    }
}

4. 堆的插入操作

向堆中插入新元素时,先将元素添加到堆的末尾,然后向上调整(sift up)以保持堆的性质。

// 向堆中插入新元素
void insertNode(int arr[], int* n, int key) {
    // 增加堆的大小
    (*n)++;
    int i = (*n) - 1;
    
    // 将新元素插入到堆的末尾
    arr[i] = key;
    
    // 向上调整堆
    while (i != 0 && arr[(i - 1) / 2] < arr[i]) {
        // 交换当前节点与父节点
        int temp = arr[i];
        arr[i] = arr[(i - 1) / 2];
        arr[(i - 1) / 2] = temp;
        
        // 移动到父节点
        i = (i - 1) / 2;
    }
}

5. 堆的删除操作

通常我们只删除堆顶元素(最大值),删除后将最后一个元素移到堆顶(删除其他元素,一样用堆底替代被删除元素),然后向下调整(sift down)。

// 删除堆顶元素
void deleteRoot(int arr[], int* n) {
    // 如果堆为空
    if (*n <= 0) {
        printf("Heap is empty\n");
        return;
    }
    
    // 将最后一个元素替换到堆顶
    arr[0] = arr[*n - 1];
    
    // 减少堆的大小
    (*n)--;
    
    // 从堆顶开始调整堆
    heapify(arr, *n, 0);
}

完整示例代码

下面是一个完整的示例程序,演示了如何创建堆、进行堆排序以及插入和删除操作:

#include <stdio.h>

// 函数声明
void heapify(int arr[], int n, int i);
void buildHeap(int arr[], int n);
void heapSort(int arr[], int n);
void insertNode(int arr[], int* n, int key);
void deleteRoot(int arr[], int* n);
void printArray(int arr[], int n);

int main() {
    int arr[] = {12, 11, 13, 5, 6, 7};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    printf("原始数组: \n");
    printArray(arr, n);
    
    // 构建堆
    buildHeap(arr, n);
    printf("\n构建后的堆: \n");
    printArray(arr, n);
    
    // 堆排序
    heapSort(arr, n);
    printf("\n排序后的数组: \n");
    printArray(arr, n);
    
    // 重置数组
    int arr2[] = {12, 11, 13, 5, 6, 7};
    n = sizeof(arr2) / sizeof(arr2[0]);
    buildHeap(arr2, n);
    
    printf("\n\n堆操作演示:\n");
    printf("初始堆: \n");
    printArray(arr2, n);
    
    // 插入元素
    insertNode(arr2, &n, 20);
    printf("\n插入20后的堆: \n");
    printArray(arr2, n);
    
    // 删除堆顶
    deleteRoot(arr2, &n);
    printf("\n删除堆顶后的堆: \n");
    printArray(arr2, n);
    
    return 0;
}

// 打印数组
void printArray(int arr[], int n) {
    for (int i = 0; i < n; ++i)
        printf("%d ", arr[i]);
    printf("\n");
}

// 调整堆
void heapify(int arr[], int n, int i) {
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;

    if (left < n && arr[left] > arr[largest])
        largest = left;

    if (right < n && arr[right] > arr[largest])
        largest = right;

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

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

// 堆排序
void heapSort(int arr[], int n) {
    buildHeap(arr, n);

    for (int i = n - 1; i > 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;

        heapify(arr, i, 0);
    }
}

// 插入节点
void insertNode(int arr[], int* n, int key) {
    (*n)++;
    int i = (*n) - 1;
    arr[i] = key;
    
    while (i != 0 && arr[(i - 1) / 2] < arr[i]) {
        int temp = arr[i];
        arr[i] = arr[(i - 1) / 2];
        arr[(i - 1) / 2] = temp;
        
        i = (i - 1) / 2;
    }
}

// 删除堆顶
void deleteRoot(int arr[], int* n) {
    if (*n <= 0) {
        printf("Heap is empty\n");
        return;
    }
    
    arr[0] = arr[*n - 1];
    (*n)--;
    heapify(arr, *n, 0);
}

程序输出示例

运行上述程序将产生类似以下输出:

原始数组: 
12 11 13 5 6 7 

构建后的堆: 
13 11 12 5 6 7 

排序后的数组: 
5 6 7 11 12 13 


堆操作演示:
初始堆: 
13 11 12 5 6 7 

插入20后的堆: 
20 13 12 5 6 7 11 

删除堆顶后的堆: 
13 11 12 5 6 7 

堆排序的时间复杂度分析

  • 构建堆:O(n)

  • 堆排序:O(n log n)

  • 插入操作:O(log n)

  • 删除操作:O(log n)

堆排序是一种原地排序算法,不需要额外的存储空间,这使得它在空间复杂度上具有优势(O(1))。

总结

堆排序是一种高效的排序算法,特别适合处理大规模数据。通过本文的介绍和代码实现,我们了解了堆的基本概念、堆的创建、堆排序的实现以及堆的插入和删除操作。掌握这些知识不仅可以帮助你理解堆排序算法,还能为解决优先级队列等相关问题打下基础。

希望这篇博客能帮助你理解堆排序以及相关操作。如果有任何问题或建议,欢迎留言讨论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值