数据结构(Python实现)------前缀树

数据结构(Python实现)------前缀树

前缀树简介

基本概念

什么是前缀树?

前缀树是N叉树的一种特殊形式。通常来说,一个前缀树是用来存储字符串的。前缀树的每一个节点代表一个字符串(前缀)。每一个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符串是由节点本身的原始字符串,以及通往该子节点路径上所有的字符组成的。

下面是前缀树的一个例子:
在这里插入图片描述
在上图示例中,我们在节点中标记的值是该节点对应表示的字符串。例如,我们从根节点开始,选择第二条路径 ‘b’,然后选择它的第一个子节点 ‘a’,接下来继续选择子节点 ‘d’,我们最终会到达叶节点 “bad”。节点的值是由从根节点开始,与其经过的路径中的字符按顺序形成的。

值得注意的是,根节点表示空字符串。

前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀。这就是前缀树名称的由来。

我们再来看这个例子。例如,以节点 “b” 为根的子树中的节点表示的字符串,都具有共同的前缀 “b”。反之亦然,具有公共前缀 “b” 的字符串,全部位于以 “b” 为根的子树中,并且具有不同前缀的字符串来自不同的分支。

前缀树有着广泛的应用,例如自动补全,拼写检查等等。我们将在后面的章节中介绍实际应用场景。

如何表示一个前缀树?
方法一 数组

第一种方法是用数组存储子节点。

例如,如果我们只存储含有字母 a 到 z 的字符串,我们可以在每个节点中声明一个大小为26的数组来存储其子节点。对于特定字符 c,我们可以使用 c - ‘a’ 作为索引来查找数组中相应的子节点。

访问子节点十分快捷。访问一个特定的子节点比较容易,因为在大多数情况下,我们很容易将一个字符转换为索引。但并非所有的子节点都需要这样的操作,所以这可能会导致空间的浪费。

方法二 Map

第二种方法是使用 Hashmap 来存储子节点。

我们可以在每个节点中声明一个Hashmap。Hashmap的键是字符,值是相对应的子节点。

通过相应的字符来访问特定的子节点更为容易。但它可能比使用数组稍慢一些。但是,由于我们只存储我们需要的子节点,因此节省了空间。这个方法也更加灵活,因为我们不受到固定长度和固定范围的限制。

基本操作

基本概念

Insertion in Trie

我们已经在另一张卡片中讨论了 (如何在二叉搜索树中实现插入操作)。

提问:

你还记得如何在二叉搜索树中插入一个新的节点吗?

当我们在二叉搜索树中插入目标值时,在每个节点中,我们都需要根据 节点值 和 目标值 之间的关系,来确定目标值需要去往哪个子节点。同样地,当我们向前缀树中插入一个目标值时,我们也需要根据插入的 目标值 来决定我们的路径。

更具体地说,如果我们在前缀树中插入一个字符串 S,我们要从根节点开始。 我们将根据 S[0](S中的第一个字符),选择一个子节点或添加一个新的子节点。然后到达第二个节点,并根据 S[1] 做出选择。 再到第三个节点,以此类推。 最后,我们依次遍历 S 中的所有字符并到达末尾。 末端节点将是表示字符串 S 的节点。

下面是一个例子:

在这里插入图片描述
我们来用伪代码总结一下以上策略:

1. Initialize: cur = root
2. for each char c in target string S:
3.      if cur does not have a child c:
4.      	cur.children[c] = new Trie node
5.      cur = cur.children[c]
6. cur is the node which represents the string S

通常情况情况下,你需要自己构建前缀树。构建前缀树实际上就是多次调用插入函数。但请记住在插入字符串之前要 初始化根节点 。

Search in Trie
搜索前缀

正如我们在前缀树的简介中提到的,所有节点的后代都与该节点相对应字符串的有着共同前缀。因此,很容易搜索以特定前缀开头的任何单词。

同样地,我们可以根据给定的前缀沿着树形结构搜索下去。一旦我们找不到我们想要的子节点,搜索就以失败终止。否则,搜索成功。为了更具体地解释搜索的过程,我们提供了下列示例:
在这里插入图片描述
在这里插入图片描述
我们来用伪代码总结一下以上策略:

1. Initialize: cur = root
2. for each char c in target string S:
3.      if cur does not have a child c:
4.          search fails
5.      cur = cur.children[c]
6. search successes
搜索单词

你可能还想知道如何搜索特定的单词,而不是前缀。我们可以将这个词作为前缀,并同样按照上述同样的方法进行搜索。

如果搜索失败,那么意味着没有单词以目标单词开头,那么目标单词绝对不会存在于前缀树中。

如果搜索成功,我们需要检查目标单词是否是前缀树中单词的前缀,或者它本身就是一个单词。为了进一步解决这个问题,你可能需要稍对节点的结构做出修改。

提示:往每个节点中加入布尔值可能会有效地帮助你解决这个问题。

Python实现

实现 Trie (前缀树)

在这里插入图片描述

思路:

一个Trie是通过不断insert新的字符串不断变大的。那么我们关注insert操作如何实现,便知道Trie的构造。以上面的例子为例,在构造完一个根节点后,我们要插入“apple”这个单词。我们要循着“a->p->p->l->e这个路径去逐个确认每一个前缀是否已经存在,如果不存在,就建立出这个前缀的最后一个根节点,如果存在,就继续确认下一个字母”。

解法1#:用dict模拟字典树
class Trie(object):
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = {}

    def insert(self, word):
        """
        Inserts a word into the trie.
        :type word: str
        :rtype: None
        """
        node  = self.root
        for char in word:
            node = node.setdefault(char,{})

        node["end"] = True

    def search(self, word):
        """
        Returns if the word is in the trie.
        :type word: str
        :rtype: bool
        """
        node = self.root
        for char in word:
            if char not in node:
                return False
            node = node[char]

        return "end" in node

    def startsWith(self, prefix):
        """
        Returns if there is any word in the trie that starts with the given prefix.
        :type prefix: str
        :rtype: bool
        """
        node = self.root
        for char in prefix:
            if char not in node:
                return False
            node = node[char]

        return True



        # Your Trie object will be instantiated and called as such:
        # obj = Trie()
        # obj.insert(word)
        # param_2 = obj.search(word)
        # param_3 = obj.startsWith(prefix)
解法2#:通过创建树节点形式实现

class TrieNode(object):
    # Initialize your data structure here.
    def __init__(self):
        self.children = collections.defaultdict(TrieNode)
        self.is_word = False
 
class Trie(object):
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = TrieNode()
 
    def insert(self, word):
        """
        Inserts a word into the trie.
        :type word: str
        :rtype: void
        """
        node = self.root
        for chars in word:
            child = node.data.get(chars)
            if not child :
                node.data[chars] = TrieNode()
            node = node.data[chars]
        node.is_word = True
 
    def search(self, word):
        """
        Returns if the word is in the trie.
        :type word: str
        :rtype: bool
        """
        node = self.root
        for chars in word:
            node = node.data.get(chars)
            if not node:
                return False
        # 判断单词是否是完整的存在在trie树中
        return node.is_word    
 
    def startsWith(self, prefix):
        """
        Returns if there is any word in the trie that starts with the given prefix.
        :type prefix: str
        :rtype: bool
        """
        node = self.root
        for chars in prefix:
            node = node.data.get(chars)
            if not node:
                return False
        return True
 
# Your Trie object will be instantiated and called as such:
# obj = Trie()
# obj.insert(word)
# param_2 = obj.search(word)
# param_3 = obj.startsWith(prefix)

实际应用

Python实现

Map Sum Pairs

实现一个 MapSum 类里的两个方法,insert 和 sum。

对于方法 insert,你将得到一对(字符串,整数)的键值对。字符串表示键,整数表示值。如果键已经存在,那么原来的键值对将被替代成新的键值对。

对于方法 sum,你将得到一个表示前缀的字符串,你需要返回所有以该前缀开头的键的值的总和。

示例 1:

输入: insert("apple", 3), 输出: Null
输入: sum("ap"), 输出: 3
输入: insert("app", 2), 输出: Null
输入: sum("ap"), 输出: 5
import collections
class Node(object):
    def __init__(self, count = 0):
        self.children = collections.defaultdict(Node)
        self.count = count
        
