实验10.1 堆的操作
内容
创建 最小堆类。最小堆的存储结构使用 数组。提供操作:插入、删除、初始化。题目第一个操作是建堆操作,接下来是对堆的插入和删除操作,插入和删除都在建好的堆上操作。
格式
输入
第一行一个数n(n<=5000),代表堆的大小。第二行n个数,代表堆的各个元素。
第三行一个数m (m<=1000),代表接下来共m个操作。接下来m行,分别代表各个操作。下面是各个操作的格式:
- 插入操作:
1 num
- 删除操作:
2
- 排序操作:第一行两个数3和n,3代表是排序操作,n代表待排序的数的数目,接下来一行n个数是待排序数
保证排序操作只出现一次且一定是最后一个操作。
输出
第一行建堆操作输出建好堆后堆顶的元素。
接下来m个操作,若是插入和删除操作,每行输出执行操作后堆顶的元素的值;若是排序操作,输出一行按升序排序好的结果,每个元素间用空格分隔。
思路和探讨
优先级队列相关知识
整体思路描述
定义小根堆及实现相关函数(删除、插入、初始化)
知识探讨&&细节思路
小根堆
每个节点的值都小于或等于
其子节点(如果有的话)值的完全二叉树
小根堆的插入
新增元素首先插入在堆的末尾元素,然后依据小根堆的性质,自底向上,递归调整。设小根堆的元素个数是n。
思路总结
-
将新元素插入到编号为n+1的位置
-
从n+1号位置开始,沿着从该位置到根的路径,判断新元素能否放在该位置(当前判断位置)
-
能,新元素放入,结束
-
不能,将父节点上的元素下移到该位置,当前判断位置上移到父节点,继续判断
-
小根堆的删除
对于最大堆和最小堆,删除操作是针对堆顶元素而言的,即把末尾元素移动到堆顶,再自定向下(重复构建堆的操作),递归调整。
思路总结
- 删除堆顶元素,并把末尾元素移动到堆顶
- 从根开始,沿着从根到叶子节点的路径,为移到堆顶的末尾元素寻找合适的位置
- 判断可以把lastElement放入当前位置吗?
- 可以,lastElement放入,结束
- 不可以,将当前位置的大孩子上移一层,当前位置下移一层,继续判断
小根堆的初始化
从最右一个有孩子的节点开始调整(
root = heapSize / 2,heapSize是最底下,/2相当于往上一层
),根据小根堆的性质,越小的数据往上移动
初始化这一部分原理和删除的依次比较流程一样,区别就是
- 删除是从上往下调整,从
currentNode = 1, child = 2
开始 - 而建堆是从
root = heapSize / 2,child = 2 * root
开始,凭root--
循环向上调整的
堆排序
若在数组theHeap[1:theSize]中建小根堆,堆排序的流程可按
初始化+依次pop
实现。依次pop
- 针对本题,提供了初始化操作,但在排序实现时,读取所给定的待排序数是一个个读取的,就是依次push进小根堆,使其一直符合小根堆,其实实际没有用到初始化操作,
单纯的插入操作就可以实现初建小根堆
。 - 初建小根堆操作后,输出一次top,就pop一下,pop完后又是合规的小根堆就又把top输出,这个过程就是如上的
依次pop操作从而实现排序
,并借助依次输出top(即根)实现了升序输出
。- 如果想让降序输出,可以尝试将每次pop掉的存入栈,存完再出去,依据
后进先出
,实现小根堆堆排序降序输出。
- 如果想让降序输出,可以尝试将每次pop掉的存入栈,存完再出去,依据
若已看懂思路,试着自己写~
实现代码
#include<iostream>
using namespace std;
//将一个一维数组的长度从oldLength变成newLength。(后续push操作会用到)
template<class T>
void changeLengthID(T*& array, int oldLength, int newLength)
{
T* newarray = new T[newLength];//函数首先分配一个新的、长度为newLength的数组
int number = (oldLength < newLength) ? oldLength : newLength; //取min {oldLength, newLength}
for (int i = 0; i < number; i++)
newarray[i] = array[i];//然后把原数组的前min {oldLength, newLength} 个元素复制到新数组中
delete[] array;//释放原数组所占用的空间
array = newarray;//将新数组赋值到旧数组完成更改
}
//小根堆定义及实现
template<class T>
class minHeap
{
public:
minHeap(int initialCapacity = 10)
{//构造
arrayLength = initialCapacity + 1;
heap = new T[arrayLength];
heapSize = 0;
}
~minHeap() { delete[] heap; }//析构
const T& top()
{//返回优先级最大的元素的引用
return heap[1];
}
void pop();//删除
void push(const T&theElement);//插入
void initialize(T*theHeap, int theSize);//初始化
void output(ostream& out) const;//输出
private:
T* heap;//一个类型为T的一维数组
int arrayLength;//数组heap的容量
int heapSize;//堆的元素个数
};
//小根堆的插入
template<class T>
void minHeap<T>::push(const T& theElement)
{//把元素theElement加入堆(参考p303)
//必要时增加数组长度
if (heapSize == arrayLength - 1)
{//数组长度加倍
changeLengthID(heap, arrayLength, 2 * arrayLength);
arrayLength *= 2;
}
//为元素theElement寻找插入位置
//小根堆要求老叶子比新叶子小
int currentNode = ++heapSize;//currentNode从新叶子向上移动,就从最底下开始
while (currentNode != 1 && heap[currentNode / 2] > theElement)
{//这个时候老叶子比新叶子大,不能把元素放在这
heap[currentNode] = heap[currentNode / 2]; //把大的那个元素赋给currentNode,相当于把大的元素往下移
currentNode /= 2;//同时把currentNode(一个打算插入theElement的位置)移向双亲,就往上移
}
//循环结束,即找到合适的位置插入
heap[currentNode] = theElement;
}
//删除操作是针对堆顶元素而言的,即把末尾元素移动到堆顶,再自顶向下(重复构建堆的操作),递归调整。
template<class T>//(参考p303-304)
void minHeap<T>::pop()
{
//删除堆顶元素
heap[1].~T();
//删除最后一个元素,然后重新建堆(这一步相当于把末尾元素拿出来)
T lastElement = heap[heapSize--];
//开始给拿出来的末尾元素找合适的放入位置,从顶开始,自顶向下调整
int currentNode = 1,
child = 2;//currentNode的孩子
while (child <= heapSize)
{
//heap[child]应该是currentNode的更大的孩子(就是说它的值太大了,应该往后头放)
if (child < heapSize && heap[child] > heap[child + 1])
child++;
//可以把lastElement放在heap[currentNode]吗?
//可以
if (lastElement <= heap[child])
break;
//不可以(以下操作和上述push相关操作同理)
heap[currentNode] = heap[child];//把孩子child向上移动
currentNode = child;//向下移动一层寻找位置
child *= 2;
}
heap[currentNode] = lastElement;
}
//初始化一个非空小根堆
template<class T>
void minHeap<T>::initialize(T* theHeap, int theSize)
{//在数组theHeap[1:theSize]中建小根堆(参考p304-305)
delete[] heap;
heap = theHeap;
heapSize = theSize;
//堆化
for (int root = heapSize / 2; root >= 1; root--)
{
T rootElement = heap[root];
int child = 2 * root;
while (child <= heapSize)
{
//heap[child]应该是兄弟中的较小者
if (child < heapSize && heap[child] > heap[child + 1])
child++;
//可以把rootElement放在heap[child / 2]吗?
//可以(原理同上
if (rootElement <= heap[child])
break;
//不可以
heap[child / 2] = heap[child];
child *= 2;
}
heap[child / 2] = rootElement;
}
}
int main(void)
{
int m, n;
cin >> n;
minHeap<int> minheap(n);
for (int i = 0; i < n; i++)
{
int num;
cin >> num;
minheap.push(num);
}
cout << minheap.top() << endl;
cin >> m;
for (int i = 0; i < m; i++)
{
int op, num;
cin >> op;
if (op == 1)
{
cin >> num;
minheap.push(num);
cout << minheap.top() << endl;
}
if (op == 2)
{
minheap.pop();
cout << minheap.top() << endl;
}
if (op == 3)
{
int p;
cin >> p;
minHeap<int> minheap1(p);
for (int i = 0; i < p; i++)
{
int number;
cin >> number;
minheap1.push(number);
}
for (int i = 0; i < p; i++)
{
cout << minheap1.top() << " ";
minheap1.pop();
}
}
}
return 0;
}