Alg-learn

技能学习 · 2024-08-31

数据结构

(数组模拟,用静态链表而不是动态链表是因为其效率相对较高)

链表与邻接表

单链表·(邻接表:存储图和树)

首先用数组e[n]表示某点的值,ne[n]表示某点的next指针,二者靠下标关联,空节点下标设为-1


//值      3    5    7    9
head -> O -> O -> O -> O -> 空节点
//下标    0    1    2    3      -1

e[0] = 3,  e[1] = 5, e[2] = 7, e[3] = 9
en[0] = 1,ne[1] = 2,ne[2] = 3,ne[3] = -1
const int N = 100010;

//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;
    idx ++;
}
//将x插入到下标是k的节点
void add(int k,int x){
    e[idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx;
    idx ++;
}
//将下标是k的点的后面节点删去
void remove(int k){
    ne[k] = ne[ne[k]];
}

双链表

此处用下标是0的点代表头节点,下标是1的点代表尾节点

//l[N]和r[N]分别是指向左右的指针

int m;
int e[N],l[N],r[N],idx;

//初始化
void init(){
    //0表示左端点,1表示右端点
    r[0] = 1;l[1] = 0;
    idx = 2;
}
//在下标是k的点的右边插入x
void add(int k,int x){

    e[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]]  = idx;
    r[k] = idx;

}

//若在左边插入,可用
add(l[k]);
//删除第k个点

void remove(int k){
    r[l[k]] = r[k];
    l[r[k]] = l[k];

}

栈和队列

栈:先进后出 FILO(first in last out)
队列:先进先出 FIFO(first in first out)

数组模拟栈十分简单

const int = 10010;

int stk[k],tt;//tt为栈顶 初始为0或1

//插入
stk[++ tt] = x;

//弹出
tt --

//判断栈是否为空
if(tt > 0) not empty
else empty

//栈顶
stk[tt]

队列

//在队尾插入元素,在队头弹出元素

int q[N],hh,tt = -1; //hh是队头,tt队尾

//插入
q[++ tt] = x;

//弹出
hh ++

//判空

if(hh <= tt) not empty
else empty

//取出队头元素
q[hh]

单调栈和单调队列

单调栈

单调栈的应用场景较少,可以看如下的例题

例:


给定一个长度为 N的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1

第一行包含整数 N,表示数列长度。

第二行包含 N 个整数,表示整数数列。

该题若使用暴力做法做的话用双重循环

用一个栈存储i左边的所有元素,i指针每往右移动一位就往栈里加入一个新的数
经过观察可知,一些元素永远不会输出,如a~3~ >= a~5~,则a~3~一定不会被输出

即若a~x~ >= a~y~ 且 x < y 则a~x~可以被删掉,剩下的序列一定是严格单调上升序列

代码实现:


const int N = 10010;
int n;
int stk[N],tt;

int main(){

    ios::sync_with_stdio(false);//

    cin >> n;

    for(int i = 0;i < n;i ++){
        int x;
        cin >> x;
        while(tt && stk[tt] >= x) tt --;
        if(tt) cout << stk[tt] <<" ";
        else cout << -1 << endl;

        stk[++ tt] = x;

    }
    return 0;
}

单调队列

例:

滑动窗口


给定一个大小为 n≤10^6的数组组。

有一个大小为 k的滑动窗口,它从数组的最左边移动到最右边。

你只能在窗口中看到 k个数字。

每次滑动窗口向右移动一个位置。

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

窗口使用队列来维护,使用暴力做法就是遍历队列里的所有元素
可以考虑优化这个问题,例如在找最小值时窗口中有3,-1,-3,那么3比-3小且在-3左边,那么一定不会被输出,则3可以删去。同理-1也可以删去

即只要在该队列里前面有一个数比后面的数大,就可将其删掉。强所有的逆序对全部删掉,这个队列就会变成严格单调上升序列

此时求其最小值只需找到队头

题解:

const itn N = 1000010;

int n;
int a[N],q[N];//q中存储的是下标

int main(){
    scanf("%d",&n);
    for(int i = 0;i < n;i ++) scanf("%d",&a[i]);

    int 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) printf("%d",a[q[hh]]);
        puts("");
    }

    int 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) printf("%d",a[q[hh]]);
        puts("");
        return 0;
    }
}

kmp

KMP算法要解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题。说简单点就是我们平时常说的关键字搜索。模式串就是关键字,如果它在一个主串中出现,就返回它的具体位置,否则返回-1(常用手段)。

若使用暴力做法,则从主串的第i个位置为起点,与模式串逐个比较,若出现不匹配,就将起点i往后移动一位

Theme Jasmine by Kent Liao