stl容器适配器 stack与queue,priority_queue

news2025/1/20 1:46:09

目录

一.stack

1.stack的使用

 2.适配器

3.stack相关的题目

最小栈. - 力扣(LeetCode)

​编辑

栈的弹出压入序列栈的压入、弹出序列_牛客题霸_牛客网

用两个栈实现队列. - 力扣(LeetCode) 

4.stack的模拟实现

二.queue队列 

 1.底层容器deque

2.queue的使用

练习题:用栈实现队列. - 力扣(LeetCode)

3.queue的模拟实现 

 三.priority_queue优先队列

1.priority_queue的使用

2.priority_queue模拟实现 


一.stack

1.stack的使用

stack就是数据结构里常见的栈,具有先进后出的特点,stl把它封装成了容器了,拿来就可以使用不用再从轮子开始造起了。
stack增删与之前类似就不多加赘述了

值得注意的是stack没有迭代器,所以无法通过迭代器进行访问。也没有vector和string容器里的方括号下标访问。栈每次只能访问栈顶元素,访问完了后再pop删除然后才能访问下一个元素。

 

 2.适配器

从构造函数来看没有fill填充构造也没有范围构造,出现次数最多的构造是Alloc,这个是什么呢

构造方面不支持:initializer_list构造,fill填充构造,范围构造也不支持

 Alloc是适配器模式,适配器和迭代器一样也是一种C++设计模式。

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总 结),该种模式是将一个类的接口转换成客户希望的另外一个接口。比如说插座不同的国家插座插头形状不同,所以需要适配器这个中间商转换一下,使插头能够使用别的国际的插头。放到stl,stack是用别的容器封装过的,利用别的容器比如vector的push_back来实现自己的push插入。所以很多时候stack都不太算是容器,因为它不能自己存储东西都要利用别的容器来装东西

正常stack的构造函数写成

 或者采用list作为底层容器

但是呢这里就有一个问题, stack<int,vector<int>> s;尖括号里填的模版类型,而stack构造基本都是传的类型,所以stack就没法在构造的时候就加入数据,也无法先构造一个vector容器,然后利用这个容器把数据加入到stack当中,只能慢慢push进数据

 

因为要满足stack的先进先出的规则,所以底层的选择上也有条件,容器内部必须要有以下几个函数

empty:判空操作

back:获取尾部元素操作

push_back:尾部插入元素操作

pop_back:尾部删除元素操作

标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器, 默认情况下使用deque。 deque也是一种容器,是list和vector折中化的容器,往后再详细介绍。

3.stack相关的题目

最小栈. - 力扣(LeetCode)

这题的解题思路就是拿两个栈相互配合,tamp2栈专门存最小值,插入数据的时候先给tamp1栈插入,如果tamp2也为空那么也给tamp2直接插入数据。如果tamp2不为空,那么就拿tamp1当中插入的数据(此时是tamp1栈顶数据)和tamp2栈顶相比较,如果tamp1比较小或者相等的话tamp2当中就也插入val值(更新栈顶最小值,tamp2必须保持栈顶的值是最小的)

删除数据的时候要注意,如果tamp1当中删除的数据正好是tamp2当中的栈顶值,那么tamp2的栈顶也得删除。其余的时候是不需要删除的,因为tamp2是tamp1当中所有现存元素的最小值,所以如果tamp1删除了与tamp2堆顶相等的数据,就相当于tamp1当中的最小值要更新了,栈顶元素是无效数据了

举个例子

插入数据1,4,0,5,2,8

删除数据,同时更新最小值 

 

当两边栈顶元素都相等时,说明此时栈里面的最小值需要更新了,所以两边都需要删除0

删除4与之前一样,直接在tamp1当中删除

最后栈顶元素相等都是1,所以两边栈都得删除

完整代码如下

#include<iostream>
using namespace std;
#include<stack>

class MinStack {
public:
    MinStack() {

    }

    void push(int val) {

        tamp1.push(val);
        if (tamp2.empty())
            tamp2.push(val);
        else
        {
            if (tamp1.top() <= tamp2.top())
                tamp2.push(val);
        }

  

    }

    void pop() {
        if (tamp1.top() == tamp2.top())
        {
            tamp2.pop();
        }
        
        tamp1.pop();
    }

    int top() {
        return tamp1.top();
    }

    int getMin() {
        return tamp2.top();
    }
    stack<int> tamp1;
    stack<int> tamp2;
};

栈的弹出压入序列栈的压入、弹出序列_牛客题霸_牛客网

这题大致思路就是模拟栈的进栈出栈顺序,如果进栈数据数量与出栈顺序不相等,说明肯定不适合,直接返回false就行了。进栈直接进就行,如果此时栈顶与出栈的数据相同那么就出栈。最后如果栈为空那么就说明按照这个出栈顺序已经全出栈了,说明这个出栈顺序是有效的

class Solution {  
public:  
    /**  
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可  
     *  
     * @param pushV int整型vector   
     * @param popV int整型vector   
     * @return bool布尔型  
     */  
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {  
        if(pushV.size()!=popV.size())
           return false;
        stack<int> tamp;   
        int k = 0;  

        for (int i = 0; i < pushV.size(); i++) {  
            tamp.push(pushV[i]);  
            // 当栈不为空且栈顶元素和当前弹出元素相等时,弹出栈顶元素  
            while (!tamp.empty() && tamp.top() == popV[k]) {  
                tamp.pop();  
                k++;  
            }  
        }  
        
        return tamp.empty();  
    }  
};  

用两个栈实现队列. - 力扣(LeetCode) 

 

这题也是用两个栈来处理,利用栈先进后出的特性来模拟先进先出。因为先进后出,所以循环每次取栈顶出来放到另一个栈中,在另一个站中的出栈顺序正好与第一个栈的出栈顺序相反这样就模拟了先进先出的特性。举个例子,1,2,3,4,5进第一个栈,此时出栈再进第二个栈的顺序是5,4,3,2,1,这时候第二栈的栈顶元素是1,再挨个取栈顶元素出栈为1,2,3,4,5.这样就模拟成功了先进先出的特性了。值得注意的是模拟的进队列函数直接进第一个栈就行,出队列和取栈顶元素把第一个栈的元素倒着插入第二个栈时要考虑第二栈是否为空,只有为空的时候才能挪动第一个栈的数据。这是因为如果第二个栈的数据不为空,此时中途又插入了数据,然后再执行出队列如果是无条件地把第一个栈的数据转移到第二个栈中就会破坏先进先出的特性。举个例子,第一个栈先进1,2,3,然后转移到第二个栈中(第二个栈此时为3,2,1,第一个栈为空了)。此时第一个栈又进了一个数据5,然后执行出队列,如果是无条件把第一个栈的数据转移到第二个栈中,此时第二个栈中的数据就变成了3,2,1,5。5是最后进的反而成了队顶,就破坏了先进先出的特性

完整代码如下

class MyQueue {
public:
    MyQueue() {

    }

    void push(int x) {
        tamp1.push(x);
    }

    int pop() {
        if (tamp2.empty())
        {
            while (!tamp1.empty())
            {
                tamp2.push(tamp1.top());
                tamp1.pop();
            }
        }
        int ret = tamp2.top();
        tamp2.pop();
        return ret;
    }

    int peek() {
          if (tamp2.empty())
         {
            while (!tamp1.empty())
            {
                tamp2.push(tamp1.top());
                tamp1.pop();
            }
         }
        return tamp2.top();
    }

    bool empty() {
        return tamp1.empty() && tamp2.empty();
    }
    stack<int> tamp1;
    stack<int> tamp2;
};

4.stack的模拟实现

首先需要明确的是为什么他叫容器适配器呢,虽然前面已经概述过了,但是可能不太直观。stack之所以叫这个是因为他不能直接装数据,都是通过别的容器来辅助存数据的。比如如果以vector为容器的话,那么它的push其实就是通过vector的push_back来实现的,pop删除也是类似的方法。几乎所有的函数都得依赖别的容器来实现。

所以有些时候你可以看到stack定义会写成这样stack<int,vector<int>>,第二个参数vector模版类型其实就是指明stack的底层容器是vector,你也可以写成list

以vector来举例实现stack的话,因为要在stack类里面封装插入删除,所以直接定义一个vector对象来作为stack的成员(大部分适配器几乎没有什么别的成员,指向底层构成就可以了)

push和pop的实现其实就是直接用尾插和尾删就行了,因为栈只在栈顶出,实际上就是队尾出

empty判空,因为所有数据都存在vector当中,所以直接调用vector的判空函数就行