class MapSum(object):

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.root = Node()
        self.keys = {}

    def insert(self, key, val):
        """
        :type key: str
        :type val: int
        :rtype: void
        """
        curr = self.root
        delta = val - self.keys.get(key, 0)
        # 更新保存键值对的keys
        self.keys[key] = val
        
        curr = self.root
        # 更新节点的count
        curr.count += delta
        for char in key:
            curr = curr.children[char]
            curr.count += delta

    def sum(self, prefix):
        """
        :type prefix: str
        :rtype: int
        """
        curr = self.root
        for char in prefix:
            if char not in curr.children:
                return 0
            curr = curr.children[char]
        return curr.count


# Your MapSum object will be instantiated and called as such:
# obj = MapSum()
# obj.insert(key,val)
# param_2 = obj.sum(prefix)
单词替换

在英语中,我们有一个叫做 词根(root)的概念,它可以跟着其他一些词组成另一个较长的单词——我们称这个词为 继承词(successor)。例如,词根an,跟随着单词 other(其他),可以形成新的单词 another(另一个)。

现在,给定一个由许多词根组成的词典和一个句子。你需要将句子中的所有继承词用词根替换掉。如果继承词有许多可以形成它的词根,则用最短的词根替换它。

你需要输出替换之后的句子。

示例 1:

输入: dict(词典) = ["cat", "bat", "rat"]
sentence(句子) = "the cattle was rattled by the battery"
输出: "the cat was rat by the bat"

注:

输入只包含小写字母。
1 <= 字典单词数 <=1000
1 <= 句中词语数 <= 1000
1 <= 词根长度 <= 100
1 <= 句中词语长度 <= 1000

暴力法:
思路:
把sentence里的每个单词的每个子单词都在转成set的dict里查找。

class Solution(object):
    def replaceWords(self, dict, sentence):
        """
        :type dict: List[str]
        :type sentence: str
        :rtype: str
        """
        dict = set(dict)
        
        s = sentence.split(" ")
        
        for i, word in enumerate(s):
            for j in range(len(word)):
                if word[:j + 1] in dict:
                    s[i] = word[:j + 1]
                    break
        return " ".join(s)

前缀树法:

class TrieNode:
    def __init__(self,x):
        self.val = x
        self.children = [None for _ in range(26)]
        self.isEnd = False
class Solution(object):
    def insertStr(self,str,root):
        if str == None or str == "":
            return
        for ch in list(str):
            pos = ord(ch) - ord('a')
            if root.children[pos] == None:
                root.children[pos] = TrieNode(ch)
            else:
                pass
            root = root.children[pos]
        root.isEnd = True

    def replaceWords(self, dict, sentence):
        """
        :type dict: List[str]
        :type sentence: str
        :rtype: str
        """
        root = TrieNode(None)
        for word in dict:
            self.insertStr(word,root)
        sentence = sentence.split(" ")
        for i in range(len(sentence)):
            res = ""
            node = root
            for j in range(len(sentence[i])):
                pos = ord(sentence[i][j]) - ord('a')
                
                if node.children[pos] != None:
                    res += node.children[pos].val
                    node = node.children[pos]
                    
                else:
                    break
                if node.isEnd == True:
                    sentence[i] = res
                    break
        return " ".join(sentence)
                    
添加与搜索单词 - 数据结构设计

设计一个支持以下两种操作的数据结构:

void addWord(word)
bool search(word)
search(word) 可以搜索文字或正则表达式字符串,字符串只包含字母 . 或 a-z 。 . 可以表示任何一个字母。

示例:

addWord("bad")
addWord("dad")
addWord("mad")
search("pad") -> false
search("bad") -> true
search(".ad") -> true
search("b..") -> true

说明:

你可以假设所有单词都是由小写字母 a-z 组成的。

class WordDictionary(object):
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.isword = '#'
        self.root = {}

    def addWord(self, word):
        """
        Adds a word into the data structure.
        :type word: str
        :rtype: None
        """
        node = self.root
        for i in word:
            node = node.setdefault(i,{})
        node['#'] = '#'

    def ennn(self, node, word):
        if not word:
            if '#' in node:
                return True
            else:
                return False
        i = word[0]
        if i == '.':
            for j in range(26):
                x = chr(j + 97)
                if x in node:
                    if self.ennn(node[x], word[1:]):
                        return True
            return False # 这里别忘了,有个returnFalse
        else:
            node = node.get(i)
            if not node:
                return False
            else:
                return self.ennn(node, word[1:])

    

    def search(self, word):
        """
        Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter.
        :type word: str
        :rtype: bool
        """
        node = self.root
        return self.ennn(node,word)



        # Your WordDictionary object will be instantiated and called as such:
        # obj = WordDictionary()
        # obj.addWord(word)
        # param_2 = obj.search(word)
