堆与队列
(一)堆
1、实现
- 堆是一颗完全二叉树(如果一共有h层,那么1~h-1层均满,在h层可能会连续缺失若干个有叶子节点),用数组实现
- 为了方便,我们直接占用了数组下标为0的位置,在0的位置放置了一个null,这样数组中实际有效值的下标就和我们完全二叉树中层序遍历的实际序号对应了。这样,完全二叉树中,如果结点值为n,那么其左子树则为2n,右子树为2n+1;换句话说,对于任一结点n,其父结点为n/2 取整即可。
2、分类
- 小根堆:如果根节点存在左子节点,则根节点的值小于左子节点的值;如果存在右子节点,则根节点的值小于右子节点
- 大根堆:如果根节点存在左子节点,则根节点的值大于左子节点的值;如果存在右子节点,则根节点的值大于右子节点
3、特性
- 对于堆(Heap)这种数据结构,从根节点到任意结点路径上所有的结点都是有序
- 堆,一般由操作人员(程序员)分配释放,若操作人员不分配释放,将由OS回收释放。
(二)栈
1、定义
- 只允许在一端进行插入或删除操作的线性表
2、栈溢出
- 栈溢出的概念:
- 栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致栈中与其相邻的变量的值被改变
- 栈溢出的原因:
- (1)局部数组过大。局部变量是存储在栈中的,当函数内部的数组过大时,有可能导致堆栈溢出。解决这类问题的办法有两个,一是增大栈空间,二是改用动态分配,使用堆(heap)而不是栈(stack)。
- (2)递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。
- (3)指针或数组越界。如字符串拷贝,处理用户输入等等。
- 栈溢出的例子:
- strcpy(buf,argv[1]);这一行发生了缓冲区溢出错误,因为源缓冲区内容是用户输入的
(三)队列
- 先进先出,每次元素的入队都只能添加到队列尾部,出队时从队列头部开始出。
- 优先级队列:不满足先进先出的条件,每次出队的元素都是队列中优先级最高的那个元素,而不是队首元素。这个优先级可以自己定义,比如元素越大优先级越高(基于二叉堆实现)
- priority_queue<int, vector, greater> minHeap;
第一个参数:元素的类型;
第二个参数:存储元素的底层容器;
第三个canshu :less:容器中最大的元素会排在队列前面;greater:最小元素会排在队列前面
- priority_queue<int, vector, greater> minHeap;
(四)请你说一说Top(K)问题
1、直接全部排序(只适用于内存够的情况)
- 当数据量较小、内存可以容纳所有数据的情况下,最简单的方法是将数据全部排序,然后取排序后的数据中的前K个。
- 但是这个方法对数据量比较敏感而且不太高效,当数据量较大的情况下,内存不能完全容纳全部数据,这种方法就不适用了。
2、快速排序的变形(只适用于内存够的情况)
- 类似于快速排序,首先选择一个划分元,将比这个划分元大的元素放到他的前面,比划分元小的放到她的后面,此时完成一趟排序。
- 如果此时这个划分元的序号index刚好等于k(k-1),那么这个划分元以及它左边的数就是前k个最大的元素。如果index > K,那么前K大的数据在index的左边,那么就继续递归的从index-1个数中进行一趟排序。
- 如果index < K,那么再从划分元的右边继续进行排序,直到找到序号index刚好等于K为止。再将前K个数进行排序后,返回Top K个元素。这种方法就避免了对除了Top K个元素以外的数据进行排序所带来的不必要的开销。
3、最小堆法
- 一种局部淘汰法。先读取前k个数,建立一个最小堆。
- 然后将剩余的所有数字依次与最小堆的堆顶进行比较,如果小于或等于堆顶数据,则继续比较下一个;
- 否则删除堆顶元素,并将新数据插入堆中,重新调整最小堆。当遍历完全部数据后,最小堆中的数据即为最大的k个数
4、分治法
- 在内存允许的情况下,将全部数据分成N份,每份数据分别读进内存进行处理,找到每份数据中最大的K个数。
- 此时剩下NK个数据,如果内存不能容纳NK个数据,则再继续分治处理,分成M份,找出每份数据中最大的K个数,如果M*K个数仍然不能读到内存中,则继续分治处理。
- 直到剩余的数可以读入内存中,那么可以对这些数使用快速排序的变形或者归并排序进行处理。
5、Hash法
- 如果所给数据中有很多重复的数据,可以通过hash法把重复的数去掉。这样子可以减少内存用量,从而缩小运算空间。
- 如果处理后的数据能够全部读入内存,则可以直接排序;否则用最小堆法或者分治法处理。