top函数,栈顶函数就是最后一个元素,vector中有函数back可以直接取到最后一个元素

 返回size,也是和上面类似

但是上面只是用vector举个例子,一般来讲只要能用判空,尾插尾删,取队尾元素的容器都可以作为stack的底层容器。所以要把底层容器类型也写成模版类型,template<class T,class alloc=vector<T>>(库里面一般默认的是deque)

完整代码如下

namespace mystack
{
	template<class T,class alloc=vector<T>>
	class stack
	{
	public:
		void push(const T& val)
		{
			tamp.push_back(val);
		}
		void pop()
		{
			tamp.pop_back();
		}
		const T& top() const
		{
			return tamp.back();//返回队尾元素
		}
		T& top()
		{
			return tamp.back();
		}

		bool empty()const
		{
			return tamp.empty();
		}

		size_t size() const
		{
			return tamp.size();
		}
	private:
		alloc tamp;
	};
}

 测试

二.queue队列 

 1.底层容器deque

queue与stack一样都是容器适配器,但是栈是先进先出,它是先进先出,也就是从尾部插入从头部出。vector是没有直接头删函数,可以用insert间接做到,但是效率太低了。虽然list可以使用头删,但是是以单个元素开辟空间存储,所以在大量插入删除数据时效率低下。因此大佬们设计了vector和list折中的容器deque,这个容器其实是双向队列,支持在头尾插入删除,融合了vector随机访问连续存储的优点(所以它也可以使用下标[ ]的访问方式),同时包含list的两端插入删除方便的优点

从下图可以看出了deque融合了vector和list的功能

具体底层实现其实是按块进行处理,比如说我有50个数据,那么就直接开50个内存块分别存10个数据,同时记录开一个中控数组(实际上就是指针数组)记录这十块空间的地址。为了方便头插所以中控数组会空出一些空间(如果是头插,就得再原先的头空间块的基础上再开辟一块空间存新的数据,这个新的空间就是新的头空间块了,中控数组多出来的部分就是存这个新内存块地址的) ,有点类似vector<vector<int>>二维数组

 那么怎么实现vector里的随机访问呢,比如我要找下标为43的数据,首先我这里设定的一个内存块存10个数据(库里实现可能更复杂一点),所以直接int j=43/10,j就是具体存哪个数据块,然后int i=43%10,i是具体在内存块里的数据位置,这样就找到了下标为43的数据是在第四个数据块里的第三个位置。

库里的实现要复杂一点(见下图),cur是当前节点位置,first是内存块的开始位置,last是末尾数据的下一位,当遍历访问的时候cur从first开始挨个往后挪动,当cur等于last时说明这个内存块访问完了,中控那里的node节点位置++,找到下一个内存块的地址,然后cur重新赋值为新内存块first地址,从头开始往后找

 那么插入时扩容是怎么扩容的呢(参考下图),又定义了两个迭代器,start指向头内存块,finish指向尾内存块,当cur等于finish内存块的last位置时,说明已经存满需要扩容。这时会开辟一个新内存块,使finish的所有成员指向新内存块中新的位置。如果中控数组满了也需要扩容,头插扩容了内存块的地址在中控数组中满了没法存了,也会发生扩容

结合上面可以很明显发现deque的缺点,首先是实现复杂,其次头尾插入倒是方便了,但是如果在中间插入,要么后面所有的数据都往后挪动空一个位置出来然后完成新数据插入(数组不方便插入删除的缺点)。要么就是在当前内存块直接扩容,但是这样就直接破坏了随机存取规则,因为这种模拟的随机存取有一个前提就是所有内存块容量长度都相等,如果有一个内存块长度不相等就破坏随机存取的规则了(正好是list的缺点)

所以deque效率其实不是很高,虽然吸收了vector和list的优点,但是缺点没有完全修复。因此一般都不怎么会用deque,要随机存取查找方便就用vector,要插入删除方便就用list。虽然作为容器效率不高过于复杂,但是作为容器适配器的底层容器还是挺方便的,因为容器适配器压根就不修改中间元素只会在末尾和头部插入删除,所以stack栈,queue队列,priority_queue优先队列基本默认底层容器是deque

2.queue的使用

