trie 字典树 在写入时复制。
首先把成员理清。在对所给项目进行实现。
一.需要具备的基础知识:
1.智能指针:shared_ptr.作用如同指针,但会记录有多少个shared_ptr共同指向一个对象。这便是所谓的引用计数(reference counting)。
一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。
创建时利用make_shared<>.例如
std::shared_ptr<T> val_p = std::make_shared<T>(std::move(value));
2.move()函数
将对象的状态或所有权转换给另一对象,之后传入的对象就为空了,这样做只是转移,没有内存的搬迁或者内存拷贝,如果不加则需要先将传入对象拷贝一份为临时对象,然后给属性成员赋值,之后销毁临时对象。因此,通过std::move(),可以避免不必要的拷贝操作。这样的构造函数称为移动构造函数
3.auto 函数() -> 类型{…} 作用是推导出函数的返回类型
auto Trie::Put(std::string_view key, T value) const -> Trie {
如此,put()函数返回的就是Trie对象。
4.类型转换
std::dynamic_cast<目标类型*>(被转换对象)
std::dynamic_pointer_cast<目标类型>(被转换智能指针)
dynamic_pointer_cast与dynamic_cast的区别
适用对象类型:dynamic_pointer_cast适用于智能指针类型,如std::shared_ptr和std::weak_ptr,用于进行智能指针的动态类型转换。而dynamic_cast适用于指针和引用类型,用于进行指针或引用类型的动态类型转换。
const auto *value_node = dynamic_cast<const TrieNodeWithValue<T> *>(node.get());
在Get()函数中用到了类型转换。
二.具体的实现
在Get()函数中,所给要求根据传入的Key参数,从根节点开始遍历字符串,若寻找到则返回value;
auto Trie::Get(std::string_view key) const -> const T * {
// throw NotImplementedException("Trie::Get is not implemented.");
// 从根节点开始
auto node = root_;
// 按输入的key值遍历Trie树
for (char ch : key) {
// 如果当前节点为nullptr,或者没有对应字符的子节点,返回nullptr,程序结束
if (node == nullptr || node->children_.find(ch) == node->children_.end()) {
return nullptr;
}
// 当前字符存在,接着往后面找
node = node->children_.at(ch);
}
// 路径存在,检查类型是否匹配
// 将节点转换为TrieNodeWithValue<T>类型
const auto *value_node = dynamic_cast<const TrieNodeWithValue<T> *>(node.get());
// 如果转换成功并且节点包含正确类型的值,则返回该值的指针
if (value_node != nullptr) {
return value_node->value_.get();
}
// 若类型不匹配,返回nullptr
return nullptr;
在Put()函数中,为键设置相应的值。如果密钥已存在,则覆盖现有值。请注意,该值的类型可能是不可复制的(即 )。此方法返回一个新的 trie。
分类讨论。
第一种情况:当值需要放在根节点中时,有跟的时候直接将值放入,没有根时创建根再放入值。
此时要注意,因为是要写入时复制,所以要分析原根有无子节点。要是原根没有子节点,则直接传入值,要是有子节点,则需要把原来根的子节点复制给新的根。
第二种情况:当值不放在根节点时
如果此时没有根,则新建一棵空树,如果有根节点,则递归的插入值。
下面介绍具体的递归的插入算法:
确认递归的出口,key结束。每一次插入都是递归开始推出时插入。
设置一个bool参数flag,分类,
如果key的值匹配路径flag设为true。如果此时剩余的Key大于1,则需要继续递归向下找(当还要继续向下找的时候,必须要先复制当前节点,再向下找。)如果此时剩余的Key小于等于1,则直接插入。
如果在当前节点的子节点没有找到匹配的Key,则新建一个节点。判断key的大小,如果是最后一个key,则创建一个带值节点,否则创一个children节点,在递归的向下寻找。
void PutCycle(const std::shared_ptr<bustub::TrieNode> &new_root, std::string_view key, T value) {
// 判断元素是否为空的标志位
bool flag = false;
// 在new_root的children找key的第一个元素
// 利用for(auto &a:b)循环体中修改a,b中对应内容也会修改及pair的特性
for (auto &pair : new_root->children_) {
// 如果找到了
if (key.at(0) == pair.first) {
flag = true;
// 剩余键长度大于1
if (key.size() > 1) {
// 复制一份找到的子节点,然后递归对其写入
std::shared_ptr<TrieNode> ptr = pair.second->Clone();
// 递归写入 .substr(1,key.size()-1)也可以
// 主要功能是复制子字符串,要求从指定位置开始,并具有指定的长度。
PutCycle<T>(ptr, key.substr(1), std::move(value));
// 覆盖原本的子节点,必须重新让pair指针(引用)指向新创建的节点,否则下次用的还是没有复制前的节点
pair.second = std::shared_ptr<const TrieNode>(ptr);
} else {
// 剩余键长度小于等于1,则直接插入
// 创建新的带value的子节点
std::shared_ptr<T> val_p = std::make_shared<T>(std::move(value));
TrieNodeWithValue node_with_val(pair.second->children_, val_p);
// 覆盖原本的子节点
pair.second = std::make_shared<const TrieNodeWithValue<T>>(node_with_val);
}
return;
}
}
if (!flag) {
// 没找到,则新建一个子节点
char c = key.at(0);
// 如果为键的最后一个元素
if (key.size() == 1) {
// 直接插入children
std::shared_ptr<T> val_p = std::make_shared<T>(std::move(value));
new_root->children_.insert({c, std::make_shared<const TrieNodeWithValue<T>>(val_p)});
} else {
// 创建一个空的children节点
auto ptr = std::make_shared<TrieNode>();
// 递归
PutCycle<T>(ptr, key.substr(1), std::move(value));
// 插入
new_root->children_.insert({c, std::move(ptr)});
}
}
}
template <class T>
auto Trie::Put(std::string_view key, T value) const -> Trie {
// Note that `T` might be a non-copyable type. Always use `std::move` when creating `shared_ptr` on that value.
// throw NotImplementedException("Trie::Put is not implemented.");
// 返回值类型是一个Trie的对象
// 第一种情况:值要放在根节点中,无根造根,有根放值
// 更改根节点的值,即测试中的 trie = trie.Put<uint32_t>("", 123);key为空
if (key.empty()) {
std::shared_ptr<T> val_p = std::make_shared<T>(std::move(value));
// 建立新根
std::unique_ptr<TrieNodeWithValue<T>> new_root = nullptr;
// 如果原根节点无子节点
if (root_->children_.empty()) {
// 直接修改根节点
new_root = std::make_unique<TrieNodeWithValue<T>>(std::move(val_p));
} else {
// 如果有,把原根的关系转移给新根:root_的children改为newRoot的children
// 这里看tire.h里TireNodeWithValue方法,可以看到,传入不同数量的参数,对应实现不同的方法。
new_root = std::make_unique<TrieNodeWithValue<T>>(root_->children_, std::move(val_p));
}
// 返回新的Trie
return Trie(std::move(new_root));
}
// 第二种情况:值不放在根节点中
// 2.1 根节点如果为空,新建一个空的TrieNode;
// 2.2 如果不为空,调用clone方法复制根节点
std::shared_ptr<TrieNode> new_root = nullptr;
if (root_ == nullptr) {
new_root = std::make_unique<TrieNode>();
} else {
new_root = root_->Clone();
}
// 递归插入,传递根节点,要放的路径:key, 要放入的值:value
PutCycle<T>(new_root, key, std::move(value));
// 返回新的Trie
return Trie(std::move(new_root));
// You should walk through the trie and create new nodes if necessary. If the node corresponding to the key already
// exists, you should create a new `TrieNodeWithValue`.
}
在Remove()函数中,根据key的值,删除value。此方法返回一个新的 trie。
先判断根是否为空,判断根节点是否有值,判断根节点是否有子节点,根节点有子节点,把子节点转移给新的根。如果键位不为空,创建新的根节点的副本,递归的删除。
具体的实现代码
// 返回表示是否删除了值
bool RemoveCycle(const std::shared_ptr<bustub::TrieNode> &new_roottry, std::string_view key) {
// 先找第一个key元素是否匹配
for (auto &pair : new_roottry->children_) {
if (key.at(0) != pair.first) {
// 不匹配就contiune
continue;
}
// 如果是键尾
if (key.size() == 1) {
// 判断是否为带值节点
if (!pair.second->is_value_node_) {
// 不带值返回false,表示没有删除值
return false;
}
// 如果带值但是没有子节点
if (pair.second->children_.empty()) {
// 直接删除
new_roottry->children_.erase(pair.first);
} else {
// 带值但是有子节点,转化为不带值节点
// 不能直接删除节点,而是 新建一个节点,把它原先的子节点重新传给他
pair.second = std::make_shared<const TrieNode>(pair.second->children_);
}
return true;
}
// 如果匹配了key,但不是键尾
// 拷贝当前节点
std::shared_ptr<TrieNode> ptr = pair.second->Clone();
bool flag = RemoveCycle(ptr, key.substr(1, key.size() - 1));
// 递归结束没有要删除的键
if (!flag) {
return false;
}
// 如果删除后当前节点没有值也没有子节点
// 则删除原先的当前节点
if (!ptr->is_value_node_ && ptr->children_.empty()) {
new_roottry->children_.erase(pair.first);
} else {
// 覆盖掉Ptr
pair.second = std::shared_ptr<TrieNode>(ptr);
}
return true;
}
// key不匹配
return false;
}
auto Trie::Remove(std::string_view key) const -> Trie {
// throw NotImplementedException("Trie::Remove is not implemented.");
if (this->root_ == nullptr) {
return *this;
}
// 如果键位空
if (key.empty()) { // 根节点有值
if (root_->is_value_node_) { // 根节点没有字节点
if (root_->children_.empty()) {
return Trie(nullptr);
}
// 根节点有子节点
// 根节点有子节点,把子结点转给新根
std::shared_ptr<TrieNode> new_root = std::make_shared<TrieNode>(root_->children_);
return Trie(new_root);
}
// 根节点无value,直接返回
return *this;
}
// 创建一个当前根节点的副本作为新的根节点
std::shared_ptr<TrieNode> newroot = root_->Clone();
// 递归删除
bool flag = RemoveCycle(newroot, key);
if (!flag) {
return *this;
}
if (newroot->children_.empty() && !newroot->is_value_node_) {
newroot = nullptr;
}
return Trie(std::move(newroot));