排序算法 之 (直接插入排序)

10.6、堆排序

对于n个关键字序列L[1...n],满足下面某一条性质,则称为堆(Heap)

  • 若满足:\(L(2i) \le L(i)\)\(L(2i+1) \le L(i)\)\(1 \le i \le n\)大根堆(大顶堆)
  • 若满足:\(L(i) \le L(2i)\)\(L(i) \le L(2i+1)\)\(1 \le i \le n\)小根堆(小顶堆)

这里需要了解二叉树的顺序存储,对于n个结点的关键字来说

\(\left \lfloor \frac{n}{2} \right \rfloor < i\)这里\(i\)就是叶子结点,

$ i \le \left \lfloor \frac{n}{2} \right \rfloor$ 这里\(i\)就是分支结点(非叶子结点);

分支结点的\(i\),左孩子:\(2i\),右孩子:\(2i + 1\)

大根堆: 左孩子、右孩子 \(\le\) 父节点

小根堆:父节点 \(\le\) 左孩子、右孩子

堆排序是不稳定的

图解大根堆、小根堆

那么应该如何建立大根堆(小根堆)了;

思路:看非叶子结点是否满足大根堆(小根堆)要求,如果不满足进行调整

图解过程

小根堆的建立也差不多;

大根堆的排序过程图解

首先,就是交换大根堆堆顶元素和最后一个为排序的元素,交换后最后一个就是该元素的排序的位置,现在需要堆顶元素进行大根堆化;然后重复操作直到全部排序完成

大(小)根堆排序的代码实现

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

#define boolean int
#define false 0;
#define true 1;

//大根堆排序,将以k为根的子树调整为大根堆
void HeadAdjust(int nums[],int k,int len){
    int temp = nums[k];//暂存子树的根结点
    for(int i=2*(k+1)-1;i <= len;i = i*2 + 1){//沿着k较大的结点,向下筛选
        if(i < len && nums[i] < nums[i+1]){//看左孩子和右孩子那个大,取那个
            i++;
        }
        if(temp >= nums[i]) break;//根结点大,就不需要交换,返回
        else{
            nums[k] = nums[i];  //否则交换
            k = i;              //修改k值,继续向下筛选
        }
    }
    nums[k] = temp;  //被筛选的结点放入最终位置
}
//建立大根堆
void BuildMaxHeap(int nums[] ,int length){
    for(int i = length/2-1;i>=0;i--){ //从后往前非叶子结点进行大根堆初始化
        HeadAdjust(nums,i,length-1);
    }
}
//堆排序(大根堆)
void MaxHeapSort(int nums[],int length){
    BuildMaxHeap(nums,length);//建立大根堆
    for(int i = length-1;i > 0;i--){
        //交换堆顶和堆堆尾
        int temp = nums[i];
        nums[i] = nums[0];
        nums[0] = temp;
        HeadAdjust(nums,0,i-1);//把新的堆变成大根堆
    }
}



//小根堆排序,将以k为根的子树调整为小根堆
void HeadAdjust1(int nums[],int k,int len){
    int temp = nums[k];//暂存子树的根结点
    for(int i=2*(k+1)-1;i <= len;i = i*2 + 1){//沿着k较大的结点,向下筛选
        if(i < len && nums[i] > nums[i+1]){//看左孩子和右孩子那个大,取那个
            i++;
        }
        if(temp <= nums[i]) break;//根结点大,就不需要交换,返回
        else{
            nums[k] = nums[i];  //否则交换
            k = i;              //修改k值,继续向下筛选
        }
    }
    nums[k] = temp;  //被筛选的结点放入最终位置
}
//建立小根堆
void BuildMinHeap(int nums[] ,int length){
    for(int i = length/2 - 1;i>=0;i--){ //从后往前非叶子结点进行大根堆初始化
        HeadAdjust1(nums,i,length-1);
    }
}
//堆排序(小根堆)
void MinHeapSort(int nums[],int length){
    BuildMinHeap(nums,length);//建立大根堆
    for(int i = length-1;i > 0;i--){
        //交换堆顶和堆堆尾
        int temp = nums[i];
        nums[i] = nums[0];
        nums[0] = temp;
        HeadAdjust1(nums,0,i-1);//把新的堆变成大根堆
    }
}

int main(){
    int nums[] = {53,17,78,9,45,65,87,32};
    int length = 8;
    printf("堆(大根堆)排序前:");
    for(int i = 0; i < length ;i++){
        printf("%d ",nums[i]);
    }
    MaxHeapSort(nums,length);
    printf("\n");
    printf("堆(大根堆)排序后:");
    for(int i = 0; i < length ;i++){
        printf("%d ",nums[i]);
    }

    printf("\n");

    int nums1[] = {49,38,65,97,76,13,27,47,89,13,48,76,88,88,99};
    int length1 = 15;
    printf("堆(小根堆)排序前:");
    for(int i = 0; i < length1 ;i++){
        printf("%d ",nums1[i]);
    }
    MinHeapSort(nums1,length1);
    printf("\n");
    printf("堆(小根堆)排序后:");
    for(int i = 0; i < length1 ;i++){
        printf("%d ",nums1[i]);
    }

    return 0;
}
//结果:
堆(大根堆)排序前:53 17 78 9 45 65 87 32 
堆(大根堆)排序后:9 17 32 45 53 65 78 87 
堆(小根堆)排序前:49 38 65 97 76 13 27 47 89 13 48 76 88 88 99 
堆(小根堆)排序后:99 97 89 88 88 76 76 65 49 48 47 38 27 13 13 

堆中插入一个新的元素

对于小根堆而言,新元素放到表尾,与父节点$\left \lfloor \frac{i} {2} \right \rfloor $ 这个结点进行对比,如果比父结点小就交换,交换后,继续与交换后的父结点对比,直到比它小或者到根了就结束

堆中插入一个元素图解

堆中删除一个元素

删除的元素如果不是最后一个,就使用最后一个代替它,然后对它进行小根堆化

堆中删除元素的图解

posted @ 2023-03-15 18:15  水三丫  阅读(46)  评论(0编辑  收藏  举报