当前位置:   article > 正文

树:Trie 数据结构_c++trie的删除

c++trie的删除

概述

Trie数据结构是一种高级数据结构,用于有效地存储和搜索字符串。Trie 源自单词 re TRIE val,意思是找到或取回某些东西。字典可以使用 Trie 数据结构有效地实现,Tries 也用于我们在搜索引擎中看到的自动完成功能。Trie 数据结构在存储和检索数据方面比二叉搜索树和哈希表更快。Trie 数据结构也称为前缀树数字树。在 Trie 的帮助下,我们可以轻松地进行基于前缀的搜索。

要点

  • 在Trie数据结构中插入、删除和搜索长度为'k'的字符串的时间复杂度为:
    • 插入- O(k)
    • 删除- O(k)
    • 搜索- O(k)
  • 构建Trie 数据结构的时间复杂度为 O(N * avgL),其中“N”是我们要在 Trie 中插入的字符串数量,“avgL”是“N”个字符串的平均长度。
  • Trie数据结构有很多应用,包括“基于前缀的搜索”、“按字典顺序对字符串进行排序”等。

什么是 Trie 数据结构?

Trie 数据结构是一种基于树的数据结构,用于存储字符串集合。trie 一词源自 re TRIE val 一词,意思是找到或拿回某些东西。如果两个字符串具有共同的前缀,那么它们在 trie 中将具有相同的祖先。在 trie 数据结构中,我们可以存储大量字符串,并可以有效地对其进行搜索操作。trie 可用于按字母顺序对字符串集合进行排序,以及搜索 trie 中是否存在具有给定前缀的字符串。字典可以在 trie 数据结构的帮助下有效地实现。现在让我们讨论何时以及如何实现 Trie 数据结构。

为什么使用 Trie 数据结构?

trie 数据结构用于存储和检索数据。我们可以想到的用于执行相同操作的另一种数据结构是哈希表。然而,trie 数据结构可以比哈希表更有效地处理这些操作。此外,trie数据结构可以用于基于前缀的搜索,而我们不能使用哈希表来进行基于前缀的搜索。基于前缀的搜索的一个示例是搜索字符串集合中存在的有多少字符串包含给定前缀。trie 数据结构给我们带来了优于哈希表的优势,主要体现在以下几个方面:

  • trie 数据结构可用于实现哈希表无法实现的广泛功能。例如,基于前缀的搜索不能使用哈希表来完成。
  • trie 数据结构中不存在冲突,这意味着比未正确实现的哈希表具有更好的最坏情况时间复杂度。
  • Trie 数据结构中不使用哈希函数。
  • 在 Trie 数据结构中搜索字符串的时间复杂度为O(k)。其中 k 是查询字符串中的单词数。当查询字符串不存在于 trie 中时,它也可能花费少于O(k)的时间。

Trie 数据结构的属性

trie 的结构类似于树的结构。每个 trie 由一个根节点组成。根节点分支成具有多个边的各种子节点。每个TrieNode由一个指针数组组成,其中数组的每个索引代表一个字符。trie 的每个节点代表一个字符串,每条边代表一个字符。根节点是一个空字符串。除根节点外的每个节点都是一个字符串,其中包含从根到该节点的路径上的字符。让我们通过一个例子来了解 Trie 数据结构的结构。

上图显示了一个 trie 数据结构,其中输入字符串为ball、bat、eAr、eAt、tea和ten。我们可以看到每个节点代表给定字符串的一部分或前缀。根节点代表一个空字符串。每条边都由一个字符表示。

Trie数据结构的每一层代表一个给定长度的前缀。如果我们认为根节点处于级别0。那么根节点代表一个长度为0的前缀或者一个空字符串。Trie 的级别 1表示长度为1的前缀,依此类推。

如果tea和ten具有相同的祖先,即te和t ,则两个子节点具有相同的祖先。这是因为它们共享相同的te前缀。