queue的使用与stack类似,只有简单的增删判空,只不过队列是从队尾插入,从队头出,先进先出所以出队的顺序与进队顺序一致。stack是每次都通过top栈顶来访问不同元素,而queue队列提供了访问队尾和队头元素值的两个函数,因为每次改变的是队头元素,所以每次访问队头元素就可以访问到所有元素了

举个例子 

练习题:用栈实现队列. - 力扣(LeetCode)

大致思路是用两个队列来模拟栈的先进后出,也就是说进1,2,3,4,第一次pop的时候要想办法取到4,但是这个4在队尾,那应该怎么取到呢。实际上就是用第二个队来进行辅助,第一个队的数据挪动size-1个数据到第二个队列当中,此时第一个队数据就剩一个数据4了,让第一个栈pop掉4(这样就实现了pop掉第一个队的尾部数据)。处理完了尾部数据后再把第二个队的数据全部挪动回来进行下一轮操作。对于模拟取栈顶元素,与pop的操作类似,只是挪动完了第一队的数据后不需要pop掉最后一个元素,取到它的值后也依旧挪动到第二队当中(取队顶和pop唯一差别就是要不要pop掉第一个队列的最后一个元素)

完整代码如下

class MyStack {  
public:  
    MyStack() {}  

    void push(int x) {  
        tamp1.push(x);  
    }  

    int pop() {  
        // 将 tamp1 中的元素转移到 tamp2,直到只剩下一个元素  
        while (tamp1.size() > 1) {  
            tamp2.push(tamp1.front());  
            tamp1.pop();  
        }  
        // 取出最后一个元素(即栈顶元素)  
        int ret = tamp1.front();  
        tamp1.pop();  

        // 将 tamp2 中的元素转回 tamp1  
        while (!tamp2.empty()) {  
            tamp1.push(tamp2.front());  
            tamp2.pop();  
        }  
        
        return ret; // 返回栈顶元素  
    }  

    int top() {  
        // 与 pop 相同,只是不移除元素  
        while (tamp1.size() > 1) {  
            tamp2.push(tamp1.front());  
            tamp1.pop();  
        }  
        int ret = tamp1.front(); // 获取栈顶元素  

        // 将最后一个元素再压回 tamp1  
        tamp2.push(tamp1.front());  
        tamp1.pop();  

        // 将 tamp2 中的元素转回 tamp1  
        while (!tamp2.empty()) {  
            tamp1.push(tamp2.front());  
            tamp2.pop();  
        }  
        
        return ret; // 返回栈顶元素  
    }  

    bool empty() {  
        return tamp1.empty();  
    }  

private:  
    std::queue<int> tamp1; // 主队列  
    std::queue<int> tamp2; // 辅助队列  
};

3.queue的模拟实现 

queue的实现与stack类似,只是pop时是从头开始出的

完整代码如下

namespace myqueue
{
	template<class T,class alloc=deque<T>>
	class queue
	{
	public:
		void push(const T& val)
		{
			tamp.push_back(val);
		}
		void pop()
		{
			tamp.pop_front();
		}

		T& front()
		{
			return tamp.front();
		}

		const T& front()const
		{
			return tamp.front();
		}

		const T& back()const
		{
			return tamp.back();
		}

		bool empty()const
		{
			return tamp.empty();
		}

		size_t size() const
		{
			return tamp.size();
		}
	private:
		alloc tamp;
	};
}

测试

 三.priority_queue优先队列

1.priority_queue的使用

priority_queue虽然名字叫优先队列但是它底层其实是堆,而且默认是大堆,它的成员函数也和stack以及queue类似,都是这几个接口

值得注意的是优先队列没有单独的头文件,它是一起定义在queue头文件里的,但是它的遍历规则并不是和队列一样先进先出。默认情况下是遵循大堆的规则,每次堆顶都是最大值,而是遍历打印出来是降序 

那么怎么打印出升序,也就是底层处理成小堆,在前期的学习中可以知道建堆都是通过向下调整来处理的(向上调整nlogn,向下调整logn),而向下调整只需要改变大于和小于符号就可以完成调整成大堆还是小堆的转换了。在库里实际上是通过仿函数来进行调整比较符号的,仿函数不是函数而是一种对象,它可以像函数一样被调用。

下面的例子中small<int>其实是个匿名对象

针对大堆小堆库里其实直接给了less<int>(虽然翻译是小,但是其实是大堆,库里面默认缺省是填的这个),greater<int>(这才是小堆)两个仿函数,可以直接拿来用

 