数组中两个数的最大异或值

给定一个非空数组,数组中元素为 a0, a1, a2, … , an-1,其中 0 ≤ ai < 231 。

找到 ai 和aj 最大的异或 (XOR) 运算结果,其中0 ≤ i, j < n 。

你能在O(n)的时间解决这个问题吗?

示例:

输入: [3, 10, 5, 25, 2, 8]

输出: 28

解释: 最大的结果是 5 ^ 25 = 28.
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FP-Tree算法用于发现频繁项集,是一种基于树结构的算法。以下是一个简单的Python实现: 首先,需要定义一个类来表示FP-Tree中的每个节点: ```python class TreeNode: def __init__(self, name, count, parent): self.name = name self.count = count self.parent = parent self.children = {} self.next = None ``` 其中,name表示节点的名称,count表示节点的出现次数,parent表示节点的父节点,children表示节点的子节点,next表示节点的下一个节点(用于连接FP-Tree的相同项)。 接下来,需要定义一个函数来构建FP-Tree: ```python def build_tree(data, min_support): # 第一遍扫描数据,统计每个项的出现次数 item_counts = {} for trans in data: for item in trans: if item in item_counts: item_counts[item] += 1 else: item_counts[item] = 1 # 删除不满足最小支持度的项 freq_items = {k: v for k, v in item_counts.items() if v >= min_support} # 如果没有频繁项,则返回空 if not freq_items: return None, None # 对频繁项按照出现次数进行排序 sorted_items = sorted(freq_items.items(), key=lambda x: (-x[1], x[0])) # 构建根节点 root = TreeNode(None, 0, None) # 第二遍扫描数据,构建FP-Tree for trans in data: # 按照出现次数排序后的项 ordered_items = [item for item, _ in sorted_items if item in trans] # 从根节点开始,添加每个项到FP-Tree curr_node = root for item in ordered_items: if item in curr_node.children: # 如果项已存在,则增加计数 child = curr_node.children[item] child.count += 1 else: # 否则,添加新的节点 child = TreeNode(item, 1, curr_node) curr_node.children[item] = child # 连接FP-Tree的相同项 if item in freq_items: if freq_items[item][1] is None: freq_items[item][1] = child else: curr = freq_items[item][1] while curr.next is not None: curr = curr.next curr.next = child curr_node = child return root, freq_items ``` 其中,data表示数据集,min_support表示最小支持度。该函数首先统计每个项的出现次数,并删除不满足最小支持度的项。然后,按照出现次数排序后的项,从根节点开始,添加每个项到FP-Tree中。如果项已存在,则增加计数;否则,添加新的节点,并连接FP-Tree的相同项。 最后,可以使用递归来挖掘频繁项集: ```python def find_patterns(tree, freq_items, prefix): patterns = [] # 对于每个频繁项,生成其对应的频繁项集 for item, node in freq_items.items(): support = node[0] # 如果项集包含前缀,则添加到结果集 curr_prefix = prefix + [item] patterns.append((curr_prefix, support)) # 递归地挖掘以该项为结尾的频繁项集 cond_tree, cond_freq_items = build_tree(get_conditional_data(node[1]), min_support) if cond_tree is not None: patterns.extend(find_patterns(cond_tree, cond_freq_items, curr_prefix)) return patterns def get_conditional_data(node): # 从当前节点追溯到根节点,生成条件模式基 cond_data = [] while node is not None: path = [] curr = node while curr.parent is not None: path.append(curr.name) curr = curr.parent cond_data.append(path) node = node.next return cond_data ``` 其中,find_patterns函数从每个频繁项生成其对应的频繁项集,并递归地挖掘以该项为结尾的频繁项集。get_conditional_data函数从当前节点追溯到根节点,生成条件模式基。 这样,就可以用Python实现FP-Tree算法了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值