C++《stack与queue》

news2024/11/24 16:53:13

在之前的章节我们学习了C++当中string、vector和list三种容器并且试着模拟实现这三种容器,那么接下来在本篇当中我们将STL当中的stack和queue,并且在学习stack和queue的使用之后和之前一样还会试着模拟实现stck和queue。由于stck和queue的模拟实现较为简单因此就不另在其他篇章中讲解。在此我们还要了解到stack和queue是一种容器适配器,那么在这之前就要了解容器适配器是什么。最后我们还要了解一种双端队列的容器,并且分析其与vector和list的区别,那么接下来就开始本篇的学习吧!!!


 1.stack使用介绍

stack - C++ Reference

通过文档就可以看出stack也是C++当中STL当中提供的一种容器,该容器其实就是之前我们在数据结构当中学习到的栈,在此在C++当中是将栈的结构和各种功能实现好,之后我们需要使用时就只需要调用模板库内的stack即可这样就不需要像之前C语言一样显示的自己实现,这样可以大大提升我们的效率

在stack当中提供了以下函数,在此这些函数和之前在数据结构就已经学习过,那么在此就不做讲解,如果你还对栈有疑惑可以数据结构之《栈》通过来查缺补漏

函数说明接口说明
stack()构造空的栈
empty()检测stack是否为空
size()返回stack中元素的个数
top()返回栈顶元素的引用
push()将元素val压入stack中
pop()将stack中尾部的元素弹出

那么在了解了stack的相关知识点之后接下来就来通过几道算法题来试着使用stck

最小栈

首先实现该算法题的代码之前我们先来分析该如何实现该算法题

该算法题其实就是要我们实现一个类在这个类当中实现以上的成员函数,在此可以看出要在这个类当中除了要实现栈通常的操作还要实现一个函数能完成将栈当中最小的数据返回,接下来就要分析要实现这样的功能要运用什么方法呢?

其实在此要实现一个最小栈的结构我们就可以使用两个stack来实现,其中一个栈PushSt来存储入栈所有的元素,另一个栈MinSt来存储PushSt内的最小元素

那么MinSt怎么才能实现存储PushSt内的最小元素呢?接下来我们就通过一个示例来分析看看

 例如以上示例所示要将 4 7 3 5 2 2依次入栈,那么就需要进行以下的操作
 1. 每次数据入栈时都先将元素入PushSt栈

 2.完成1之后查看MinSt内的栈顶元素是否比新插入PushSt内的元素的值要大,若大或者相等就也将插入PushSt内的元素的值插入到MinSt。在此还会有一种特殊情况是当MinSt为空时,无论插入到PushSt内的元素是什么都要再将该元素插入到MinSt

完成了以上操作之后所有元素入栈后两个栈PushSt和MinSt内的情况如下所示:

那么接下来如果要进行最小栈出栈的操作时MinSt内部又应该如何变化呢?
继续来对以上的示例进行出栈的分析

1.当进行出栈操作时候,无论要进行出栈的元素的值是多大都要将PushSt内的栈顶元素出栈

2.之后再比较进行此次出栈时PushSt内的栈顶元素的值是否和PushSt内的栈顶元素值相等,相等就需要也将MinSt内的栈顶元素出栈

通过以上的最小栈需要出栈时的操作就可以保证出栈之后在MinSt内的栈顶元素是PushSt内的最小值
 

完成了算法实现的分析之后接下来就来实现该算法题的代码:

class MinStack 
{
public:
    //构造函数
    MinStack() {
        
    }
    //入栈
    void push(int val) 
    {
        if(MinSt.empty() || val<=MinSt.top())
        {
            MinSt.push(val);
        }
        PushSt.push(val);
        
    }
    //出栈
    void pop() 
    {
        if(PushSt.top()==MinSt.top())
        {
            MinSt.pop();
        }
        PushSt.pop();
    }
    //取栈顶元素
    int top() 
    {
        return PushSt.top();
        
    }
    //得到最小的元素值
    int getMin() 
    {
         return MinSt.top();
        
    }