自定义仿函数可以用来排序复杂的自定义类型,比如日期类,你光按年份比较如果年份相等就无法比较了,所以要定义一个仿函数在年份相等的情况下按月份去排序,在月份相等的情况下按天数去比较。

#include<iostream>
using namespace std;
#include<queue>
class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 : _year(year)
 , _month(month)
 , _day(day)
 {}
 bool operator<(const Date & d)const
 {
 return (_year < d._year) ||
 (_year == d._year && _month < d._month) ||
 (_year == d._year && _month == d._month && _day < d._day);
 }
 bool operator>(const Date & d)const
 {
 return (_year > d._year) ||
 (_year == d._year && _month > d._month) ||
 (_year == d._year && _month == d._month && _day > d._day);
 }
 friend ostream & operator<<(ostream & _cout, const Date & d)//打印
 {
 _cout << d._year << "-" << d._month << "-" << d._day;
 return _cout;
 }
private:
 int _year;
 int _month;
 int _day;
};
void TestPriorityQueue()
{
	// 大堆,需要用户在自定义类型中提供<的重载
	priority_queue<Date> q1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	cout << q1.top() << endl;
	// 如果要创建小堆,需要用户提供>的重载
	priority_queue<Date, vector<Date>, greater<Date>> q2;
	q2.push(Date(2018, 10, 29));
	q2.push(Date(2018, 10, 28));
	q2.push(Date(2018, 10, 30));
	cout << q2.top() << endl;
}

int main()
{
	TestPriorityQueue();
}

2.priority_queue模拟实现 

不加仿函数版本

#pragma once  
#include <iostream>  
#include <deque>  
using namespace std;

namespace mypriority {

    template<class T, class alloc = deque<T>>
    class priority_queue {
    public:
        // 下沉调整  
        void adjustdown(size_t parent) {
            size_t child = 2 * parent + 1; // 假设最左边的孩子就是最大的  
            while (child < tamp.size()) {
                // 如果右边的孩子更大,更新孩子节点下标  
                if (child + 1 < tamp.size() && tamp[child] < tamp[child + 1]) {
                    child++;
                }
                // 父节点比孩子节点小,进行交换  
                if (tamp[parent] < tamp[child]) {
                    swap(tamp[parent], tamp[child]);
                    // 更新父节点下标,继续调整  
                    parent = child;
                    child = 2 * parent + 1;
                }
                else {
                    break; // 如果不需要交换,退出  
                }
            }
        }

        // 向上调整  
        void adjustup(size_t child) {
            size_t parent = (child - 1) / 2;
            while (child > 0) {
                // 如果父节点小于子节点,进行交换  
                if (tamp[parent] < tamp[child]) {
                    swap(tamp[parent], tamp[child]);
                    // 更新child和parent  
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else {
                    break; // 如果不需要交换,退出  
                }
            }
        }

        // 添加元素  
        void push(const T& val) {
            tamp.push_back(val);       // 在末尾添加新元素  
            adjustup(tamp.size() - 1); // 调整新添加的元素位置  
        }

        // 移除最大元素  
        void pop() {
            if (!empty()) { // 确保队列不为空  
                swap(tamp[0], tamp[tamp.size() - 1]); // 将最大元素移到末尾  
                tamp.pop_back();                       // 移除末尾元素  
                adjustdown(0);                         // 调整根元素  
            }
        }

        // 获取最大元素  
        const T& top() const {
            if (!empty())
                return tamp[0]; // 确保队列不为空  
            throw out_of_range("Priority queue is empty"); // 抛出异常处理  
        }

        // 检查队列是否为空  
        bool empty() const {
            return tamp.empty();
        }

        // 获取队列中的元素数量  
        size_t size() const {
            return tamp.size();
        }
        priority_queue() = default;//强制生成默认构造函数
        template <class InputIterator>
        priority_queue(InputIterator first, InputIterator last) {
            while (first != last) {
                tamp.push_back(*first);
                ++first;
            }
            for (int i = (tamp.size() - 1 - 1) / 2; i >= 0; --i) {
                adjustdown(i);
            }
        }
    private:
        alloc tamp; // 用于存储优先队列的元素  
    };
}

如果直接用符号进行比较的话只能单独处理大堆或者小堆(除非再重新写一个类),解决办法是通过仿函数来进行处理,在仿函数内部直接进行元素的比较

仿函数版本

#pragma once
#include <iostream>
#include <vector> // Include vector to use as default container
#include <deque>
using namespace std;
namespace mypriority {
    template<class T>
    class mygreater {
    public:
        bool operator()(const T& x, const T& y) const {
            return x >y;
        }
    };

