首页 > 其他分享 >基本子串结构

基本子串结构

时间:2024-09-10 18:03:13浏览次数:6  
标签:子串 基本 sam SAM int row id 结构

基本子串结构

xtq 的 2023 年集训队论文《一类基础子串数据结构》提及。能够对子串问题减少一定思维量。相比于 sam 只能固定 endpos 向前扩展,基本子串能方便处理子串前后扩展。

这个东西有点科技了,我不是能有自信能解释清楚,这里指自己认为写的比较详细的博客供参考。crashed | 127_127_127

这里不讲详细内容,只做一些和补充,和自己的理解。

相信大家都了解过了基本概念,这里从基本子串的构建讲起。

求 代表元 $ rep $

格点图
127大神的图,侵删

这里的每个行是 SAM 对应的节点,每个列是反 SAM 对应的节点(不理解的非常推荐自己画翻转字符串后的图),想通颜色的对应节点在正反 SAM 中是同一个节点。例如, \((1,6),(2,6)\) 是同 SAM 里一个节点代表的多个子串, \((1,6),(1,5)\) 是同反 SAM 里一个节点代表的多个子串。 \((1,6),(3,8)\) 是完全相同的,在 SAM 是一个节点,所代表的一个相同的子串。在 parent 树上右下角是根节点。一个串的左边是 SAM 的儿子,上面是反 SAM 的儿子。因为 parent 树可能存在多个儿子,所以可能有多个格点对应同一节点。

考虑在格点图上画出 SAM 的 \(link\)。会很自然的发现代表元 \(rep\) 就是 SAM 和反 SAM 对应同时经过的节点。那么倍增求 \(rep\) 的方法已经呼之欲出了。满足 $ r-l+1=len_{正SAM} = len_{反SAM} $ 的子串 \(s_{[l,r]}\) 就是代表元。

格点图2

如图,行是正 SAM 对应的节点,蓝色圆圈出来的点,就是 SAM 每个节点对应的最长串。同理,黄色五角星圈出来的就是反 SAM 对应的最长串。那么 \(rep\) 就是同时经过的节点,同时被圆和五角星圈出来的点。因为完全相同的块在 SAM 里只出现一次,所以对应的 \(rep\) 是唯一的。

其他点属于的等价类

不难发现,一个点在 DAG 上只有一个出边的话,那么这个点的出现次数一定和指向的节点一致。否则一定不一致(到达的点,代表的子串一定包含自己这个串,如果能到达多个点,说明有不同串,包含这个子串)。
论文指出,由于 SAM 构建的特殊方式,我们可以从后往前合并其他节点。(格点图,同一个等价类一定是个阶梯状的图,所以一定连续,所以直接倒着合并即可)

O(n) 实现

维护一个类似双指针的结构在正反 SAM 上移动,即可 O(n) 求出所有 \(rep\)。

应用

127大神博客给出的例题都推荐做一下

这里再给出两个多校遇到的模板题:

字符串

hdu:7462
实现以下两种操作:
操作1,给定 \(l,r\) ,询问有多少本质不同的串 \(t\) ,满足 \(s[l,r]\) 是 \(t\) 的子串,且 \(occ(s[l,r]) = occ(t)\)
操作2,给定 \(l,r\) ,询问有多少本质不同的串 \(t\) ,满足 \(t\) 是 \(s[l,r]\) 的子串,且 \(occ(s[l,r])= occ(t)\)
询问在线
\(1 \leq |s| \leq 5 \times 10^5\)

构建基本子串结构,询问1,即同一等价类左上角的点数,一定是个矩形,找到询问区间的 \(rep\) 即可。询问2,即同一等价类右下角的点数,对每个等价类,做个前缀和,询问即前缀和减去左下角的点数即可。代码比较容易实现

这里给出样例对应的格点图。(用excel画的,有点丑)

