目录
链表
单链表
概念:单向链表是最基本的链表结构,由data数据和next节点组成。data指的是存放的数据,而next则指向下一个节点。链表一般还会有一个head表头,head指向链表的第一个节点,因为我们添加完所以节点后链表会指向链表尾部,而使用一般从表头开始使用,所以需要head
单链表的基本操作:
初始化 init 头节点的下标指向-1,目前使用的节点的下标指向头节点下标的下一个
将x插到头结点 add_to_head 目前节点下标等于头节点下标,头节点指向下一个
将x插到下标是k的后面add 目前节点下标的下一个指向k,k的下一个指向插入节点
将下标是k的点后面的点删掉remove k的后面一个值直接指向k后面的后面一个数
例题:Acwing 826 单链表
实现一个单链表,链表初始为空,支持三种操作:
- 向链表头插入一个数;
- 删除第 k 个插入的数后面的数;
- 在第 k 个插入的数后插入一个数。
数组做法
#include<iostream>
using namespace std;
const int N=1e5+5;
//head 表示头结点的下标
//e[i] 表示节点i的值
//ne[i] 表示节点i的next指针是多少,下标
//idx 存储当前已经用到了哪个点
int head,e[N],ne[N],idx;
//初始化
void init(){
head=-1;
idx=0;
}
//将x插到头结点
void add_to_head(int x){
e[idx]=x,ne[idx]=head,head=idx++;
}
//将x插到下标是k的后面
void add(int k,int x){
e[idx]=x,ne[idx]=ne[k],ne[k]=idx++;
}
//将下标是k的点后面的点删掉
void remove(int k){
ne[k]=ne[ne[k]];
}
int main(){
int m;
cin>>m;
init();
while(m--){
int k,x;
char op;
cin>>op;
if(op=='H'){
cin>>x;
add_to_head(x);
}
else if(op=='D'){
cin>>k;
if(!k) head=ne[head];
else remove(k-1);
}
else{
cin>>k>>x;
add(k-1,x);
}
}
for(int i=head;i!=-1;i=ne[i]){
cout<<e[i]<<' ';
}
return 0;
}
答疑:
双链表
双链表的基本操作:
初始化:左端点左边是1,右端点左边是0
在节点k右边插入x: 先让k指针右边的点的左边指向新的点,k指针右边指向新的点(此步k指 针右边的点发生改变)
在节点k的左边插入x: 调用 add[ l[k] , x] ,在k节点的左边一个点的后面插入等价于在k节点的 左边插入
删除第k个点:此节点的右边直接等于左边,左边直接等于右边
例题 Acwing 827 双链表
实现一个双链表,双链表初始为空,支持 5 种操作:
- 在最左侧插入一个数;
- 在最右侧插入一个数;
- 将第 k 个插入的数删除;
- 在第 k 个插入的数左侧插入一个数;
- 在第 k 个插入的数右侧插入一个数
数组做法
#include<iostream>
using namespace std;
const int N=1e5+5;
//e[i] 存储当前指针的值
//l[i] 存储i节点左边的下标
//r[i] 存储i节点右边的下标
//idx 记作当前节点的下标
int n,m;
int e[N],l[N],r[N],idx;
//初始化
void init(){
r[0]=1,l[1]=0; //0:head 1:tail
}
//在节点a的右边插入一个数x
void insert(int a,int x){
e[idx]=x;
l[idx]=a,r[idx]=r[a];
l[r[a]]=idx,r[a]=idx++;
}
//删除节点a
void remove(int a){
l[r[a]]=l[a];
r[l[a]]=r[a];
}
int main(){
cin>>m;
init();
idx=2;
while(m--){
string op;
cin>>op;
int k,x;
if(op=="L"){
cin>>x;
insert(0,x);
}
else if(op=="R"){
cin>>x;
insert(l[1],x);
}
else if(op=="D"){
cin>>k;
remove(k+1);
}
else if(op=="IL"){
cin>>k>>x;
insert(l[k+1],x);
}
else{
cin>>k>>x;
insert(k+1,x);
}
}
for(int i=r[0];i!=1;i=r[i]){
cout<<e[i]<<' ';
}
return 0;
}
stl做法
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5+5;
int main(){
int n;
cin>>n;
list<int> lis;
unordered_map<int,list<int>::iterator> mp;
int num=0;
while(n--){
string op;
cin>>op;
if(op=="IL"){
int k,x;
cin>>k>>x;
mp[++num]=lis.insert(mp[k],x);
}
else if(op=="IR"){
int k,x;
cin>>k>>x;
auto nxt=next(mp[k]);
mp[++num]=lis.insert(nxt,x);
}
else if(op=="L"){
int x;
cin>>x;
mp[++num]=lis.insert(lis.begin(),x);
}
else if(op=="R"){
int x;
cin>>x;
mp[++num]=lis.insert(lis.end(),x);
}
else if(op=="D"){
int k;
cin>>k;
lis.erase(mp[k]);
mp.erase(k);
}
}
for(auto i:lis){
cout<<i<<' ';
}
cout<<"\n";
return 0;
}
题目
队列安排 https://www.luogu.com.cn/problem/P1160
abc 344 E - Insert or Erase https://atcoder.jp/contests/abc344/tasks/abc344_e
牛客周赛31第4题 https://ac.nowcoder.com/acm/problem/267787
洛谷 单链表 https://www.luogu.com.cn/problem/B3631
栈
栈:先进后出 ,从队尾进,队尾出,队顶为队尾
栈的基本操作
数组模拟栈的基本操作
插入 stk[++tt]=x;
弹出 tt--;
判断栈是否为空 if(tt>0) not empty
栈顶 stk[tt];
stack <int> st 基本操作
st.push() 插入一个元素
st.top() 取最后一个插入元素
st.pop() 删除最后一个插入元素
st.empty() 判断栈是否为空
栈的数组模拟做法
//数组模拟做法
#include<iostream>
using namespace std;
const int N=1e5+5;
int m;
int st[N],tt;
int main(){
cin>>m;
while(m--){
string op;
int x;
cin>>op;
if(op=="push"){
cin>>x;
st[++tt]=x;
}
else if(op=="pop"){
tt--;
}
else if(op=="empty"){
cout<<(tt ? "NO" : "YES")<<endl;
}
else cout<<st[tt]<<endl;
}
return 0;
}
栈的stl做法
//stl做法
#include<iostream>
#include<stack>
using namespace std;
stack<int> st;
int main(){
string op;
int x;
int m;
cin>>m;
while(m--){
cin>>op;
if(op=="push"){
cin>>x;
st.push(x);
}
else if(op=="pop"){
st.pop();
}
else if(op=="empty"){
if(st.empty()) cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
else if(op=="query"){
cout<<st.top()<<endl;
}
}
return 0;
}
题目
Acwing 828 模拟栈
队列
队列:先进先出,从队尾进,队头出,队顶为队头
队列的基本操作
数组模拟基本操作:
push :入队,从队尾入队
pop : 队头出队,队头下标直接向后移一位
empty: 队尾大于队头即不为空
query:查询队头元素
队列的stl做法:
push:从队尾插入一个元素
pop:从队头出队
empty:判空
query:查询队头元素
队列的数组模拟做法
#include<iostream>
using namespace std;
const int N=1000010;
int n,k;
int a[N],q[N]; //a[i]存储每个值,q[i]存储下标
int main(){
cin>>n>>k;
for(int i=0;i<n;i++){
cin>>a[i];
}
//求最大值
int hh=0,tt=-1; //保证队头在队尾的右边
for(int i=0;i<n;i++){
if(hh<=tt&&i-k+1>q[hh]) hh++; //i向前走一步,长度大于k,便减一
while(hh<=tt&&a[q[tt]]>=a[i]) tt--; //队尾与新值比较,新值较大,队尾出队
q[++tt]=i; //队尾下标加一 ,即删除
if(i>=k-1) cout<<a[q[hh]]<<' '; //结束的时候,空栈时,队头为窗口的最小值
}
puts("");
//求最小值。同理
hh=0,tt=-1;
for(int i=0;i<n;i++){
if(hh<=tt&&i-k+1>q[hh]) hh++;
while(hh<=tt&&a[q[tt]]<=a[i]) tt--;
q[++tt]=i;
if(i>=k-1) cout<<a[q[hh]]<<' ';
}
puts("");
return 0;
}
队列的stl做法
#include <iostream>
#include <cstring>
#include <algorithm>
#include <deque>
using namespace std;
const int N = 1000010;
int a[N];
int main()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i ++ ) cin >> a[i];//读入数据
deque<int> q;
for(int i = 1; i <= n; i++)
{
while(q.size() && q.back() > a[i]) //新进入窗口的值小于队尾元素,则队尾出队列
q.pop_back();
q.push_back(a[i]);//将新进入的元素入队
if(i - k >= 1 && q.front() == a[i - k])//若队头是否滑出了窗口,队头出队
q.pop_front();
if(i >= k)//当窗口形成,输出队头对应的值
cout << q.front() <<" ";
}
q.clear();
cout << endl;
//最大值亦然
for(int i = 1; i <= n; i++)
{
while(q.size() && q.back() < a[i]) q.pop_back();
q.push_back(a[i]);
if(i - k >= 1 && a[i - k] == q.front()) q.pop_front();
if(i >= k) cout << q.front() << " ";
}
}
题目
acwing 154 滑动窗口
Acwing 829 模拟栈