stack,queue的模拟实现,deque的模拟实现和小知识点的杂糅

1.stack的模拟实现

由于之前更新了一期链表的模拟实现,其实链表,stack,和queue的模拟实现的方式都差不多.

但这次,有一个新的玩法.

那就是不用自己写,直接去用别人现成的接口就行.

怎么得出这个结论 的?

首先我们可以看到,stack和queue大同小异,而且很多的操作和vector有着非常多的相似之处.

因此,我们可以尝试使用vector来实现stack和queue

namespace ay_s
{
    template <class T, class Container = std::vector<int>>
    class stack
    {
    public:
        void push(const T& x)
        {
            _con.push_back(x);            
        }
        
        void pop()
        {
            assert(_con.empty()!= true);
            _con.pop_back();
        }

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

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

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

        void Print()
        {
            for (auto e : _con)
            {
                std::cout << e << ' ';
            }
            std::cout << std::endl;
        }

        void Check()
        {
           size_t top = _con.back();
           std::cout << top << std::endl;
        }

    private:
        Container _con;
    };

代码非常简短,就直接上代码了.

在这里我的思路就是定义一个模板类,然后第一个传的是参数类型,第二个传的则是类,什么类?

这边我给的是一个缺省值vector,也就是说,如果不传任何东西的话,那他这边的默认值则是vector,而里面的接口则是vector的底层;同样的,如果我们不想用vector,也是可以传一个list来用的,虽然这个时候我们在用的时候没有感受到任何区别,但是他们的底层已经发生了翻天覆地的变化.

2.queue的模拟实现

namespace ay_q
{
    template<class T ,class Container=std:: deque <int>>
    class queue
    {
    public:
        void push(const T& x)
        {
            _con.push_back(x);
        }
        void pop()
        {
           // assert(_con.empty() != true)
            _con.pop_back();
        }
        void Print()
        {
            for (auto e : _con)
            {
                std::cout << e << std::endl;
            }
        }

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

        void Check()
        {
            size_t head = _con.front();
            std::cout << head << std::endl;
        }
        bool empty()
        {
            return _con.empty();
        }
    private:
        Container _con;
    };

而queue的模拟实现,我们调用的则是deque,除了这点跟上面的stack实现的方式不同以外,其它的都是差不多的,在函数里面调用deque的接口就行.

3.优先级队列priority_queue的详解

1.priority_queue的使用

接下来我们来详细谈谈这个priority_queue是一个什么东西.

首先,他被包含在一个叫做deque的文件里面,我们来看看他的接口有哪些.

接口就这几个,是不是发现他和stack特别相似?

int main()
{
	priority_queue<int, vector<int>> pq;
	pq.push(2);
	pq.push(1);
	pq.push(3);
	pq.push(4);
	while (!pq.empty())
	{
		cout << pq.top() << endl;
		pq.pop();
	}
	return 0;
}

上面这段代码运行过后的结果是,4  3  2  1,有没有发现他跟之前的某个数据结构很像?

输入一段乱序的数据,结果输出来的是有序数据,这个不就是堆嘛~

是的,他的底层就是一个大堆

那他默认是一个大堆,我们有没有办法改成一个小堆呢?

首先我们来看看他的所接收的模板参数

他接收的模板参数,第一个就是 类型 ,第二个是一个vector,说明他的缺省值就是vector , 然后第三个却是一个less,而这个less,则就是让这个是大堆的"罪魁祸首"

这点有点奇怪的是,这边传入的less , 却是一个大堆 , 跟之前的完全是相反的 , 那相应的 , 传入一个greater则是一个小堆~

而sort也是可以这样用的,比如

这样输出的也是一个大堆.

通过对比我们可以发现,为什么在上面的那个地方,最后一个传的是greater int<> ; 而在下面这里,我们传的却是一个greater <int>() 呢?

我们通过看文档得知,由于priority_queue是一个类模板,传的第三个参数是一个类型,而sort传的是一个函数参数,则传的是一个对象,传了一个什么对象呢?是一个匿名对象.

2.仿函数

仿函数故名思意,就是仿造一个函数.

对运算符()进行重载.也是第一次见到这个东西,记录一下.

3.priority_queue的模拟实现

接下来,就来模拟实现一下这个函数

前面讲了,这个东西的底层是一个大堆,也就是说,如果你不加以控制,他默认排序方式是大堆,

1.pop,empty,push,size,top的实现.

首先我们仍然是要用一个模板类来实现,而这些接口,是这个东西的最基本的接口,跟之前的stack也是差不多的.

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

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

			_con.pop_back();
			adjust_down(0);
		}

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

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

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

我们可以看到,其实这个东西的设置跟堆是大同小异的,当我们插入一个数据的时候,他会做一个向上调整,如果是小堆,则把小的调整上去;如果是大堆,则会把大堆调整上去.而当我们要弹出数据的时候,对于堆来说,弹出的一般是堆顶,那这个时候,就得让堆顶和堆尾来交换一下位置,然后再进行弹出.

2.adjust_up , adjust_down的模拟实现

这就是之前堆模拟实现的一个一模一样的东西了.

来回顾一下,向上调整算法,首先,向上调整是一个当有数据插入时的一种情况,当有数据插入时,我们此时堆已经是乱的了,需要向上调整,如果这个堆是一个小堆,那么就让这个子节点和父节点来比较,如果是小于父节点,则两个交换,然后子节点就找自己的父节点,父节点则找自己的父节点;而向下调整算法也是一样,这个则用于删除数据,一般来说,删掉的数据就是一个堆顶的数据,把他跟堆尾的数据交换一下,就可以直接删掉了,那此时,堆尾的数据就跑到了堆顶,那这个时候就需要用到向下调整了.来说一下这个的思路,首先,下面的两个子节点来比较大小,一般来说,都会假设左节点(child)大,如果他比右节点小,则child++,然后就可以用这个较大的子节点跟当前父节点来向下调整了.比较完了就子节点找自己的子节点,父节点找自己的子节点

child = parent*2 +1

parent = (child - 1) / 2

以上是计算子节点和父节点的公式

		void adjust_up(size_t child)
		{
			Comp com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[child], _con[parent]))
				{
					std::swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}

		}

		void adjust_down(size_t parent)
		{
			Comp com;
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && com(_con[child + 1], _con[child]))
				{
					child++;
				}
				if (com(_con[child] , _con[parent]))
				{
					std::swap(_con[child], _con[parent]);
					parent = child;
					child = child * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

3.任意的控制大堆和小堆

priority_queue这个函数的第三个参数是传一个greater来控制小堆,传一个less来控制大堆,在这里,我们可以通过使用仿函数来控制.

	template<class T>
	class less
	{
	public:
		bool operator() (const T& a, const T& b)
		{
			return a < b;
		}
	};

	template<class T>
	class greater
	{
	public:
		bool operator() (const T& a, const T& b)
		{
			return a > b;
		}
	};

是弄出来了,那具体该怎么控制呢?我们都知道,决定大小堆的,是在调整函数中的交换部分,这个地方的判断语句就是如果 a > b 或者 a < b ,那我们由此可以用less 和greater 来创造一个变量,然后用这个反函数来对其进行比较,如果需要用小堆,我们就传less进去,如果是大堆,我们就传一个greater进去

	template<class T, class Container = std::vector<T> , class Comp = greater<int>>
	class priority_queue
	{
	public:
		
		void adjust_up(size_t child)
		{
			Comp com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[child], _con[parent]))
				{
					std::swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}

		}

		void adjust_down(size_t parent)
		{
			Comp com;
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 > _con.size() && _con[child + 1] > _con[child])
				{
					child++;
				}
				if (com(_con[child] , _con[parent]))
				{
					std::swap(_con[child], _con[parent]);
					parent = parent * 2 + 1;
					child = child * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

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

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

			_con.pop_back();
			adjust_down(0);
		}

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

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

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

	private:
		Container _con;
	};
}

这个也就是整体的代码.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值