数据结构——堆

C语言实现堆

在介绍堆之前我们先需要介绍一个数据结构——二叉树的概念。
二叉树的概念:
一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树 的二叉树组成。

二叉树的特点:

  1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒。

特殊的二叉树:
1.满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是 说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对 应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。
而我们的堆就是建立在完全二叉树的基础之上所提出的。
堆的概念:
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质
1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。
堆的实现:
现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
在具体实现堆的操作之前,我们先来讲述两个堆的调整算法:
1.向下调整算法:
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整 成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。所以,我们必须从整个二叉树的最后一个根节点开始调整。
算法的核心思想:
从根结点开始,然后一个一个的把结点插入堆中。当把一个新的结点插入堆中时,需要对结点进行调整,以保证插入结点后的堆依然是大根堆。如下图所示,是采用自顶向下的方法建立的大根堆。
图示如下:
在这里插入图片描述
具体实现如下:

//a为数组首地址,n为结点个数,root为当前需处理的根节点
void ShiftDown(HPDataType* a, int n, int root)
{
 assert(a);
 int parent = root;
 int child = 2 * parent + 1;
 //当前结点是否有孩子结点,有孩子进入循环,无孩子则无需调整
 while (child < n)
 {
  //判断是否有右孩子,如果有,两者之间选较大值
  if (child + 1 < n && a[child + 1] > a[child])
  {
   ++child;
  }
  //孩子是否大于父亲
  if (a[child] > a[parent])
  {
   //交换
   HPDataType tmp = *pa;
   *pa = *pb;
   *pb = tmp;
   //更新下一次调整的位置
   parent = child;
   child = 2 * parent + 1;
  }
  else
  {
   //以parent为根的子树已经是一个大堆,结束调整
   break;
  }
 }
}

向下调整算法的时间复杂度O(logN),多在创建堆时使用。
2.向上调整算法
向上调整算法相比较于向下调整算法要简洁许多,多用于在原有的堆的基础上进行调整的算法,其时间复杂度为O(N)。
算法的核心思想:
从第一个非叶子结点开始进行判断该子树是否满足堆的性质。如果满足就继续判断下一个点。否则,如果子树里面某个子结点有最大元素,则交换他们,并依次递归判断其子树是否仍满足堆性质。
图示:在原有大堆的基础上增加一个新结点,并将其调整成大堆。
在这里插入图片描述
使其一直沿着其祖先节点进行比较,如果大于其父节点,则交换,如果小于,则调整结束。
具体实现:

ShiftUp(HPDataType* a, int n, int child)
{
 assert(a);//对参数的合法性进行检查
 int parent = (child-1)/2;
 while (child > 0)
 {
  if (a[child] > a[parent])
  {
   Swap(&a[child], &a[parent]);
   child = parent;
   parent = (child - 1) / 2;
  }
  else
  {
   break;
  }
 }
}

总结:向下调整算法多用于创建堆的过程,而向上调整算法则多用于在原有堆的基础上进行操作。

堆的操作:
Heap.h文件

typedef int HPDataType;
typedef struct Heap
{
 HPDataType* _a;
 int _size;
 int _capacity;
}Heap;
void HeapInit(Heap* hp, HPDataType* a, int n);
void HeapEmptyInit(Heap* hp);
void ShiftDown(HPDataType* a, int n, int root);//向下调整算法
void ShiftUp(HPDataType* a, int n, int child);//向上调整算法
void HeapDestory(Heap* hp);
void HeapPush(Heap* hp, HPDataType x); 
void HeapPop(Heap* hp); 
HPDataType HeapTop(Heap* hp); 
int HeapSize(Heap* hp); 
int HeapEmpty(Heap* hp);
void HeapSort(int* a, int n);// 堆排序 
void TestHeap();
void HeapPrint(Heap* hp);
void TestHeapSort()

Heap.c文件

