数据结构堆的创建

定义: 一颗完全二叉树,堆中的元素存储到一维数组中,对于任意节点,如果该节点小于其左右孩子,称之为小堆,如果任意一节点值大于其孩子,则称为大堆。(上面最大就是大堆,上面最小就是小堆)
堆的特征: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] << "  ";
	}
}

堆排序嘛,没堆咋排序,堆创建完成后,因为头的位置都是最大的或者最小的元素,那么可以利用头删的思想,进行排序。 升序----->大堆,降序—>小堆。
测试结果:在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值