请注意,我们可以在 Trie 数据结构中包含任意数量的字符,包括字母、数字和特殊字符。在本文中,我们将讨论带有字符az 的字符串。因此,对于 Trie 中的每个节点,我们都需要一个大小为26的指针数组。其中第 0 个索引表示a,第25 个索引表示z。

另一个需要注意的重要事项是 Trie 数据结构中的每个字符串都是按字典顺序从左到右排序的。我们将在讨论 Trie 中的插入操作时学习如何实现它。

现在让我们讨论如何在 Trie 数据结构中实现插入、搜索和删除操作。

Trie 数据结构中的基本操作

在本节中,我们将讨论 Trie 数据结构中插入、搜索和删除操作背后的逻辑。我们将一一理解每一个操作的代码,然后在文章的后半部分实现Trie数据结构的整个代码。

在 Trie 中插入操作。

借助插入操作,我们可以将新字符串插入到Trie数据结构中。首先我们来了解一下Trie节点的结构。当我们考虑带有字符a - z的字符串时。每个 Trie 节点都会有一个大小为26的字符指针数组。。我们将创建TrieNode的结构如下 -

 
  1. struct TrieNode {
  2. //pointer array for child nodes of each node
  3. TrieNode *childNode[26];
  4. int wordEndCnt;
  5. //constructor
  6. TrieNode()
  7. {
  8. //initialize the wordEndCnt variable with 0
  9. //initialize every index of childNode array with NULL
  10. wordEndCnt = 0;
  11. for (int i = 0; i < 26; i++)
  12. {
  13. childNode[i] = NULL;
  14. }
  15. }
  16. };
  • 每个 TrieNode 将有26 个从a - z 的子节点,由字符指针数组表示。
  • 每个节点都会有一个wordEndCnt整型变量。该变量将存储 Trie 中字符串的计数,该计数与 Trie 的该节点表示的前缀的计数相同。
  • 在TrieNode的结构内部,我们创建了一个构造函数TrieNode() ,每当创建新节点时,它将用 NULL初始化childNode指针数组的每个索引。它还会将每个节点的wordEndCnt值初始化为0。

在将任何字符串插入 Trie 之前,我们为 Trie 创建一个根节点。根节点的结构如下。

根由一个空字符串和26 个指针组成,每个指针都用 NULL 初始化。图中的N表示每个索引处的 NULL 值。现在让我们尝试将字符串tea插入到 Trie 中。

插入操作:“茶”

上图显示了在 Trie 中插入Tea字符串的步骤。在每一步中,完成操作的节点都用绿色标记。我们将该节点称为当前节点。

  1. 根节点已创建。childNode指针数组的每个索引都是 NULL。当前节点最初将是根节点。输入字符串的第一个字符是t。在根节点中,我们将检查childNode数组的第一个索引是否为 NULL。由于childNode数组的第t - a索引为 NULL。我们借助TrieNode结构中定义的构造函数创建一个新的TrieNode。我们将根节点的childNode指针数组的ta索引指向创建的新节点。我们将使创建的新节点作为当前节点并继续下一步。由于字符串t不是字符串tea的结尾,因此我们不会增加当前节点或步骤 1 中创建的新节点的wordEndCnt变量。
  2. 在步骤 2 中,我们将执行与步骤 1 相同的步骤。输入字符串的第二个字符是e。在当前节点中,我们将检查e th 索引是否为NULL。由于childNode数组的每个索引为 NULL,我们将再次创建一个新节点。我们将当前节点的ea索引指向新创建的节点。我们将使新节点成为当前节点并继续下一步。由于字符串te不是字符串tea的末尾,因此我们不会增加该节点的wordEndCnt值。
  3. 在步骤 3 中,我们将移至输入字符串的第三个字符,即a。我们将检查childNode指针数组的第a个索引或第0个索引是否为空。由于它是 NULL,我们将创建一个新节点并执行与步骤 1 和 2 相同的步骤。我们将使新节点成为当前节点。然而,由于字符串 tea是字符串tea的末尾,我们将增加该节点的wordEndCnt值。