    private:
    std::stack<int> PushSt;
    std::stack<int> MinSt;
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack* obj = new MinStack();
 * obj->push(val);
 * obj->pop();
 * int param_3 = obj->top();
 * int param_4 = obj->getMin();
 */

注:在以上的代码当中由于MinStack的成员变量都是自定义类型的变量,那么就就不需要我们显示的写MinStack的构造函数,这是由于在MinStack对象定义时会自动调用stck的构造函数

栈的压入、弹出序列

接下来我们继续来看以下算法题

通过以上的题目描述就可以看出该算法题要我们实现一个函数能判断两个vector对象内的第一个vector内存储的为入栈序列时,第二个序列能为第一个序列的出栈序列,如果能满足条件函数就返回true,否则就返回flase

接下来在实现该算法题代码之前就先来分析这个题的解题思路是什么

在此我们来通过一个示例来分析
当我们要判断[1,2,3,4,5],[4,5,3,2,1]这两个序列第二个序列是否为第一个序列的一种出栈序列时就可以借助栈来判断,具体过程如下所示:

首先创建一个栈st,在分别创建一个变量pushi和popi分别指向第一个和第二个序列的首个元素,接下来进行重复进行以下操作

1.当栈st为空时或者是栈st的栈顶元素不和popi指向的元素相等时就将pushi指向的元素插入到栈st中,之后再pushi++,直到不符合以上为空或者相等的要求为止

2.之后当popi指向的元素和栈st的栈顶元素相等时就将st栈顶元素出栈,之后再将popi++,重复该操作直到不匹配为止,之后再进行以上的操作1

3.以上的操作的结束条件是当pushi走到第一个序列的末尾时就结束操作,这时看栈st是否为空;为空就说明第二个序列不是为第一个序列的一种出栈序列,否则则是

完成了以上操作之后以上示例使用以上方法的判断过程如下所示:

 先是直到pushi指向5为止都没未出现st栈顶的元素和popi指向的元素匹配,那么就要把1到4都入栈

之后st栈顶的元素4和popi指向的元素4匹配,就将st的栈顶元素出栈,之后再将popi++

之后st栈顶的元素和popi指向的元素不匹配就继续将pushi指向的元素入栈

之后st栈顶的元素4和popi指向的元素4匹配,popi++,之后一直符合要求就一直重复该操作

由以上栈st最后再pushi走到第一个序列的末尾时st为空就说明第二个序列是为第一个序列的一种出栈序列

完成了算法实现的分析之后接下来就来实现该算法题的代码:

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pushV int整型vector 
     * @param popV int整型vector 
     * @return bool布尔型
     */
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) 
    {
        std::stack<int> st;
        int pushi=0,popi=0;
        while(pushi<pushV.size())
        {
            st.push(pushV[pushi]);
            while(!st.empty() && st.top()==popV[popi])
            {
                st.pop();
                ++popi;
            }
            ++pushi;

        }
        return st.empty();
    }
};

逆波兰表达式求值

通过以上的题目描述就知道了该算法题要我们实现的是对一个逆波兰表达式进行运算,在此的逆波兰表达式其实就是中缀表达式,这时你可能就会有疑惑那么中缀表达式是什么呢?

来看以下示例:
在以下示例当中就是这两个中缀表达式的后缀表达式的形式

在此中缀表达式转后缀表达式的技巧是运算数的顺序不变,运算符按照对应的优先级插入到运算数之后,每个运算符对其前面的两个运算数进行运算,并且在使用后缀表达式计算时是按照从左到右对操作符前面的操作数进行运算的

在了解了中缀表达式以及后缀表达式之后接下来我们就来思考该使用什么方式来解答这道算法题呢?
其实在此还是要用到栈来解决,在此先创建一个栈,之后遇到操作数则入栈;遇到操作符则取出栈顶两个操作数进行计算,并将结果压入栈中

实现的代码如下所示:

class Solution {
public:
    int evalRPN(vector<string>& tokens) 
    {
         stack<int> st;
         for(auto& x:tokens)
         {
         //当是操作符时
            if(x=="+" || x=="-" || x=="*" ||x=="/")
            {
                int right=st.top();
                st.pop();
                int left=st.top();
                st.pop();
                switch(x[0])
                {
                    case('+'):
                    st.push(left+right);
                    break;

                    case('-'):
                     st.push(left-right);
                    break;

                    case('*'):
                     st.push(left*right);
                    break;

                    case('/'):
                     st.push(left/right);
                    break;         

                }

            }
        //当是操作数时
            else
            {
                st.push(stoi(x));
            }
         }
         return st.top();
        
    }
};

