栈的概念
栈是⼀种只允许在⼀端进⾏数据插⼊和删除操作的线性表。
- 进⾏数据插⼊或删除的⼀端称为栈顶,另⼀端称为栈底。不含元素的栈称为空栈。
- 进栈就是往栈中放⼊元素,出栈就是将元素弹出栈顶。
栈其实是⼀个⽐较简单的数据结构。学习的重点在于⽤栈去解决问题,这也是难点
如果定义了⼀个栈结构,那么添加和删除元素只能在栈顶进⾏。不能随意位置添加和删除元素,这是栈这个数据结构的特性,也是规定
栈的模拟实现
创建
- 本质还是线性表,因此可以创建⼀个⾜够⼤的数组,充当栈结构
- 再定义⼀个变量 n ,⽤来记录栈中元素的个数,同时还可以标记栈顶的位置
const int N = 1e6 + 10;
int n;
int stk[N];
进栈
这⾥依旧舍弃下标为0 的位置,有效元素从1开始记录。
进栈操作,那就把元素放在栈顶位置即可
// 进栈
void push(int x)
{
stk[++n] = x;
}
时间复杂度:
显然是O(1)
出栈
不⽤真的删除元素,只⽤将元素个数减1 ,就相当于删除栈顶元素
// 出栈
void pop()
{
n--;
}
时间复杂度:
显然是O(1)
栈顶元素
查询栈顶元素。
这⾥要注意,因为栈特殊的规定,不⽀持遍历整个栈中的元素。因此,需要查找栈中元素的时候,只能查找到栈顶元素
// 栈顶元素
int top()
{
return stk[n];
}
时间复杂度:
显然是O(1)
判空
判断栈是否为空
// 判空
bool empty()
{
return n == 0;
}
时间复杂度:
显然是O(1)
有效元素的个数
// 栈中元素个数
int size()
{
return n;
}
时间复杂度:
显然是O(1)
所有测试代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// 创建栈
int stk[N], n;
// 进栈 - 本质就是顺序表⾥⾯的尾插
void push(int x)
{
stk[++n] = x;
}
// 出栈 - 顺序表的尾删操作
void pop()
{
n--;
}
// 查询栈顶元素
int top()
{
return stk[n];
}
// 判断是否为空
bool empty()
{
return n == 0;
}
// 查询有效元素的个数
int size()
{
return n;
}
int main()
{
for(int i = 1; i <= 10; i++)
{
push(i);
}
// 当栈不为空的时候
while(size()) // while(!empty())
{
cout << top() << endl;
pop();
}
return 0;
}
stack
创建
stack<T> st;
T 可以是任意类型的数据
size/empty
- size :返回栈⾥实际元素的个数;
- empty :返回栈是否为空。
时间复杂度:O(1)
push/pop
- push :进栈;
- pop :出栈。
时间复杂度:O(1)
top
- top :返回栈顶元素,但是不会删除栈顶元素。
时间复杂度:O(1)
所有测试代码
#include <iostream>
#include <stack>
using namespace std;
int main()
{
stack<int> st;
// 先讲 1~10 进栈
for(int i = 1; i <= 10; i++)
{
st.push(i);
}
while(st.size()) // !st.empty()
{
cout << st.top() << endl;
st.pop();
}
return 0;
}
队列的概念
队列也是⼀种访问受限的线性表,它只允许在表的⼀端进⾏插⼊操作,在另⼀端进⾏删除操作
- 允许插⼊的⼀端称为队尾,允许删除的⼀端称为队头。
- 先进⼊队列的元素会先出队,故队列具有先进先出(First In First Out)的特性
队列的模拟实现
创建
- ⼀个⾜够⼤的数组充当队列;
- ⼀个变量 h 标记队头元素的前⼀个位置;
- ⼀个变量 t 标记队尾元素的位置。
两个变量 (h, t] 是⼀种左开右闭的形式,这样设定纯属个⼈喜好,因为后续的代码写着⽐较舒服
当然,也可以 h 标记队头元素的位置。只要能控制住代码不出现 bug ,想怎么实现就怎么实现
const int N = 1e6 + 10;
int h, t; // 队头指针,队尾指针
int q[N]; // 队列
⼊队
- 将x这个元素放⼊到队列中。
注意,我们这⾥依旧从下标为1的位置开始存储有效元素
// ⼊队
void push(int x)
{
q[++t] = x;
}
时间复杂度:
显然是O(1)
出队
- 删除队头元素
// 出队
void pop()
{
h++;
}
队头
- 返回队列⾥⾯的第⼀个元素。
这⾥要注意,不是h所指的位置,⽽是h所指的下⼀个位置
// 队头元素
int front()
{
return q[h + 1];
}
时间复杂度:
显然是O(1)
队尾
- 返回队列⾥⾯的最后⼀个元素
// 队尾元素
int back()
{
return q[t];
}
时间复杂度:
显然是O(1)
判空
- 判断队列是否为空
// 队列是否为空
bool empty()
{
return t == h;
}
时间复杂度:
显然是O(1)
元素个数
- 返回队列⾥⾯元素的个数
// 队列的⼤⼩
int size()
{
return t - h;
}
时间复杂度:
显然是O(1)
所有测试代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// 创建
int q[N], h, t;
// ⼊队
void push(int x)
{
q[++t] = x;
}
// 出队
void pop()
{
h++;
}
// 查询队头元素
int front()
{
return q[h + 1];
}
// 查询队尾元素
int back()
{
return q[t];
}
// 判断是否为空
bool empty()
{
return h == t;
}
// 有效元素的个数
int size()
{
return t - h;
}
int main()
{
// 测试
for(int i = 1; i <= 10; i++)
{
push(i);
}
while(size()) // while(!empty())
{
cout << front() << " " << back() << endl;
pop();
}
return 0;
}
queue
创建
queue<T> q;
T可以是任意类型的数据
size/empty
- size :返回队列⾥实际元素的个数;
- empty :返回队列是否为空。
时间复杂度:O(1)
push/pop
- push :⼊队;
- pop :出队。
时间复杂度:O(1)
front/back
- front :返回队头元素,但不会删除;
- back :返回队尾元素,但不会删除。
时间复杂度:O(1)
所有测试代码
#include <iostream>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
int main()
{
// 测试 queue
queue<PII> q;
for(int i = 1; i <= 10; i++)
{
q.push({i, i * 10});
}
while(q.size()) // while(!q.empty())
{
auto t = q.front();
q.pop();
cout << t.first << " " << t.second << endl;
}
return 0;
}