插入操作:“十”

现在让我们尝试将字符串10插入同一个 trie 中。插入步骤如下图所示。

  1. 最初,当前节点是根节点。输入字符串的第一个字符是t。由于childNode指针数组的第t - a索引不为 NULL,因此我们不会创建新节点。当前节点从根节点移动到t -根节点的childNode指针数组的第一个索引所指向的节点
  2. 现在我们检查输入字符串的第二个字符,即e。由于当前节点的第e - a索引也不为空,因此我们将当前节点移动到由当前节点的第 e - a索引指向的节点。
  3. 现在我们检查输入字符串的最后一个字符,即n。由于当前节点的第 n个索引为 NULL ,我们创建一个新节点并遵循与上述相同的步骤。我们增加该节点的wordEndCnt变量,因为该字符是输入字符串的最后一个字符。

Trie数据结构中Insert函数的实现

让我们定义一个函数insert_key (TrieNode *root,string &key) ,它将带有两个参数。第一个参数是 Trie 的根。第二个参数是我们要插入到 Trie 数据结构中的字符串。我们创建一个currentNode指针并用根节点初始化它。然后我们循环遍历字符串键的整个长度并遵循上面讨论的算法。最后从insert_key()函数返回更新后的根节点。

C++ 实现

 
  1. TrieNode* insert_key(TrieNode *root, string &key)
  2. {
  3. //initialize the currentNode pointer with the root node
  4. TrieNode *currentNode = root;
  5. //Store the length of the key string
  6. int length = key.size();
  7. //iterate across the length of the string
  8. for (int i = 0; i < length; i++)
  9. {
  10. //Check X-'a' th index is NULL or not
  11. if (currentNode->childNode[key[i] - 'a'] == NULL)
  12. {
  13. //If null make a new node
  14. TrieNode * newNode = new TrieNode();
  15. //Point the X-'a' th index of current node to the new node
  16. currentNode->childNode[key[i] - 'a'] = newNode;
  17. }
  18. //Move the current node pointer to the newly created node.
  19. currentNode = currentNode->childNode[key[i] - 'a'];
  20. }
  21. //Increment the wordEndCnt for the last currentNode pointer as it will point
  22. //the string that is the end of the string key.
  23. currentNode->wordEndCnt++;
  24. //return the updated root node
  25. return root;
  26. }

Trie数据结构中的搜索操作

借助搜索操作,我们可以搜索某个字符串是否存在于 Trie 数据结构中。搜索操作可以修改为以不同的方式使用。在本文中,我们将讨论搜索操作,以检查查询函数中给出的整个字符串是否存在于 Trie 数据结构中。如果查询字符串存在,我们将返回布尔值 true,否则我们将返回布尔值 false。

Trie 数据结构中的搜索操作与插入操作类似。Search 操作中唯一的区别是,每当我们发现一个节点的X -当前节点的childNode指针数组的第一个索引为 NULL 时,我们都会返回 false,而不是创建一个新节点。这里X可以是查询字符串的任何字符。仅当我们处理整个查询字符串而没有遇到空指针并且最后一个节点的wordEndCnt大于0时,我们才返回 True 。

我们将使用上面讨论的第二次插入操作后获得的相同 Trie。让我们尝试在给定的 Trie 中搜索字符串tea 。

搜索操作:“茶”

上图显示了 在 Trie 中搜索字符串tea所涉及的步骤。

  1. 最初,当前节点是根节点。查询字符串的第一个字符是t。我们检查当前节点的childNode指针数组的第t - a索引是否为NULL。由于它不为空,我们将当前节点指针移动到t -当前节点的第一个索引(即步骤 1 中的根节点)所指向的节点。

  2. 现在我们检查查询字符串的第二个字符,即e。由于当前节点的第e个索引也不为NULL。我们将当前节点指针移动到e -当前节点的第一个索引所指向的节点。

  3. 然后,我们处理查询字符串的最后一个字符,即a。由于当前节点的a - a索引不等于 null,我们将当前节点指针移动到当前节点的childNode指针数组的a - a th 索引所指向的节点。

  4. 最后,我们检查当前节点的wordEndCnt是否大于 0。如果wordEndCnt大于 0(查询字符串tea就是这种情况) ,我们将返回 true。否则,我们返回 false。

