C语言模拟实现堆(动图演示)

  • Post author:
  • Post category:其他





堆的概念

堆:堆通常是一个可以被看一颗完全二叉树的数组对象

而堆又可以分成俩种:

大堆:左右孩子(子树)都比父亲大的完全二叉树

小堆:左右孩子(子树)都比父亲小的完全二叉树


#大堆:

我们以一组数来举例:75 56 46 45 32

请添加图片描述

小堆:

请添加图片描述



堆的结构声明

堆的底层结构实际上是一个动态的

顺序表

typedef int Datatype;

typedef struct Heap
{
	Datatype* a;
	int size;      //记录当前元素个数
	int capacity;  //记录容量大小
}Hp;



堆的初始化

就是将a初始化为空指针,然后将size和capacity

//初始化函数
void InitHeap(Hp* hp)
{
	assert(hp);
	//hp = (Hp*)malloc(sizeof(Hp)); //错误的
	hp->a = NULL;
	hp->capacity = hp->size = 0;

}



堆的销毁

就是将a在动态区的管理的空间释放掉,然后将size和capacity归0

//销毁函数
void DestroyHeap(Hp* hp)
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->capacity = hp->size = 0;
}



插入数据

由于我们一开始是没有为a开辟好

动态内存空间

的,所以我们在进行增容的时候要处理第一次插入数据时的内存开辟:

int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;

而我们发现:将一个数插入进去时,是还没形成堆结构的。

如果我们要实现堆,就要保证树中的父亲要大于或者小于他的俩个孩子,所以我们每次插入时都需要进行数据位置的调整。

下面我们以构建大堆来举例:

还是拿原来的数据:75 56 46 45 32

假设我们现在要插入一个100到堆里面:

初始状态如下:

我们发现对于父亲46来说 是比100要小的 不满足我们大堆的定义

请添加图片描述

所以我们要做出调整 怎么调整呢? 根据定义出发(左右孩子都比父亲要小)

只要将比父亲大的孩子与父亲的位置进行调整

先将100和其父亲46的位置进行调整, 然后我们会再次发现100还是会比他的新父亲(75)要大的,所以我们还要再次做出调整 将75和100互换

这里寻找的父亲坐标是有公式的:




p

a

r

e

n

t

=

(

c

h

i

l

d

1

)

/

2

parent=(child-1)/2






p


a


r


e


n


t




=








(


c


h


i


l


d













1


)


/


2









L

e

f

t

c

h

i

l

d

=

2

p

a

r

e

n

t

+

1

Leftchild=2*parent+1






L


e


f


t


c


h


i


l


d




=








2













p


a


r


e


n


t




+








1









R

i

g

h

t

c

h

i

l

d

=

2

p

a

r

e

n

t

+

2

Rightchild=2*parent+2






R


i


g


h


t


c


h


i


l


d




=








2













p


a


r


e


n


t




+








2






例子:比如说我们现在要找46的父亲下标 即(2-1)/2=0 就求出46的父亲的下标为0

或者是56左右孩子的下标 由公式 左孩子的下标:1乘2+1=3 ,右孩子的下标:1乘2+2=4

调整的方法为:将新插入的数据下标赋给child ,然后再求出父亲的下标parent ,将俩个位置的值进行比较,如果a[child]>a[parent],就将俩个位置的值互换,然后将parent再次赋给child,再利用新的child求出新的parent ,继续与新的父亲进行比较,如此迭代下去,然后此调整的出口有俩个:

1.当child走到祖先节点时

2.当a[child]不再大于a[parent]

请添加图片描述

//向上调整函数
void AdjustUp(Datatype * tree ,int Child)
{
	assert(tree);
	int parent = (Child - 1) / 2;//得到父节点的下标
	   //(parent >=0 ) //此处不好的原因是 child=0的时 即走到堆顶了 parent=0 还会进去循环 从else 出去
	while(Child>0)
	{	
		//大堆
		if ( tree[Child] >tree[parent]  )
		{
			/*Datatype tmp = tree[parent];
			tree[parent] = tree[Child];
			tree[Child] = tmp;*/
			Swap( &tree[Child], &tree[parent] );

			//迭代往上走
			Child = parent;
			parent = (Child - 1) / 2;
			
		}
		else
		{
			break;
		}
	}
}
void PushHeap(Hp* hp, Datatype x)
{
	assert(hp);
	//先检测容量
	if (hp->capacity == hp->size)
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
		Datatype* tmp = (Datatype*)realloc(hp->a, sizeof(Datatype) * newcapacity );

		hp->capacity = newcapacity;
		hp->a = tmp;
	}
	//插入数据
	hp->a[hp->size] = x;
	++hp->size;

	//调整堆
	AdjustUp(hp->a,hp->size-1);

}



删除数据

堆的删除数据的定义和其他数据结构不同,堆删除数据是从堆顶进行删除的

如果我们直接删除堆顶元素的话,然后将数组整体往前挪的,就会出现以下状况,直接全部乱套了
请添加图片描述

所以我们发现这种方式是不可取的。

下面我们给出思路

  1. 将堆顶位置的数据和堆尾位置的数据互换
  2. 将堆尾数据删除
  3. 进行向下调整: 向下调整主要看我们构建的是小堆还是大堆(大小堆取决我们向上与向下调整时的比较符号),就拿我们当前大堆来举例,我们每次进行向下调整都选择左右孩子中较大的孩子,当我们的父亲节点46不再小于俩个子节点或者已经走到了叶子节点时,调整就结束了

动图演示如下:

请添加图片描述

//弹出堆顶元素
void PopHeap(Hp* hp)
{
   assert(hp);
   //交换堆顶元素和堆底元素
   Swap( &hp->a[0], &hp->a[hp->size - 1]);
   //去除堆底元素
   --hp->size;

   //向下调整
   Adjustdown(hp->a,hp->size,0);
}
//向下调整                              //俩个出口 一个是当子节点遍历到叶节点时 二:当父节点不大于或者小与子节点时
void Adjustdown(Datatype * tree,int n,int parent)
{
   int child = parent * 2 + 1;//默认较小或者较大的子节点为左孩子  
   while (child < n)
   {
   	if ( (child+1)<n && tree[child +1] > tree[child] )        
   	{
   		child++;//更换成右节点
   	}
   	if (tree[child] > tree[parent])
   	{
   		Swap( &tree[child], &tree[parent] );
   		//往下迭代
   		parent = child;
   		child = parent * 2 + 1;
   	}
   	else
   	{
   		break;
   	}
   }

}



堆的其他接口

//获取堆的元素大小
int Heapsize(Hp* hp)
{
	assert(hp);
	return hp->size;
}
//获取堆的容量大小
int HeapCapacity(Hp* hp)
{
	assert(hp);
	return hp->capacity;
}
//获取堆顶元素
Datatype HeapTop(Hp* hp)
{
	assert(hp);
	assert(!emptyHeap(hp) ) ;

	return hp->a[0];
}

感谢大佬们的支持,欢迎讨论与学习。



版权声明:本文为sea18323原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。