格点图3
code
#include <bits/stdc++.h>
#define ll long long
#define enl putchar('\n')
#define all(x) (x).begin(),(x).end()
#define debug(x) printf(" "#x":%d\n",x);
using namespace std;
const int MAXN = 5e5 + 5, LOG = 20;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
typedef pair<int, int> pii;
char buf[1 << 21], * p1 = buf, * p2 = buf, obuf[1 << 21], * o = obuf, of[35];
#define gc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline ll qpow(ll a, ll n) { ll res = 1; while (n) { if (n & 1)res = res * a % mod; n >>= 1; a = a * a % mod; }return res; }
template <class T = int>inline T read() { T s = 0, f = 1; char c = gc(); for (; !isdigit(c); c = gc())if (c == '-')f = -1; for (; isdigit(c); c = gc())s = s * 10 + c - '0'; return s * f; }
inline void read(int* a, int n) { for (int i = 1; i <= n; ++i)a[i] = read(); }
inline int inal(char* s) { int n = 0; for (s[0] = gc(); !isalpha(s[0]); s[0] = gc()); for (; isalpha(s[n]); s[++n] = gc()); return s[n] = 0, n; }
inline void outd(int* a, int n) { for (int i = 1; i <= n; ++i)printf("%d ", a[i]); enl; }
int n, m, q;
struct SAM { //最多2n-1个点和3n-4条边
    int len[MAXN << 1], link[MAXN << 1], ch[MAXN << 1][26]; //我们记 longest(v) 为其中最长的一个字符串,记 len(v) 为它的长度。
    int cnt[MAXN << 1], pos[MAXN], rpos[MAXN << 1], dif[MAXN << 1], id[MAXN << 1];
    int cur, lst, siz;
    vector<int>e[MAXN << 1];
    int fa[MAXN << 1][LOG];
    SAM() { clear(); }
    void clear() {  //设置起始点S
        memset(ch, 0, sizeof(int) * (siz + 1) * 26);
        memset(cnt, 0, sizeof(int) * (siz + 1));
        memset(id, 0, sizeof(int) * (siz + 1));
        memset(rpos, 0, sizeof(int) * (siz + 1));
        len[0] = 0;
        link[0] = -1;
        for (int i = 0; i <= siz; ++i)e[i].clear();
        siz = 0;    //siz设置成0实际上有一个点,方便标记
        lst = cur = 0;
    }
    void extend(int c, int id) {
        lst = cur, cur = ++siz;
        len[cur] = len[lst] + 1;
        cnt[cur] = 1;
        for (; ~lst && !ch[lst][c]; lst = link[lst])ch[lst][c] = cur;

        if (lst == -1) {
            link[cur] = 0;
        } else {
            int q = ch[lst][c];
            if (len[lst] + 1 == len[q]) {
                link[cur] = q;
            } else {        //克隆的点是q(lst的c后继)
                int clone = ++siz;
                link[clone] = link[q];
                len[clone] = len[lst] + 1;
                link[cur] = link[q] = clone;
                for (; ~lst && ch[lst][c] == q; lst = link[lst])ch[lst][c] = clone;
                memcpy(ch[clone], ch[q], sizeof(ch[q]));
            }
        }
        pos[id] = cur;
        rpos[cur] = id;
    }


    void chk(int& x, int v) { if (!x) x = v; else if (v) x = min(x, v); }
    void dfs(int x) {
        for (int i = 1; i < LOG; ++i)
            fa[x][i] = fa[fa[x][i - 1]][i - 1];
        for (auto v : e[x]) {
            dfs(v);
            chk(rpos[x], rpos[v]);
        }
    }
    
    void build() {
        for (int i = 1; i <= siz; ++i) {
            e[link[i]].push_back(i);
            fa[i][0] = link[i];
            dif[i] = len[i] - len[link[i]];
        }
        dfs(0);
    }
    void merge() {
        for (int i = siz; i >= 1; --i)if (!id[i])
            for (int c = 0; c < 26; ++c)if (ch[i][c]) {
                id[i] = id[ch[i][c]];
                break;
            }
    }


    int FindR(int l, int r) {
        int x = pos[r];
        for (int i = LOG - 1; i >= 0; --i)
            if (len[fa[x][i]] >= r - l + 1)
                x = fa[x][i];
        return x;
    }

}sam, rsam;
char s[MAXN];
vector<int>row[MAXN], col[MAXN], spr[MAXN];
ll sum[MAXN << 1];
void work() {
    int Ec = 0;
    n = inal(s + 1);
    for (int i = 1; i <= n; ++i)
        sam.extend(s[i] - 'a', i);
    reverse(s + 1, s + n + 1);
    for (int i = 1; i <= n; ++i)
        rsam.extend(s[i] - 'a', i);
    sam.build(); rsam.build();
    for (int i = 1; i <= sam.siz; ++i) {
        int r = sam.rpos[i],
            l = r - sam.len[i] + 1,
            u = rsam.FindR(n - r + 1, n - l + 1);
        if (r - l + 1 == rsam.len[u]) sam.id[i] = rsam.id[u] = ++Ec;
    }
    sam.merge(); rsam.merge();
    for (int i = sam.siz; i >= 1; --i)
        row[sam.id[i]].push_back(i);
    for (int i = rsam.siz; i >= 1; --i)
        col[rsam.id[i]].push_back(i);
    // outd(sam.id, sam.siz);
    // outd(rsam.id, rsam.siz);
    for (int i = 1; i <= Ec; ++i) {
        for (int j = row[i].size() - 1, pre = 0; j >= 0; --j) {
            sum[row[i][j]] = sum[pre] + sam.dif[row[i][j]];
            pre = row[i][j];
            spr[i].push_back(sam.dif[pre]);
        }
    }
}

