这篇的主题主要是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,从上面堆的维护操作等就可以看出,堆还有着具有优先级的先出的特性(注意与栈并不同),优先队列是基于最大/小堆实现的,虽然名字中有个队列,但区别还是很大的。