C语言堆排序(HeapSort)的思想和代码实现

C语言堆排序(HeapSort)的思想和代码实现

经过一晚上和有一早上的思考和学习,在Clion上反复的单步调试之后,我总结了关于堆排序这个算法的一点体会。现在来记录一下,如有错误,欢迎批评指出,谢谢!

首先:什么是堆排序,为什么叫堆?

Heapsort是一种根据选择排序的思想利用堆这种数据结构 所设计的一种排序算法

选择排序的思想是什么?每一趟比较找到这个序列中的最值,拿出来和最前面的元素交换,交换完之后,这个序列从前面开始减去一个(因为前面放的是最值,不需要放在序列里再次比较)

那么这里的堆是什么意思呢?:堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

什么是完全二叉树?即,每个节点都一一有序对应的满二叉树,如下图所示

当一个序列满足 双亲位置的值 大于或者小于 孩子位置的值的时候,就满足堆的关系,(这里为什么要叫做位置?因为一般都是在序列里面排序,储存是线性的,比大根堆和小根堆如顺序表)

#堆的种类,大根堆和小根堆

这个好理解,就是对应上面的图来说,双亲位置的值 大于 孩子位置的值 就是大根堆

双亲位置的值 小于 孩子位置的值 就是小根堆

实现堆排序,我们需要解决什么问题?

  1. 怎么创建一个初始的堆?即满足 双亲位置的值 大于或者小于 孩子位置的值,我们只需要关注每一个双亲的孩子是不是大于或者小于自己孩子,而不需要去管“别人家的孩子”是不是比自己家孩子大或者小
  2. 有了初始的堆之后,我们怎么调整剩下的元素,这个时候就需要看看“别人家的孩子”,这样处理之后,这个二叉树就满足完全二叉树的特点,按照序列排列下来就是一个有序的序列

Part1:创建初始堆,我们要考虑什么?

既然我们不需要去管整个序列是否有序,不需要去管“别人家的孩子”怎么样,那么我们先要

找到所有双亲节点。

怎么找呢?根据完全二叉树的性质:

观察每个双亲节点的序号,我们不难发现,他们的孩子节点的序号都是满足:比如双亲节点是i,那么他的左孩子就是2*i,右孩子就是2*i+1。

找到之后我们就开始调整每一个双亲位置的值和她的孩子的值:

我们这里以创建一个小根堆为例子:

我们遍历调整每个双亲节点的顺序是:

从最大的双亲节点(非终端节点)((整个顺序表的长度)/2)一直倒着来,直到下标为1的根节点

为什么是这个顺序?为什么不能倒着来?从1~length/2不是一样的么?

其实是不一样的,我们创建小根堆的目的,就是为了将最小的交换到根节点,也就是说,最后调整完初始堆

我们的根位置的值一定是整个序列中最小的值  —— 这是很重要的性质

我们从length/2开始对每个双亲位置进行堆的调整,那么到了最后,最小的元素会出现在根位置

如果从1开始一直调整到length/2的双亲位置,那么整个序列中最小的元素,不一定会出现在根位置,因为第一次调整之后根位置的值就不再变了,只是第一个双亲位置的最小的元素。

这里有一个根据无序序列(62,25,49,25,16,8)创建小根堆的例子,顺序如下

Part2:得到了初始小根堆,我们怎么调整剩下的堆使得它有顺序

根据前面提到的选择排序的思想:

我们在这个heapsort里面怎么体现这种思想呢?

前面创建初始堆的时候,我们已经把最小的元素排出来,放在根位置了。那么我们就相当于是拿到了选择排序中的最值,这个时候我们只需要把他放在某个位置上之后,接下去就不再管它了,我们把它从序列中隔过去,在接下去的“找最值”的过程中把它忽视过去。这个“找最值”的过程就是上面Part 1 所说的,创建初始堆的过程

我们这里算法的操作过程就是:

  1. 拿到最上面的根位置的值,和序列(长度n)最后一个元素交换位置。
  2. 然后把这个序列从后面缩小一个(序列长度n-1),也就是说,把刚刚那个元素隔过去
  3. 对剩下的这个被打乱的堆,再次进行Part 1的初始堆调整,我们还是想要得到剩下序列中最小的值

 ......(循环往复)直到 这个序列的长度变成1 这个堆排序就执行完毕,得到了一个有序的序列。

还是上面那个(62....)的序列,我们从上面得到的小根堆开始调整到有序序列的例子

 

#到此为止,这个堆排序就算是理解完毕了,具体怎么实现,在下面的代码中根据代码再次理解一次

1,创建顺序表,由一个int数组和一个指示长度的元素构成:

注意:这个数组是从下标为1的地方开始储存数据的!

注意:这个数组是从下标为1的地方开始储存数据的!

注意:这个数组是从下标为1的地方开始储存数据的!

#include "stdio.h"
#define Max_Num 100

typedef struct {
    int record[Max_Num];
    int length;
}OrderList;

2,还需要一个创建顺序表的函数

这个比较简单,也就是数组的赋值,别忘了给长度的元素赋值

OrderList CreatOrderList(int n){
    int i;
    OrderList orderList;
    orderList.length = n;
    for(i=1;i<=n;i++){
        scanf("%d",&orderList.record[i]);
    }
    return orderList;
}

3,先简单看一下main函数的调用结构吧

首先输入长度,然后进入创建顺序表的函数之后得到一个无序的顺序表。

对这个顺序表进行核心的 堆排序操作 ,这里传送一个指针过去

然后我们把这个顺序表输出查看一下就行,printOrderList这个函数的代码会在后面给出

