C++初阶:容器适配器stack与queue

本文详细介绍了C++中stack、queue和priority_queue的基本接口和常见使用场景,包括最小栈、栈的弹出压入序列、逆波兰表达式求值、堆排序以及TopK算法。此外,还讨论了容器适配器如何封装基础数据结构以实现高级数据结构。
摘要由CSDN通过智能技术生成

1. stack与queue的使用练习

1.1 stack的常用接口(栈)

  1. push(尾插)
  2. pop(尾删)
  3. top(取栈顶)
  4. empty(判空)
  5. size(栈的长度)

1. 最小栈

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    最小栈
  3. 一个栈存数据,一个栈存最小值
    <1> 每次插入一个数据都进行判断,并将当前栈的最小值插入
    <2> 优化1:当插入数据大于当前最小值时不插入,删除数据栈中数据时,调整最小栈当删除值等于当前最小值再删除
    <3> 优化2:最小栈中存复合类型数据,最小值与最小值的计数
class MinStack 
{
public:
    //会调用成员变量的默认构造
    MinStack() 
    {}
    
    void push(int val) 
    {
        if(minst.empty() || val <= minst.top())
        {
            minst.push(val);
        }

        st.push(val);
    }
    
    void pop() 
    {
        if(st.top() == minst.top())
        {
            minst.pop();
        }

        st.pop();
    }
    
    int top() 
    {
        return st.top();
    }
    
    int getMin() 
    {
        return minst.top();
    }

    stack<int> st;
    stack<int> minst;
};

2. 栈的弹出压入序列

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    栈的弹出压入序列
  3. 思路:创建一个栈来模拟弹出插入过程
class Solution 
{
public:
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) 
    {
        //用栈模拟
        //先入栈,再判断
        //1.栈中数据与出栈序列相同,出栈,出栈序列++(循环进行)
        //2. 不同,接着入栈
        //入栈序列结束后,出栈序列遍历完,没遍历完
        stack<int> st;
        int pushi = 0;
        int popi = 0;
        while(pushi < pushV.size())
        {
            st.push(pushV[pushi]);
            pushi++;
			
			//注意判断条件与越界可能
            while(!st.empty() && popV[popi] == st.top())
            {
                st.pop();
                popi++;
            }
        }

        if(popi == popV.size())
        {
            return true;
        }

        return false;
    }
};

3. 逆波兰表达式求值

  1. 逆波兰表达式,即后缀表达式,表示方式为运算符在运算数之后。
  2. 中缀表达式转后缀表达式的方法:
    遍历中缀表达式,创建一个栈存储运算符
    <1> 当遍历到运算数时,直接将运算数输出
    <2> 当遍历到运算符时:
    一,若栈为空将运算符直接入栈
    二,若栈不为空,当前运算符优先级高于栈顶运算符,将此运算符入栈;若当前运算符优先级低于栈顶运算符,将栈顶运算符输出。
  3. 当运算表达式中存在括号时,我们遇到括号就进行递归,再开一个栈
  4. switch…case…语句只能使用整形家族的数据做参数,atoi(字符串转int),atod(字符串转double)
  5. 题目信息:
    在这里插入图片描述
  6. 题目链接:
    逆波兰表达式求值
  7. 思路:后缀表达式运算方式,创建一个栈存储运算数,当遍历到运算符时,取出两个栈顶元素进行相应运算之后再入栈,重复上述步骤,直至遍历完整个后缀表达式。
class Solution
{
public:
	int evalRPN(vector<string>& tokens)
	{
		stack<int> num_st;;

		for (auto it : tokens)
		{
            if(it != "+" && it != "-" && it != "*" && it != "/")
            {
                num_st.push(atoi(it.c_str()));
            }
            else
            {
                int right = num_st.top();
				num_st.pop();
				int left = num_st.top();
				num_st.pop();
                switch (it[0])
                {
                    case '+':
                        num_st.push(left + right);
                        break;
                    case '-':
                        num_st.push(left - right);
                        break;
                    case '*':
                        num_st.push(left * right);
                        break;
                    case '/':
                        num_st.push(left / right);
                        break;
                }
            }
		}

		return num_st.top();
	}
};
  1. 补充:C++两个队列实现栈

1.2 queue常用接口(队列)

  1. push(尾插)
  2. pop(头删)
  3. front(队首元素)
  4. back(队尾元素)
  5. empty(判空)
  6. size(队列长度)

1. 二叉树层序遍历

  1. 题目信息:
    在这里插入图片描述
  2. 题目链接:
    二叉树的层序遍历
  3. 思路:队列存储,计数变量
class Solution 
{
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        vector<vector<int>> ret;
        queue<TreeNode*> travel;
        int levelSize = 0;

        if(root == NULL)
        {
            return ret;
        }

        travel.push(root);
        levelSize++;

        while(!travel.empty())
        {
            vector<int> part;
            while(levelSize)
            {
                TreeNode* node = travel.front();
                part.push_back(node->val);
                travel.pop();
                levelSize--;
                if(node->left)
                    travel.push(node->left);
                if(node->right)
                    travel.push(node->right);
            }
            ret.push_back(part);
            levelSize = travel.size();
        }

        return ret;
    }
};

1.3 priority_queue的常用接口(堆)

  1. push
  2. pop(删除堆顶元素)
  3. top(返回堆顶元素)
  4. empty(判空)
  5. size(返回堆的长度)
    补充:默认为大堆

1. 堆排序

priority_queue<int> heap;
heap.push(10);
heap.push(3);
heap.push(6);
heap.push(2);
heap.push(1);

while (!heap.empty())
{
	cout << heap.top() << " ";
	heap.pop();
}
cout << endl;
  1. 补充:此种堆排的空间复杂度很高O(n),推荐在vector容器中直接建堆堆排

2. TopK算法

int arr[] = { 10,3,2,5,6,8,9,4,1,7,11 };
int k = 3;
//greater建小堆,less建大堆
priority_queue<int, vector<int>, greater<int>> heap(arr, arr + k);
while (k < sizeof(arr) / sizeof(arr[0]))
{
	if (arr[k] > heap.top())
	{
		heap.pop();
		heap.push(arr[k]);
	}
	k++;
}

while (!heap.empty())
{
	cout << heap.top() << " ";
	heap.pop();
}
cout << endl;
  1. 取数据头部k个数据建立一个小堆,在从第k + 1个数开始遍历,当数据大于堆顶数据时,交换二者,制止遍历完整个数组。
  2. 建堆的时间复杂度为O(N),堆的一次向下调整时间复杂度为O( l o g N log^N logN),TopK算法的时间复杂度为K + (N - K) l o g K log^K logK

2. 容器适配器

  1. 在数据结构的学习中,我们知道栈,队列,堆等数据结构,底层数据存储的方式上仍使用的是实现过链表顺序表等基础结构,只是对外的接口不同,仅支持规定的插入删除操作,且不支持遍历。
  2. 因此,再去专门实现一份重复性极高的代码于效率上是极低的也没有必要,我们将已经实现过的顺序表链表等数据结构进行封装,只对外开放特定的接口,从而来到达栈,队列等数据结构的效果。这里采用模板参数的方式进行具体实现。
  3. const迭代器增加模板参数的原因为,只有一个运算符重载的返回值不同,其他参数与返回值都相同

2.1 栈的实现

  1. 自定义类型的成员变量会自动调用自己的默认成员函数
template<class T, class Container = vector<T>>
class stack
{
public:
	void push(const T& val)
	{
		con.push_back(val);
	}

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

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

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

	int size()
	{
		return con.size();
	}

	bool empty()
	{
		return con.size() == 0;
	}

private:
	Container con;
};

2.2 队列的实现

template<class T, class Container = list<T>>
class queue
{
public:
	void push(T val)
	{
		con.push_back(val);
	}

	void pop()
	{
		con.pop_front();
	}

	T& top()
	{
		return con.front();
	}

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

	int size()
	{
		return con.size();
	}

	bool empty()
	{
		return con.size() == 0;
	}

private:
	Container con;
};

3. 堆(priority_queue)的实现

template<class T>
class Greater
{
public:
	bool operator()(const T& left, const T& right)
	{
		return left > right;
	}
};

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

//支持[]访问
template<class T, class Container = vector<T>, class Compare = Greater<T>>
class pirority_queue
{
public:
	int size()
	{
		return con.size();
	}

	bool emtpy()
	{
		return con.size() == 0;
	}

	T& top()
	{
		return con.front();
	}

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

	void Adjustup()
	{
		int child = con.size() - 1;
		int parent = (child - 1) / 2;

		while (child > 0)
		{
			if (comp(con[parent], con[child]))
			{
				std::swap(con[child], con[parent]);
			}

			child = parent;
			parent = (child - 1) / 2;
		}
	}

	//向上调整
	void push(T val)
	{
		con.push_back(val);
		Adjustup();
	}

	void Adjustdown()
	{
		size_t parent = 0;
		size_t child = parent * 2 + 1;
		while (child < con.size())
		{
			if (child + 1 < con.size() && comp(con[child], con[child + 1]))
			{
				child++;
			}

			if (comp(con[parent], con[child]))
			{
				std::swap(con[parent], con[child]);
			}
			else
			{
				break;
			}

			parent = child;
			child = parent * 2 + 1;
		}
	}

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

private:
	Container con;
	Compare comp;
};
  1. 堆的数据结构可以细分为大堆与小堆,面对不同的使用场景我们使用的堆也不同,正常情况下,我们更改堆的模式只能通过手动调整源码,更改比较符号的方式。
  2. 手动调整的方式使得堆的切换十分麻烦且失去了应用价值,大小堆分别实现又增加了代码的冗余性,这里我们采用仿函数或回调函数的方式来解决这一问题。
  3. 回调函数,C语言中函数也是有指针的,我们可以通过传递函数指针类型的参数对象,从而达到在一个函数内部通过指针调用其他需要函数的效果。(函数指针类型相同,内部实现不同,返回值不同)
  4. 仿函数是一个类,是通过重载operator()运算符来达到函数调用的表现形式与效果。
  5. 回调函数使用的函数指针其本质只是一个指针,我们将其作为参数使用时还需要传递所需要函数的确定地址,当作为类成员时我们需要专门为其编写构造函数,调用时也需要传参,而仿函数其每个对象的内部都具有operator()重载。
  6. 函数指针可以作为函数模板的类型参数,在函数的内部实现中通过传参调用对应函数。(库函数sort)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值