2.queue使用介绍

queue - C++ Reference

通过文档就可以看出queue也是C++当中STL当中提供的一种容器,该容器其实就是之前我们在数据结构当中学习到的队列,在此在C++当中是将队列的结构和各种功能实现好,之后我们需要使用时就只需要调用模板库内的queue即可这样就不需要像之前C语言一样显示的自己实现,这样可以大大提升我们的效率

在queue当中提供了以下函数,在此这些函数和之前在数据结构就已经学习过,那么在此就不做讲解,如果你还对栈有疑惑可以通过数据结构之《队列》来查缺补漏

函数说明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

 那么在了解了stack的相关知识点之后接下来就来通过几道算法题来试着使用queue

用队列实现栈

以上的算法题其实在数据结构就写过了,不过在之前我们是使用C语言实现的,那时要解决这道算法题需要我们先自己实现队列的结构,之后才能使用创建两个队列来解决。但在C++中就不用这么繁琐了,我们可以直接调用STL当中的queue来实现,因此我们就只需要实现Mystack内的代码即可,并且该类内的构造函数还不需要我们显示的写

代码如下所示:

class MyStack {
public:
    MyStack() {
        
    }
    
    void push(int x) 
    {
        if(!q1.empty())
        {
            q1.push(x);
        }
        else
        {
            q2.push(x);
        }
    }
    
    int pop() 
    {
        int tmp=0;
        if(!q1.empty())
        {
            int n=q1.size();
            while(--n)
            {
                q2.push(q1.front());
                q1.pop();
            }
           tmp=q1.front();
            q1.pop();
    
        }
        else
        {
            int n=q2.size();
            while(--n)
            {
                q1.push(q2.front());
                q2.pop();
            }
             tmp=q2.front();
            q2.pop();
        }

            return tmp;
        
    }
    
    int top() 
    {
        if(!q1.empty())
        {
            return q1.back();
        }
        else
        {
            return q2.back();

        }
        
    }
    
    bool empty() 
    {
        return q1.empty() && q2.empty();
        
    }

    private:
    queue<int> q1;
    queue<int> q2;
};

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack* obj = new MyStack();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->top();
 * bool param_4 = obj->empty();
 */

3.stack和queue的模拟实现

在了解了STL内的stack和queue的使用之后接下来我们来试着模拟实现stack和queue,但在此之前我们要了解什么是容器适配器,这时由于STL内的stack和queue都是容器适配器

什么是适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设
计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口

例如电脑的充电器其实就是一种电源适配器

我们可以通过文档看到STL标准库中stack和queue的底层结构

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为
容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认
使用deque,比如:

 

 那么在此stack内使用到的deque是什么容器呢,接下来我们就来简单了解一下

1.deque的原理介绍:

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端
进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与
list比较,空间利用率比较高。

其实deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问
的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

 2.deque的缺陷

那么在了解了deque之后你可能就会有疑惑了,deque这个容器相比vector在中间和头部插入数据有优势,又相比list空间利用率较高,那么是不是用deque就可以替代vector和list了呢?

确实与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其
是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实
际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看
到的一个应用就是,STL用其作为stack和queue的底层数据结构。

3.为什么选择deque作为stack和queue的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性
结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据
结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如
list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进
行操作。

2. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的
元素增长时,deque不仅效率高,而且内存使用率高。

在此可以看出选择deque作为stack和queue的底层默认容器结合了deque的优点,而完美的避开了其缺陷。

了解deque接下来我们就来模拟实现stack和queue

stack:

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


namespace zhz
{
	template<class T,class container =deque<T>>
	class stack
	{
	public:

		size_t size()const
		{
			return _st.size();
		}
		bool empty()const
		{
			return _st.empty();
		}


		void push(const T& val)
		{
			_st.push_back(val);
		}

		void pop()
		{
			_st.pop_back();
		}


		T& top()
		{
			return _st.back();
		}

		const T& top()const
		{
			return _st.back();
		}


	private:
		container _st;

	};
}

queue:

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


namespace zhz
{
	template<class T, class container = deque<T>>
	class queue
	{
	public:
		size_t size()const
		{
			return _qe.size();
		}

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

		void push(const T& val)
		{
			_qe.push_back(val);
		}
		void pop()
		{
			_qe.pop_front();
		}

