数据结构《堆》

        堆是一种特殊的数据结构,它不同与我们队列和栈,堆属于二叉树的范畴,另外,堆分为大堆和小堆,大堆简单理解为就是父节点大于等于它的子节点,小堆就是父节点小于等于它的子节点,大堆小堆都属于完全二叉树。

        为了便于访问各个节点,这里使用数组实现堆。(已知父节点是i,那么左子树就是2i+1,柚子树就是2i+2;已知任意一个子树节点的下标是i,那么它的父节点就是(i-1)/2,这是因为整数的除法只保留整数位)。

一、创建堆的结构体

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* _a;
	int _size;
	int _capacity;
}Heap;

        这里的结构体内容同顺序表一样

二、堆的初始化

//堆的初始化
void HeapInit(Heap* php)
{
	assert(php);
	php->_a = NULL;
	php->_capacity = 0;
	php->_size = 0;
}

三、堆的销毁

// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	if (hp->_a == NULL)
	{
		return;
	}
	free(hp->_a);
	hp->_capacity = 0;
	hp->_size = 0;
}

四、堆的插入

//向上调整
void AdjustUp(HPDataType* arr,int size)
{

	assert(arr);
	int sub = size - 1;//取到数组最后一个节点的下标
	int son = sub;
	int father = (son - 1) / 2;//找到它的父亲
	while (son > 0)//子节点下标是永远不为0的
	{
		if (arr[son] < arr[father])//如果子节点小于父亲,那么进行交换
		{
			HPDataType flag = arr[son];
			arr[son] = arr[father];
			arr[father] = flag;
			son = father;//交换完成,父节点成为新的子节点,即向上调整
			father = (son - 1) / 2;//向上更新父节点
		}
		else//不满足更新条件,退出函数
		{
			return;
		}

	}

}



// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	//判断是否需要扩容
	if (hp->_capacity == hp->_size)
	{
		int newcapacity = (hp->_capacity == 0) ? 4 : hp->_capacity * 2;

		HPDataType* newhp = (HPDataType*)realloc(hp->_a, newcapacity * sizeof(HPDataType));
		if (newhp == NULL)
		{
			perror("realloc error!");
			return;
		}
		hp->_a = newhp;
		hp->_capacity = newcapacity;
	}
	//插入数据在叶子上
	hp->_a[hp->_size] = x;
	hp->_size++;
	//向上调整
	AdjustUp(hp->_a, hp->_size);

}

        为了保持小堆的格式(父节点大于子节点),这里使用尾插并向上调整的算法,这个算法中,传入的形式参数是需要调整的数组和该数组有效元素个数,具体逻辑请看注释。

五、堆数据的删除

//向下调整
//sub是调整的目标的下标,目的是将较小的数据移到靠近根
void AdjustDown(HPDataType* arr, int size ,int sub)
{
	
	//确保有左孩子,满足最小的交换条件
	while (sub * 2 + 1 < size)
	{
		int  secondson;
		int  bigson;
		//确保有右孩子,才能进行左右孩子的交换
		if (sub * 2 + 2 < size)
		{
			//假设左孩子小于右孩子
			secondson = 2 * sub + 1;
			bigson = secondson + 1;
			//如果左孩子大于右孩子,则交换左右孩子下标
			if (arr[secondson] > arr[bigson])
			{
				int flag = bigson;
				bigson = secondson;
				secondson = flag;
			}

		}
		else//没有右孩子,那么小儿子就是左孩子
		{
			secondson = 2 * sub + 1;
		}
		//开始向下调整
		//如果父亲大于两个孩子之间最小的那一个,那么久做一次交换
		if (arr[secondson] < arr[sub])
		{
			HPDataType newdata = arr[secondson];
			arr[secondson] = arr[sub];
			arr[sub] = newdata;
			sub = secondson;
		}
		else//未满足交换条件,退出函数
		{
			return;
		}

	}
}

// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp);
	assert(hp->_size);
	hp->_a[0] = hp->_a[hp->_size - 1];//交换最后一个节点和第一个节点
	hp->_size--;//删除节点,原理同顺序表
	//向下调整
	AdjustDown(hp->_a, hp->_size,0);


}

        因为删除没有插入那样简单直接,删除掉根之后,他下边的所有子节点便群龙无首,因此我们不能直接删除根,所以有大神使用将首位节点互换,然后再向下调整根的数据的算法。

因为互换的叶子节点肯定比互换前的根大,所以需要向下调整互换后的根节点。至于为什么要比较左右节点的大小,那是因为我们要把尽可能小的数据移动到尽可能靠近根的位置,所以才需要比较左右节点的大小,以满足小堆的格式。

六、取堆顶的数据

// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->_a[0];
}

七、求堆的数据个数

// 堆的数据个数
int HeapSize(Heap* hp)
{
	return hp->_size;//返回数据个数
}

八、堆的判空

// 堆的判空
int HeapEmpty(Heap* hp)
{
	return hp->_size == 0;//判断堆的数据个数是否等于0,如果是,返回非0,否,返回0
}

九、测试代码

void test()
{

	Heap HP;
	HeapInit(&HP);
	for (int i = 0; i < 100; i++)//随机生成100个数据,并插入进堆
	{
		HeapPush(&HP, rand()%1000);
	}
	while (!HeapEmpty(&HP))//按照顺序取出数据
	{
		printf("%d ", HeapTop(&HP));
		HeapPop(&HP);
	}
	HeapDestory(&HP);
}

int main()
{
	test();
	return 0;
}

十、测试结果

满足小堆的结构。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值