数据结构(c++实现)

数据结构

目录

  • 数据结构
      • 1.链表实现
        • 单链表
        • 双链表
      • 2.栈(先进后出,后进先出)
      • 3.单调栈
      • 4.队列(先进先出)
      • 5.单调队列
      • 6.小根堆
        • 操作
      • 7.KMP
      • 8.Trie树(字典树)

1.链表实现

单链表

#include 
 
using namespace std;
 
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 ++ ;
}
 
// 将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] << ' ';
    cout << endl;
 
    return 0;
}

双链表

#include 

using namespace std;

const int N = 100010;

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

// 在节点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;

    // 0是左端点,1是右端点
    r[0] = 1, l[1] = 0;
    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] << ' ';
    cout << endl;

    return 0;
}


2.栈(先进后出,后进先出)

数组模拟栈:

用top表示栈顶所在的索引。初始时,top = -1。表示没有元素。

push x :栈顶所在索引往后移动一格,然后放入x。st[++top] = x。

pop : top 往前移动一格。top–。

empty :top 大于等于 0 栈非空,小于 0 栈空。top == -1 ? “YES” : “NO”

query : 返回栈顶元素。st[top]

#include 

using namespace std;

const int N = 100010;

int m;
int stk[N], tt;

int main()
{
    cin >> m;
    while (m -- )
    {
        string op;
        int x;

        cin >> op;
        if (op == "push")
        {
            cin >> x;
            stk[ ++ tt] = x;
        }
        else if (op == "pop") tt -- ;
        else if (op == "empty") cout << (tt ? "NO" : "YES") << endl;
        else cout << stk[tt] << endl;
    }

    return 0;
}

// stl版本
#include 
#include 
using namespace std;
stack<int> stk;
int main() {
    int n;
    cin >> n;
    while (n--) {
        string s;
        cin >> s;
        int x;
        if (s == "push") {
            cin >> x;
            stk.push(x);
        }
        else if (s == "pop") {
            stk.pop();
        }
        else if (s == "query")
            cout << stk.top() << endl;
        else
            cout << ((stk.empty()) ? "YES": "NO") << endl;

    }
    return 0;
}

3.单调栈

  • 常见模型: 找出每个数左边(右边)离他最近的比他大/小的数
int tt = 0;
for (int i = 1; i <= n; ++i) {
    while (tt && check(q[tt], i)) tt --;
    stk[++ tt] = i
}

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

#include 
#include 

using namespace std;

const int N = 1e5 + 10;

int stk[N], tt;

int main() {
   
    int n;
    scanf("%d", &n);
    while (n--) {
        int x;
        cin >> x;
        while (tt && stk[tt] >= x)   tt --;
        if (!tt)     printf("-1 ");
        else    printf("%d ", stk[tt]);
        stk[++tt] = x;
    }

    return 0;
}

4.队列(先进先出)

用一个数组 q 保存数据。

用 hh 代表队头,q[hh] 就是队头元素, q[hh + 1] 就是第二个元素。

用 tt 代表队尾, q[tt] 就是队尾元素, q[tt + 1] 就是下一次入队,元素应该放的位置。

[hh, tt] 左闭右闭,代表队列中元素所在的区间。

出队pop:因为 hh 代表队头,[hh, tt] 代表元素所在区间。所以出队可以用 hh++实现,hh++后,区间变为[hh + 1, tt]。

入队push:因为 tt 代表队尾,[hh, tt] 代表元素所在区间。所以入出队可以用 tt++实现,tt++后,区间变为[hh, tt + 1], 然后在q[tt+1]位置放入入队元素。

是否为空empty:[hh, tt] 代表元素所在区间,当区间非空的时候,对列非空。也就是tt >= hh的时候,对列非空。

询问队头query:用 hh 代表队头,q[hh] 就是队头元素,返回 q[hh] 即可。

#include 

using namespace std;

const int N = 1e5 + 10;

int q[N];
int hh, tt = -1;

int main() {
    int n;
    cin >> n;
    while (n--) {
        string s;
        cin >> s;
        int x;
        if (s == "push") {
            cin >> x;
            q[++tt] = x;
        }
        else if (s == "empty") 
            cout << (hh <= tt? "NO": "YES") << endl;
        else if (s == "query")
            cout << q[hh] << endl;
        else    ++hh;
    }

    return 0;
}
// STL版本
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define ll long long
#define PII pair<int, int>

using namespace std;
queue<int> q;

int main()
{
    int n;
    cin >> n;
    while (n--) {
        int x; string s;
        cin >> s;
        if (s == "push") 
        {
            cin >> x;
            q.push(x);
        }
        else if (s == "pop") {
            q.pop();
        }
        else if (s == "empty") {
            cout << (q.empty()? "YES": "NO") << endl;
        }
        else {
            cout << q.front() << endl;
        }
    }
    return 0;
}

5.单调队列

滑动窗口

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

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

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

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

只要后一个进队的比前面的小,前面的将永无出头之日

#include 
#include 
#include 

using namespace std;

const int N = 1e6 + 10;
int q[N], a[N];
int hh, tt = -1;

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; ++i)     scanf("%d", &a[i]);
    for (int i = 0; i < n; ++i) {
        while (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]]); 
    }
    printf("\n");
    hh = 0, tt = -1;
    for (int i = 0; i < n; ++i) {
        while (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]]); 
    }

    return 0;
}
// stl版本
#include 
#include 
#include 

using namespace std;