		T& front()
		{
			return _qe.front();
		}
		T& back()
		{
			return _qe.back();
		}

		const T& front()const
		{
			return _qe.front();
		}
		const T& back()const
		{
			return _qe.back();
		}



	private:
		container _qe;

	};
}

4.priority_queue的介绍和使用

4.1 priority_queue的介绍

在queue的文档当中可以看到除了queue还有priority_queue

那么priority_queue是什么呢?

其实priority_queue是一种优先队列; 优先队列也是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。并且优先队列类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。

并且优先队列和queue一样被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。

4.2 priority_queue的使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中
元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用
priority_queue。
注意:默认情况下priority_queue是大堆。

函数说明接口说明
priority_queue()/priority_queue(first,
last)
构造一个空的优先级队列
empty( )检测优先级队列是否为空,是返回true,否
则返回false
top( )返回优先级队列中最大(最小元素),即堆顶元
push(x)在优先级队列中插入元素x
pop()删除优先级队列中最大(最小)元素,即堆顶元

例如以下示例:

#include <vector>
#include <queue>
#include <functional>   // greater算法的头文件

void TestPriorityQueue()
{
	// 默认情况下,创建的是大堆,其底层按照小于号比较
	vector<int> v{ 3,2,7,6,0,4,1,9,8,5 };
	priority_queue<int> q1;
	for (auto& e : v)
		q1.push(e);
	cout << q1.top() << endl;

	// 如果要创建小堆,将第三个模板参数换成greater比较方式
	priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
	cout << q2.top() << endl;
}

接下来来看以下的算法题:

数组中第K个大的元素

通过以上的题目描述可以看出该算法题要我们实现的是将数组当中的第k大的元素找出来,并且实现时间复杂度为 O(n) 的算法解决此问题 

其实在此使用priority_queue就能很轻松的解决该算法题,代码如下所示:
 

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        priority_queue<int> pq;
        for(auto x:nums)
        {
            pq.push(x);
        }
        while(--k)
        {
            pq.pop();
        }
        return pq.top();
        
    }
};

4.3 priority_queue的模拟实现 

在了解了优先队列的使用方式之后接下来就来试着模拟实现priority_queue,并且我们知道优先队列类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)

那么优先队列的结构以及入队和出队的操作,在此就要用到之前数据结构当中堆相应的知识,代码如下所示

namespace zhz
{

	template<class T, class container = vector<T>>
	class priority_queue
	{
	public:

		//向上调整法算法
		void AdjustUp(int child)
		{
            //以上为大堆时的向上调整法算法
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[child] > _con[parent])
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}

			}
		}

    //向上调整法算法
		void AdjustDown(int parent)
		{
            //以上为大堆时的向下调整法算法
			int child = 2 * parent + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && _con[child + 1] > _con[child])
				{
					child++;
				}
				if (_con[child] > _con[parent])
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					break;
				}

			}
		}

    //入队列
		void push(const T& x)
		{
			_con.push_back(x);
			AdjustUp(_con.size() - 1);
		}
    //出队列
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);

		}
		

	private:
    //底层为vector
		container _con;
	};
}




注:以上使用到向上和向下调整算法以及堆元素的插入和堆顶元素的删除在之前的数据结构堆章节有详细了解过,在此就不再讲解其的原理

注:如果你还对以上的堆相关知识有疑惑看一看之前的数据结构之《二叉树》(中)

在以上的代码我们是实现了默认情况下priority_queue为大堆时的入队列和出队列,但是我们知道STL内实现的priority_queue还可以通过实现对小堆的操作,那么我们该如何修改我们实现的代码呢?
其实在此就要使用到仿函数来实现队列的底层结构是小堆还是大堆,在此实现两个类less和greater类分别实现大堆和小堆构建,并且在这两个类内都实现()的运算符重载函数

实现代码如下所示:
 

template<class T>
struct less
{
	bool operator()(const T& x1, const T& x2)
	{
		return x1 < x2;
	}
};

template<class T>
struct greater
{
	bool operator()(const T& x1, const T& x2)
	{
		return x1 > x2;
	}
};

在此我们实现的priority_queue的类模板也要加上第三个参数,在此将该参数命名为compare,并且缺省参数为less<T>。实现了类模板的修改之后在函数内部向上调整和向下调整的函数也要作出修改,修改完的代码如下所示:
 

