字符串檢索算法:KMP和Trie樹

目錄

1.引言

2.KMP算法

3.Trie樹

3.1.簡介

3.2.Trie樹的應用場景

3.3.復雜度分析

3.4.Trie 樹的優缺點

3.5.示例


1.引言

????????字符串匹配,給定一個主串?S?和一個模式串?P,判斷?P?是否是?S?的子串,即找到?P?在?S?中第一次出現的位置。暴力匹配的思路是:從主串?S?的每個位置開始,逐個字符與模式串?P?比較。若匹配失敗,主串指針回退到起始位置的下一個位置,重新開始匹配。?時間復雜度:最壞情況下為?O(n×m)(n?為主串長度,m?為模式串長度)。缺陷:當模式串存在重復前綴或后綴時,重復比較了很多已知信息,效率低下。于是就引出了KMP算法。

2.KMP算法

? ? ? ? 要理解KMP算法,首先要搞清楚真前綴與真后綴。在一個字符串中,真前綴是指除了最后一個字符外,一個字符串的頭部連續的若干字符;真后綴是指除了第一個字符外,一個字符串的尾部連續的若干字符。舉個例子:

????????字符串:"ABCDABD"

????????真前綴:"A"、"AB"、"ABC"、"ABCD"、"ABCDA"、"ABCDAB"

????????真后綴:"BCDABD"、"CDABD"、"DABD"、"ABD"、"BD"、"D"

遞推計算next數組

????????next 數組的求解基于“真前綴”和“真后綴”,即next[i]等于P[0]...P[i - 1]最長的相同真前后綴的長度(首先設置next[0]=-1,邊界條件)。我們以表格為例:

i01234567
模式串ABCDABD'\0'
next[ i ]-10000120
  1. i = 0,對于模式串的首字符,我們統一為next[0] = -1
  2. i = 1,前面的字符串為A,其最長相同真前后綴長度為 0,即next[1] = 0
  3. i = 2,前面的字符串為AB,其最長相同真前后綴長度為 0,即next[2] = 0
  4. i = 3,前面的字符串為ABC,其最長相同真前后綴長度為 0,即next[3] = 0
  5. i = 4,前面的字符串為ABCD,其最長相同真前后綴長度為 0,即next[4] = 0
  6. i = 5,前面的字符串為ABCDA,其最長相同真前后綴為A,即next[5] = 1
  7. i = 6,前面的字符串為ABCDAB,其最長相同真前后綴為AB,即next[6] = 2
  8. i = 7,前面的字符串為ABCDABD,其最長相同真前后綴長度為 0,即next[7] = 0

????????那么,為什么根據最長相同真前后綴的長度就可以實現在不匹配情況下的跳轉呢?舉個代表性的例子:假如i = 6時不匹配,此時我們是知道其位置前的字符串為ABCDAB,仔細觀察這個字符串,首尾都有一個AB,既然在i = 6處的 D 不匹配,我們為何不直接把i = 2處的 C 拿過來繼續比較呢,因為都有一個AB啊,而這個AB就是ABCDAB的最長相同真前后綴,其長度 2 正好是跳轉的下標位置。

????????思路如此簡單,接下來就是代碼實現了,如下:

// 生成Next數組, 示例:“GTGTGCF”
std::vector<int> buildNext(const std::string& pattern) {std::vector<int> next(pattern.size(), 0);int j = 0;for (int i = 2; i < pattern.length(); i++) {while (j != 0 && pattern[j] != pattern[i - 1]) {//從next[i+1]的求解回溯到 next[j]j = next[j];}if (pattern[j] == pattern[i - 1]) {j++;}next[i] = j;}return next;
}
int kmpSearch(const std::string& text, const std::string& pattern) {//預處理,生成next數組std::vector<int> next(std::move(buildNext(pattern)));int j = 0;//主循環,遍歷主串字符for (int i = 0; i < text.length(); i++) {while (j > 0 && text[i] != pattern[j]) {//遇到壞字符時,查詢next數組并改變模式串的起點j = next[j];}if (text[i] == pattern[j]) {j++;}if (j == pattern.length()) {//匹配成功,返回下標return i - pattern.length() + 1;}}return  -1;
}

復雜度分析:

  • 時間復雜度
    • 構建?next?數組:O(m)(每個字符最多被訪問兩次)。
    • 匹配過程:O(n)(主串指針?i?僅遞增,不回退)。
    • 總復雜度:O(n+m),優于暴力匹配的?O(n×m)。
  • 空間復雜度:O(m)(存儲?next?數組)。

3.Trie樹

3.1.簡介

????????Trie樹,即前綴樹,又稱單詞查找樹,字典樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用于統計和排序大量的字符串(但不僅限于字符串),所以經常被搜索引擎系統用于文本詞頻統計。

??Trie樹的核心思想是空間換時間,利用字符串的公共前綴來降低查詢時間的開銷以達到提高效率的目的。 它的優點是:最大限度地減少無謂的字符串比較,查詢效率比哈希表高。

它有3個基本性質:

  1. 根節點不包含字符,除根節點外每一個節點都只包含一個字符。
  2. 從根節點到某一節點,路徑上經過的字符連接起來,為該節點對應的字符串。
  3. 每個節點的所有子節點包含的字符都不相同。

舉一個例子。給出一組單詞,inn, int, at, age, adv, ant, 我們可以得到下面的Trie:

3.2.Trie樹的應用場景

字符串檢索,詞頻統計,搜索引擎的熱門查詢

? ? ? ? trie樹在大數據查找和檢索方面具有獨特的優勢,不過就是要求內存比較高,不過在沒有內存限制的情況不適為一種好的方式,如:(節選自此文:海量數據處理面試題集錦與Bit-map詳解)

a)有一個1G大小的一個文件,里面每一行是一個詞,詞的大小不超過16字節,內存限制大小是1M。返回頻數最高的100個詞。

b)?1000萬字符串,其中有些是重復的,需要把重復的全部去掉,保留沒有重復的字符串。請怎么設計和實現?

c)尋找熱門查詢:搜索引擎會通過日志文件把用戶每次檢索使用的所有檢索串都記錄下來,每個查詢串的長度為1-255字節。假設目前有一千萬個記錄,這些查詢串的重復讀比較高,雖然總數是1千萬,但是如果去除重復和,不超過3百萬個。一個查詢串的重復度越高,說明查詢它的用戶越多,也就越熱門。請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。

d)一個文本文件,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前10個詞,請給出思想,給出時間復雜度分析

e)?給出一個詞典,其中的單詞為不良單詞。單詞均為小寫字母。再給出一段文本,文本的每一行也由小寫字母構成。判斷文本中是否含有任何不良單詞。例如,若rob是不良單詞,那么文本problem含有不良單詞。

(1) 請描述你解決這個問題的思路;
(2) 請給出主要的處理流程,算法,以及算法的復雜度。

字符串最長公共前綴

????????Trie樹利用多個字符串的公共前綴來節省存儲空間,反之,當我們把大量字符串存儲到一棵trie樹上時,我們可以快速得到某些字符串的公共前綴。舉例:

? ? ? 給出N 個小寫英文字母串,以及Q 個詢問,即詢問某兩個串的最長公共前綴的長度是多少. ?解決方案:

? ? ? ? 首先對所有的串建立其對應的字母樹。此時發現,對于兩個串的最長公共前綴的長度即它們所在結點的公共祖先個數,于是,問題就轉化為了離線 ?(Offline)的最近公共祖先(Least Common Ancestor,簡稱LCA)問題。

? ? ? ?而最近公共祖先問題同樣是一個經典問題,可以用下面幾種方法:

? ? ? ? 1.?利用并查集(Disjoint Set),可以采用采用經典的Tarjan 算法;

? ? ? ? 2. 求出字母樹的歐拉序列(Euler Sequence )后,就可以轉為經典的最小值查詢(Range Minimum Query,簡稱RMQ)問題了;

3.3.復雜度分析

  • 插入操作:時間復雜度為 O (m),這里的 m 指的是字符串的長度。
  • 查找操作:時間復雜度同樣為 O (m)。
  • 空間復雜度:空間復雜度為 O (n),n 表示所有字符串中不同字符的總數,這一特點使得 Trie 樹在處理大量字符串時非常高效。

