实验10.1 堆的操作

实验10.1 堆的操作

内容

创建 最小堆类。最小堆的存储结构使用 数组。提供操作:插入、删除、初始化。题目第一个操作是建堆操作,接下来是对堆的插入和删除操作,插入和删除都在建好的堆上操作。

格式

输入

第一行一个数n(n<=5000),代表堆的大小。第二行n个数,代表堆的各个元素。
第三行一个数m (m<=1000),代表接下来共m个操作。接下来m行,分别代表各个操作。下面是各个操作的格式:

  • 插入操作:1 num
  • 删除操作:2
  • 排序操作:第一行两个数3和n,3代表是排序操作,n代表待排序的数的数目,接下来一行n个数是待排序数

保证排序操作只出现一次且一定是最后一个操作。

输出

第一行建堆操作输出建好堆后堆顶的元素。
接下来m个操作,若是插入和删除操作,每行输出执行操作后堆顶的元素的值;若是排序操作,输出一行按升序排序好的结果,每个元素间用空格分隔。

思路和探讨

优先级队列相关知识

笔记补充——第十二章:优先级队列,指路12.4

整体思路描述

定义小根堆及实现相关函数(删除、插入、初始化)

知识探讨&&细节思路

小根堆

每个节点的值都小于或等于其子节点(如果有的话)值的完全二叉树在这里插入图片描述

小根堆的插入

新增元素首先插入在堆的末尾元素,然后依据小根堆的性质,自底向上,递归调整。设小根堆的元素个数是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掉的存入栈,存完再出去,依据后进先出,实现小根堆堆排序降序输出。

若已看懂思路,试着自己写~


实现代码

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啦啦右一

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值