C++笔试题模版汇总(二)

目录

1、单链表

2、双链表

3、栈和队列

4、KMP算法

5、trie树-高效的存储和查找字符串集合的数据结构

6、并查集

8、堆

9、哈希表


1、单链表

//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)
{
    //1、将next指针指向head节点指向的值 2、将head指针删除,指向插入节点x指针 3、当前idx节点,将x值存起来 4、idx走到下一个位置
    e[idx] = x, ne[idx] = head, head = idx, idx ++;
}
 
//将x插到下标是k的点后面
void add(int k, int x)
{
    //1、将当前点next指针指向k点下一个位置 2、将k点下一个位置指向第二个点
    e[idx] = x;
    ne[idx] = ne[k];//1、next指针指向k->next
    ne[k] = idx;//2、k点下一个位置指向idx
    idx ++;
}
//把k后面的点删除掉
void remove(int k)
{
    ne[k] = ne[ne[k]];//ne移到ne->ne后面
}

2、双链表

#include <iostream>
 
using namespace std;
 
const int N = 100010;
 
int m;
int e[N], l[N], r[N], idx;
 
//初始化
void init()
{
    //0表示左端点,1表示右端点
    r[0] = 1, l[1] = 0;
    idx = 2;
}
 
//在下标是k的点的右边,插入x.如果是插入到左边输入这个:add(l[k],x)
void add(int k, int x)
{
    e[idx] = x;//1、先赋值
    r[idx] = r[k];//2、红颜色点右边指针指好=r[k]
    l[idx] = k;//3、红颜色点左边=k
    l[r[k]] = idx;//4、右边的l[k]=红颜色的指针
    r[k] = idx;//5、k的r=红颜色指针
}
 
//删除第k个节点
void remove(int k)
{
    r[l[k]] = r[k];//点k的左边的右边 = k的右边
    l[r[k]] = l[k];//点k的右边的左边 = k的左边
}

3、栈和队列

概念:

1、栈:先进后出

2、队列先进先出

//栈的操作
//插入
stk[++ tt] = x;
//弹出
tt --;
//判断栈是否为空
if (tt > 0) not empty
else empty
//栈顶
stk[tt];

//Example--输出每个数左边第一个比它小的数
#include <iostream>
 
using namespace std;
 
const int N = 100010;
 
int n;
 
int stk[N], tt;
 
int main()
{
    cin >> n;
    
    for (int i = 0; i < n; i ++)
    {
        int x;//读入x
        cin >> x;
        while(tt && stk[tt] >= x) tt --;//如果栈不空且栈顶元素大于x
        if (tt) cout << stk[tt] << ' ';//如果tt不是空的,存在输出那个值,将其输出
        else cout << -1 << ' ';//否则栈不是空的所以输出-1
        
        stk[++ tt] = x;//插入
        
    }
    
    return 0;
}

//队列
//插入
q[++ tt] = x;
//弹出
hh ++;
//判断队列是否为空
if (hh <= tt) not empty
else empty
//取出队头元素
q[hh]
q[tt]

//Example--确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
//1、思考使用普通队列怎么做。
//2、将队列没有用的元素删除掉。
//3、可以用O(1)的时间从队头/队尾取最值
#include <iostream>
#include <algorithm>
 
using namespace std;
 
const int N = 1000010;
int n, k;
int a[N], q[N];//a表示原数组,q表示队列
 
 
int main()
{
    //第一行包含两个整数n和k,分别代表数组长度和滑动窗口的长度
    scanf("%d%d", &n, &k);
    //第二行有n个整数,代表数组的具体数值。同行数据之间用空格隔开。
    for (int i = 0; i < n; i ++) scanf("%d", &a[i]);
    
    int hh = 0, tt = -1;//队头hh,对尾tt
    
    //找最小值
    for (int i = 0; i < n; i ++)
    {
        //判断队头是否在窗口内部,如果不在就删除
        if (hh <= tt && q[hh] < i - k + 1) hh ++;// 若队首出窗口,hh加1
        //判断队尾元素>=当前数, 
        while (hh <= tt && a[q[tt]] >= a[i]) tt --;//若队尾不单调,tt减1
        q[++ tt] = i;//把当前数插入进来
        
        if (i >= k - 1) printf("%d ", a[q[hh]]);
    }
    
    puts("");
    
    hh = 0; tt = -1;  //初始化队列                                 // 重置
    //找最大值
    for (int i = 0; i < n; i ++)
    {
        if (hh <= tt && q[hh] < i - k + 1) 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;
}

4、KMP算法

//字符串匹配
#include <iostream>
 
using namespace std;
 
const int N = 10010, M = 100010;
 
int n, m;
char p[N], s[M];
int ne[N];
 
int main()
{
    //输入参数:
    /*
    第一行输入整数N,表示字符串P的长度。
    第二行输入字符串P。
    第三行输入整数M,表示字符串S的长度。
    第四行输入字符串S。
    */
    cin >> n >> p + 1 >> m >> s + 1;
    
    //求next过程
    for (int i = 2, j = 0; i <= n; i ++)//i从第二个数开始,j从0开始
    {
        while (j && p[i] != p[j + 1]) j = ne[j];//如果j与i不匹配,j往后走知道匹配为止
        if (p[i] == p[j + 1]) j ++;//如果匹配,j++,下一位继续匹配
        ne[i] = j;//next = j ++
    }
    
    //kmp过程
    
    for (int i = 1, j = 0; i <= m; i ++)
    {
        while (j && s[i] != p[j + 1]) j = ne[j];//j匹配
        if (s[i] == p[j + 1]) j ++;//匹配到就往后走
        if (j == n)//知道匹配完
        {
            printf("%d ", i - n);//输出结果
            j = ne[j];
        }
    }
    
    return 0;
}

5、trie树-高效的存储和查找字符串集合的数据结构

//节点个数100010
#include <iostream>
using namespace std;
const int N = 100010;
 
//trie树一般存son[N][26]所有点的儿子,26是因为字符串仅包含小写英文字母和cnt[N]存以当前节点结尾的单词有多少个
int son[N][26], cnt[N], idx;//下标是0的点,即是根节点,又是空节点
char str[N];
 
//插入操作(存储)
void insert(char str[])
{
    int p = 0;
    for (int i = 0; str[i]; i ++)
    {
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx;//如果在p节点没有u节点则创建新的子节点
        p = son[p][u];//存在则将找到节点下标给p,继续往循环下查找
    }
    cnt[p] ++;//添加以p结尾的单词
}
 
//查询操作
int query(char str[])
{
    int p = 0;
    for (int i = 0; str[i]; i ++)
    {
        int u = str[i] - 'a';//取字符串序号
        if (!son[p][u]) return 0;//从0开始找u如果没找到返回0
        p = son[p][u];//存在则将找到节点下标给p,继续往循环下查找
    }
    
    return cnt[p];
}
 
int main()
{
    int n;
    scanf("%d", &n);
    while (n --)
    {
        char op[2];
        scanf("%s%s", op, str);
        if (op[0] == 'I') insert(str);
        else printf("%d\n", query(str));
    }
    
    return 0;
}
 
 

6、并查集

每一个集合用树来表示,树根的编号就是整个集合的编号。每个节点存储他的父节点,p[x]表示x的父节点。

(1)朴素并查集:

    int p[N]; //存储每个点的祖宗节点

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);