int main( )
{
    int i,j;
    int n;
    printf("输入序列长度");
    scanf("%d",&n);
    printf("输入序列元素");

    OrderList orderList = CreatOrderList(n);
    HeapSort(&orderList);
    printOrderList(orderList);
    return 0;
}

4,最最最核心的堆排序代码部分

这个部分分成两个函数,一个是 void HeapSort(OrderList *list)这个函数控制整个堆排序算法的流程,也就是上面所说的part1,2

先创建初始堆,再递归调整剩余堆的这样两个操作。

HeapAdjust(OrderList *list, int s, int m)这个函数功能就很清楚明白,对传入的顺序表,以及传送的参数index(对应这次调整的开始位置),参数length(对应这次调整的顺序表的长度)。HeapAdjust在整个流程中有两种调用,一个是开始的创建初始堆,一个是后面的递归调整。

/**
 * 这个函数有两个功能,一个是创建堆,一个是调整剩下节点
 * @param list
 * @param s
 * @param m
 */
void HeapAdjust(OrderList *list, int index, int length) {
    //保存传入节点的值
    int rc;
    int j;
    rc = list->record[index];
    for(j = 2*index;j<=length;j*=2){
        //如果左子树(j=s*2)比右子树j+1的大,说明右子树更需要和双亲节点交换,则移动到record[j+1];
        if((j<length)&&(list->record[j]>list->record[j+1])){
            j++;  //下标移动
        }
        //如果孩子节点的值比双亲节点的值大,说明顺序正确,不用交换,退出循环
        if(rc<list->record[j]){
            break;
        }
        //否则说明孩子节点值比双亲节点的小,交换
        list->record[index] = list->record[j];
        //如果换了,说明原来的双亲节点的数值被j的值覆盖,
        //s的下标应该指向原来交换的地方(子节点)
        index = j;
    }
    //原来交换的地方(子节点)应该是原来双亲节点的值,之前被rc保存,现在取出
    list->record[index] = rc;
}

void HeapSort(OrderList *list){
    int i;
    int temp;
    //循环第一次找到最后一个非叶子节点,循环下一次找到倒数第二个非叶子节点......
    for(i=list->length/2 ; i>0;--i){
        HeapAdjust(list,i,list->length);
    }
    /**
     * 把堆底元素和堆顶元素进行交换之后,删除最后一个节点,对剩下的节点进行堆调整
     */
    for(i=list->length;i>1;--i){
        temp = list->record[1];
        list->record[1] = list->record[i];
        list->record[i] = temp;
        HeapAdjust(list,1,i-1);
    }
}

#完整代码如下:

包括顺序表的创建,输出,HeapSort和HeapAdjust

能实现的功能就是给定长度的顺序表进行堆排序并输出

#include "stdio.h"
#define Max_Num 100

typedef struct {
    int record[Max_Num];
    int length;
}OrderList;

void printOrderList(OrderList list){
    int i;
    for(i = 1;i<=list.length;i++){
        printf("%d ",list.record[i]);
    }
}
OrderList CreatOrderList(int n){
    int i;
    OrderList orderList;
    orderList.length = n;
    for(i=1;i<=n;i++){
        scanf("%d",&orderList.record[i]);
    }
    return orderList;
}

/**
 * 这个函数有两个功能,一个是创建堆,一个是调整剩下节点
 * @param list
 * @param s
 * @param m
 */
void HeapAdjust(OrderList *list, int index, int length) {
    //保存传入节点的值
    int rc;
    int j;
    rc = list->record[index];
    for(j = 2*index;j<=length;j*=2){
        //如果左子树(j=s*2)比右子树j+1的大,说明右子树更需要和双亲节点交换,则移动到record[j+1];
        if((j<length)&&(list->record[j]>list->record[j+1])){
            j++;  //下标移动
        }
        //如果孩子节点的值比双亲节点的值大,说明顺序正确,不用交换,退出循环
        if(rc<list->record[j]){
            break;
        }
        //否则说明孩子节点值比双亲节点的小,交换
        list->record[index] = list->record[j];
        //如果换了,说明原来的双亲节点的数值被j的值覆盖,
        //s的下标应该指向原来交换的地方(子节点)
        index = j;
    }
    //原来交换的地方(子节点)应该是原来双亲节点的值,之前被rc保存,现在取出
    list->record[index] = rc;
}

void HeapSort(OrderList *list){
    int i;
    int temp;
    //循环第一次找到最后一个非叶子节点,循环下一次找到倒数第二个非叶子节点......
    for(i=list->length/2 ; i>0;--i){
        HeapAdjust(list,i,list->length);
    }
    /**
     * 把堆底元素和堆顶元素进行交换之后,删除最后一个节点,对剩下的节点进行堆调整
     */
    for(i=list->length;i>1;--i){
        temp = list->record[1];
        list->record[1] = list->record[i];
        list->record[i] = temp;
        HeapAdjust(list,1,i-1);
    }
}

int main( )
{
    int i,j;
    int n;
    printf("输入序列长度");
    scanf("%d",&n);
    printf("输入序列元素");

    OrderList orderList = CreatOrderList(n);
    HeapSort(&orderList);
    printOrderList(orderList);
    return 0;
}

#总结:

这篇blog其实主要是是捋了捋堆排序的思路和实现过程,没有阐述堆排的优缺点和应用之类的话题,接下去的复习应该多注意一下

                                                                                                                                             2018年12月9日 14点07分

猜你喜欢

转载自blog.csdn.net/Xenoverse/article/details/84920362
今日推荐