现在让我们讨论一下 Trie 中不存在查询字符串的情况。让我们尝试在 Trie 中搜索字符串。

搜索操作:“到”

  1. 我们从根节点开始,检查查询字符串的第一个字符,即t。由于第t - a索引不为 NULL。我们将当前节点指针移动到当前节点的第t - a索引所指向的节点。
  2. 在步骤 2 中,我们在当前节点中查找字符o 。然而,由于当前节点的第o - a索引等于 NULL。这意味着trie 中不存在查询字符串to 。因此在这种情况下我们返回 false。

Trie数据结构中搜索操作的实现

让我们定义一个函数search_key (TrieNode *root,string &queryString) ,它将采用两个参数。第一个参数是 Trie 的根。第二个参数是我们要在 Trie 中搜索的字符串。我们创建一个currentNode指针并用根节点初始化它。然后我们循环遍历字符串queryString的整个长度。如果我们 在 Trie 中找到了queryString,则返回 true,否则返回 false。

 
  1. bool search_key(TrieNode *root, string &queryString)
  2. {
  3. //Initialize the currentNode pointer with the root node
  4. TrieNode *currentNode = root;
  5. //Store the length of the query string
  6. int length = queryString.size();
  7. for (int i = 0; i < length; i++)
  8. {
  9. //Check if the X-'a' th index is NULL or not
  10. if (currentNode->childNode[queryString[i] - 'a'] == NULL)
  11. {
  12. //If null then the query string is not present in the Trie
  13. //return false
  14. return false;
  15. }
  16. //If not NULL
  17. //Move the currentNode pointer to the node pointed by X-'a' th index of the
  18. //current node
  19. currentNode = currentNode->childNode[queryString[i] - 'a'];
  20. }
  21. //If currentNode pointer is not NULL
  22. //and wordEndCnt for the currentNode pointer
  23. //is greater than 0 then return true else
  24. //return false
  25. return currentNode != NULL && currentNode->wordEndCnt > 0;
  26. }

Trie数据结构中的删除操作。

借助删除操作,我们可以删除 Trie 数据结构中先前出现的字符串。如果 Trie 数据结构中不存在某个字符串,我们就无法删除该字符串。如果使用删除操作成功删除字符串,我们将返回布尔值 True。如果该字符串不存在于 Trie 中,则返回布尔值 false。

为了在 Trie 数据结构中实现删除操作,我们首先搜索查询字符串是否存在于 Trie 中。为了搜索字符串,我们应用上一节中讨论的相同逻辑。如果该字符串不存在于 Trie 数据结构中,我们将返回 false。如果字符串存在于 Trie 数据结构中,我们将最后一个当前节点指针的节点 pointe 的wordEndCnt减少1。

让我们尝试从上面获得的Trie数据结构中删除字符串tea 。

删除操作:“茶”

上图显示了从给定 Trie 中删除字符串tea所涉及的步骤。

  1. 首先,我们使用 Trie 中搜索操作中讨论的相同逻辑来搜索字符串tea 。
  2. 当字符串tea存在于 Trie 中时,当前节点指针将从根节点移动到最后一个节点。最后一个节点代表字符串tea。现在我们将检查该节点的wordEndCnt是否大于0。在本例中,节点的wordEndCnt大于0,我们减少该节点的wordEndCnt并返回 True。

请注意,如果我们在上述操作之后再次尝试从 Trie 中删除字符串tea 。我们将无法删除该字符串,因为Trie中只有一个茶字符串,并且我们已经删除了该字符串。如果我们尝试删除字符串,那么对于最后一个当前节点,我们将得到值为0的wordEndCnt。所以我们返回一个布尔假值。