ll ans;

void ask() {
    int op = read(), l = (read() + ans - 1) % n + 1, r = (read() + ans - 1) % n + 1;
    if (l > r)swap(l, r);

    int u = sam.FindR(l, r);
    int dif = sam.len[u] - (r - l + 1) + 1;
    int id = sam.id[u];
    if (op == 1) {
        ans = 1ll * dif * (sam.len[row[id][0]] - sam.len[u] + 1);
    } else {
        ans = sum[u];
        ll stw = upper_bound(all(spr[id]), dif - 1) - spr[id].begin();
        int rowid = row[id].size() - stw;
        if (stw) {
            ans -= sum[row[id][rowid]];
            ans -= 1ll * (dif - 1) * (sam.len[u] - sam.len[row[id][rowid]]);
        } else {
            ans -= 1ll * (dif - 1) * (sam.len[u] - sam.len[row[id].back()] + 1);
        }
    }
    printf("%lld\n", ans);
}


void solve() {
    sam.clear(); rsam.clear();
    for (int i = 1; i <= n; ++i)
        row[i].clear(), col[i].clear(), spr[i].clear();
    memset(sum, 0, sizeof(sum));
    ans = 0;
    work();
    q = read();
    while (q--)
        ask();
}
signed main(signed argc, char const* argv[]) {
    clock_t c1 = clock();
#ifdef LOCAL
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    //=============================================================
    int TxT = 1;
    TxT = read();
    while (TxT--)
        solve();
    //=============================================================
#ifdef LOCAL
    end :
    cerr << "Time Used:" << clock() - c1 << "ms" << endl;
#endif
    return 0;
}
串串

hdu:7492
给定一个字符串 \(s\) ,每次可以选择删除两段的一个字符,代价是删除前字符串 \(t\) ,在原串 \(s\) 中出现的次数。求最小代价。
\(1 \leq |s| \leq 10^6\)

注意到,题目相当于格点图从左上角走到右下角的代价,而每步的代价就是这个等价类的 occ ,在每个等价类的周长上实现 dp 即可。因为左上角的 occ 一定小于等于右下的,所以转移考虑正上方,和正左方的点转移即可。用一个拓扑排序或者记忆化都可以维护 dp 的转移顺序。

code

因为是赛时写的代码,所以有点抽象。

#include <bits/stdc++.h>
#define ll long long
#define enl putchar('\n')
#define all(x) (x).begin(),(x).end()
#define debug(x) printf(" "#x":%d\n",x);
using namespace std;
const int MAXN = 1e6 + 5, LOG = 22;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
typedef pair<int, int> pii;
char buf[1 << 21], * p1 = buf, * p2 = buf, obuf[1 << 21], * o = obuf, of[35];
#define gc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline ll qpow(ll a, ll n) { ll res = 1; while (n) { if (n & 1)res = res * a % mod; n >>= 1; a = a * a % mod; }return res; }
template <class T = int>inline T read() { T s = 0, f = 1; char c = gc(); for (; !isdigit(c); c = gc())if (c == '-')f = -1; for (; isdigit(c); c = gc())s = s * 10 + c - '0'; return s * f; }
inline void read(int* a, int n) { for (int i = 1; i <= n; ++i)a[i] = read(); }
inline int inal(char* s) { int n = 0; for (s[0] = gc(); !isalpha(s[0]); s[0] = gc()); for (; isalpha(s[n]); s[++n] = gc()); return s[n] = 0, n; }
inline void outd(int* a, int n) { for (int i = 1; i <= n; ++i)printf("%d ", a[i]); enl; }
int n, m, q;