    template<class T>
    class myless {
    public:
        bool operator()(const T& x, const T& y) const {
            return x < y;
        }
    };

    template<class T, class alloc = std::deque<T>, class Judge = myless<T>>
    class priority_queue {
    public:
        void adjustdown(size_t parent) {
            Judge fun;
            size_t child = 2 * parent + 1;

            while (child < tamp.size()) {
                if (child + 1 < tamp.size() && fun(tamp[child], tamp[child+1])) {
                    child++;
                }
                if (fun(tamp[parent], tamp[child])) {
                    std::swap(tamp[parent], tamp[child]);
                    parent = child;
                    child = parent * 2 + 1;
                }
                else {
                    break;
                }
            }
        }


        void adjustup(size_t child) {
            Judge fun;
            size_t parent = (child - 1) / 2;
            while (child > 0) {
                if (fun(tamp[parent], tamp[child])) { // Here swap if child is less than parent
                    std::swap(tamp[parent], tamp[child]);
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else {
                    break;
                }
            }
        }

        priority_queue() = default;

        template <class InputIterator>
        priority_queue(InputIterator first, InputIterator last) {
            while (first != last) {
                tamp.push_back(*first);
                ++first;
            }
            for (int i = (tamp.size() - 1 - 1) / 2; i >= 0; --i) {
                adjustdown(i);
            }
        }

        void push(const T& val) {
            tamp.push_back(val);
            adjustup(tamp.size() - 1);
        }

        void pop() {
            std::swap(tamp[0], tamp[tamp.size() - 1]);
            tamp.pop_back();
            adjustdown(0);
        }

        const T& top() const {
            return tamp[0];
        }

        bool empty() const {
            return tamp.empty();
        }

        size_t size() const {
            return tamp.size();
        }

    private:
        alloc tamp;
    };
}


模拟实现取堆顶,判空,取size与之前类似就不细讲了

 push插入

 void push(const T& val) {
            tamp.push_back(val);
            adjustup(tamp.size() - 1);
        }

