堆
定义: 一颗完全二叉树,堆中的元素存储到一维数组中,对于任意节点,如果该节点小于其左右孩子,称之为小堆,如果任意一节点值大于其孩子,则称为大堆。(上面最大就是大堆,上面最小就是小堆)
堆的特征:1.堆的顶端一定为最大的,或者最小的
2.如果根节点满足堆,那么他的左子树和右字数也满足堆得性质。(小堆如果不满足,则该节点可以和孩子节点较小的相替换)
3.堆创建的时间复杂度,因为叶子节点比度为2的节点多1,那么就可以大致认为有2/n个节点需要移动。而单次调整从二叉树顶到二叉树底最多要调整long2 n,所以堆的创建时间复杂度为nlong2 n;
堆的创建
堆的结构类似于一个完全二叉树,但是呢底层一般都是顺序结构,用数组表示一个堆。与二叉树完全不同的是二叉树有指针指向下一个元素,而堆直接用size当作索引。
请看代码:
typedef int HeapDataType;
typedef struct Heap
{
HeapDataType* _array; //堆的元素所在空间
size_t _size; //堆元素个数
size_t _cap; //堆的容量
~Heap() { //析构函数
if (_array)
free(_array);
}
}Heap;
//堆的初始化,为了提高下效率直接引用传参不会闯将临时变量,用者更爽
//传指针,指针也只是拷贝
void initHeap(Heap& hp,HeapDataType* arr, int size)
{
cout << arr << endl;
cout << &hp._size << endl;
hp._array = (HeapDataType*)malloc(sizeof(HeapDataType)*size);
if (hp._array == nullptr)
{
assert(0);
return;
}
for (int i = 0; i < size; i++)
{
hp._array[i] = arr[i];
}
hp._cap = size;
hp._size = size;
//找倒数第一个叶子节点
int leatLeaf = ((size - 2) >> 1);
for(;leatLeaf>=0;leatLeaf--)
{
ADjustDown(hp,size, leatLeaf);
}
}
void ADjustDown(Heap& hp,int size,int parent)
{
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size&&hp._array[child] > hp._array[child + 1])
child += 1;
if ((hp._array[parent] >hp._array[child]))
{
swap(hp._array[child], hp._array[parent]);
parent = child;
child = child * 2 + 1;
}
else
{
return;
}
}
}
本段代码结合既用了C的风格也用了C++的特性,原本是可以直接用一个类直接封装到一起的,但是这样一来就既复习了C也复习了C++;
解释:在initHeap中传过来的是数组,首先开辟空间,然后将数组的值复制到新的空间中。之后调用ADjustDown()完成堆的创建。
调整原理:从最后一个叶子节点,依次向下调整。(不是只调整一次)。
向下调整原理:
我们从最后一个非叶子节点开始,然后一次调整到根的位置,那么就完成了堆的创建。
注意: 堆中不存在叶子左孩子右娃子的概念,只不过和二叉树类似这样叫而已。
本来堆的销毁也应该给出,但是没必要,这是析构函数的事了。
堆顶的删除
堆在底层是个顺序结构,那么删头直接删不好删,(删还不简单,只不过删了没头,不好调整),我们可以和最后一个节点相互交换,size减1,那么头就没了,之后最后一个元素在头的位置,一次向下调整就好了。
// 删除堆顶元素
void ErasureHeadp(Heap& ph)
{
if (ph._size == 0)
return;
swap(ph._array[0], ph._array[ph._size - 1]);
ph._size -= 1;
ADjustDown(ph, ph._size, 0);
}
堆的插入
想在一个数组里面插入一个元素,在数组头的位置有元素的情况下,在头插元素,是不明智的,那么可以先尾插,之后向上调整一下,和向下调整类似。
还需考虑的问题: 放不放的下的问题 那就是扩容
//扩容
void Dilatation(Heap& ph)
{
HeapDataType* pCur = (HeapDataType*)malloc(sizeof(HeapDataType) * 2*ph._cap);
if (nullptr == pCur)
{
assert(0);
return;
}
for (int i = 0; i < ph._size; i++)
{
pCur[i] = ph._array[i];
}
free(ph._array);
ph._array = nullptr;
ph._array = pCur;
ph._cap = ph._cap * 2;
}
//堆的向上调整,插入专用
void ADjustUP(HeapDataType* arr, int size,int child )
{
if (nullptr == arr)
return;
while (child)
{
int parent = (child - 1) / 2;
if ((arr[child] < arr[parent]))
{
swap(arr[child], arr[parent]);
child = parent;
}
else
{
return;
}
}
}
//堆的插入,堆在底层是顺序结构,那么头插是非常不划算的。可以插在尾部,然后在进行向上调整
void InsertHeadp(Heap& ph, HeapDataType arr)
{
//如果没有容量就扩容把
if (ph._size == ph._cap)
Dilatation(ph);
ph._array[ph._size] = arr;
ph._size++;
ADjustUP(ph._array, ph._size, ph._size - 1);
}
测试函数:
void test()
{
Heap h;
h._size = 1;
cout << &h << endl;
cout << &h._size << endl;
int arr[] = { 7,3,4,2,5,8,1,6 };
cout << arr << endl;
initHeap(h, arr, sizeof(arr) / sizeof(arr[0]));
PrintfHead(h);
cout << "堆的容量为:" << h._cap << "堆中元素为:" << h._size << endl;
ErasureHeadp(h);
PrintfHead(h);
cout << "堆的容量为:" << h._cap << "堆中元素为:" << h._size << endl;
InsertHeadp(h, 0);
InsertHeadp(h, -1);
PrintfHead(h);
cout << "堆的容量为:" << h._cap << "堆中元素为:"<< h._size << endl;
}
打印结果:
这里传引用的目的是为了减少开销,以及可能发生的浅拷贝。我们可以看到,在两个函数中传引用地址打印结果相同,如果传指针那么就会创建指针临时变量。然后这个临时变量指向我们传进的对象。 有点绕,仔细看不难。
不得不说引用用着很舒服。
本文创建的是一个小堆,大堆的话只需要,把向下调整,还有向上调整中的大小于号换个位置就行了。 代码不用重新写,直接用函数指针就实现。
堆排序
//堆排序
void ADjustDown2(int* arr ,int parent ,int size) {
int child = parent * 2 + 1;
while (child < size)
{
if (child + 1 < size&&arr[child] > arr[child + 1])
{
child += 1;
}
if (arr[parent] > arr[child])
{
swap(arr[parent], arr[child]);
parent = child;
child = child * 2 + 1;
}
else
{
return;
}
}
}
void SorkHeap(int* arr, int size) {
if (nullptr == arr || size <= 0)
return;
int pRoot = ((size - 2) / 2);
for (; pRoot >= 0; pRoot--)
{
ADjustDown2(arr, pRoot, size);
}
int end = size - 1;
while (end) // 核心代码
{
swap(arr[0], arr[end]);
ADjustDown2(arr,0 ,end);
end--;
}
}
void test2()
{
int arr[] = { 12,4,5,2,4,1,5,5,7,3,2,34,1 };
SorkHeap(arr, 13);
for (int i = 0; i < 13; i++)
{
cout << arr[i] << " ";
}
}
堆排序嘛,没堆咋排序,堆创建完成后,因为头的位置都是最大的或者最小的元素,那么可以利用头删的思想,进行排序。 升序----->大堆,降序—>小堆。
测试结果: