目录
- [1. stack与queue的使用练习](#1. stack与queue的使用练习)
-
- [1.1 stack的常用接口(栈)](#1.1 stack的常用接口(栈))
- [1.2 queue常用接口(队列)](#1.2 queue常用接口(队列))
- [1.3 priority_queue的常用接口(堆)](#1.3 priority_queue的常用接口(堆))
- [2. 容器适配器](#2. 容器适配器)
-
- [2.1 栈的实现](#2.1 栈的实现)
- [2.2 队列的实现](#2.2 队列的实现)
- [3. 堆(priority_queue)的实现](#3. 堆(priority_queue)的实现)
1. stack与queue的使用练习
1.1 stack的常用接口(栈)
- push(尾插)
- pop(尾删)
- top(取栈顶)
- empty(判空)
- size(栈的长度)
1. 最小栈
- 题目信息:
- 题目链接:
最小栈- 一个栈存数据,一个栈存最小值
<1> 每次插入一个数据都进行判断,并将当前栈的最小值插入
<2> 优化1:当插入数据大于当前最小值时不插入,删除数据栈中数据时,调整最小栈当删除值等于当前最小值再删除
<3> 优化2:最小栈中存复合类型数据,最小值与最小值的计数
cpp
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. 栈的弹出压入序列
- 题目信息:
- 题目链接:
栈的弹出压入序列- 思路:创建一个栈来模拟弹出插入过程
cpp
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> 当遍历到运算符时:
一,若栈为空将运算符直接入栈
二,若栈不为空,当前运算符优先级高于栈顶运算符,将此运算符入栈;若当前运算符优先级低于栈顶运算符,将栈顶运算符输出。- 当运算表达式中存在括号时,我们遇到括号就进行递归,再开一个栈
- switch...case...语句只能使用整形家族的数据做参数,atoi(字符串转int),atod(字符串转double)
- 题目信息:
- 题目链接:
逆波兰表达式求值- 思路:后缀表达式运算方式,创建一个栈存储运算数,当遍历到运算符时,取出两个栈顶元素进行相应运算之后再入栈,重复上述步骤,直至遍历完整个后缀表达式。
cpp
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();
}
};
- 补充:C++两个队列实现栈
1.2 queue常用接口(队列)
- push(尾插)
- pop(头删)
- front(队首元素)
- back(队尾元素)
- empty(判空)
- size(队列长度)
1. 二叉树层序遍历
- 题目信息:
- 题目链接:
二叉树的层序遍历- 思路:队列存储,计数变量
cpp
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的常用接口(堆)
- push
- pop(删除堆顶元素)
- top(返回堆顶元素)
- empty(判空)
- size(返回堆的长度)
补充:默认为大堆
1. 堆排序
cpp
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;
- 补充:此种堆排的空间复杂度很高O(n),推荐在vector容器中直接建堆堆排
2. TopK算法
cpp
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;
- 取数据头部k个数据建立一个小堆,在从第k + 1个数开始遍历,当数据大于堆顶数据时,交换二者,制止遍历完整个数组。
- 建堆的时间复杂度为O(N),堆的一次向下调整时间复杂度为O( l o g N log^N logN),TopK算法的时间复杂度为K + (N - K) l o g K log^K logK
2. 容器适配器
- 在数据结构的学习中,我们知道栈,队列,堆等数据结构,底层数据存储的方式上仍使用的是实现过链表顺序表等基础结构,只是对外的接口不同,仅支持规定的插入删除操作,且不支持遍历。
- 因此,再去专门实现一份重复性极高的代码于效率上是极低的也没有必要,我们将已经实现过的顺序表链表等数据结构进行封装,只对外开放特定的接口,从而来到达栈,队列等数据结构的效果。这里采用模板参数的方式进行具体实现。
- const迭代器增加模板参数的原因为,只有一个运算符重载的返回值不同,其他参数与返回值都相同
2.1 栈的实现
- 自定义类型的成员变量会自动调用自己的默认成员函数
cpp
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 队列的实现
cpp
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)的实现
cpp
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;
};
- 堆的数据结构可以细分为大堆与小堆,面对不同的使用场景我们使用的堆也不同,正常情况下,我们更改堆的模式只能通过手动调整源码,更改比较符号的方式。
- 手动调整的方式使得堆的切换十分麻烦且失去了应用价值,大小堆分别实现又增加了代码的冗余性,这里我们采用仿函数或回调函数的方式来解决这一问题。
- 回调函数,C语言中函数也是有指针的,我们可以通过传递函数指针类型的参数对象,从而达到在一个函数内部通过指针调用其他需要函数的效果。(函数指针类型相同,内部实现不同,返回值不同)
- 仿函数是一个类,是通过重载operator()运算符来达到函数调用的表现形式与效果。