面试题答案
一键面试设计思路
- 使用
std::weak_ptr
打破循环引用:在存在循环引用的地方,将其中一个方向的引用改为std::weak_ptr
。通常将子节点对父节点的引用改为std::weak_ptr
,因为在遍历树时,从父节点到子节点的遍历更常见,这样可以保持从父节点到子节点的强引用关系,保证正常的遍历操作。 - 获取有效对象:在需要通过
std::weak_ptr
访问对象时,使用lock()
方法检查对象是否仍然存在(即对应的std::shared_ptr
是否还有引用)。如果存在,则返回一个std::shared_ptr
,可以安全地访问对象。 - 遍历和操作不受影响:对于树的遍历,如深度优先搜索(DFS)或广度优先搜索(BFS),由于父节点到子节点的引用仍然是强引用,所以遍历逻辑与普通树类似。对于需要通过子节点访问父节点的操作,先通过
std::weak_ptr
获取父节点的std::shared_ptr
,再进行操作。
关键代码片段
#include <memory>
#include <iostream>
#include <vector>
// 树节点类
class TreeNode {
public:
int value;
std::shared_ptr<TreeNode> parent;
std::vector<std::shared_ptr<TreeNode>> children;
std::weak_ptr<TreeNode> weakParent; // 使用std::weak_ptr打破循环引用
TreeNode(int val) : value(val) {}
// 添加子节点
void addChild(std::shared_ptr<TreeNode> child) {
children.push_back(child);
child->weakParent = shared_from_this(); // 设置子节点的弱引用父节点
}
// 通过子节点获取父节点
std::shared_ptr<TreeNode> getParent() {
return weakParent.lock();
}
};
// 深度优先搜索遍历树
void dfs(std::shared_ptr<TreeNode> node) {
if (!node) return;
std::cout << "Visiting node: " << node->value << std::endl;
for (const auto& child : node->children) {
dfs(child);
}
}
可以通过以下方式使用这些代码:
int main() {
std::shared_ptr<TreeNode> root = std::make_shared<TreeNode>(1);
std::shared_ptr<TreeNode> child1 = std::make_shared<TreeNode>(2);
std::shared_ptr<TreeNode> child2 = std::make_shared<TreeNode>(3);
root->addChild(child1);
root->addChild(child2);
dfs(root);
// 通过子节点获取父节点
if (auto parent = child1->getParent()) {
std::cout << "Child 1's parent value: " << parent->value << std::endl;
}
return 0;
}
在上述代码中,TreeNode
类使用std::weak_ptr
来表示子节点对父节点的引用,从而打破了可能存在的循环引用。dfs
函数展示了如何在这种数据结构上进行正常的遍历,getParent
函数展示了如何通过std::weak_ptr
获取父节点。