namespace zhz
{
	template<class T>
	struct less
	{
		bool operator()(const T& x1, const T& x2)
		{
			return x1 < x2;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(const T& x1, const T& x2)
		{
			return x1 > x2;
		}
	};


	template<class T, class container = vector<T>, class compare = less<T>>
	class priority_queue
	{
	public:


		void AdjustUp(int child)
		{
			compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[child] > _con[parent])
				if (com( _con[parent],_con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}

			}
		}

		void AdjustDown(int parent)
		{
			compare com;
			int child = 2 * parent + 1;
			while (child < _con.size())
			{
				//if (child + 1 < _con.size() && _con[child + 1] > _con[child])
				if(child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					child++;
				}
				//if (_con[child] > _con[parent])
				if(com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					break;
				}

			}
		}


		void push(const T& x)
		{
			_con.push_back(x);
			AdjustUp(_con.size() - 1);
		}

		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);

		}
		
	private:
		container _con;
	};
}




之后在对以上代码补充一些构造函数以及取队头元素的代码之后就实现了完整的优先队列模拟实现代码

完整代码 

#pragma once
#include<iostream>
#include<vector>
//#include<queue>
using namespace std;


namespace zhz
{
	template<class T>
	struct less
	{
		bool operator()(const T& x1, const T& x2)
		{
			return x1 < x2;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(const T& x1, const T& x2)
		{
			return x1 > x2;
		}
	};


	template<class T, class container = vector<T>, class compare = less<T>>
	class priority_queue
	{
	public:

		priority_queue() = default;

		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			for (int i = _con.size() - 1 - 1 / 2; i <= 0; i--)
			{
				AdjustDown(i);
			}

		}



		void AdjustUp(int child)
		{
			compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[child] > _con[parent])
				if (com( _con[parent],_con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}

			}
		}

		void AdjustDown(int parent)
		{
			compare com;
			int child = 2 * parent + 1;
			while (child < _con.size())
			{
				//if (child + 1 < _con.size() && _con[child + 1] > _con[child])
				if(child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					child++;
				}
				//if (_con[child] > _con[parent])
				if(com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					break;
				}

			}
		}


		void push(const T& x)
		{
			_con.push_back(x);
			AdjustUp(_con.size() - 1);
		}

		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);

		}
		size_t size()
		{
			return _con.size();
		}

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

		const T& top()
		{
			return _con.front();
		}


	private:
		container _con;
	};
}

对以上代码我们来实现以下代码来测试看看是否正确

#include"priority_queue.h"
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;
};

int main()
{
	
	zhz::priority_queue<Date, vector<Date>, zhz::greater<Date>> pq1;
	pq1.push(Date(2014,11,1));
	pq1.push(Date(2024,11,5));
	pq1.push(Date(2024,11,10));
	pq1.push(Date(2014,9,20));
	pq1.push(Date(2024,7,1));

	while (!pq1.empty())
	{
		cout << pq1.top() << " ";
		pq1.pop();
	}


	return 0;
}

 在此我们实现的是日期类对象小堆实现,不断取堆顶元素输出结果如下所示:

通过以上输出结果为升序的就说明我们实现的priority_queue的代码没问题

在此我们还有可能会使用以上的形式来创建日期类对象,那么这时是否能实现正确的排序呢?

int main()
{
	
	zhz::priority_queue<Date*, vector<Date*>, zhz::greater<Date*>> pq1;
	pq1.push(new Date{ 2014, 11, 1 });
	pq1.push(new Date{ 2024, 11, 5 });
	pq1.push(new Date{ 2024, 11, 10 });
	pq1.push(new Date{ 2014, 9, 20 });
	pq1.push(new Date{ 2024, 7, 1 });

	while (!pq1.empty())
	{
		cout << *pq1.top() << " ";
		pq1.pop();
	}



	return 0;
}

我们通过运行该程序就会发现每次输出结果都是不同的,这时为什么呢?

这其实是由于以上是按照地址比较的,但每次new的对象在堆区的存储位置都不一样这就使得会出现每次输出结果都是不同的,那么要怎么才能解决呢?

这就需要我们再显示的相应的类,如下所示:

struct less_dest
{
	bool operator()(const Date* x1, const Date* x2)
	{
		return *x1 < *x2;
	}
};


struct greater_dest
{
	bool operator()(const Date* x1, const Date* x2)
	{
		return *x1 > *x2;
	}
};

这时main函数内的代码就变为以下形式:

int main()
{
	
	zhz::priority_queue<Date*, vector<Date*>,greater_dest> pq1;
	//zhz::priority_queue<Date*, vector<Date*>, zhz::greater<Date*>> pq1;
	pq1.push(new Date{ 2014, 11, 1 });
	pq1.push(new Date{ 2024, 11, 5 });
	pq1.push(new Date{ 2024, 11, 10 });
	pq1.push(new Date{ 2014, 9, 20 });
	pq1.push(new Date{ 2024, 7, 1 });

	while (!pq1.empty())
	{
		cout << *pq1.top() << " ";
		pq1.pop();
	}


	return 0;
}

这时输出结果就是正确的了:


 

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

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

相关文章

网页web无插件播放器EasyPlayer.js点播播放器遇到视频地址播放不了的现象及措施

在数字媒体时代&#xff0c;视频点播已成为用户获取信息和娱乐的重要方式。EasyPlayer.js作为一款流行的点播播放器&#xff0c;以其强大的功能和易用性受到广泛欢迎。然而&#xff0c;在使用过程中&#xff0c;用户可能会遇到视频地址无法播放的问题&#xff0c;这不仅影响用户…

【前端】HTML标签汇总

目录 展示用户信息的标签 1.文本标签 span 2.标题标签 h1~h6 3.竖着布局的标签 div 4.段落标签 p 5.超链接标签 a 5.1跳转至网上的资源 5.2锚点 6.列表标签 6.1有序列表 ol 6.2无序列表 ul 7.图片标签 img 7.1相对路径 7.1.1兄弟关系 7.1.2叔侄关系 7.1.3表兄弟…

xtu oj 加一

样例输入# 2 4 1 2 3 4 4 3 2 4 1样例输出# 3 5 解题思路&#xff1a;最小操作次数一定是把所有数变成数组中最大值max。 1、找最大值&#xff0c;一开始我把max初始值设为0&#xff0c;如果a[i]>max,maxa[i],WA了。又看了一遍题目&#xff0c;发现所有整数的绝对值小于…

Windows10/11开启卓越性能模式 windows开启卓越性能电源模式 工作电脑开启卓越性能模式 电脑开启性能模式

