new algo
This commit is contained in:
		
							
								
								
									
										71
									
								
								Algorithm/StringRelated/KMP/D KMP模式匹配算法.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								Algorithm/StringRelated/KMP/D KMP模式匹配算法.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
using namespace std;
 | 
			
		||||
vector<int> build_next(const string& p) {
 | 
			
		||||
    int n = p.size();                 // 模式串长度
 | 
			
		||||
    vector<int> nxt(n, 0);            // 存放 next 数组,初始化全 0
 | 
			
		||||
 | 
			
		||||
    for (int i = 1, j = 0; i < n; i++) {
 | 
			
		||||
        // --- step1: 如果当前字符不匹配,回退 j ---
 | 
			
		||||
        while (j > 0 && p[i] != p[j]) 
 | 
			
		||||
            j = nxt[j - 1];           // j 回退到上一个最长前后缀的长度,采用动态规划思想
 | 
			
		||||
 | 
			
		||||
        // --- step2: 如果匹配成功,j 前进 ---
 | 
			
		||||
        if (p[i] == p[j]) 
 | 
			
		||||
            j++;
 | 
			
		||||
 | 
			
		||||
        // --- step3: 记录当前前缀的 next 值 ---
 | 
			
		||||
        nxt[i] = j;                   // 表示 p[0..i] 的最长公共前后缀长度
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nxt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
vector<int> next_arr(const string& x) {
 | 
			
		||||
    int n = x.size();
 | 
			
		||||
    vector<int> tmp(n, 0);
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < n; i++) {       
 | 
			
		||||
        int j = 0;
 | 
			
		||||
        for (int len = i; len > 0; len--) {
 | 
			
		||||
            if (x.substr(0, len) == x.substr(i - len + 1, len)) {
 | 
			
		||||
                j = len;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        tmp[i] = j;
 | 
			
		||||
    }
 | 
			
		||||
    return tmp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int main(){
 | 
			
		||||
    string mod, input;
 | 
			
		||||
    cin >> input >> mod;
 | 
			
		||||
    vector<int> next = build_next(mod);
 | 
			
		||||
    int n = mod.size();
 | 
			
		||||
    int pi = 0, pm = 0;
 | 
			
		||||
    cout << next[n / 4] << " " << next[n / 2] << " " << next[3 * n / 4] << endl;
 | 
			
		||||
    while (pi < input.size()) {
 | 
			
		||||
        if (input[pi] == mod[pm]) {
 | 
			
		||||
            pi++;
 | 
			
		||||
            pm++;
 | 
			
		||||
            if (pm == mod.size()) {
 | 
			
		||||
                cout << pi - pm << " ";   // 匹配位置(起始下标)
 | 
			
		||||
                return 0;
 | 
			
		||||
                pm = next[pm - 1];       // 回退
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (pm > 0)
 | 
			
		||||
                pm = next[pm - 1];       // 回退
 | 
			
		||||
            else
 | 
			
		||||
                pi++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    cout << -1 << endl;
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qwerababcabcabcabcdaabcabhlk
 | 
			
		||||
							
								
								
									
										182
									
								
								Algorithm/StringRelated/KMP/readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								Algorithm/StringRelated/KMP/readme.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,182 @@
 | 
			
		||||
好——我把 **KMP(Knuth–Morris–Pratt)算法** 讲得尽量细致、直观并配上代码与举例,帮助你完全搞清楚它的思想、实现与常见的下标/`next`(或 `pi`)定义差异。
 | 
			
		||||
 | 
			
		||||
# 1) 问题与直观想法
 | 
			
		||||
 | 
			
		||||
目标:在长度为 `n` 的主串 `s` 中查找长度为 `m` 的模式串 `p` 是否出现,并返回第一次出现的位置(下标从 0 开始)。
 | 
			
		||||
朴素做法在遇到失配时会把模式整段右移 1,可能导致重复比较,最坏 `O(n*m)`。
 | 
			
		||||
KMP 的关键:当模式在某个位置发生部分匹配然后失配时,利用已经匹配的信息**不回头重比**,直接把模式滑动到可以继续利用已匹配前缀的位置,从而保证整体复杂度线性 `O(n + m)`。
 | 
			
		||||
 | 
			
		||||
# 2) 前缀函数(prefix function / π 或常说的 next)
 | 
			
		||||
 | 
			
		||||
常用的定义(0 基)——称为 `pi[i]`:
 | 
			
		||||
 | 
			
		||||
> `pi[i]` 表示模式 `p[0..i]`(长度 `i+1`)的 **最长的真前缀(proper prefix)同时也是后缀(suffix)** 的长度。
 | 
			
		||||
 | 
			
		||||
例:若 `p = "ababaca"`,则
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
i:   0 1 2 3 4 5 6
 | 
			
		||||
p:   a b a b a c a
 | 
			
		||||
pi:  0 0 1 2 3 0 1
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
解释:例如 `pi[4] = 3` 表示 `p[0..2] == p[2..4] == "aba"`。
 | 
			
		||||
 | 
			
		||||
**注意:** 文献里 `next` 数组有多种约定。有的把 `next[i] = pi[i-1]`,也有的把 `next[i] = pi[i-1] + 1`(1-based 的位置)。务必分清你所用的下标约定。下面我用最常见的 0-based `pi`,它和匹配阶段的 `j = pi[j-1]` 关系最直接。
 | 
			
		||||
 | 
			
		||||
# 3) 如何线性时间构造 `pi`(核心技巧)
 | 
			
		||||
 | 
			
		||||
思路:用已经计算出的 `pi` 值“回退”来扩展新位置,而不是每次从头匹配。
 | 
			
		||||
伪代码(0-based):
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
pi[0] = 0
 | 
			
		||||
j = 0   // 当前已匹配的长度(也是 p 的下一个比较下标)
 | 
			
		||||
for i = 1 .. m-1:
 | 
			
		||||
    while j > 0 and p[i] != p[j]:
 | 
			
		||||
        j = pi[j-1]    // 回退到较短的已知匹配
 | 
			
		||||
    if p[i] == p[j]:
 | 
			
		||||
        j += 1
 | 
			
		||||
    pi[i] = j
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
时间复杂度:每个 `i` 的 `while` 回退总次数在整个过程是 O(m)(每次回退减少 `j`),所以构造 `pi` 是线性时间 O(m)。
 | 
			
		||||
 | 
			
		||||
# 4) 匹配阶段(利用 `pi`)
 | 
			
		||||
 | 
			
		||||
匹配 `s`(长度 `n`)与 `p`(长度 `m`):
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
j = 0   // 当前匹配到模式的长度
 | 
			
		||||
for i = 0 .. n-1:
 | 
			
		||||
    while j > 0 and s[i] != p[j]:
 | 
			
		||||
        j = pi[j-1]    // 失配时回退,利用前缀信息
 | 
			
		||||
    if s[i] == p[j]:
 | 
			
		||||
        j += 1
 | 
			
		||||
    if j == m:
 | 
			
		||||
        // 找到一次 Occurrence,起始下标 = i - m + 1
 | 
			
		||||
        return i - m + 1   // 若只要第一次出现
 | 
			
		||||
        // 若想继续找下一个,可 j = pi[j-1]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
关键:当发生失配时,我们并不是把 `j` 置为 0,而是设置为 `pi[j-1]`(可能为 0 或更小),这样保证不会重新比较已经确认匹配过的字符,从而节省工作。
 | 
			
		||||
 | 
			
		||||
# 5) 小例子演示(一步步算 `pi`,并演示一次匹配失配后的回退)
 | 
			
		||||
 | 
			
		||||
**构造 `pi` 的例子**: `p = "ababaca"`(如上)
 | 
			
		||||
我们已经给出 `pi = [0,0,1,2,3,0,1]`。在 i=5 时(字符 `c`),之前 j=3(表示已匹配 `"aba"`),但 `p[5] != p[3]`,于是回退 `j = pi[2] = 1`,再比较,若仍不等继续回退... 这就是 `while j>0` 的意义。
 | 
			
		||||
 | 
			
		||||
**匹配中的回退示意**:
 | 
			
		||||
取 `s = "ababcababaca"`, `p = "ababaca"`。
 | 
			
		||||
匹配到 `i = 4` 时,可能出现失配(示例中 `s[4] = c`,`p[4] = a`),我们不用把 `i` 回到 `i - j + 1 + 1` 再重新从 `j=0` 开始,而是把 `j` 设为 `pi[j-1]`(例如从 4 回退到 2、再回退到 0),然后继续用当前 `i` 与新 `j` 比较 —— 节省了重复比较已经确认的前缀。
 | 
			
		||||
 | 
			
		||||
# 6) 复杂度与空间
 | 
			
		||||
 | 
			
		||||
* 构建 `pi`:O(m) 时间,O(m) 空间(存 `pi`)。
 | 
			
		||||
* KMP 匹配(一次完整查找):O(n) 时间(匹配阶段),总共 O(n + m)。
 | 
			
		||||
* 常数空间(除了 `pi` 数组以外)。
 | 
			
		||||
 | 
			
		||||
# 7) 关于 `next` / `pi` 的下标约定(容易混淆)
 | 
			
		||||
 | 
			
		||||
* 我常用的 `pi[i]` 是长度(0-based)。
 | 
			
		||||
* 有些题目/实现中会直接把 `next[j]` 当作“当在模式的第 j 个位置失配时,下次应该从模式的哪个位置继续匹配”的值。
 | 
			
		||||
 | 
			
		||||
  * 若问题给的 `j` 是 1-based 的位置,你会看到 `next[j] = pi[j-1] + 1`(把长度变成“1-based 的位置”)。
 | 
			
		||||
  * 若问题给的 `j` 是 0-based 的位置或要求输出 `pi[j-1]`,你就按 `pi[j-1]` 输出(这是你之前题目里的做法:`j = m/4, 2m/4, 3m/4`,输出 `pi[j-1]`)。
 | 
			
		||||
    总结:**遇到输出/题意时先确认下标是 0-based 还是 1-based,再决定是否要 +1 的位移**。
 | 
			
		||||
 | 
			
		||||
# 8) 对你之前样例的说明(与题目要求的映射)
 | 
			
		||||
 | 
			
		||||
你之前的样例:`p = "abcabcabcabc"`, `m = 12`。
 | 
			
		||||
计算 `pi`(0-based)会得到:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
pi = [0,0,0,1,2,3,4,5,6,7,8,9]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
题里要求输出 `j = m/4, 2m/4, 3m/4` 处的值(以他们的约定,要输出 `next[j]` 或 `f[j-1]+1`),在我们用 0-based `pi` 的实现里对应的就是 `pi[j-1]`:
 | 
			
		||||
 | 
			
		||||
* j = 3 -> 输出 `pi[2] = 0`
 | 
			
		||||
* j = 6 -> 输出 `pi[5] = 3`
 | 
			
		||||
* j = 9 -> 输出 `pi[8] = 6`
 | 
			
		||||
  正好是样例 `0 3 6`。
 | 
			
		||||
 | 
			
		||||
# 9) 参考实现(C++,带注释 —— 与之前题目输出约定一致)
 | 
			
		||||
 | 
			
		||||
```cpp
 | 
			
		||||
#include <bits/stdc++.h>
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
// 构造 pi 数组(0-based)
 | 
			
		||||
vector<int> buildPi(const string &p) {
 | 
			
		||||
    int m = (int)p.size();
 | 
			
		||||
    vector<int> pi(m, 0);
 | 
			
		||||
    int j = 0;
 | 
			
		||||
    for (int i = 1; i < m; ++i) {
 | 
			
		||||
        while (j > 0 && p[i] != p[j]) j = pi[j - 1];
 | 
			
		||||
        if (p[i] == p[j]) ++j;
 | 
			
		||||
        pi[i] = j;
 | 
			
		||||
    }
 | 
			
		||||
    return pi;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// KMP 搜索,返回 p 在 s 中首次出现的起始下标(若无返回 -1)
 | 
			
		||||
int kmpSearchFirst(const string &s, const string &p, const vector<int> &pi) {
 | 
			
		||||
    int n = (int)s.size(), m = (int)p.size();
 | 
			
		||||
    int j = 0;
 | 
			
		||||
    for (int i = 0; i < n; ++i) {
 | 
			
		||||
        while (j > 0 && s[i] != p[j]) j = pi[j - 1];
 | 
			
		||||
        if (s[i] == p[j]) ++j;
 | 
			
		||||
        if (j == m) {
 | 
			
		||||
            return i - m + 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
    ios::sync_with_stdio(false);
 | 
			
		||||
    cin.tie(nullptr);
 | 
			
		||||
 | 
			
		||||
    string s, p;
 | 
			
		||||
    // 读两行(主串和模式串)
 | 
			
		||||
    if (!getline(cin, s)) return 0;
 | 
			
		||||
    if (!getline(cin, p)) return 0;
 | 
			
		||||
 | 
			
		||||
    int m = (int)p.size();
 | 
			
		||||
    vector<int> pi = buildPi(p);
 | 
			
		||||
 | 
			
		||||
    // j1, j2, j3 分别为 m/4, 2m/4, 3m/4(整除向下)
 | 
			
		||||
    int j1 = m / 4;
 | 
			
		||||
    int j2 = 2 * m / 4;
 | 
			
		||||
    int j3 = 3 * m / 4;
 | 
			
		||||
 | 
			
		||||
    auto getVal = [&](int j) -> int {
 | 
			
		||||
        if (j <= 0) return 0;        // 若 j==0(极小 m 的情况),按题意可以返回 0
 | 
			
		||||
        return pi[j - 1];           // 输出 pi[j-1]
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    cout << getVal(j1) << " " << getVal(j2) << " " << getVal(j3) << " \n";
 | 
			
		||||
    cout << kmpSearchFirst(s, p, pi) << "\n";
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
这段代码与我们上面的理论一致:`pi[i]` 存最长相等前后缀长度;对于题里要求的 “在模式串第 j 处失配后下一次匹配的位置” 输出 `pi[j-1]`(当把 `j` 看成从 1 开始的位置时)。我在 `getVal` 中也对 `j <= 0` 做了保护,避免越界(实际在线测评一般 `m >= 4`,但加上保险更稳健)。
 | 
			
		||||
 | 
			
		||||
# 10) 小结与常见坑
 | 
			
		||||
 | 
			
		||||
* 核心在于 **前缀函数 `pi`** 的正确理解和构造;匹配阶段使用 `j = pi[j-1]` 来回退。
 | 
			
		||||
* 小心 **下标约定**(0-based vs 1-based):很多题把 `next` 定义为 `pi[j-1]` 或 `pi[j-1]+1`,看清题目要求再输出。
 | 
			
		||||
* 边界情况:`m = 0` 或 `m` 非常小要注意越界检查。(通常题目会保证 `m >= 1`。)
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
如果你愿意我可以:
 | 
			
		||||
 | 
			
		||||
* 把上面的 C++ 程序逐步在一个具体例子上“手动运行”并显示每一步 `i, j, pi` 的变化,帮助你更直观地理解;或
 | 
			
		||||
* 给出一个 Python 版本;或
 | 
			
		||||
* 讲讲 KMP 的一些变体(比如计算全部出现位置、在线流式匹配、或与 Z 算法的比较)。
 | 
			
		||||
 | 
			
		||||
你想先看哪一种?
 | 
			
		||||
							
								
								
									
										108
									
								
								Exercise/Homework1/A 单链表基本操作.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								Exercise/Homework1/A 单链表基本操作.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
#include <iostream>
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
struct Node {
 | 
			
		||||
    T element;
 | 
			
		||||
    Node<T>* next;
 | 
			
		||||
    Node(const T& e, Node<T>* n = nullptr) : element(e), next(n) {}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class Chain {
 | 
			
		||||
private:
 | 
			
		||||
    Node<T>* head;
 | 
			
		||||
    int size;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    Chain() : head(nullptr), size(0) {}
 | 
			
		||||
 | 
			
		||||
    ~Chain() {
 | 
			
		||||
        while (head != nullptr) {
 | 
			
		||||
            Node<T>* tmp = head;
 | 
			
		||||
            head = head->next;
 | 
			
		||||
            delete tmp;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void insert(int pos, const T& e) {
 | 
			
		||||
        if (pos < 0 || pos > size) {
 | 
			
		||||
            throw invalid_argument("Invalid insert position");
 | 
			
		||||
        }
 | 
			
		||||
        if (pos == 0) {
 | 
			
		||||
            head = new Node<T>(e, head);
 | 
			
		||||
        } else {
 | 
			
		||||
            Node<T>* prev = head;
 | 
			
		||||
            for (int i = 0; i < pos - 1; i++) {
 | 
			
		||||
                prev = prev->next;
 | 
			
		||||
            }
 | 
			
		||||
            prev->next = new Node<T>(e, prev->next);
 | 
			
		||||
        }
 | 
			
		||||
        size++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void erase(int pos) {
 | 
			
		||||
        if (pos < 0 || pos >= size) {
 | 
			
		||||
            throw invalid_argument("Invalid erase position");
 | 
			
		||||
        }
 | 
			
		||||
        Node<T>* toDelete;
 | 
			
		||||
        if (pos == 0) {
 | 
			
		||||
            toDelete = head;
 | 
			
		||||
            head = head->next;
 | 
			
		||||
        } else {
 | 
			
		||||
            Node<T>* prev = head;
 | 
			
		||||
            for (int i = 0; i < pos - 1; i++) {
 | 
			
		||||
                prev = prev->next;
 | 
			
		||||
            }
 | 
			
		||||
            toDelete = prev->next;
 | 
			
		||||
            prev->next = toDelete->next;
 | 
			
		||||
        }
 | 
			
		||||
        delete toDelete;
 | 
			
		||||
        size--;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void print() const {
 | 
			
		||||
        Node<T>* cur = head;
 | 
			
		||||
        while (cur != nullptr) {
 | 
			
		||||
            cout << cur->element << " ";
 | 
			
		||||
            cur = cur->next;
 | 
			
		||||
        }
 | 
			
		||||
        cout << endl;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int main(){
 | 
			
		||||
     int n;
 | 
			
		||||
    cin >> n;
 | 
			
		||||
    Chain<int> list;
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < n; i++) {
 | 
			
		||||
        int x;
 | 
			
		||||
        cin >> x;
 | 
			
		||||
        try {
 | 
			
		||||
            list.insert(i, x);
 | 
			
		||||
        }
 | 
			
		||||
        catch(const invalid_argument& e) {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int m;
 | 
			
		||||
    cin >> m;
 | 
			
		||||
    for (int i = 0; i < m; i++) {
 | 
			
		||||
        int op, k, d;
 | 
			
		||||
        cin >> op;
 | 
			
		||||
        if (op == 0) {
 | 
			
		||||
            cin >> k >> d;
 | 
			
		||||
            try { 
 | 
			
		||||
                list.insert(k, d);
 | 
			
		||||
            } catch(const invalid_argument& e) {}
 | 
			
		||||
        } else if (op == 1) {
 | 
			
		||||
            cin >> k;
 | 
			
		||||
            try {
 | 
			
		||||
                list.erase(k - 1);
 | 
			
		||||
            } catch(const invalid_argument& e) {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    list.print();
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								Exercise/Homework1/B 栈的实现及基本操作.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								Exercise/Homework1/B 栈的实现及基本操作.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <limits>
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class Stack {
 | 
			
		||||
    T* element;
 | 
			
		||||
    int length;
 | 
			
		||||
    int stackTop;
 | 
			
		||||
 | 
			
		||||
    const T INF = std::numeric_limits<T>::max();
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    Stack(int l) {
 | 
			
		||||
        stackTop = -1;
 | 
			
		||||
        length = l;
 | 
			
		||||
        element = new T[l];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~Stack() {
 | 
			
		||||
        delete[] element;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    T& top() { 
 | 
			
		||||
        if (stackTop == -1) throw std::invalid_argument("No Top Element.");
 | 
			
		||||
        return element[stackTop];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void pop() {
 | 
			
		||||
        if (stackTop == -1) throw std::invalid_argument("No Top Element.");
 | 
			
		||||
        stackTop--;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void push(const T& e) {
 | 
			
		||||
        if (stackTop == length - 1) {
 | 
			
		||||
            T* tmp = new T[2 * length];
 | 
			
		||||
            for (int i = 0; i < length; i++) {
 | 
			
		||||
                tmp[i] = element[i];
 | 
			
		||||
            }
 | 
			
		||||
            delete[] element;
 | 
			
		||||
            element = tmp;
 | 
			
		||||
            length *= 2;
 | 
			
		||||
        }
 | 
			
		||||
        stackTop++;
 | 
			
		||||
        element[stackTop] = e;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool empty() const {
 | 
			
		||||
        return stackTop == -1;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
    int n;
 | 
			
		||||
    cin >> n;
 | 
			
		||||
    Stack<int> s(n);
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < n; i++) {
 | 
			
		||||
        int p;
 | 
			
		||||
        cin >> p;
 | 
			
		||||
        switch (p) {
 | 
			
		||||
            case 0: {
 | 
			
		||||
                if (!s.empty()) {
 | 
			
		||||
                    cout << s.top() << endl;
 | 
			
		||||
                    s.pop();
 | 
			
		||||
                } else {
 | 
			
		||||
                    cout << "invalid" << endl;
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case 1: {
 | 
			
		||||
                int val;
 | 
			
		||||
                cin >> val;
 | 
			
		||||
                s.push(val);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            default:
 | 
			
		||||
                return 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										94
									
								
								Exercise/Homework1/C 队列的实现及基本操作.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								Exercise/Homework1/C 队列的实现及基本操作.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <limits>
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
template <class T>
 | 
			
		||||
class Queue {
 | 
			
		||||
private:
 | 
			
		||||
    T* element;
 | 
			
		||||
    int begin;
 | 
			
		||||
    int end;
 | 
			
		||||
    int capacity;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    static constexpr T INF = numeric_limits<T>::max();
 | 
			
		||||
 | 
			
		||||
    Queue(int len) : begin(0), end(0), capacity(len) {
 | 
			
		||||
        element = new T[len];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ~Queue() {
 | 
			
		||||
        delete[] element;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void push(const T& e) {
 | 
			
		||||
        if (end == capacity) {
 | 
			
		||||
            int newCap = capacity * 2;
 | 
			
		||||
            T* tmp = new T[newCap];
 | 
			
		||||
            for (int i = begin; i < end; i++) {
 | 
			
		||||
                tmp[i - begin] = element[i];
 | 
			
		||||
            }
 | 
			
		||||
            end -= begin;
 | 
			
		||||
            begin = 0;
 | 
			
		||||
            capacity = newCap;
 | 
			
		||||
            delete[] element;
 | 
			
		||||
            element = tmp;
 | 
			
		||||
        }
 | 
			
		||||
        element[end++] = e;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void pop() {
 | 
			
		||||
        if (empty()) throw std::invalid_argument("Queue is empty.");
 | 
			
		||||
        begin++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    T& front() {
 | 
			
		||||
        if (empty()) throw std::invalid_argument("Queue is empty.");
 | 
			
		||||
        return element[begin];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    T& back() {
 | 
			
		||||
        if (empty()) throw std::invalid_argument("Queue is empty.");
 | 
			
		||||
        return element[end - 1];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool empty() const {
 | 
			
		||||
        return begin == end;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int size() const {
 | 
			
		||||
        return end - begin;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
    int n;
 | 
			
		||||
    cin >> n;
 | 
			
		||||
    Queue<int> q(n);
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < n; i++) {
 | 
			
		||||
        int p;
 | 
			
		||||
        cin >> p;
 | 
			
		||||
        switch (p) {
 | 
			
		||||
            case 0: {
 | 
			
		||||
                if (q.empty()) {
 | 
			
		||||
                    cout << "invalid" << endl;
 | 
			
		||||
                } else {
 | 
			
		||||
                    cout << q.front() << endl;
 | 
			
		||||
                    q.pop();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            case 1: {
 | 
			
		||||
                int val;
 | 
			
		||||
                cin >> val;
 | 
			
		||||
                q.push(val);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            default: {
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								Exercise/Homework1/D KMP模式匹配算法.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								Exercise/Homework1/D KMP模式匹配算法.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
using namespace std;
 | 
			
		||||
vector<int> build_next(const string& p) {
 | 
			
		||||
    int n = p.size();                 // 模式串长度
 | 
			
		||||
    vector<int> nxt(n, 0);            // 存放 next 数组,初始化全 0
 | 
			
		||||
 | 
			
		||||
    for (int i = 1, j = 0; i < n; i++) {
 | 
			
		||||
        // --- step1: 如果当前字符不匹配,回退 j ---
 | 
			
		||||
        while (j > 0 && p[i] != p[j]) 
 | 
			
		||||
            j = nxt[j - 1];           // j 回退到上一个最长前后缀的长度,采用动态规划思想
 | 
			
		||||
 | 
			
		||||
        // --- step2: 如果匹配成功,j 前进 ---
 | 
			
		||||
        if (p[i] == p[j]) 
 | 
			
		||||
            j++;
 | 
			
		||||
 | 
			
		||||
        // --- step3: 记录当前前缀的 next 值 ---
 | 
			
		||||
        nxt[i] = j;                   // 表示 p[0..i] 的最长公共前后缀长度
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return nxt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
vector<int> next_arr(const string& x) {
 | 
			
		||||
    int n = x.size();
 | 
			
		||||
    vector<int> tmp(n, 0);
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < n; i++) {       
 | 
			
		||||
        int j = 0;
 | 
			
		||||
        for (int len = i; len > 0; len--) {
 | 
			
		||||
            if (x.substr(0, len) == x.substr(i - len + 1, len)) {
 | 
			
		||||
                j = len;
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        tmp[i] = j;
 | 
			
		||||
    }
 | 
			
		||||
    return tmp;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int main(){
 | 
			
		||||
    string mod, input;
 | 
			
		||||
    cin >> input >> mod;
 | 
			
		||||
    vector<int> next = build_next(mod);
 | 
			
		||||
    int n = mod.size();
 | 
			
		||||
    int pi = 0, pm = 0;
 | 
			
		||||
    cout << next[n / 4] << " " << next[n / 2] << " " << next[3 * n / 4] << endl;
 | 
			
		||||
    while (pi < input.size()) {
 | 
			
		||||
        if (input[pi] == mod[pm]) {
 | 
			
		||||
            pi++;
 | 
			
		||||
            pm++;
 | 
			
		||||
            if (pm == mod.size()) {
 | 
			
		||||
                cout << pi - pm << " ";   // 匹配位置(起始下标)
 | 
			
		||||
                return 0;
 | 
			
		||||
                pm = next[pm - 1];       // 回退
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (pm > 0)
 | 
			
		||||
                pm = next[pm - 1];       // 回退
 | 
			
		||||
            else
 | 
			
		||||
                pi++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    cout << -1 << endl;
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qwerababcabcabcabcdaabcabhlk
 | 
			
		||||
							
								
								
									
										167
									
								
								Exercise/Homework1/readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										167
									
								
								Exercise/Homework1/readme.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,167 @@
 | 
			
		||||
``` html
 | 
			
		||||
A 单链表基本操作
 | 
			
		||||
分数 10
 | 
			
		||||
作者 朱允刚
 | 
			
		||||
单位 吉林大学
 | 
			
		||||
请编写程序实现单链表插入、删除结点等基本算法。给定一个单链表和一系列插入、删除结点的操作序列,输出实施上述操作后的链表。单链表数据域值为整数。
 | 
			
		||||
 | 
			
		||||
输入格式:
 | 
			
		||||
输入第1行为1个正整数n,表示当前单链表长度;第2行为n个空格间隔的整数,为该链表n个元素的数据域值。第3行为1个正整数m,表示对该链表施加的操作数量;接下来m行,每行表示一个操作,为2个或3个整数,格式为0 k d或1 k。0 k d表示在链表第k个结点后插入一个数据域值为d的结点,若k=0则表示表头插入。1 k表示删除链表中第k个结点,此时k不能为0。注:操作序列中若含有不合法的操作(如在长度为5的链表中删除第8个结点、删除第0个结点等),则忽略该操作。n和m不超过100000。
 | 
			
		||||
 | 
			
		||||
输出格式:
 | 
			
		||||
输出为一行整数,表示实施上述m个操作后的链表,每个整数后一个空格。输入数据保证结果链表不空。 
 | 
			
		||||
 | 
			
		||||
输入样例:
 | 
			
		||||
5
 | 
			
		||||
1 2 3 4 5
 | 
			
		||||
5
 | 
			
		||||
0 2 8
 | 
			
		||||
0 9 6
 | 
			
		||||
0 0 7
 | 
			
		||||
1 0 
 | 
			
		||||
1 6
 | 
			
		||||
 | 
			
		||||
输出样例:
 | 
			
		||||
7 1 2 8 3 5 
 | 
			
		||||
 | 
			
		||||
代码长度限制
 | 
			
		||||
16 KB
 | 
			
		||||
Python (python3)
 | 
			
		||||
时间限制
 | 
			
		||||
1000 ms
 | 
			
		||||
内存限制
 | 
			
		||||
256 MB
 | 
			
		||||
Java (javac)
 | 
			
		||||
时间限制
 | 
			
		||||
5000 ms
 | 
			
		||||
内存限制
 | 
			
		||||
256 MB
 | 
			
		||||
其他编译器
 | 
			
		||||
时间限制
 | 
			
		||||
100 ms
 | 
			
		||||
内存限制
 | 
			
		||||
10 MB
 | 
			
		||||
栈限制
 | 
			
		||||
8192 KB
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
B 栈的实现及基本操作
 | 
			
		||||
分数 10
 | 
			
		||||
作者 朱允刚
 | 
			
		||||
单位 吉林大学
 | 
			
		||||
给定一个初始为空的栈和一系列压栈、弹栈操作,请编写程序输出每次弹栈的元素。栈的元素值均为整数。本题不允许使用stack、queue、vector等STL容器。
 | 
			
		||||
 | 
			
		||||
输入格式:
 | 
			
		||||
输入第1行为1个正整数n,表示操作个数;接下来n行,每行表示一个操作,格式为1 d或0。1 d表示将整数d压栈,0表示弹栈。n不超过20000。
 | 
			
		||||
 | 
			
		||||
输出格式:
 | 
			
		||||
按顺序输出每次弹栈的元素,每个元素一行。若某弹栈操作不合法(如在栈空时弹栈),则对该操作输出invalid。 
 | 
			
		||||
 | 
			
		||||
输入样例:
 | 
			
		||||
7
 | 
			
		||||
1 1
 | 
			
		||||
1 2
 | 
			
		||||
0
 | 
			
		||||
0
 | 
			
		||||
0
 | 
			
		||||
1 3
 | 
			
		||||
0
 | 
			
		||||
 | 
			
		||||
输出样例:
 | 
			
		||||
2
 | 
			
		||||
1
 | 
			
		||||
invalid
 | 
			
		||||
3
 | 
			
		||||
 | 
			
		||||
代码长度限制
 | 
			
		||||
16 KB
 | 
			
		||||
时间限制
 | 
			
		||||
50 ms
 | 
			
		||||
内存限制
 | 
			
		||||
10 MB
 | 
			
		||||
栈限制
 | 
			
		||||
131000 KB
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
C 队列的实现及基本操作
 | 
			
		||||
分数 10
 | 
			
		||||
作者 朱允刚
 | 
			
		||||
单位 吉林大学
 | 
			
		||||
给定一个初始为空的队列和一系列入队、出队操作,请编写程序输出每次出队的元素。队列的元素值均为整数。
 | 
			
		||||
备注:本题不允许使用stack、queue、vector等STL容器。
 | 
			
		||||
 | 
			
		||||
输入格式:
 | 
			
		||||
输入第1行为1个正整数n,表示操作个数;接下来n行,每行表示一个操作,格式为1 d或0。1 d表示将整数d入队,0表示出队。n不超过20000。
 | 
			
		||||
 | 
			
		||||
输出格式:
 | 
			
		||||
按顺序输出每次出队的元素,每个元素一行。若某出队操作不合法(如在队列空时出队),则对该操作输出invalid。 
 | 
			
		||||
 | 
			
		||||
输入样例:
 | 
			
		||||
7
 | 
			
		||||
1 1
 | 
			
		||||
1 2
 | 
			
		||||
0
 | 
			
		||||
0
 | 
			
		||||
0
 | 
			
		||||
1 3
 | 
			
		||||
0
 | 
			
		||||
 | 
			
		||||
输出样例:
 | 
			
		||||
1
 | 
			
		||||
2
 | 
			
		||||
invalid
 | 
			
		||||
3
 | 
			
		||||
 | 
			
		||||
代码长度限制
 | 
			
		||||
16 KB
 | 
			
		||||
时间限制
 | 
			
		||||
50 ms
 | 
			
		||||
内存限制
 | 
			
		||||
20 MB
 | 
			
		||||
栈限制
 | 
			
		||||
8192 KB
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
D KMP模式匹配算法
 | 
			
		||||
分数 10
 | 
			
		||||
作者 朱允刚
 | 
			
		||||
单位 吉林大学
 | 
			
		||||
给定目标串s和模式串p,编写程序使用KMP算法进行模式匹配,计算p在s中首次出现的位置,若p不在s中则输出−1。字符串下标从0开始。
 | 
			
		||||
 | 
			
		||||
输入格式:
 | 
			
		||||
输入为2行,第1行为主串s,第2行为模式串p。主串和模式串长度不超过10 
 | 
			
		||||
5
 | 
			
		||||
 。
 | 
			
		||||
 | 
			
		||||
输出格式:
 | 
			
		||||
输出为2行,第1行为3个整数,表示分别在模式串p的p 
 | 
			
		||||
m/4
 | 
			
		||||
 | 
			
		||||
 ,p 
 | 
			
		||||
2m/4
 | 
			
		||||
 | 
			
		||||
 ,p 
 | 
			
		||||
3m/4
 | 
			
		||||
 | 
			
		||||
 处失配后,模式串下一次匹配的位置(即next[j]或f[j−1]+1的值,j=m/4,2m/4,3m/4),每个整数后一个空格,m表示模式串p的长度;第2行为一个整数,表示p在s中首次出现的位置,若p不在s中则输出−1。
 | 
			
		||||
 | 
			
		||||
输入样例:
 | 
			
		||||
qwerababcabcabcabcdaabcabhlk
 | 
			
		||||
abcabcabcabc
 | 
			
		||||
 | 
			
		||||
输出样例:
 | 
			
		||||
0 3 6 
 | 
			
		||||
6
 | 
			
		||||
代码长度限制
 | 
			
		||||
16 KB
 | 
			
		||||
时间限制
 | 
			
		||||
20 ms
 | 
			
		||||
内存限制
 | 
			
		||||
10 MB
 | 
			
		||||
栈限制
 | 
			
		||||
8192 KB
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
		Reference in New Issue
	
	Block a user