Trie数据结构中删除操作的实现

让我们定义一个函数delete_key (TrieNode *root,string &queryString),它将带有两个参数。第一个参数是 Trie 的根。第二个参数是我们要在 Trie 中删除的字符串。我们创建一个currentNode指针并用根节点初始化它。然后我们循环遍历字符串queryString的整个长度。如果 Trie 中不存在这样的字符串,我们返回 false。否则,我们递减Trie 中queryString的wordEndCnt并返回 true。

 
  1. bool delete_key(TrieNode *root, string &queryString)
  2. {
  3. //Initialize the currentNode pointer with the root node
  4. TrieNode *currentNode = root;
  5. //Store the length of the queryString
  6. int length = queryString.size();
  7. for (int i = 0; i < length; i++)
  8. {
  9. //Check if the X-'a' th index is NULL or not
  10. if (currentNode->childNode[queryString[i] - 'a'] == NULL)
  11. {
  12. //If null the query string is not present in the Trie
  13. //return false
  14. return false;
  15. }
  16. //Move the currentNode pointer to the next node
  17. currentNode = currentNode->childNode[queryString[i] - 'a'];
  18. }
  19. //If currentNode pointer is not NULL and
  20. //wordEndCnt for the currentNode is greater than 0
  21. if (currentNode != NULL && currentNode->wordEndCnt > 0)
  22. {
  23. //then decrement the wordEndCnt for the
  24. //currentNode
  25. currentNode->wordEndCnt--;
  26. //return true
  27. return true;
  28. }
  29. //If the wordEndCnt for the currentNode is equal to 0
  30. //then the queryString is not present in Trie
  31. //return false
  32. return false;
  33. }

Trie 数据结构的应用

  1. 自动完成功能- Trie 数据结构通常用于实现我们在搜索引擎中看到的自动完成功能。如果我们输入所需字符串的前缀,那么我们将看到许多具有相同前缀的字符串的建议。这可以在 Trie 的帮助下有效地实现,因为在 Trie 中,具有公共前缀的字符串共享相同的祖先。然后,我们可以在这些祖先以下的级别进行搜索,并根据我们遇到的不同字符串的流行程度显示建议。
  2. 拼写检查器- 拼写检查器检查键入的单词是否存在于字典中。如果字典中没有该单词,则会根据键入的单词显示建议。它还可以根据建议的受欢迎程度对建议进行排序。使用 Trie 可以有效地实现字典。使用 Trie 可以轻松地在 Trie 中搜索字符串以及根据给定单词显示建议。
  3. 字符串匹配算法- Trie 数据结构也可用于字符串匹配算法,以匹配字符串集合中的给定模式。

Trie数据结构的优点

  1. 尝试可以比二叉搜索树更快地实现插入和搜索操作。Trie 也比哈希表更快,因为 Trie 中没有哈希函数和冲突。
  2. 尝试可用于按字母顺序对字符串进行排序。
  3. 尝试可用于实现基于前缀的搜索,而哈希表无法实现这一点。这有助于实现对给定单词的自动完成和拼写检查等功能。

Trie数据结构的缺点

  1. Trie 的主要缺点是与其他数据结构相比,它们需要大量内存来存储字符串。这是因为每个节点都包含子节点的指针数组,并且还包含一些附加变量,例如我们在 TrieNode 结构中使用的wordEndCnt变量。

Trie 的完整实现

  • 首先,我们借助TrieNode()构造函数为 Trie 创建一个根节点。
  • 我们将要插入到 trie 中的字符串存储在字符串向量中。我们将此向量命名为inputStrings。
  • 借助insertkey()函数将所有字符串插入inputStrings向量后,我们借助上面讨论的search_key()函数搜索字符串。我们将要搜索的字符串存储在名为searchQueryStrings 的向量中。
  • 然后我们删除deleteQueryStrings向量中存在的字符串。

