Binary Heap(二叉堆)

  这篇的主题主要是Heapsort(堆排序),下一篇ADT数据结构随笔再谈谈 - 优先队列(堆)。

  首先,我们先来了解一点与堆相关的东西。堆可以实现优先队列(Priority Queue),看到队列,我们马上就想到了队列的一个特点 - 先进先出(FIFO - first in first out),但优先队列有些不同的地方,优先队列是一种具有优先级先出的数据结构。

  堆的结构:

typedef int ElemType;

typedef struct
{
    ElemType * arr;
    int size;
}Heap;

  和数组栈的结构类似。

  初始化堆:

Heap * InitHeap(ElemType * nums, int ArraySize)
{
    Heap * heap;

    heap = (Heap *)malloc(sizeof(Heap));
    heap -> arr = (ElemType *)malloc(sizeof(ElemType));
    for(heap -> size = 0; heap -> size < ArraySize; heap -> size ++)
        heap -> arr[heap -> size] = nums[heap -> size];
    
    return heap;
}

  找节点的关系:

/*
 *    节点i的双亲
 */
static int HeapParent(int i)
{
    return i/2;
}

/*
 *    节点i的左孩子
 */
static int HeapLeft(int i)
{
    return 2*i + 1;
}

/*
 *    节点i的右孩子
 */
static int HeapRight(int i)
{
    return 2*(i + 1);
}

  这里要保持的是一个大根堆:

/*
 *    维护的最大堆(或最小堆)的性质
 *    参数说明:
 *      1.接收一个已存在的堆
 *      2.节点位置
 *        无返回值
 */
void Max_Heapify(Heap * heap, int i)
{
    int _L = HeapLeft(i);
    int _R = HeapRight(i);
    int largest;
    int temp;

    if(_L < heap -> size && heap -> arr[_L] > heap -> arr[i])
        largest = _L;
    else
        largest = i;
    if(_R < heap -> size && heap -> arr[_R] > heap -> arr[largest])
        largest = _R;
    if(largest != i)
    {
        temp = heap -> arr[i];
        heap -> arr[i] = heap -> arr[largest];
        heap -> arr[largest] = temp;
        Max_Heapify(heap, largest);
    }
}

  建大根堆:

static void Build_Max_Heap(Heap * heap)
{
    int i;

    for(i = heap -> size/2; i >= 0; i--)
        Max_Heapify(heap, i);
}

  排序:

void HeapSort(Heap * heap)
{
    int i;
    int temp;

    //维护堆,把初始建立的混乱的堆变为大根堆,以便进行排序
    Build_Max_Heap(heap);
    for(i = heap -> size - 1; i >= 0; i--)
    {
        temp = heap -> arr[0];
        heap -> arr[0] = heap -> arr[i];
        heap -> arr[i] = temp;
        -- heap -> size;        //每次把具有最小值的节点交换到根节点上,则把需要维护的堆的大小减小
        Max_Heapify(heap, 0);   //交换根节点后的堆的性质变了,需要不断维护
    }
}

  至此,用二叉堆对一个数组的排序就完成了。

  然后,测试一下:

int main()
{
    Heap * heap;
    ElemType nums[] = {5, 13, 2, 25, 7, 17, 20, 8, 4}, * arr = nums;
    int arrSize = sizeof(nums)/sizeof(int);
    int i;

    heap = InitHeap(arr, arrSize);

    HeapSort(heap);

    for(i = 0; i < arrSize; i++)
        printf("%d ", heap -> arr[i]);
    printf("
");

    return 0;
}

  运行结果:

2 4 5 7 8 13 17 20 25

  完整的堆排序代码:

/**
 *  堆排序(Heap Sort)
 */
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType;

typedef struct
{
    ElemType * arr;
    int size;
}Heap;

Heap * InitHeap();
static int HeapParent();
static int HeapLeft();
static int HeapRight();
void Max_Heapify();
static void Build_Max_Heap();
void HeapSort();

/*
 *    初始化堆
 *
 *    参数说明:
 *        1.传入数组
 *        2.数组大小
 *
 *    返回值:堆
 */
Heap * InitHeap(ElemType * nums, int ArraySize)
{
    Heap * heap;

    heap = (Heap *)malloc(sizeof(Heap));
    heap -> arr = (ElemType *)malloc(sizeof(ElemType));
    for(heap -> size = 0; heap -> size < ArraySize; heap -> size ++)
        heap -> arr[heap -> size] = nums[heap -> size];

    return heap;
}

/*
 *    某节点i的双亲
 */
static int HeapParent(int i)
{
    return i/2;
}

/*
 *    某节点i的左孩子
 */
static int HeapLeft(int i)
{
    return 2*i + 1;
}

/*
 *    某节点i的右孩子
 */
static int HeapRight(int i)
{
    return 2*(i + 1);
}

/*
 *    维护堆
 */
void Max_Heapify(Heap * heap, int i)
{
    int _L = HeapLeft(i);
    int _R = HeapRight(i);
    int largest;
    int temp;

    if(_L < heap -> size && heap -> arr[_L] > heap -> arr[i])
        largest = _L;
    else
        largest = i;
    if(_R < heap -> size && heap -> arr[_R] > heap -> arr[largest])
        largest = _R;
    if(largest != i)
    {
        temp = heap -> arr[i];
        heap -> arr[i] = heap -> arr[largest];
        heap -> arr[largest] = temp;
        Max_Heapify(heap, largest);
    }
}

/*
 *    建堆
 */
static void Build_Max_Heap(Heap * heap)
{
    int i;

    for(i = heap -> size/2; i >= 0; i--)
        Max_Heapify(heap, i);
}

/*
 *    排序
 */
void HeapSort(Heap * heap)
{
    int i;
    int temp;

    Build_Max_Heap(heap);            //维护堆,把初始建立的混乱的堆变为大根堆,以便进行排序
    for(i = heap -> size - 1; i >= 0; i--)
    {
        temp = heap -> arr[0];
        heap -> arr[0] = heap -> arr[i];
        heap -> arr[i] = temp;
        -- heap -> size;            //每次把具有最小值的节点交换到根节点上,则把需要维护的堆的大小减小
        Max_Heapify(heap, 0);        //交换根节点后的堆的性质变了,需要不断维护
    }
}

int main(void)
{
    Heap * heap;
    ElemType nums[] = {5, 13, 2, 25, 7, 17, 20, 8, 4}, * arr = nums;
    int arrSize = sizeof(nums)/sizeof(int);
    int i;

    heap = InitHeap(arr, arrSize);

    HeapSort(heap);

    for(i = 0; i < arrSize; i++)
        printf("%d ", heap -> arr[i]);
    printf("
");

    return 0;
}

  这里的堆显然是一个二叉堆,因此可以把它想象成二叉树的样子来理解,关于整个堆排序的过程,它的时间复杂度为O(nlgn),因为调用Build_Max_Heap()的代价为O(n),而n - 1次调用Max_Heapify()的代价为O(lgn)。

  但至于为什么Build_Max_Heap()函数的时间复杂的只有O(n)...看起来应该是O(nlgn),我也还没完全理解这里的时间复杂度的计算...留个坑慢慢理解...懂了之后再补充。

  前面提到了堆的结构型和数组栈相似,实际上,堆与队列和栈有很大的关系,我们知道队列是FIFO,栈是FILO,从上面堆的维护操作等就可以看出,堆还有着具有优先级先出的特性(注意与栈并不同),优先队列是基于最大/小堆实现的,虽然名字中有个队列,但区别还是很大的。

原文地址:https://www.cnblogs.com/darkchii/p/7591093.html