3.4.Trie 樹的優缺點

優點

  • 高效前綴匹配:快速查找所有以某前綴開頭的字符串(如搜索提示)。
  • 避免重復存儲:共享公共前綴,節省空間。
  • 時間復雜度穩定:插入、查詢、刪除的時間復雜度均為?O(n)(n 為字符串長度)。

缺點

  • 空間開銷大:每個字符占用一個節點,可能浪費內存(尤其是字符集大時)。
  • 實現復雜:需要處理動態節點分配和指針操作。

3.5.示例

示例一:一個字符串類型的數組arr1,另一個字符串類型的數組arr2。

  • arr2中有哪些字符串,是arr1中出現的?請打印
  • arr2中有哪些字符串,是作為arr1中某個字符串前綴出現的?請打印
  • arr2中有哪些字符串,是作為arr1中某個字符串前綴出現的?請打印arr2中出現次數最大的前綴。

實現代碼如下:

#include <iostream>
#include <string>
#include <string.h>using namespace std;
const int MaxBranchNum = 26;//可以擴展class TrieNode{
public:string word;int path;  //該字符被劃過多少次,用以統計以該字符串作為前綴的字符串的個數int End; //以該字符結尾的字符串TrieNode* nexts[MaxBranchNum];TrieNode(){word = "";path = 0;End = 0;memset(nexts,NULL,sizeof(TrieNode*) * MaxBranchNum);}};class TrieTree{
private:TrieNode *root;
public:TrieTree();~TrieTree();//插入字符串strvoid insert(string str);//查詢字符串str是否出現過,并返回作為前綴幾次int search(string str);//刪除字符串strvoid Delete(string str);void destory(TrieNode* root);//打印樹中的所有節點void printAll();//打印以str作為前綴的單詞void printPre(string str);//按照字典順序輸出以root為根的所有單詞void Print(TrieNode* root);//返回以str為前綴的單詞的個數int prefixNumbers(string str);
};TrieTree::TrieTree()
{root = new TrieNode();
}TrieTree::~TrieTree()
{destory(root);
}void TrieTree::destory(TrieNode* root)
{if(root == nullptr)return ;for(int i=0;i<MaxBranchNum;i++){destory(root->nexts[i]);}delete root;root = nullptr;
}void TrieTree::insert(string str)
{if(str == "")return ;char buf[str.size()];strcpy(buf, str.c_str());TrieNode* node = root;int index = 0;for(int i=0; i<strlen(buf); i++){index = buf[i] - 'a';if(node->nexts[index] == nullptr){node->nexts[index] = new TrieNode();}node = node->nexts[index];node->path++;//有一條路徑劃過這個節點}node->End++;node->word = str;
}int TrieTree::search(string str)
{if(str == "")return 0;char buf[str.size()];strcpy(buf, str.c_str());TrieNode* node = root;int index = 0;for(int i=0;i<strlen(buf);i++){index = buf[i] - 'a';if(node->nexts[index] == nullptr){return 0;}node = node->nexts[index];}if(node != nullptr){return node->End;}else{return 0;}
}void TrieTree::Delete(string str)
{if(str == "")return ;char buf[str.size()];strcpy(buf, str.c_str());TrieNode* node = root;TrieNode* tmp;int index = 0;for(int i = 0 ; i<str.size();i++){index = buf[i] - 'a';tmp = node->nexts[index];if(--node->nexts[index]->path == 0){delete node->nexts[index];}node = tmp;}node->End--;
}int TrieTree::prefixNumbers(string str)
{if(str == "")return 0;char buf[str.size()];strcpy(buf, str.c_str());TrieNode* node = root;int index = 0;for(int i=0;i<strlen(buf);i++){index = buf[i] - 'a';if(node->nexts[index] == nullptr){return 0;}node = node->nexts[index];}return node->path;
}
void TrieTree::printPre(string str)
{if(str == "")return ;char buf[str.size()];strcpy(buf, str.c_str());TrieNode* node = root;int index = 0;for(int i=0;i<strlen(buf);i++){index = buf[i] - 'a';if(node->nexts[index] == nullptr){return ;}node = node->nexts[index];}Print(node);
}void TrieTree::Print(TrieNode* node)
{if(node == nullptr)return ;if(node->word != ""){cout<<node->word<<" "<<node->path<<endl;}for(int i = 0;i<MaxBranchNum;i++){Print(node->nexts[i]);}
}void TrieTree::printAll()
{Print(root);
}int main()
{cout << "Hello world!" << endl;TrieTree trie;string str = "li";cout<<trie.search(str)<<endl;trie.insert(str);cout<<trie.search(str)<<endl;trie.Delete(str);cout<<trie.search(str)<<endl;trie.insert(str);cout<<trie.search(str)<<endl;trie.insert(str);cout<<trie.search(str)<<endl;trie.Delete("li");cout<<trie.search(str)<<endl;trie.Delete("li");cout<<trie.search(str)<<endl;trie.insert("lia");trie.insert("lic");trie.insert("liab");trie.insert("liad");trie.Delete("lia");cout<<trie.search("lia")<<endl;cout<<trie.prefixNumbers("lia")<<endl;return 0;
}

示例二:實現?Trie 樹,包含插入、查找、前綴搜索和刪除功能。這個實現使用智能指針管理內存,確保內存安全。代碼如下:

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>class TrieNode {
public:std::unordered_map<char, std::unique_ptr<TrieNode>> children;bool is_end_of_word;TrieNode() : is_end_of_word(false) {}
};class Trie {
private:std::unique_ptr<TrieNode> root;// 輔助函數:遞歸刪除單詞bool remove(TrieNode* current, const std::string& word, int index) {if (index == word.length()) {if (!current->is_end_of_word)return false;current->is_end_of_word = false;return current->children.empty();}char ch = word[index];auto it = current->children.find(ch);if (it == current->children.end())return false;bool shouldDeleteCurrentNode = remove(it->second.get(), word, index + 1) && !it->second->is_end_of_word;if (shouldDeleteCurrentNode) {current->children.erase(ch);return current->children.empty();}return false;}public:Trie() : root(std::make_unique<TrieNode>()) {}// 插入單詞void insert(const std::string& word) {TrieNode* current = root.get();for (char ch : word) {if (!current->children.count(ch)) {current->children[ch] = std::make_unique<TrieNode>();}current = current->children[ch].get();}current->is_end_of_word = true;}// 查找單詞bool search(const std::string& word) const {const TrieNode* current = root.get();for (char ch : word) {auto it = current->children.find(ch);if (it == current->children.end())return false;current = it->second.get();}return current->is_end_of_word;}// 查找前綴bool startsWith(const std::string& prefix) const {const TrieNode* current = root.get();for (char ch : prefix) {auto it = current->children.find(ch);if (it == current->children.end())return false;current = it->second.get();}return true;}// 刪除單詞void deleteWord(const std::string& word) {remove(root.get(), word, 0);}
};// 使用示例
int main() {Trie trie;trie.insert("apple");std::cout << std::boolalpha;std::cout << trie.search("apple") << std::endl;   // 輸出: truestd::cout << trie.search("app") << std::endl;     // 輸出: falsestd::cout << trie.startsWith("app") << std::endl; // 輸出: truetrie.insert("app");std::cout << trie.search("app") << std::endl;     // 輸出: truetrie.deleteWord("apple");std::cout << trie.search("apple") << std::endl;   // 輸出: falsestd::cout << trie.search("app") << std::endl;     // 輸出: truereturn 0;
}

這個 C++ 實現具有以下特點:

  1. 內存安全:使用?std::unique_ptr?管理節點內存,避免內存泄漏
  2. 高效查找:利用?unordered_map?實現 O (1) 的子節點查找
  3. 完整功能:包含插入、查找、前綴搜索和刪除操作
  4. 遞歸刪除:刪除操作會自動清理不再使用的節點

你可以根據需要擴展這個實現,例如添加統計單詞數量、獲取所有以特定前綴開頭的單詞等功能。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/83044.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83044.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/83044.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

計算機組成原理:I/O

計算機組成:I/O I/O概述I/O系統構成I/O接口I/O端口兩種編址區分I/O數據傳送控制方式程序查詢方式獨占查詢中斷控制方式硬件判優法(向量中斷法)多重中斷嵌套DMA控制方式三種DMA方式DMA操作步驟內部異常和中斷異常和中斷的關系I/O概述 I/O系統構成 一個最基礎I/O系統的構成:CPU…

ssti模板注入學習

ssti模板注入原理 ssti模板注入是一種基于服務器的模板引擎的特性和漏洞產生的一種漏洞&#xff0c;通過將而已代碼注入模板中實現的服務器的攻擊 模板引擎 為什么要有模板引擎 在web開發中&#xff0c;為了使用戶界面與業務數據&#xff08;內容&#xff09;分離而產生的&…

NVMe簡介2

共分2部分&#xff0c;這里是第2部分。 NVMe數據結構 NVMe協議中規定每個提交命令的大小為64字節&#xff0c;完成命令大小為16字節&#xff0c;NVMe命令分為Admin和IO兩類&#xff0c;NVMe的數據塊組織方式有PRP和SGL兩種。提交命令的格式如圖5所示。 圖5 提交命令數據格 N…

高壓啟動電路--學習記錄

常見反激的啟動電路 優點&#xff1a;電路設計簡單&#xff0c;價格便宜 缺點&#xff1a;損壞大&#xff0c;輸入寬范圍的時候&#xff0c;為了保證低壓能正常啟動&#xff0c;啟動電阻阻值需要選小&#xff0c;那么高壓時損耗會非常大&#xff0c;設計的不好很容易在高壓時損…

VS打印printf、cout或者Qt的qDebug等傳出的打印信息

在vs中打印printf、cout或者Qt的qDebug等常見的打印信息有時也是必要的&#xff0c;簡單的敘述一下過程&#xff1a; 1、在vs中打開你的解決方案。 2、鼠標移動到你的項目名稱上&#xff0c;點擊鼠標右鍵&#xff0c;再點擊屬性&#xff0c;此刻會此項目的屬性頁。 3、在配置…

蒼穹外賣--新增菜品

1.需求分析和設計 產品原型 業務規則&#xff1a; 菜品名稱必須是唯一的 菜品必須屬于某個分類下&#xff0c;不能單獨存在 新增菜品時可以根據情況選擇菜品的口味 每個菜品必須對應一張圖片 接口設計&#xff1a; 根據類型查詢分類(已完成) 文件上傳 新增菜品 根據類型…

如何高效集成MySQL數據到金蝶云星空

MySQL數據集成到金蝶云星空&#xff1a;SC采購入庫-深圳天一-OK案例分享 在企業信息化建設中&#xff0c;數據的高效流轉和準確對接是實現業務流程自動化的關鍵。本文將聚焦于一個具體的系統對接集成案例——“SC采購入庫-深圳天一-OK”&#xff0c;詳細探討如何通過輕易云數據…

【springcloud學習(dalston.sr1)】使用Feign實現接口調用(八)

該系列項目整體介紹及源代碼請參照前面寫的一篇文章【springcloud學習(dalston.sr1)】項目整體介紹&#xff08;含源代碼&#xff09;&#xff08;一&#xff09; &#xff08;一&#xff09;Feign的理解 前面文章【springcloud學習(dalston.sr1)】服務消費者通過restTemplat…

SpringbBoot nginx代理獲取用戶真實IP

為了演示多級代理場景&#xff0c;我們分配了以下服務器資源&#xff1a; 10.1.9.98&#xff1a;充當客戶端10.0.3.137&#xff1a;一級代理10.0.4.105&#xff1a;二級代理10.0.4.129&#xff1a;三級代理10.0.4.120&#xff1a;服務器端 各級代理配置 以下是各級代理的基本配…

實驗九視圖索引

設計性實驗 1. 創建視圖V_A包括學號&#xff0c;姓名&#xff0c;性別&#xff0c;課程號&#xff0c;課程名、成績&#xff1b; 一個語句把學號103 課程號3-105 的姓名改為陸君茹1&#xff0c;性別為女 &#xff0c;然后查看學生表的信息變化&#xff0c;再把上述數據改為原…

typeof運算符和深拷貝

typeof運算符 識別所有值類型識別函數判斷是否是引用類型&#xff08;不可再細分&#xff09; //判斷所有值類型 let a; typeof a //undefined const strabc; typeof str //string const n100; typeof n //number const …

NAT/代理服務器/內網穿透

目錄 一 NAT技術 二 內網穿透/內網打洞 三 代理服務器 一 NAT技術 跨網絡傳輸的時候&#xff0c;私網不能直接訪問公網&#xff0c;就引入了NAT能講私網轉換為公網進行訪問&#xff0c;主要解決IPv4(2^32)地址不足的問題。 1. NAT原理 當某個內網想訪問公網&#xff0c;就必…

Git的安裝和配置(idea中配置Git)

一、Git的下載和安裝 前提條件&#xff1a;IntelliJ IDEA 版本是2023.3 &#xff0c;那么配置 Git 時推薦使用 Git 2.40.x 或更高版本 下載地址&#xff1a;CNPM Binaries Mirror 操作&#xff1a;打開鏈接 → 滾動到頁面底部 → 選擇2.40.x或更高版本的 .exe 文件&#xf…

【教程】Docker更換存儲位置

轉載請注明出處&#xff1a;小鋒學長生活大爆炸[xfxuezhagn.cn] 如果本文幫助到了你&#xff0c;歡迎[點贊、收藏、關注]哦~ 目錄 背景說明 更換教程 1. 停止 Docker 服務 2. 創建新的存儲目錄 3. 編輯 Docker 配置文件 4. 遷移已有數據到新位置 5. 啟動 Docker 服務 6…

PostgreSQL 配置設置函數

PostgreSQL 配置設置函數 PostgreSQL 提供了一組配置設置函數&#xff08;Configuration Settings Functions&#xff09;&#xff0c;用于查詢和修改數據庫服務器的運行時配置參數。這些函數為數據庫管理員提供了動態管理數據庫配置的能力&#xff0c;無需重啟數據庫服務。 …

sql server 2019 將單用戶狀態修改為多用戶狀態

記錄兩種將單用戶狀態修改為多用戶狀態&#xff0c;我曾經成功過的方法&#xff0c;供參考 第一種方法 USE master; GO -- 終止所有活動連接 DECLARE kill_connections NVARCHAR(MAX) ; SELECT kill_connections KILL CAST(session_id AS NVARCHAR(10)) ; FROM sys.dm_ex…

主機A向主機B發送一個長度為L字節的文件,假設TCP的MSS為1460字節,則在TCP的序號不重復使用的前提下,L的最大值是多少?

&#x1f4d8;題干回顧&#xff1a; 主機A向主機B發送一個長度為L字節的文件&#xff0c;假設TCP的MSS為1460字節&#xff0c;則在TCP的序號不重復使用的前提下&#xff0c;L的最大值是多少&#xff1f; 這個問題關鍵在于“TCP序號不重復使用”。 ? 正確答案是&#xff1a;D.…

一次因校時服務器異常引起的性能差異分析

一次因校時服務器異常引起的性能差異分析 一.背景知識1. **TSC 頻率**:硬件級高精度計時2. **gettimeofday**:用戶態時間接口3. **adjtimex**:系統時鐘的軟件校準4. **`clock_adjtime(CLOCK_REALTIME, {modes=ADJ_TICK})`**: 用于修改系統時鐘中斷間隔(`tick` 值)。5. 關系…

acwing 4275. Dijkstra序列

題目背景 輸入 輸出 完整代碼 #include<bits/stdc.h> using namespace std; int n,m,k,a[1010],dist[1010],g[1010][1010],st[1010];int dij(int u){memset(st,0,sizeof st);memset(dist,0x3f,sizeof dist);dist[u]0;for(int i0;i<n;i){int ta[i];for(int j1;j<n;…

[思維模式-37]:什么是事?什么是物?什么事物?如何通過數學的方法闡述事物?

一、基本概念 1、事&#xff08;Event) “事”通常指的是人類在社會生活中的各種活動、行為、事件或情況&#xff0c;具有動態性和過程性&#xff0c;強調的是一種變化、發展或相互作用的流程。 特點 動態性&#xff1a;“事”往往涉及一系列的動作、變化和發展過程。例如&a…