目录
1、单链表
实现单链表实际上有两种方式
第一种是使用结构体+指针,称为动态链表,在面试中是可以使用的,但是在算法题中因为new太慢了,所以不推荐使用
第二种是使用数组模拟,称为静态链表,我们这里用的就是静态链表
静态链表有几个参数:
数组e表示某个点的值
数组ne表示某个点的next指针
head是头指针,链表为空时用-1
idx表示当前使用到了哪一个点
#include<iostream>
#include<stdio.h>
using namespace std;
const int N = 1e5 + 10;
int e[N], ne[N], head, idx, n;
void init()
{
head = -1;
idx = 0;
}
void push_front(int x)
{
e[idx] = x;
ne[idx] = head;
head = idx;
idx++;
}
void insert(int k, int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}
void erase(int k)
{
ne[k] = ne[ne[k]];
}
int main()
{
init();
scanf("%d", &n);
while(n--)
{
int k, x;
char ch;
cin>>ch; // 不要使用scanf,因为缓冲区
// while(scanf(" %c", &ch) != 1 || ch == ' '); 若要使用scanf,就这样
if(ch == 'H')
{
scanf("%d", &x);
push_front(x);
}
else if(ch == 'D')
{
scanf("%d", &k);
if(!k) head = ne[head];
else erase(k - 1);
}
else
{
scanf("%d %d", &k, &x);
insert(k - 1, x);
}
}
for(int i = head;i != -1;i = ne[i]) printf("%d ", e[i]);
printf("\n");
return 0;
}
注意:
(1)insert中是将x插到下标为k的结点的后面
(2)删除第k个插入,就是删除下标为k-1的,因为第1个插入,下标为0
(3)对于头删需要特殊处理,否则head没变
2、双链表
(1)双链表是让下标为0的点为头结点,下标为1的点为尾结点。刚初始化完就已经有2个结点了,所以idx是从2开始的
(2)因为idx是从2开始的,所以我们在处理第k个插入时,传参时下标时k + 1
#include<iostream>
#include<string>
using namespace std;
const int N = 1e5 + 10;
int e[N], l[N], r[N], idx, m;
void init()
{
r[0] = 1,l[1] = 0;
idx = 2;
}
void insert_r(int k, int x)
{
e[idx] = x;
l[idx] = k;
r[idx] = r[k];
l[r[k]] = idx;
r[k] = idx++;
}
void insert_l(int k, int x)
{
/*e[idx] = x;
r[idx] = k;
l[idx] = l[k];
r[l[k]] = idx;
l[k] = idx++;*/
insert_r(l[k], x); // 后面3个直接复用insert_r即可
}
void push_front(int x)
{
/*e[idx] = x;
l[idx] = 0;
r[idx] = r[0];
l[r[0]] = idx;
r[0] = idx++;*/
insert_r(0, x);
}
void push_back(int x)
{
/*e[idx] = x;
r[idx] = 1;
l[idx] = l[1];
r[l[1]] = idx;
l[1] = idx++;*/
insert_l(1, x);
}
void erase(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}
int main()
{
init();
scanf("%d", &m);
while(m--)
{
string ch;cin>>ch;
if(ch == "L")
{
int x;scanf("%d", &x);
push_front(x);
}
else if(ch == "R")
{
int x;scanf("%d", &x);
push_back(x);
}
else if(ch == "D")
{
int k;scanf("%d", &k);
erase(k + 1);
}
else if(ch == "IL")
{
int x, k;scanf("%d%d", &k, &x);
insert_l(k + 1, x);
}
else
{
int x, k;scanf("%d%d", &k, &x);
insert_r(k + 1, x);
}
}
for(int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
return 0;
}
3、栈
3.1 模拟栈
tt是栈顶下标,初始化tt为-1
#include<iostream>
#include<string>
using namespace std;
const int N = 1e5 + 10;
int st[N], tt;
void init()
{
tt = -1;
}
void push(int x)
{
st[++tt] = x;
}
void pop()
{
--tt;
}
bool empty()
{
return tt == -1;
}
int query()
{
return st[tt];
}
int main()
{
init();
int m;scanf("%d", &m);
while(m--)
{
string s;cin>>s;
if(s == "push")
{
int x;scanf("%d", &x);
push(x);
}
else if(s == "pop") pop();
else if(s == "empty")
{
if(empty()) printf("YES\n");
else printf("NO\n");
}
else printf("%d\n", query());
}
return 0;
}
3.2 表达式求值
这道题看起来是很简单的,明显就是开两个栈,一个栈用来存数字,一个栈用来存符号,当遇到右括号就拿出两个数和一个符号来计算,再将计算结果放入栈,直到左括号。最后直到存放符号的栈为空,存放数字的栈中有唯一的数,这个数就是答案。
这样是不对的,没有考虑到运算的优先级,乘除法的优先级是高于加减法的。如2 * 3 + 5,按上面的思路,最终存数字的栈2 3 5,存符号的栈* +,我们会先算加法,导致出错
为了考虑优先级,此时有4步。开一个存数字的栈num,存符号的栈op
考虑优先级主要有两条规则:
优先级高的要比优先级低的先计算
优先级相同的要从左向右依次计算
(1)遇到数字,放入num
(2)遇到'(',放入op
(3)遇到')',从num中取出两个数,从op中取出一个符号,计算完再将结果放入num,直到op的栈顶是'(',然后将'('弹出栈
(4)遇到+、-、*、/,要看一下op栈顶的符号的优先级是否大于等于当前的符号,若大于等于,要先计算,直到op栈顶的符号优先级低于当前符号。像上面的2 * 3 + 5,我们遍历到+时,num里面是2 3,op里里面是*,*的优先级明显高于+,所以我们要先计算,直到op的栈顶元素的优先级低于+,第一次计算后,num里面是6,op里面没有元素了,直接放入+,此时就先计算了*。注意:这一步为什么要有等于,因为我们计算优先级相同的运算符时需要从左向右依次计算,比方说0 - 5 + 8,如果没有等于,会先计算5 + 8,结果是-13,明显是错的
#include<iostream>
#include<stack>
#include<unordered_map>
#include<string>
using namespace std;
stack<int> num;
stack<char> op;
unordered_map<char, int> cmp = {
{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; // 存放个运算符的优先级
void equl() // 计算过程
{
int a = num.top(); nu