(2)维护size的并查集:

    int p[N], size[N];
    //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        size[i] = 1;
    }

    // 合并a和b所在的两个集合:
    size[find(b)] += size[find(a)];
    p[find(a)] = find(b);


(3)维护到祖宗节点距离的并查集:

    int p[N], d[N];
    //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

8、堆

// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;

void heap_swap(int u,int v)
{   
     swap(h[u],h[v]); 
     swap(hp[u],hp[v]);     
     swap(ph[hp[u]],ph[hp[v]]);            
}
void down(int u)
{
    int t = u;
    if (u * 2 <= size && h[u * 2] < h[t]) t = u*2;//判断左子树是否小于h[t],是就交换小的
    if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;//同理判断右子树,哪个小就换哪个
    if (u != t)
    {
        swap(h[u], h[t]);
        down(t);//递归往下走
    }

}
void up(int u)
{
    if(u/2>0&&h[u]<h[u/2]) 
    {
        heap_swap(u,u/2);
        up(u>>1);
    }
}

//步骤:
//定义h[],size,h是heap,size是存在h[]数组上有多少个元素
//down:判断左右子树是否小于h[t],是就交换小的,然后交换,递归down
//从小到大输出比m小的数:h[1]最小,删除最小值h[1] = h[size1];size--;然后down(1);
 
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int p[N], size1;//p是heap,size是存在h[]数组上有多少个元素
void down(int u){
    int t = u;
    if (u *2 <= size1 && p[u * 2] <= p[t]) t = u * 2;//这里不是p[t * 2],而是p[u * 2],下面一样的,因为t是变的
    if (u *2 + 1 <= size1 && p[u * 2 + 1] <= p[t]) t = u * 2 + 1;
    if (t != u){
        swap(p[u], p[t]);
        down(t);//忘了,应该是t,因为t变了
    }
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >>p[i];
    size1 = n;
    //建堆
    for (int i = n / 2; i ; i --) down(i);//这里是i = n / 2,因为是树的形式,所以得除2,而且是down(i)
    while (m --){
        cout << p[1] << ' ';//h[1]最小
        p[1] = p[size1];//删除最小值
        size1 --;
        down(1);
    }
}

9、哈希表

具体参考:https://blog.csdn.net/qq_27262727/article/details/104390583

(1) 拉链法
    int h[N], e[N], ne[N], idx;

    // 向哈希表中插入一个数
    void insert(int x)
    {
        int k = (x % N + N) % N;
        e[idx] = x;
        ne[idx] = h[k];
        h[k] = idx ++ ;
    }

    // 在哈希表中查询某个数是否存在
    bool find(int x)
    {
        int k = (x % N + N) % N;
        for (int i = h[k]; i != -1; i = ne[i])
            if (e[i] == x)
                return true;

        return false;
    }

(2) 开放寻址法//
    int h[N];

    // 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
    int find(int x)
    {
        int t = (x % N + N) % N;
        while (h[t] != null && h[t] != x)
        {
            t ++ ;
            if (t == N) t = 0;
        }
        return t;
    }

(3)字符串哈希
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果

typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64

// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
    h[i] = h[i - 1] * P + str[i];
    p[i] = p[i - 1] * P;
}

// 计算子串 str[l ~ r] 的哈希值,按照公式来
ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值