int du[MAXN];

struct SAM { //最多2n-1个点和3n-4条边
    int len[MAXN << 1], link[MAXN << 1], ch[MAXN << 1][26]; //我们记 longest(v) 为其中最长的一个字符串,记 len(v) 为它的长度。
    int cnt[MAXN << 1], pos[MAXN], rpos[MAXN << 1], dif[MAXN << 1], id[MAXN << 1];
    ll dp[MAXN << 1];
    int cur, lst, siz;

    vector<int>e[MAXN << 1];
    int fa[MAXN << 1][LOG];
    
    SAM() { clear(); }
    void clear() {  //设置起始点S
        memset(ch, 0, sizeof(int) * (siz + 1) * 26);
        memset(id, 0, sizeof(int) * (siz + 1));
        memset(rpos, 0, sizeof(int) * (siz + 1));
        memset(cnt, 0, sizeof(int) * (siz + 1));


        for (int i = 0; i <= siz; i++)e[i].clear();
        len[0] = 0;
        link[0] = -1;
        siz = 0;    //siz设置成0实际上有一个点,方便标记
        lst = cur = 0;
    }
    void extend(int c, int id) {
        lst = cur, cur = ++siz;
        len[cur] = len[lst] + 1;
        cnt[cur] = 1;
        for (; ~lst && !ch[lst][c]; lst = link[lst])ch[lst][c] = cur;
    
        if (lst == -1) {
            link[cur] = 0;
        } else {
            int q = ch[lst][c];
            if (len[lst] + 1 == len[q]) {
                link[cur] = q;
            } else {        //克隆的点是q(lst的c后继)
                int clone = ++siz;
                link[clone] = link[q];
                len[clone] = len[lst] + 1;
                link[cur] = link[q] = clone;
                for (; ~lst && ch[lst][c] == q; lst = link[lst])ch[lst][c] = clone;
                memcpy(ch[clone], ch[q], sizeof(ch[q]));
            }
        }
        pos[id] = cur;
        rpos[cur] = id;
    }
    
    void chk(int& x, int v) { if (!x) x = v; else if (v) x = min(x, v); }
    void dfs(int x) {
        for (int i = 1; i < LOG; ++i)
            fa[x][i] = fa[fa[x][i - 1]][i - 1];
        for (auto v : e[x]) {
            dfs(v);
            cnt[x] += cnt[v];
            chk(rpos[x], rpos[v]);
        }
    }
    
    void build() {
        for (int i = 1; i <= siz; ++i) {
            e[link[i]].push_back(i);
            fa[i][0] = link[i];
            dif[i] = len[i] - len[link[i]];
        }
        dfs(0);
    }
    void merge() {
        for (int i = siz; i >= 1; --i)if (!id[i])
            for (int c = 0; c < 26; ++c)if (ch[i][c]) {
                id[i] = id[ch[i][c]];
                break;
            }
    }


    void dfs_du(int u) { for (auto v : e[u]) dfs_du(v), du[id[u]]++; }
    void initdp() {
        memset(dp, 0x3f, sizeof(ll) * (siz + 1));
    }
    
    int FindR(int l, int r) {
        int x = pos[r];
        for (signed i = LOG - 1; i >= 0; --i)
            if (len[fa[x][i]] >= r - l + 1)
                x = fa[x][i];
        return x;
    }

}sam, rsam;
char s[MAXN];
vector<int>row[MAXN], col[MAXN];


void work() {
    int Ec = 0;
    sam.clear(), rsam.clear();
    n = inal(s + 1);
    for (int i = 1; i <= n; ++i)
        sam.extend(s[i] - 'a', i);
    reverse(s + 1, s + n + 1);
    for (int i = 1; i <= n; ++i)
        rsam.extend(s[i] - 'a', i);
    sam.build(); rsam.build();
    for (int i = 1; i <= sam.siz; ++i) {
        int r = sam.rpos[i],
            l = r - sam.len[i] + 1,
            u = rsam.FindR(n - r + 1, n - l + 1);
        if (r - l + 1 == rsam.len[u]) sam.id[i] = rsam.id[u] = ++Ec;
    }
    sam.merge(); rsam.merge();
    for (int i = sam.siz; i >= 1; --i)
        row[sam.id[i]].push_back(i);
    for (int i = rsam.siz; i >= 1; --i)
        col[rsam.id[i]].push_back(i);
    sam.dfs_du(0); rsam.dfs_du(0);
    auto topsort = [&]() -> void {
        queue<int>q;
        sam.initdp(), rsam.initdp();
        for (int i = 1; i <= Ec; ++i)
            if (!du[i]) {
                for (auto pos : row[i])sam.dp[pos] = n - sam.len[pos];
                for (auto pos : col[i])rsam.dp[pos] = n - rsam.len[pos];
                q.push(i);
            }
        ll ans = INF;
        while (!q.empty()) {
            int i = q.front(); q.pop();
            int R = row[i].size(), C = col[i].size();
            int j = R - 1;
            ll rowval = sam.dp[row[i][j]], occ = sam.cnt[row[i][0]], colval;
            for (int x = 0; x < C; ++x) {
                colval = rsam.dp[col[i][x]];
                while (sam.dif[row[i][j]] <= x) {
                    ll val = min(sam.dp[row[i][j]] + sam.dif[row[i][j]] * occ, colval + (j + 1) * occ);
                    int nxt = sam.link[row[i][j]];
                    if (!nxt) {
                        ans = min(ans, val);
                    } else {
                        sam.dp[nxt] = min(sam.dp[nxt], val);
                        du[sam.id[nxt]]--;
                        if (!du[sam.id[nxt]])
                            q.push(sam.id[nxt]);
                    }
                    --j;
                    rowval = sam.dp[row[i][j]];
                }
                ll val = min(rsam.dp[col[i][x]] + rsam.dif[col[i][x]] * occ, rowval + (x + 1) * occ);
                int nxt = rsam.link[col[i][x]];
                if (!nxt) {
                    ans = min(ans, val);
                } else {
                    rsam.dp[nxt] = min(rsam.dp[nxt], val);
                    du[rsam.id[nxt]]--;
                    if (!du[rsam.id[nxt]])
                        q.push(rsam.id[nxt]);
                }
            }

            while (j >= 0) {
                ll val = min(sam.dp[row[i][j]] + sam.dif[row[i][j]] * occ, colval + (j + 1) * occ);
                if (!sam.link[row[i][j]]) {
                    ans = min(ans, val);
                } else {
                    int nxt = sam.link[row[i][j]];
                    sam.dp[nxt] = min(sam.dp[nxt], val);
                    du[sam.id[nxt]]--;
                    if (!du[sam.id[nxt]])
                        q.push(sam.id[nxt]);
                }
                --j;
            }
    
        }
        printf("%lld\n", ans);
    };
    
    topsort();
    
    for (int i = 1; i <= Ec; ++i)
        col[i].clear(), row[i].clear();
    memset(du + 1, 0, sizeof(int) * Ec);
}
void solve() {
    work();
}
signed main(signed argc, char const* argv[]) {
    clock_t c1 = clock();
#ifdef LOCAL
    freopen("in.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    //=============================================================
    int TxT = 1;
    TxT = read();
    while (TxT--)
        solve();
    //=============================================================
#ifdef LOCAL
    end :
    cerr << "Time Used:" << clock() - c1 << "ms" << endl;
#endif
    return 0;
}

parent 树的树链剖分

我们直接说结论。

树剖的每一条重链都会延伸到叶子,而所有叶子的 \(occ = 1\) 。也就是说,每一条链的链底的长度标号都是连续的。(在格点图中,最左上角的那一块,所有 \(occ = 1\) 的 SAM 节点都对应一条链)

我们重新对所有子串映射成新的点,\((这个点在反串的重链标号,长度)\)。不难看出,这个新编号,是双射的。这样我们就很好的用 $ O(n) $ 的空间刻画出,所有的子串,并且对应了正反 SAM 上的点。

  1. 对于反串 SAM 的一个结点和一条链,其代表的所有等价类在 \(xOy\) 平面上为一条竖线段。

  2. 对于正串 SAM 的一个结点,其代表的所有等价类在 \(xOy\) 平面上为一条斜线段。(但链不是)

反串的重链是同一条,长度不断增加,所以是竖线段。
正串的一个节点,长度不断增加的的同时,对应的反串所在的链增加(因为链是连续的),所以是斜线。(详细看127大神的图)

现在我们能看 [BJWC2018] Border 的四种求法

在 SAM 里找到右边界,反 SAM 找到左边界。然后逐渐增加简短长度,看看是否匹配即可。匹配即,反串 SAM 的竖线和正串 SAM 的斜线有交点。询问离线挂在正串 SAM 重链上。反串 SAM 的竖线构成 \(log\) 个竖线段。找交点就是把正串 SAM 的点逐个加入线段树,线段树上找靠右的最大即可。详细写法参开127的题解

p.s. 推荐学学 \(border\) 理论的解法。

【UNR #6】Border 的第五种求法

和b4写法一致,只需要改几行代码就行。

标签:子串,基本,sam,SAM,int,row,id,结构
From: https://www.cnblogs.com/Qing17/p/18383795

相关文章

  • 数据结构实验报告1(集合)
    学习目标:        掌握抽象数据类型的表示与实现方法。学习内容:        描述一个集合的抽象数据类型ASet,其中所有元素为整数且所有元素不相同,集合的基本操作包括:由整数数组a[0..n-1]创建一个集合。输出集合中的所有元素。判断一个元素是否在一个集合中。求......
  • 数据结构—链表
    一:链表1、数组是连续的内存空间;而链表可以不一定是连续的内存空间2、单端链表;从前一个元素指向后一个元素3、链表的功能(1)访问o(n):数组是通过下表或者索引访问元素;链表是通过next指针以此递归的进行查询判断(2)搜索o(n):从头部遍历;知道找到位置(3)插入o(1):(4)删除o(1):4、特......
  • Kafka集群搭建与基本原理详解
    目录一、Kafka介绍1、MQ的作用MQ的作用主要有以下三个方面:1.异步2.解耦3.削峰2、为什么要用Kafka(特点)二、Kafka快速上手1、实验环境2、单机服务体验1、启动Kafka之前需要先启动Zookeeper。2、启动Kafka。3、简单收发消息4、其他消费模式指定消费进度分组......
  • WinFrom项目结构
    示例项目结构在VisualStudio中创建一个WinForms应用程序后,项目结构如下所示:MyWinFormsApp/│├───Properties/│└───Settings.settings│├───bin/│├───Debug/│└───Release/│├───obj/│├───Debug/│└───Releas......
  • PLC结构化文本(ST)——FB系统内置方法(Init、exit、reinit)
    PLCStructuredTextObjectOrientedProgrammingPLC结构化文本(ST)——FB系统内置方法(Init、exit、reinit)IEC61131-3FB系统内置方法FB_init隐式或显式初始化功能块,第一次下载运行程序时初始化时自动调用。该方法类似于C#类的构造函数,用于初始化类。FB_exit在功能块被销毁时......
  • Shell脚本基础、组成结构、调试、运算符
    思维导图:一、脚本基础1、shell编程介绍与特性        Shell是种命令解释程序,也可以看作是一种命令语言的解释器。用户在与Shell交互时所输入的命令行必须符合Shell命令的语法和语义规范,才能够被Shell理解并执行。Shell所使用的这种命令语言称为Shell语言。Shell语......
  • 图与网络模型的基本概念精解
    图是一种最简单且直观的语言,它通过点和线的组合来表达复杂系统中的关系。点代表对象或位置,线代表它们之间的连接或交互。这种简洁的表达方式使得图在众多领域中具有强大的应用能力。无论是社交网络中的好友关系、城市中的交通系统,还是生物学中的基因网络,图都能通过简单的结构,呈现......
  • C语言13--结构体
    结构体基本概念        C语言提供了众多的基本类型,但现实生活中的对象一般都不是单纯的整型、浮点型或字符串,而是这些基本类型的综合体。比如一个学生,典型地应该拥有学号(整型)、姓名(字符串)、分数(浮点型)、性别(枚举)等不同侧面的属性,这些所有的属性都不应该被拆分开来,而是......
  • 55.C结构体共用体阐论
    零零雨天飒风图书馆记下先来看结构体的”结构“struct结构体类型名{ 成员1; 成员2; .....;}结构体变量名;系统不会为struct结构体类型分配空间而是为结构体变量分配空间关于结构体变量命名 在main函数中 struct结构体类型名结构体变量名如:structStua={初始化复制......
  • 数据结构(王道考研书)
    第一章绪论1.1数据结构的基本概念1.1.1基本概念和术语     数据:是信息的载体,是描述客观事物属性的数、字符及所有能输入到计算机中并被计算机程序识别和处理的符号的集合。数据是计算机程序加工的原料。    数据元素:是数据的基本单位,通常作为一个整体......