其他分享
首页 > 其他分享> > [高级数据结构] 4. 习题课

[高级数据结构] 4. 习题课

作者:互联网

马拉车算法

  1. 解决回文串问题
  2. 处理回文串的四种算法
    1. 暴力匹配
      2种情况:1种是以某个字符为对称中心,1种是以间隙(2个字母之间)为对称中心
int b[100];
int d[100]; // i + 1

for (int i = 0; i < size(str); i++) {
	b[i] = 1; // i为中心的回文长度是它本身字符
	while (i - b[i] >= 0 && i + b[i] < n && str[i - b[i]] == str[i + b[i]]) {
		b[i]++;
	}
}
  1. 二分+哈希
  2. 马拉车
  3. 回文自动机

Leetcode 5.最长回文子串

class Solution {
public:
    // 预处理:统一奇数、偶数串为奇数串
    string getNewString(string s) {
        string ns = "#";
        for (int i = 0; s[i]; i++) {
            (ns += s[i]) += "#"; // s[i] -> ns, '#' -> ns
        }
        return ns;
    }

    string longestPalindrome(string s) {
        if (s.size() == 0) return "";
        string ns = getNewString(s);
        vector<int> dis(ns.size()); // d[]数组,求以i为中心最长得回文子串长度
        int l = 0, r = -1; // 错开,无交集
        for (int i = 0; i < ns.size(); i++) {
            if (i > r) dis[i] = 1;
            else dis[i] = min(dis[l + r - i], r - i); // j = l + r - i;
            // 暴力匹配
            while (i - dis[i] >= 0 && i + dis[i] < ns.size() && ns[i - dis[i]] == ns[i + dis[i]]) {
                dis[i]++;  
            }
            // 更新l, r
            if (i + dis[i] > r && i - dis[i] > 0) {
                l = i - dis[i];
                r = i + dis[i];
            }
        }
        // 输出最长回文子串
        string ret;
        int tmp = 0; // 暂存长度
        for (int i = 0; ns[i]; i++) {
            if (tmp >= dis[i]) continue;
            tmp = dis[i];
            ret = "";
            for (int j = i - dis[i] + 1; j < i + dis[i]; j++) {
                if (ns[j] == '#') continue;
                ret += ns[j];
            }
        }
        return ret;
    }
};
  1. 马拉车算法:暴力匹配 [ 复杂度o(n2) ] 的升级版, o(n) 解决 最大回文子串 问题

  2. 应用到缓存思想,减少一段暴力匹配,利用到前面已经求得的信息

  3. 求d[ ]数组

    1. [ l , r ] 最初暴力求得,后续维护这个区间
    2. d[k] 以k为中心最长的回文长度,其中l,r暴力求得,d[k] = (r - l)
    3. 核心公式
      1. i <= r时,d[i] = min(r - i, d[j]) ( l<j<k<i<r ),因为j,i关于k对称且d[j]已知
      2. i > r时,朴素实现
  4. 填充#,字符串长度n变为2n+1,一定是奇数,避免两次循环(以字符为中心,以2个字符之间为中心)

[并查集预习课] 游戏分组

#include <stdio.h>
#include <stdlib.h>

#define maxn 1000010 //c不支持 const int maxn = 。。。写法

int p[maxn];

int find(int x) {
    if (p[x] != x) return p[x] = find(p[x]);
    return p[x];
}

void merge(int x, int y) {
    int fx = find(x);
    int fy = find(y);
    if (fx != fy) p[fx] = fy;
    return ;
}

int main() {
    int n, m, a, b;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        p[i] = i;
    }
    for (int i = 0; i < m; i++) {
        scanf("%d%d", &a, &b);
        merge(a, b);
    }
    int ans = 0;
    for (int i = 0; i < n; i++) {
        ans += (p[i] == i); // 判断多少组
    }
    printf("%d\n", ans);   
    return 0;
}
  1. c不支持 const int maxn 写法,使用 #define
  2. 判断存在多少组:ans += (p[i] == i)

leetcode 685. 冗余连接 II

class Solution {
public:
    /*
        思路:逐个删除边,判断剩下的图,是不是一个有向树
        判断思路:
            1.有且只有一个节点入度为0  -> 集合无环 使用并查集判断,新插入节点已经在集合中时形成闭环
            2.集合中没有节点的入度为2
    */
    
     // p[]并查集. vis[]入度数组(n <= 1000), flag1->条件1, flag2->条件2
    int p[2000], vis[2000], flag1 = 0, flag2 = 0;