Trie数据结构的C++实现

 
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. struct TrieNode {
  4. //pointer array for child nodes of each node
  5. TrieNode *childNode[26];
  6. int wordEndCnt;
  7. TrieNode()
  8. {
  9. // constructor
  10. //initialize the wordCnt variable with 0
  11. //initialize every index of childNode array with NULL
  12. wordEndCnt = 0;
  13. for (int i = 0; i < 26; i++)
  14. {
  15. childNode[i] = NULL;
  16. }
  17. }
  18. };
  19. TrieNode * insert_key(TrieNode *root, string &key)
  20. {
  21. //initialize the currentNode pointer with the root node
  22. TrieNode *currentNode = root;
  23. //Store the length of the key string
  24. int length = key.size();
  25. //iterate across the length of the string
  26. for (int i = 0; i < length; i++)
  27. {
  28. //Check X-'a' th index is NULL or not
  29. if (currentNode->childNode[key[i] - 'a'] == NULL)
  30. {
  31. //If null make a new node
  32. TrieNode * newNode = new TrieNode();
  33. //Point the X-'a' th index of current node to the new node
  34. currentNode->childNode[key[i] - 'a'] = newNode;
  35. }
  36. //Move the current node pointer to the newly created node.
  37. currentNode = currentNode->childNode[key[i] - 'a'];
  38. }
  39. //Increment the wordEndCnt for the last currentNode pointer as it will point
  40. //the string that is the end of the string key.
  41. currentNode->wordEndCnt++;
  42. //return the updated root node
  43. return root;
  44. }
  45. bool search_key(TrieNode *root, string &queryString)
  46. {
  47. //Initialize the currentNode pointer with the root node
  48. TrieNode *currentNode = root;
  49. //Store the length of the query string
  50. int length = queryString.size();
  51. for (int i = 0; i < length; i++)
  52. {
  53. //Check if the X-'a' th index is NULL or not
  54. if (currentNode->childNode[queryString[i] - 'a'] == NULL)
  55. {
  56. //If null then the query string is not present in the Trie
  57. //return false
  58. return false;
  59. }
  60. //If not NULL
  61. //Move the currentNode pointer to the node pointed by X-'a' th index of the
  62. //current node
  63. currentNode = currentNode->childNode[queryString[i] - 'a'];
  64. }
  65. //If currentNode pointer is not NULL
  66. //and wordEndCnt for the currentNode pointer
  67. //is greater than 0 then return true else
  68. //return false
  69. return currentNode != NULL && currentNode->wordEndCnt > 0;
  70. }
  71. bool delete_key(TrieNode *root, string &queryString)
  72. {
  73. //Initialize the currentNode pointer with the root node
  74. TrieNode *currentNode = root;
  75. //Store the length of the queryString
  76. int length = queryString.size();
  77. for (int i = 0; i < length; i++)
  78. {
  79. //Check if the X-'a' th index is NULL or not
  80. if (currentNode->childNode[queryString[i] - 'a'] == NULL)
  81. {
  82. //If null the query string is not present in the Trie
  83. //return false
  84. return false;
  85. }
  86. //Move the currentNode pointer to the next node
  87. currentNode = currentNode->childNode[queryString[i] - 'a'];
  88. }
  89. //If currentNode pointer is not NULL and
  90. //wordEndCnt for the currentNode is greater than 0
  91. if (currentNode != NULL && currentNode->wordEndCnt > 0)
  92. {
  93. //then decrement the wordEndCnt for the
  94. //currentNode
  95. currentNode->wordEndCnt--;
  96. //return true
  97. return true;
  98. }
  99. //If the wordEndCnt for the currentNode is equal to 0
  100. //Then that string is not present in Trie
  101. //return false
  102. return false;
  103. }
  104. int main()
  105. {
  106. //make a root node for the Trie
  107. TrieNode *root = new TrieNode();
  108. //stores the strings that we want to insert in the
  109. //Trie
  110. vector<string> inputStrings = {"tea", "ten", "eat", "ear", "bat", "ball"};
  111. //number of insert operations in the Trie
  112. int n = inputStrings.size();
  113. for (int i = 0; i < n; i++)
  114. {
  115. root = insert_key(root, inputStrings[i]);
  116. }
  117. //stores the strings that we want to search in the Trie
  118. vector<string>searchQueryStrings = {"tea", "to", "bal"};
  119. //number of search operations in the Trie
  120. int searchQueries = searchQueryStrings.size();
  121. for (int i = 0; i < searchQueries; i++)
  122. {
  123. cout << "Query String : " << searchQueryStrings[i] << "\n";
  124. if (search_key(root, searchQueryStrings[i]))
  125. {
  126. //the queryString is present in the Trie
  127. cout << "The query string is present in the Trie\n";
  128. }
  129. else
  130. {
  131. //the queryString is not present in the Trie
  132. cout << "The query string is not present in the Trie\n";
  133. }
  134. }
  135. //stores the strings that we want to delete from the Trie
  136. vector<string>deleteQueryStrings = {"tea", "tea"};
  137. //number of delete operations from the Trie
  138. int deleteQueries = deleteQueryStrings.size();
  139. for (int i = 0; i < deleteQueries; i++)
  140. {
  141. cout << "Query String : " << deleteQueryStrings[i] << "\n";
  142. if (delete_key(root, deleteQueryStrings[i]))
  143. {
  144. //The queryString is successfully deleted from
  145. //the Trie
  146. cout << "The query string is successfully deleted\n";
  147. }
  148. else
  149. {
  150. //The query string is not present in the Trie
  151. cout << "The query string is not present in the Trie\n";
  152. }
  153. }
  154. return 0;
  155. }

