堆:堆是一颗完全二叉树。如果父节点的值小于等于孩子节点的值,则称为小根堆;否则称为大根堆。
堆排序:
对小根堆来说,不断取出堆顶元素,取出的堆顶元素就构成了一个非严格递增序列。
小根堆的接口定义与实现:
1.堆的结构
一个线性数组 b a s e base base用于存储元素。堆有最大容量限制 c a p a c i t y capacity capacity以及元素个数 l e n g t h length length。
#define HEAPMAXSIZE 128
typedef int HElemType;
typedef struct
{
HElemType *base;
int capacity;
int length;
}MinHeap;
2.建堆并初始化
先建立一个空堆。然后根据给定数据来初始化堆。
// 创建空堆
MinHeap* createMinHeap()
{
MinHeap *h = malloc(sizeof(MinHeap));
h->base = (HElemType *)malloc(sizeof(HElemType)*HEAPMAXSIZE);
if(!h->base)
{
return NULL;
}
h->capacity = HEAPMAXSIZE;
h->length = 0;
return h;
}
// 根据给定数据初始化堆
int initWithData(MinHeap *h, HElemType *arr, int length)
{
if(length >= h->capacity) return -1;
for(int i=0;i<length;i++)
{
insert(h, &arr[i]);
}
return 0;
}
3.添加新元素
将新元素加到末尾。如果新元素的值比父节点小,则上浮,直至其大于等于父节点的值。使堆重新满足小根堆。时间复杂度 O ( l o g n ) O(logn) O(logn)。
// 交换两元素的值
void swap(HElemType *a, HElemType *b)
{
HElemType temp = *a;
*a = *b;
*b = temp;
}
// 调整堆尾元素到合适的位置
void upAdjust(MinHeap *h)
{
int childIndex = h->length - 1;
int parentIndex = (childIndex-1)/2;
// 孩子节点值小于父节点时,需上浮调整
while(childIndex>0 && h->base[childIndex] < h->base[parentIndex])
{
// 交换值并更新索引
swap(&h->base[parentIndex], &h->base[childIndex]);
childIndex = parentIndex;
parentIndex = (parentIndex-1)/2;
}
}
// 添加新元素
int insert(MinHeap *h, HElemType *e)
{
if(h->length+1 == HEAPMAXSIZE) return -1;//堆满,插入失败
// 上浮调整新元素
h->base[h->length++] = *e;
upAdjust(h);
return 0;
}
4.取堆顶元素
取堆顶元素后,最好的办法是把堆尾元素放到堆顶。如果该元素大于孩子节点,则下沉调整到合适的位置,使堆重新满足小根堆。时间复杂度 O ( l o g n ) O(logn) O(logn)。
// 调整堆顶元素到合适的位置
void downAdjust(MinHeap *h)
{
int parentIndex = 0;
int childIndex = 2 * parentIndex + 1;//左孩子索引
// 父节点值大于子节点时,需要进行下沉调整
while (childIndex < h->length && h->base[parentIndex] > h->base[childIndex])
{
// 孩子节点间比较,父节点与最小的孩子进行交换
if (childIndex + 1 < h->length && h->base[childIndex + 1] < h->base[childIndex]) {
childIndex++;
}
swap(&h->base[parentIndex], &h->base[childIndex]);
// 更新父节点索引为交换的孩子索引,并计算出新的孩子索引
parentIndex = childIndex;
childIndex = 2 * parentIndex + 1;
}
}
// 取堆顶元素
int extract(MinHeap *h, HElemType *e)
{
if(h->length == 0) return -1;//堆空,取堆顶元素失败
*e = h->base[0];
// 堆尾元素放到堆顶,然后下沉调整
h->base[0] = h->base[--h->length];
downAdjust(h);
return 0;
}
5.显示堆
// 显示堆
void printMinHeap(MinHeap *h)
{
for (int i=0; i<h->length; i++)
{
printf("%d ", h->base[i]);
}
printf("\n");
}
6.销毁堆
// 释放堆空间
void destroy(MinHeap *h)
{
free(h->base);
free(h);
}
7.测试代码
对数据序列
11
,
17
,
4
,
10
,
29
,
4
,
18
,
18
,
22
,
1
11,17,4,10,29,4,18,18,22,1
11,17,4,10,29,4,18,18,22,1进行堆排序。
int main()
{
HElemType e;
MinHeap *h = createMinHeap();
// 用指定数据创建一个最小堆
int testArr[10] = {11,17,4,10,29,4,18,18,22,1};
initWithData(h, testArr, sizeof(testArr)/sizeof(testArr[0]));
printf("capacity:%d\n", h->capacity);
printf("length:%d\n", h->length);
printMinHeap(h);
//取堆顶元素
int n = h->length;
for(int i=0; i<n; i++)
{
extract(h, &e);
printf("%d\n", e);
}
destroy(h);
return 0;
}
运行结果:
capacity:100
length:10
1 4 4 17 10 11 18 18 22 29
1
4
4
10
11
17
18
18
22
29
取出的堆顶元素形成了一个非严格递增序列。
堆排序时空复杂度分析:
根据给定数据建堆,时间复杂度
O
(
l
o
g
n
)
O(logn)
O(logn),再取堆顶元素,时间复杂度
O
(
l
o
g
n
)
O(logn)
O(logn),故总时间复杂度
O
(
l
o
g
n
)
O(logn)
O(logn)。
空间复杂度
O
(
n
)
O(n)
O(n)。