    int find(int x) {
        if (p[x] != x) return p[x] = find(p[x]);
        return p[x];
    }
    void union_find(int x, int y) {
        int fx = find(x);
        int fy = find(y);
        if (fx != fy) p[fx] = fy;
        else flag1 = 1; // 闭环,条件1不满足
    }

    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {

        // 从后往前遍历:返回最后出现在给定二维数组的答案   
        for (int i = edges.size() - 1; i >= 0; i--) { // 逐个删除边
            flag1 = 0, flag2 = 0;
            int x = edges[i][0]; // 起点
            int y = edges[i][1]; // 终点
            memset(vis, 0, sizeof(vis));
            for (int k = 0; k < 2000; k++) {
                p[k] = k;
                vis[k] = 0; // 入度数组,初始化为0
            }
            for (int j = 0; j < edges.size(); j++) {
                if (edges[j][0] == x && edges[j][1] == y) continue;
                union_find(edges[j][0], edges[j][1]);
                if (flag1 == 1) break;
                // 入度+1
                vis[edges[j][1]]++;
                if (vis[edges[j][1]] > 1) {
                    flag2 = 1;
                    break;
                }
            }
            if (flag1 == 0 && flag2 == 0) {
                return {x, y};
            }
        }
        return edges[0]; // 随便返回一个值,要不编译不过
    }
};
  1. 思路:逐个删除边,判断剩下的图,是不是一个有向树
  2. 判断思路:
    1. 有且只有一个节点入度为0 -> 集合无环 使用并查集判断,新插入节点已经在
    2. 集合中没有节点的入度为2

[图论预习课] 灌溉

  1. 最小生成树- 克鲁斯卡尔算法(贪心选边),适合邻接表
    1. sort 对边
    2. 遍历所有边
    3. 当前最短边的点加入并查集,如果点在集合中则跳过该边继续遍历
  2. 最小生成树- prim算法(贪心选点),适合邻接矩阵
    1. 离当前集合最近的点加入
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int maxn = 1e4 + 10;

int p[maxn];
struct edge {
    int u, v, c;
};
vector<edge> e;

bool cmp(edge x, edge y) {
    return x.c < y.c;
}

int find(int x) {
    if (p[x] != x) return p[x] = find(p[x]);
    return p[x];
}
int union_find(int x, int y) {
    int fx = find(x), fy = find(y);
    if (fx != fy) {
        p[fx] = fy;
        return 1;
    } 
    return 0;
}

int main() {
    int n, x;
    scanf("%d", &n);
    for (int i = 0; i <= n; i++) {
        p[i] = i;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            scanf("%d", &x);
            if (x == 0) continue;
            e.push_back((edge){i, j, x});
        }
    }
    sort(e.begin(), e.end(), cmp);
    int ans = 0;
    for (int i = 0; i < e.size(); i++) {
        int v = e[i].v, u = e[i].u, c = e[i].c;
        if (union_find(v, u)) {
            ans += c;   
        }
    }
    printf("%d\n", ans);
    return 0;
}
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <sstream>
#include <map>
#include <vector>
#include <set>
#include <unordered_map>
#include <time.h>
#include <stdint.h>
#include <queue>
#include <unordered_set>
#include <stack>

using namespace std;

const int maxn = 500 + 10;
const int inf = 0x3f3f3f3f;

int n, x;
int mp[maxn][maxn]; // 邻接矩阵
int vis[maxn], dis[maxn]; // vis 哪些点在G集合, 哪些在T集合, dis G中与T可达的点距离

void prim() {
    vis[1] = 1; // 1节点加入到我们的T集合中去
    for(int i = 1; i <= n; i++) {
        dis[i] = mp[1][i]; // G集合中的点到T集合中的距离
    }

    for(int i = 2; i <= n; i++) { // 遍历所有的点
        int minn = inf, v = -1; // v是我们找到的点
        for(int j = 1; j <= n; j++) {
            if(!vis[j] && minn > dis[j]) {
                minn = dis[j];
                v = j;
            }
        }
        vis[v] = 1;

        // 遍历所有非T集合中的点, 更新这些点到集合中的距离
        for(int j = 1; j <= n; j++) {
            if(!vis[j] && dis[j] > mp[v][j]) {
                dis[j] = mp[v][j];
            }
        }
    }

    int sum = 0;
    for(int i = 1; i <= n; i++) {
        sum += dis[i];
    }
    printf("%d\n", sum);

    return ;
}

int main() {

    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            scanf("%d", &x);
            mp[i][j] = x;
        }
    }
    prim();
    return 0;
}

标签:数据结构,return,int,高级,++,习题课,include,find,dis
来源: https://blog.csdn.net/weixin_44506866/article/details/118983822