Trie数据结构的C语言实现

 
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<stdbool.h>
  4. struct TrieNode {
  5. //pointer array for child nodes of each node
  6. struct TrieNode *childNode[26];
  7. int wordEndCnt;
  8. };
  9. struct TrieNode* new_object()
  10. {
  11. struct TrieNode *new_node = (struct TrieNode*) malloc(sizeof(struct TrieNode));
  12. new_node->wordEndCnt = 0;
  13. for (int i = 0; i < 26; i++)
  14. {
  15. new_node->childNode[i] = NULL;
  16. }
  17. return new_node;
  18. }
  19. struct TrieNode * insert_key(struct TrieNode *root, const char *key)
  20. {
  21. //initialize the currentNode pointer with the root node
  22. struct TrieNode *currentNode = root;
  23. int i = 0;
  24. while (key[i] != '\0')
  25. {
  26. if (currentNode->childNode[key[i] - 'a'] == NULL)
  27. {
  28. //If null make a new node
  29. struct TrieNode * newNode = new_object();
  30. //Point the X-'a' th index of current node to the new node
  31. currentNode->childNode[key[i] - 'a'] = newNode;
  32. }
  33. //Move the current node pointer to the newly created node.
  34. currentNode = currentNode->childNode[key[i] - 'a'];
  35. i++;
  36. }
  37. //Increment the wordEndCnt for the last currentNode pointer as it will point
  38. //the string that is the end of the string key.
  39. currentNode->wordEndCnt++;
  40. //return the updated root node
  41. return root;
  42. }
  43. bool search_key(struct TrieNode *root, const char *queryString)
  44. {
  45. //Initialize the currentNode pointer with the root node
  46. struct TrieNode *currentNode = root;
  47. int i = 0;
  48. while (queryString[i] != '\0')
  49. {
  50. if (currentNode->childNode[queryString[i] - 'a'] == NULL)
  51. {
  52. //If null then the query string is not present in the Trie
  53. //return false
  54. return false;
  55. }
  56. //If not NULL
  57. //Move the currentNode pointer to the node pointed by X-'a' th index of the
  58. //current node
  59. currentNode = currentNode->childNode[queryString[i] - 'a'];
  60. i++;
  61. }
  62. //If currentNode pointer is not NULL
  63. //and wordEndCnt for the currentNode pointer
  64. //is greater than 0 then return true else
  65. //return false
  66. return currentNode != NULL && currentNode->wordEndCnt > 0;
  67. }
  68. bool delete_key(struct TrieNode *root, const char *queryString)
  69. {
  70. //Initialize the currentNode pointer with the root node
  71. struct TrieNode *currentNode = root;
  72. int i = 0;
  73. while (queryString[i] != '\0')
  74. {
  75. //Check if the X-'a' th index is NULL or not
  76. if (currentNode->childNode[queryString[i] - 'a'] == NULL)
  77. {
  78. //If null the query string is not present in the Trie
  79. //return false
  80. return false;
  81. }
  82. //Move the currentNode pointer to the next node
  83. currentNode = currentNode->childNode[queryString[i] - 'a'];
  84. i++;
  85. }
  86. //If currentNode pointer is not NULL and
  87. //wordEndCnt for the currentNode is greater than 0
  88. if (currentNode != NULL && currentNode->wordEndCnt > 0)
  89. {
  90. //then decrement the wordEndCnt for the
  91. //currentNode
  92. currentNode->wordEndCnt--;
  93. //return true
  94. return true;
  95. }
  96. //If the wordEndCnt for the currentNode is equal to 0
  97. //Then that string is not present in Trie
  98. //return false
  99. return false;
  100. }
  101. int main()
  102. {
  103. //make a root node for the Trie
  104. struct TrieNode *root = new_object();
  105. //stores the strings that we want to insert in the
  106. //Trie
  107. const char *inputStrings[] = {"tea\0", "ten\0", "eat\0", "ear\0", "bat\0", "ball\0"};
  108. //number of insert operations in the Trie
  109. int n = sizeof(inputStrings) / sizeof(const char *);
  110. for (int i = 0; i < n; i++)
  111. {
  112. root = insert_key(root, inputStrings[i]);
  113. }
  114. // stores the strings that we want to search in the Trie
  115. const char *searchQueryStrings[] = {"tea\0", "to\0", "bal\0"};
  116. // number of search operations in the Trie
  117. int searchQueries = sizeof(searchQueryStrings) / sizeof(const char *);
  118. for (int i = 0; i < searchQueries; i++)
  119. {
  120. printf("Query String : %s\n", searchQueryStrings[i]);
  121. if (search_key(root, searchQueryStrings[i]))
  122. {
  123. //the queryString is present in the Trie
  124. printf("The query string is present in the Trie\n");
  125. }
  126. else
  127. {
  128. //the queryString is not present in the Trie
  129. printf("The query string is not present in the Trie\n");
  130. }
  131. }
  132. // stores the strings that we want to delete from the Trie
  133. const char *deleteQueryStrings[] = {"tea\0", "tea\0"};
  134. // number of delete operations from the Trie
  135. int deleteQueries = sizeof(deleteQueryStrings) / sizeof(const char *);
  136. for (int i = 0; i < deleteQueries; i++)
  137. {
  138. printf("Query String : %s\n", deleteQueryStrings[i]);
  139. if (delete_key(root, deleteQueryStrings[i]))
  140. {
  141. //The queryString is successfully deleted from
  142. //the Trie
  143. printf("The query string is successfully deleted\n");
  144. }
  145. else
  146. {
  147. //The query string is not present in the Trie
  148. printf("The query string is not present in the Trie\n");
  149. }
  150. }
  151. return 0;
  152. }

结论

  • 构建 Trie 数据结构所需的时间为O(N*x)。这里N是我们要插入到Trie中的字符串的数量,x是我们要插入的字符串的平均长度。
  • 使用 Trie 数据结构,我们可以以O(k)的时间复杂度插入、搜索和删除键,其中 k 是键的长度。
  • 使用 Trie 数据结构的唯一缺点是它们最终可能比其他数据结构占用更多的空间。
  • Trie 数据结构有许多实际应用。一些常见的例子是自动完成功能、词典和拼写检查器。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/994765
推荐阅读
相关标签
  

闽ICP备14008679号