//创建一个大堆
void HeapInit(Heap* hp, HPDataType* a, int n)
{
 int i = 0;
 assert(hp && a);
 hp->_a = (HPDataType*)malloc(sizeof(HPDataType) * n);
 for (i = 0; i < n; i++)
 {
  hp->_a[i] = a[i];
 }
 //调整,从最后一颗子树开始调整
 for (i = (n - 2) / 2; i >= 0; --i)
 {
  ShiftDown(hp->_a, n, i);
 }
 hp->_size = n;
 hp->_capacity = n;
}
//初始化为空堆
void HeapEmptyInit(Heap* hp)
{
 hp->_a = NULL;
 hp->_capacity = 0;
 hp->_size = 0;
}
//交换
void Swap(HPDataType* pa, HPDataType* pb)
{
 HPDataType tmp = *pa;
 *pa = *pb;
 *pb = tmp;
}
//销毁堆
void HeapDestory(Heap* hp)
{
  free(hp->_a);
  hp->_a = NULL;
  hp->_capacity = 0;
  hp->_size = 0;
 }

//插入一个新节点:先进性尾插,在使用向上调整算法进行调整
void HeapPush(Heap* hp, HPDataType x)
{
 assert(hp);
 //检查容量
 if (hp->_size == hp->_capacity)
 {
  int newcapacity = hp->_capacity == 0 ? 10 : 2 * hp->_capacity;
  hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType) * newcapacity);
  hp->_capacity = newcapacity;
 }
 //尾插
 hp->_a[hp->_size] = x;
 hp->_size++;
 //向上调整
 ShiftUp(hp->_a, hp->_size, hp->_size - 1);
}
//删除堆顶元素,最值。
void HeapPop(Heap* hp)
{
 assert(hp);
 //对堆有效性进行检查
 if (HeapEmpty(hp) == 0)
 {
  //交换堆顶元素
  Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
  //尾删
  hp->_size--;
  //进行调整
  ShiftDown(hp->_a, hp->_size, 0);
 }
}
//返回堆顶元素
HPDataType HeapTop(Heap* hp)
{
 assert(hp && hp->_size);
 return hp->_a[0];
}
//返回堆的大小
int HeapSize(Heap* hp)
{
 assert(hp && hp->_size);
 return hp->_size;
}
//判断堆是否为空
int HeapEmpty(Heap* hp)
{
 assert(hp);
 return hp->_size == 0 ? 1 : 0;
}
// 堆排序 :O(N*log(N)+N(建堆))--->O(N)
void HeapSort(int* a, int n)
{
 //建堆
 int i = 0;
 int end = n - 1;
 for (i = (n - 2) / 2; i >= 0; --i)
 {
  ShiftDown(a, n, i);
 }
 //堆排序
 //交换
 while (end > 0)
 {
  Swap(&a[0], &a[end]);
  ShiftDown(a, end, 0);
  end--;
 }
}
//测试
void TestHeap()
{
 Heeap hp;
 HeapEmptyInit(&hp);
 HeapPush(&hp, 1);
 HeapPush(&hp, 2);
 HeapPush(&hp, 3);
 HeapPush(&hp, 4);
 HeapPush(&hp, 5);
 HeapPush(&hp, 6);
 HeapPush(&hp, 7);
 HeapPop(&hp);
 HeapPrint(&hp);
}
//打印堆
void HeapPrint(Heap* hp)
{
 assert(hp);
 for (int i = 0; i < hp->_size; ++i)
 {
  printf("%d ", hp->_a[i]);
 }
 printf("\n");
}
//测试堆排序
void TestHeapSort()
{
 int i = 0;
 int a[] = { 1,5,3,8,7,6 };
 HeapSort(a, sizeof(a) / sizeof(a[0]));
 for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
 {
  printf("%d ", a[i]);
 }
 printf("\n");
}

int main()
{
 TestHeap();
 TestHeapSort();
 return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值