const int N = 1e6 + 10;
int q[N], a[N];
int hh, tt = -1;

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; ++i)     scanf("%d", &a[i]);
    deque<int> dq;
    for (int i = 0; i < n; ++i) {
        // 判断队头是否划出窗口
        while (!dq.empty() && i - k + 1 > dq.front())   dq.pop_front();
        while (!dq.empty() && a[dq.back()] >= a[i])     dq.pop_back();
        dq.push_back(i);
        if (i >= k - 1)     printf("%d ", a[dq.front()]);
    }
    printf("\n");
    dq.clear();
    for (int i = 0; i < n; ++i) {
        // 判断队头是否划出窗口
        while (!dq.empty() && i - k + 1 > dq.front())   dq.pop_front();
        while (!dq.empty() && a[dq.back()] <= a[i])     dq.pop_back();
        dq.push_back(i);
        if (i >= k - 1)     printf("%d ", a[dq.front()]);
    }

    return 0;
}

6.小根堆

优先队列–大根堆

完全二叉树, 每个父节点都小于两个子节点。

存储:x的做儿子2x,有儿子2x+1(下标从1 开始)

**down操作:**把某个数变大,将其往下移

void down(int u) {
    int t = u;
    if (2 * u <= size && h[u] < h[2 * u])    t = 2 * u;
    if (2 * u + 1 <= size && h[u] < h[2 * u + 1])        t = 2 * u + 1;
    if (u != t) {
        swap(h[u], h[t]);
        down(t);
    }
}
void up(int u) {
	while (u / 2 && h[u/2] > h[u]) {
        swap(h[u], h[u/2]);
        u
    }
}

操作

  • 插入一个数
heap[++size] = x;
up(size);
  • 求集合最小值
heap[1]
  • 删除最小值
    • 把最后元素移到堆顶 size--
    • down
heap[1] = heap[size];
size--;
down(1);
  • 删除任意一个元素
heap[k] = heap[size];
size --;
down(k);
up(k);
  • 修改任意一个元素
heap[k] = x;
down(k);
up(k);

维护一个集合,初始时集合为空,支持如下几种操作:

  1. I x,插入一个数 x;
  2. PM,输出当前集合中的最小值;
  3. DM,删除当前集合中的最小值(数据保证此时的最小值唯一);
  4. D k,删除第 k 个插入的数;
  5. C k x,修改第 k 个插入的数,将其变为 x;

现在要进行 N� 次操作,对于所有第 22 个操作,输出当前集合的最小值。

#include 
#include 
#include 

using namespace std;

const int N = 100010;

int h[N], ph[N], hp[N], cnt;

void heap_swap(int a, int b)
{
    swap(ph[hp[a]],ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

void down(int u)
{
    int t = u;
    if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    if (u != t)
    {
        heap_swap(u, t);
        down(t);
    }
}

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

int main()
{
    int n, m = 0;
    scanf("%d", &n);
    while (n -- )
    {
        char op[5];
        int k, x;
        scanf("%s", op);
        if (!strcmp(op, "I"))
        {
            scanf("%d", &x);
            cnt ++ ;
            m ++ ;
            ph[m] = cnt, hp[cnt] = m;
            h[cnt] = x;
            up(cnt);
        }
        else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
        else if (!strcmp(op, "DM"))
        {
            heap_swap(1, cnt);
            cnt -- ;
            down(1);
        }
        else if (!strcmp(op, "D"))
        {
            scanf("%d", &k);
            k = ph[k];
            heap_swap(k, cnt);
            cnt -- ;
            up(k);
            down(k);
        }
        else
        {
            scanf("%d%d", &k, &x);
            k = ph[k];
            h[k] = x;
            up(k);
            down(k);
        }
    }

    return 0;
}

7.KMP

next数组的值是代表着字符串(模式串)的前缀与后缀相同的最大长度,(不能包括自身)

给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。

模式串 P 在字符串 S中多次作为子串出现。

求出模式串 P 在字符串 S 中所有出现的位置的起始下标。

#include 

using namespace std;

const int N = 1e5 + 10, M = 1e6 + 10;

int ne[N];
char p[N], s[M];

int main() {
    int n, m;
    cin >> n >> p + 1 >> m >> s + 1;
    
    // next数组
    for (int i = 2, j = 0; i <= n; i ++) 
    {
        while (j && p[i] != p[j + 1])       j = ne[j];
        if (p[i] == p[1 + j])    j ++;
        ne[i] = j;
    }
    
    for (int i = 1, j = 0; i <= m; i ++) 
    {
        while (j && s[i] != p[j + 1])       j = ne[j];
        if (s[i] == p[1 + j])   j ++;
        if (j == n)     cout << i - n << ' ';
    }
    
    return 0;
}

8.Trie树(字典树)

数据结构(c++实现)_第1张图片

维护一个字符串集合,支持两种操作:

  1. I x 向集合中插入一个字符串 x;
  2. Q x 询问一个字符串在集合中出现了多少次。

共有 N 个操作,所有输入的字符串总长度不超过 105105,字符串仅包含小写英文字母。

#include 

using namespace std;

const int N = 1e5 + 10;

int son[N][26], cnt[N], idx;

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 = son[p][u];

    }
    cnt[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;
        p = son[p][u];
    }
    return cnt[p];
}

int main() {
    int n;
    cin >> n;
    while (n--) {
        char c;
        char s[N];
        cin >> c >> s;
        if (c == 'I')
            insert(s);
        else 
            cout  << query(s) << endl;

    }

    return 0;
}

你可能感兴趣的:(Algorithm,C++,数据结构,c++,算法)