Windows10/11开启卓越性能模式 windows开启卓越性能电源模式 工作电脑开启卓越性能模式 电脑开启性能模式 1、所要用到的激活工具2、开启电脑卓越性能模式Windows11Windows10在电源模式中选择卓越性能模式 3、将系统版本切换为 工作站版本 1、所要用到的激活工具 KMS激活工具(…

人工智能、机器学习与深度学习:层层递进的技术解读

引言 在当今科技快速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;已经成为一个热门话题&#xff0c;几乎渗透到了我们生活的方方面面。从智能手机的语音助手&#xff0c;到自动驾驶汽车&#xff0c;再到医疗诊断中的图像识别&#xff0c;人工智能的应用正在改变我…

光流法(Optical Flow)

一、简介 光流法&#xff08;Optical Flow&#xff09;是一种用于检测图像序列中像素运动的计算机视觉技术。其基于以下假设&#xff1a; 1.亮度恒定性假设&#xff1a;物体在运动过程中&#xff0c;其像素值在不同帧中保持不变。 2.空间和时间上的连续性&#xff1a;相邻像素之…

OkHttp网络请求框架

添加依赖 在 build.gradle 文件中添加 OkHttp 依赖&#xff1a; dependencies {implementation("com.squareup.okhttp3:okhttp:4.10.0") }使用OkHttp发起GET请求 同步请求 public class MainActivity extends AppCompatActivity {// Used to load the okhttptes…

《XGBoost算法的原理推导》12-14决策树复杂度的正则化项 公式解析

本文是将文章《XGBoost算法的原理推导》中的公式单独拿出来做一个详细的解析&#xff0c;便于初学者更好的理解。 我们定义一颗树的复杂度 Ω Ω Ω&#xff0c;它由两部分组成&#xff1a; 叶子结点的数量&#xff1b;叶子结点权重向量的 L 2 L2 L2范数&#xff1b; 公式(…

使用postmain 测试下载文件接口

文章目录 前言使用postmain 测试下载文件接口 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在白嫖的话&#xff0c;那欢迎常来啊!!…

Python函数详解

目录 一、函数的定义 二、函数的特性 三、函数参数 四、返回值 五、文档字符串 六、高级函数 七、偏函数 八、装饰器 总结 在Python编程中&#xff0c;函数是构建程序的基本模块&#xff0c;它提供了一种封装特定任务的方式&#xff0c;使得代码更加模块化、可重用和易…

【贪心算法】贪心算法三

贪心算法三 1.买卖股票的最佳时机2.买卖股票的最佳时机 II3.K 次取反后最大化的数组和4.按身高排序5.优势洗牌&#xff08;田忌赛马&#xff09; 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#…

【Android、IOS、Flutter、鸿蒙、ReactNative 】文本点击事件

Android Studio 版本 Android Java TextView 实现 点击事件 参考 import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.TextView; import android.widget.Toast;public c…

超30万亿消费规模下,低龄VS高龄、他VS她、共性VS个性的市场机会

作者 | NewAgingPro团队 前言 NewAgingPro团队基于多年研究沉淀&#xff0c;发现每个行业都具备为老年人群再次细分的机会&#xff0c;中国的银发经济市场将围绕老年人需求不断往细分场景垂直深耕。具体哪些需求是市场增长的源动力&#xff1f;40岁泛银发群体的需求趋势如何…

贪心算法-汽车加油

这道题目描述了一个汽车旅行场景&#xff0c;需要设计一个有效的算法来决定在哪几个加油站停车加油&#xff0c;以便最小化加油次数。题目给出了汽车加满油后的行驶距离n公里&#xff0c;以及沿途若干个加油站的位置。我们需要找出一个方案&#xff0c;使得汽车能够完成整个旅程…

【动手学电机驱动】STM32-FOC(6)基于 IHM03 的无感方波控制

STM32-FOC&#xff08;1&#xff09;STM32 电机控制的软件开发环境 STM32-FOC&#xff08;2&#xff09;STM32 导入和创建项目 STM32-FOC&#xff08;3&#xff09;STM32 三路互补 PWM 输出 STM32-FOC&#xff08;4&#xff09;IHM03 电机控制套件介绍 STM32-FOC&#xff08;5&…

CNN实现地铁短时客流预测

项目源码获取方式见文章末尾&#xff01; 600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 《------往期经典推荐------》 项目名称 1.【基于CNN-RNN的影像报告生成】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【GAN模型实现二次元头像生成】 4.【CNN模型实现…

SDL打开YUV视频

文章目录 问题1&#xff1a;如何控制帧率&#xff1f;问题2&#xff1a;如何触发退出事件&#xff1f;问题3&#xff1a;如何实时调整视频窗口的大小问题4&#xff1a;YUV如何一次读取一帧的数据&#xff1f; 问题1&#xff1a;如何控制帧率&#xff1f; 单独用一个子线程给主线…

Linux 系统结构

Linux系统一般有4个主要部分&#xff1a;内核、shell、文件系统和应用程序。内核、shell和文件系统一起形成了基本的操作系统结构&#xff0c;它们使得用户可以运行程序、管理文件并使用系统。 1. linux内核 内核是操作系统的核心&#xff0c;具有很多最基本功能&#xff0c;它…

使用vscode 连接linux进行开发

1. 在Vscode中安装扩展功能remote ssh 2. 打开命令窗口 3. 在弹出的命令窗口输入ssh&#xff0c;并从弹出的提示中选择 Add New SSH Host 4. 在弹出的输入窗口中输入类似下面形式的 连接地址&#xff1a; 5. 输入回车后出现下面的对话框&#xff0c;这个对话框是说你要用哪个…

【C++】 C++游戏设计---五子棋小游戏

1. 游戏介绍 一个简单的 C 五子棋小游戏 1.1 游戏规则&#xff1a; 双人轮流输入下入点坐标横竖撇捺先成五子连线者胜同一坐标点不允许重复输入 1.2 初始化与游戏界面 初始化界面 X 输入坐标后 O 输入坐标后 X 先达到胜出条件 2. 源代码 #include <iostream> #i…