堆的概念与结构🤔
前面讲了二叉树的相关概念,堆就是把他的所有元素按照完全二叉树的顺序存储方式存储在一个一维数组中。堆可以用来解决堆排序,topk 问题,以后还会涉及到优先级队列。
堆又分为大堆和小堆,我们把根节点最大的堆叫做大(根)堆,即树中父节点 ≥ 子节点,根节点最小的堆叫做小(根)堆,父节点 ≤ 子节点。
堆的性质:
堆中的某个节点的值总是不大于或者不小于其父节点的值;
堆总是一棵完全二叉树;
堆的实现🤔
一般这种实现我们就直接考虑动态版本:
底层结构我们采用的是顺序表的结构,但注意仅仅只是借鉴他的结构,逻辑上他并不是线性表,不应支持头插尾插头删尾删等操作,是不是有了疑问:他的存储结构不就是一个数组吗,为毛不支持啊?原因很简单,要是支持这些操作不就是一个顺序表了嘛,那我干嘛叫堆是吧。
typedef int HPDataType; typedef struct Heap { HPDataType* a; int size; int capacity; }Heap;
向上(向下)调整算法🤔
这里就有一个细节,格局在此提高,我们所谓入堆出堆,都应该时刻维持他作为堆的结构,想象一下我插入一个数后,结果是他有可能是堆有可能不是堆,因为对于父节点的相对大小我并不知道,所以我们实际上插入删除有两步:
插入需要的数据;
对插入后的数据进行调整;
比如给出一个情景:
这里插入的 4 就很明显的破坏了堆的结构,我们小堆必须保证父节点 ≤ 子节点,那我就要做出调整,我们能用下标表示父节点与子节点的数学关系,让他和父节点进行比较再交换,换完观察现阶段结构是否满足,不满足继续换,我们把这种方法称之为:向上调整算法
堆的删除是删除堆顶的最大或最大值删除后,但依然需要每次调整堆的数据来满足结构,注意这么一个细节,我们删除是在堆顶删除!
栈顶删除后面子节点就会变成父节点,直接破坏了所有父子间关系,所以采用一般的挪动覆盖是不行的,其实我们根本不用抹去这个数,我们只需要把堆顶和堆尾元素交换,删除最后一个数据,然后再向下调整就行了。
首先为了方便,我们单独把交换功能写成一个函数接口;
void Swap(HPDataType* x1, HPDataType* x2) { int tem; tem = x1; x1 = x2; x2 = tem; }
实现如下:
void AdjustDown(HPDataType* a, int n, int root) { assert(a); int parent = root; int child = (parent/2)-1; while (child < n) { if (child + 1 < n && a[child] < a[child + 1]) { child++;//找到子节点中较大的一个作为参与调整的子节点 } if (a[child] > a[parent]) { Swap(&a[child],&a[parent]);//不满足小堆就交换 parent = child; child = parent * 2 + 1; } else { break; } } }
向上调整:
void AdjustUp(HPDataType* a, int child) { assert(a); int parent = (child- 1)/2; while (child > 0)//不能用parent>=0判断,因为parent始终不小于0 { if (a[parent] > a[child]) { Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } }