初识 基本数据结构和stl
基本数据结构
双向链表
用数组模拟链表实现
双向链表初始化
l[1]=r[1]=0;r[0]=1;
结点1的左边和右边都是1,结点0的右边是1,为什么结点0会存在?因为后续元素有可能插入到结点1的前面
插入
将i插入到x的左边
-
x原本的左边为l[x]
-
调整后的顺序为l[x],i,x
-
想象l[x]和x有两根线牵着,现在要增加两根线,想清楚,要怎样修改,才能确保三者的练习
-
l[x]是依托x的存在而存在,不如先对与l[x]相关的数据进行修改
r[l[x]]=i;l[i]=l[x];
l[x]=i;r[i]=x;
将i插入到x的右边
- 同理
l[r[x]]=i;r[i]=r[x];
r[x]=i;l[i]=x;
删除
删除位置位x的元素
-
x左边元素为l[x]
-
x右边元素为r[x]
-
先解决左右两边元素的的问题
r[l[x]]=r[x];l[r[x]]=l[x];
切换
从当前元素切换到下一个元素
x=r[x]
从当前元素切换到上一个元素
x=l[x]
应用——遍历
cur=0
do{
cur=r[cur];
}while(cur!=0)
其他实现方法数组和指针
STL--变长数组vector
定义
vector<数据类型> 数组名;
添加数据
数组名.push_back(数据);
第i位数据的访问
v[i];
数组长度
v.size();
- 注意变长数组的下标也是从0开始的
清空数组
v.clear()
好处
相对于二维数组不用浪费多余的内存空间
STL--栈stack
定义
stack<数据类型> 变量名
进栈
栈名.push(x)
出栈
栈名.pop()
栈长度
栈名.size()
判断栈是否为空
栈名.empty()
应用——左右括号匹配问题
-
括号类型:()[]{}
-
解决思路:左括号进栈,右括号判断
-
特判:右括号进栈,栈长为0
-
判断:退群情况:每种右括号都遇到与自己不同类型的两种相应左括号
入群情况:除退群情况外
-
细节:
- 数字要一次性读完(542)(在读取字符里的while语句再内嵌一个while语句)
- 边读入边处理
STL--队列queue
定义
queue<数据类型>队列名
入队
队列名.push(数据)
离队
队列名.pop()
队头
队列名.front()
队列长度
队列名.size()
判断队列是否为空
队列名.empty()
STL--双端队列deque
定义
deque<数据类型> 队列名
头/尾部进
队列名.push_front/back(数据);
头/尾部出
队列名.pop_front/back()
头/尾部出的前提
- 双端队列不为空
- 不为空的情况
!deque1.empty()
注:为空,返回值为0,取反为1,if才执行deque1.empty()==0
ordeque1.empty()!=1
deque1.empty()==false
ordeque1,empty()!=true
应用
单调队列的构造
- 原理:将加入的元素与队首或队尾的元素进行比较,再根据预设的单调性决定要加入的位置
给定一个n个数的数列,从左到右输出每个区间长度为m的数列段的最大数
-
方法一:一个一个区间段的进行查找,共需(n-m+1)*m次操作,坏处:空间复杂度高
-
方法二:双指针法
头指针指向头,尾指针指向尾,先根据第一个区间段确立最大值,坏处:最大值可能有多个(需要多设一个变量来统计最大值个数,比较复杂)
-
方法三:单调队列, 先根据第一个区间创建一个单调队列,添加元素时比较头尾,长度到达m时开始去头操作
-
方法四:单调队列,单调队列存位置
- 加入元素为x,x所对应的值为a[x]
- 队列长度为
x-dq.front()+1大于等·x-dq.front()+1
- 队列开始去头的条件为
x-dq.front()+1>m - 由于去头是留到最后的,不妨把最大值的坐标留到第一位,而为了实现这样的操作,在从尾巴入的时候就要入小的,而入小的,换种想法可以把前面大的踢掉(但题目要求的是大的),所以这种想法是错的,所以队列的尾巴应该入大的,把前面小的给踢掉。
#include<iostream>
#include<deque>
using namespace std;
int main()
{
deque<int> deque1;
int n,m;
cin>>n>>m;
int a[n+1];
for(int i=0;i<n;i++)
{
cin>>a[i];
while(!deque1.empty()&&a[deque1.back()]<=a[i])
deque1.pop_back();
deque1.push_back(i);
while(deque1.empty()!=1&&i-deque1.front()+1>m)
deque1.pop_front();
//if(i-deque1.front()+1==m) deque1.front()有可能被更新掉
if(i>=m-1)
cout<<a[deque1.front()]<<" ";
}
return 0;
}
1714切蛋糕
max(sum[i]-sum[j]) (j属于[i-(m-1),i-1])
- 方法一是
sum[i]-min(sum[j])
(j属于[i-(m-1),i-1])
技巧
- 要求一个含减号的式子的最大值,若被减数是不变的,则原问题可转换为求减数的最小值
- 若减数随区间移动而变化,可考虑使用单调队列
树
- 定义:一种集合
- 度:一个结点的子结点数
- 叶结点:没有度的结点
- 父结点:可以往上爬的结点
- 子结点:可以往下跑的结点
- 根结点:没有父节点的结点
- 兄弟结点:与自己共用父结点的结点
二叉树
数组实现方式
- 当前结点x
- 左子节点l[x],建立l数组用来存放各个数的左结点
- 右左节点r[x],建立r数组用来存放各个数的右结点
- -1表示无该子结点
- l[l[l[l[l[x]]]]]x的左左左左结点,寻找方法:
dfs()
或while()
前序遍历
void dfs(int x){
cout<<x;
if(l[x]!=-1)dfs(l[x]);
if(r[x]!=-1)dfs(r[x]);
}
中序遍历
void dfs(int x){
if(l[x]!=-1)dfs(l[x]);
cout<<x;
if(r[x]!=-1)dfs(r[x]);
}
应用——已知中序后序求前序
- 思路:字符串存储,用后序最后一个元素去确认中序的的位置,然后对中序字符串分左右,并把中序的左子树的最右位置用到后序上(中序和后序的左子树部分在内容上(不包括顺序)是重合的)。
STL--优先队列
``priority_queue<``int``,vector<``int``>,greater<``int``> >que3;``//注意“>>”会被认为错误,
``//这是右移运算符,所以这里用空格号隔开
模板
priority_queue<int,vector<int>,greater/less<int> > pqx
在优先队列与结构体的结合
模板
使用优先队列时,如果需要对Time中的start从小到大排序,有两种方法:
priority_queue<Time> pq;
一.在结构体外重载结构体小于运算符:
bool operator <(const Time& a,const Time& b){ return a.start > b.start; } //这里以大于重载小于是因为默认情况下,优先队列是以大的作为队首,这样一反,就可以再默认情况下使得小的作为队首二.直接在结构体中重载小于运算符:
二.直接在结构体中重载小于运算符:
struct Time{ int start, end; bool operator < (const Time& t)const{ return start > t.start; } //用大于号去重载小于号 };
其他
1e10和128M的关系
1MB=1024KB=1024KB*1024=1048576B=1.048576e6B
1int占4个字节,1e10占4e10的字节
重载
- 重新定义
register int
register 是表示使用cpu内部寄存器(寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件)的变量,而平时的int是把变量放在内存中,存到寄存器中可以加快变量的读写速度。
断环为链
- 二倍长度
- a[i+n]=a[i]