数据结构和算法是一种思想,理解了思想就是忘记了代码也能找回原来的记忆。
栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆(操作系统):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收,分配方式倒是类似于链表。
堆(数据结构):堆可以被看成是一棵树,如:堆排序。
栈(数据结构):一种先进后出的数据结构。
二叉堆
二叉堆其实就是一种特殊的二叉树(完全二叉树),只不过存储在数组里,通过下标索引父子节点。
// 父节点的索引
int parent(int root) {
return root / 2;
}
// 左孩子的索引
int left(int root) {
return root * 2;
}
// 右孩子的索引
int right(int root) {
return root * 2 + 1;
}
二叉堆有两种:最大堆和最小堆。
最大堆:父结点的键值总是大于或等于任何一个子节点的键值;
最小堆:父结点的键值总是小于或等于任何一个子节点的键值。
主要应用有两个:排序方法「堆排序」,数据结构「优先级队列」。主要操作就两个,sink(下沉)和swim(上浮)。
优先队列:是基于二叉堆实现的,主要操作是插入和删除。插入是先插到最后,然后上浮到正确位置;删除是把第一个元素 pq[1](最值)调换到最后再删除,然后把新的 pq[1] 下沉到正确位置。
堆排序:是基于二叉堆实现的,将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
d-堆
d-堆是二叉堆的简单推广,它恰像一个二叉堆,只是所有的节点都有d个儿子(因此,二叉堆又叫2-堆)
左式堆
零路径长:到没有两个儿子的节点最短距离
定义:一棵具有堆序性质的二叉树 + 零路径长
左式堆的基本操作是merge,既合并
merge基本步骤是,先将H2合并到H1的右孩子上,如果右零路径长大于左零路径长,交换左右孩子。
merge时注意的点,如果H1有右孩子时,先根据merge将H1的右孩子合并到H2的右孩子上。
merge操作是基于递归操作实现的。
斜堆
斜堆是左式堆的自调节形式,它和左式堆的关系类似于伸展树和AVL树的关系。斜堆是有堆序的二叉树,但没有结构限制,不保留节点的零路径长。
所有的操作最坏运行时间为 O(N) ,摊还时间为 O(logN) 。
斜堆的合并操作会无条件地交换右路径上除最大值节点外的所有节点的左右儿子。同样可以递归和非递归地实现。
斜堆的优点是不需要保存零路径长和测试是否交换儿子
二项队列
二项队列不是一棵树,它是一个森林,由一组堆序的树组成的深林,叫做二项队列。
结构:每个二项树包含数据、一个儿子及右兄弟,二项树中的诸儿子以递减次序排列。
二项队列有几个性质比较重要
每一颗树都是一个有约束的堆序树,叫做二项树
高度为k的第k个二项树Bk由一个根节点和B0, B1, .......B(k-1)构成
高度为k的二项树的结点个数为2^k
二项队列的操作
查找最小项:只需要查找每个二项树的根节点就可以了,因此时间复杂度为O(logN)。
合并:通过把两个队列相加在一起完成。因为有O(logN)棵树,所以合并的时间复杂度也是O(logN)。
插入:插入也是一种合并,只不过是把插入的结点当做B0。虽然感觉插入的时间复杂度是O(logN),但是实际是O(1),因为有一定的概率是被插入的二项队列没有B0。
删除最小:在根结点找到最小值,然后把最小值所在的树单独拿出分列为二项队列,然后把这个新的二项队列与原二项队列进行合并。每一个过程的时间复杂度为O(logN)。故加起来的时间复杂度仍为O(logN)。
这些操作归根结底是合并Merge。