  // 向上调整  
      void adjustup(size_t child) {
            Judge fun;//实例化仿函数对象
            size_t parent = (child - 1) / 2;
            while (child > 0) {
                if (fun(tamp[parent], tamp[child])) { // Here swap if child is less than parent
                    std::swap(tamp[parent], tamp[child]);
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else {
                    break;
                }
            }
        }

插入一般在末尾直接插入就可以了,但是直接插入不符合堆的规则,所以要向上调整进行处理一下。

size_t parent = (child - 1) / 2;先求出父节点下标,右节点是减2,但是(child-2)/2值是一样的。比如父亲节点是1,两个孩子节点是2和3,结果算出来都是1;

while (child > 0) {
                if (fun(tamp[parent], tamp[child])) { // Here swap if child is less than parent
                    std::swap(tamp[parent], tamp[child]);
                    child = parent;
                    parent = (child - 1) / 2;
                }

这一段循环是具体处理堆元素,用fun控制大小比较,默认情况下如果父节点值比子节点值小就交换值,然后通过child = parent; parent = (child - 1) / 2;一直往下处理

pop删除

  void pop() {
            std::swap(tamp[0], tamp[tamp.size() - 1]);
            tamp.pop_back();
            adjustdown(0);
        }

 

void adjustdown(size_t parent) {
            Judge fun;
            size_t child = 2 * parent + 1;

            while (child < tamp.size()) {
                if (child + 1 < tamp.size() && fun(tamp[child], tamp[child+1])) {
                    child++;
                }
                if (fun(tamp[parent], tamp[child])) {
                    std::swap(tamp[parent], tamp[child]);
                    parent = child;
                    child = parent * 2 + 1;
                }
                else {
                    break;
                }
            }
        }
删除每次删除都是堆顶元素,也就是下标为0的元素,如果是直接堆顶删除是非常困难的,但是在尾部删除是很简单的,所以直接进行交换到最后进行删除,然后从堆顶出发向下调整至符合堆的规则

向下调整函数

向上调整是用孩子节点的值与自己的父节点进行比较,而向下调整一开始就是最上面的父亲节点,所以是和孩子节点中的大的那个进行比较。但是我们并不知道哪个孩子节点是最大节点,所以先假设左孩子节点是大的节点,在循环里面再去比较和右孩子节点哪个大(这个是每一趟都要进行比较的)   if (child + 1 < tamp.size() && fun(tamp[child], tamp[child+1])) { child++;}

范围构造函数

与queue和stack不同的是优先队列提供了范围构造函数

  template <class InputIterator>
        priority_queue(InputIterator first, InputIterator last) {
            while (first != last) {
                tamp.push_back(*first);
                ++first;
            }
            for (int i = (tamp.size() - 1 - 1) / 2; i >= 0; --i) {
                adjustdown(i);
            }
        }

范围构造第一步都是先全部将这个范围内的所有数值都插入到容器中,堆里面还得多一步按照堆的规则进行调整,向下建堆时间复杂度是O(n),而向上建堆的O(nlogn)(详情请看排序堆排序那一节),所以我们直接用向下调整建堆。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2059488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一起学习LeetCode热题100道(48/100)

48.路径总和 III(学习) 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只…

前端获取主流浏览器的信息进行判断 实现自适应内容(360浏览器)

我一般都是用谷歌浏览器进行开发&#xff0c;在开发大屏可视化的时候出现了浏览器不适应的问题&#xff0c;需要不同的浏览器进行判断&#xff0c;360返回 Chrome 内核&#xff0c; 获取的信息无法跟谷歌浏览器区别 这个是中国的主流浏览器&#xff1a; 比如谷歌可以正常显示&…

十要素超声波气象传感器

十要素微型气象传感器&#xff08;也称为全要素微型气象传感器&#xff09;通常具有以下几个基本功能&#xff1a; 温度测量&#xff1a;测量环境的温度&#xff0c;并提供实时温度数据。 湿度测量&#xff1a;测量环境的湿度水平&#xff0c;并提供实时湿度数据。 大气压力测…

【安全靶场】-DC-5

❤️博客主页&#xff1a; iknow181&#x1f525;系列专栏&#xff1a; 网络安全、 Python、JavaSE、JavaWeb、CCNP&#x1f389;欢迎大家点赞&#x1f44d;收藏⭐评论✍ 一、收集信息 1.用burp测试穷尽文件名 使用两个字典 发现footer页面 可能存在文件包含&#xff0c;因为co…

记一次 MIGO 短缺BA 非限制使用 35,713.970 USD : 100002919-Z040 100Z 1000 99991231

mb52数量 一想到该物料的启用了批次管理 &#xff0c; 应该去查一下 批次管理的底表 MCHB &#xff08;各种库存地表见 SAP MM 库存分类及对应的存储表-CSDN博客&#xff09; 实际调用migo的参数 这下明确了&#xff0c;总共一起60000多是足够调出的&#xff0c;但是99991231这…

zabbix通过snmp监控物理服务器硬件信息

背景&#xff1a;公司的华三服务器周末的时候市电跳闸&#xff0c;监控没有设置告警&#xff0c;幸好有UPS供电&#xff0c;工作日发现问题后市电恢复。 方法&#xff1a; 1、登陆物理服务器带外&#xff0c;开放snmp并设置团体名 2、找一台安装了nmap的机器&#xff0c;查看…

python使用gurobi用法解析和案例

文章目录 1. Gurobi Python接口的基本使用2. 变量类型3. 目标函数4. 约束条件5. 模型求解和结果分析6. 常见注意事项7. gurobi代码示例 1. Gurobi Python接口的基本使用 在Python中使用Gurobi进行优化&#xff0c;通常需要按以下步骤操作&#xff1a; 导入Gurobi包 &#xff…

【Java】—— 使用Java在控制台实现海豚记账软件

目录 1. 项目背景 2. 代码思路 2.1 主要功能 2.2 数据结构 2.3 控制流程 3. 实现步骤 3.1 初始化变量 3.2 显示菜单 3.3 处理用户输入 3.4 退出程序 4. 知识点解析 4.1 Scanner类 4.2 字符与数字转换 4.3 循环与条件判断 5.完整代码 6.运行结果展示 1. 项目背景…

【秋招笔试】8.18字节跳动秋招(第一场)-三语言题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

IOS 12 自定义用户协议对话框

实现效果 实现逻辑 本文使用QMUI里面提供的控制器 自定义控件 实现。 添加依赖 #腾讯开源的UI框架&#xff0c;提供了很多功能&#xff0c;例如&#xff1a;圆角按钮&#xff0c;空心按钮&#xff0c;TextView支持placeholder #https://github.com/QMUI/QMUIDemo_iOS #http…

《向量数据库指南》——解决方案:采用安全、高性能的Milvus Cloud向量数据库,赋能Dopple AI的创新与发展

解决方案:采用安全、高性能的Milvus Cloud向量数据库,赋能Dopple AI的创新与发展 在当今这个数据驱动的时代,向量数据库作为机器学习、人工智能等领域的重要基础设施,正发挥着越来越关键的作用。对于Dopple AI这样一个致力于创新的前沿团队来说,选择一个合适的向量数据库…

盘点8大跨境电商平台发展前景及选品分析(亚马逊、速卖通篇)

跨境电商行业在全球范围内持续发展&#xff0c;各大平台各有特色&#xff0c;针对不同的市场和消费者群体提供多元化的服务。以下是亚马逊、Shopee、TikTok、TEMU、速卖通、eBay、Lazada、SHEIN这八大跨境电商平台的背景、主要针对群体、消费者购物偏好及选品建议的简要介绍&am…

C语言:函数详解(1)

目录 一、函数的概念 二、库函数 三、自定义函数 3.1 函数的语法形式 3.2 函数的举例 四、形参和实参 4.1 实参 4.2 形参 4.3 实参与形参的关系 一、函数的概念 数学中我们其实就见过函数的概念&#xff0c;比如&#xff1a;⼀次函数 ykxb &#xff0c;k和b都是常数&am…

[Meachines] [Easy] Buff Gym-CMS-RCE+Chisel端口转发+CloudMe云文件存储-缓冲区溢出权限提升

信息收集 IP AddressOpening Ports10.10.10.198TCP:7680,8080 $ nmap -p- 10.10.10.198 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 7680/tcp open pando-pub? 8080/tcp open http Apache httpd 2.4.43 ((Win64) OpenSSL/1.1.1g PHP/7.4.6) |…

力扣:移动零

文章目录 需求分析优化下双指针结尾 需求 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,1…

中国平安银行笔试考什么?及如何通过平安测评|附真题库面试攻略

一、中国平安银行公司介绍 中国平安银行&#xff0c;作为中国金融领域的重要力量&#xff0c;以其雄厚的实力和创新的金融服务在行业内脱颖而出。平安银行依托中国平安集团的强大资源&#xff0c;致力于为客户提供全方位的金融解决方案。 平安银行拥有广泛的业务网络&#xff0…

堆排序的插入和删除

插入&#xff1a; 1. 检查你的顺序表是否还有位置去插入&#xff0c;如果没有需要扩展 2. 插入到已有序列的后一位置 3. 和其父节点进行比较&#xff0c;是否满足大根堆/小根堆规则 4. 不满足则需要交换数值 删除&#xff1a; 1. 将最后一个元素覆盖将要删除的元素&#xff0…

第八季完美童模全球十佳人气超模【许馨予】荣耀加冕 见证星芒风采!

7月20-23日&#xff0c;2024第八季完美童模全球总决赛在青岛圆满落幕&#xff0c;在盛大的颁奖典礼上&#xff0c; 全球观众网友通过现场参与和网络直播的方式&#xff0c;见证了一位人气榜样的诞生&#xff01;在众多优秀的小超模中&#xff0c;性格开朗的10岁女孩许馨予从本次…

二叉树 - 二叉树的层序遍历

二叉树的层序遍历 102. 二叉树的层序遍历 /*** Definition for a binary tree node.* function TreeNode(val, left, right) {* this.val (valundefined ? 0 : val)* this.left (leftundefined ? null : left)* this.right (rightundefined ? null : right)…

揭秘!华为手表如何成为运动达人的秘密武器

巴黎奥运会已经告一段落&#xff0c;但大家的运动热情仍旧高涨。我发现&#xff0c;身边喜欢健身的小伙伴都需要更智能的设备去精准的记录运动轨迹、心率、配速等数据&#xff0c;想要成为一个运动健身达人&#xff0c;拥有一款适合自己的运动穿戴设备&#xff0c;会让你的健身…