今天天气好,我高高兴兴地打算用 priority_queue
实现一个哈夫曼树,我是这么做的:
struct Node
{
int val;
Node *left = nullptr;
Node *right = nullptr;
Node(int v, Node *l = nullptr, Node *r = nullptr): val(v), left(l), right(r) {}
bool operator < (const Node &n) const
{
return this->val > n.val;
}
};
int main()
{
priority_queue<Node, vector<Node>> heap;
int n, x;
cin >> n;
for (int i = 0; i < n; ++i)
{
cin >> x;
Node n(x);
heap.push(n);
}
while (heap.size() > 1)
{
Node a = heap.top(); heap.pop();
Node b = heap.top(); heap.pop();
Node newNode(a.val + b.val, &a, &b);
heap.push(newNode);
}
Node root = heap.top();
//层序遍历
queue<Node> q;
q.push(root);
while (!q.empty())
{
Node n = q.front(); q.pop();
cout << n.val << " ";
if (n.left && n.right)
{
q.push(*n.left);
q.push(*n.right);
}
}
return 0;
}
这代码一旦运行,就会看见满屏的输出,永无止境。我一点一点的 debug ,发现是因为简历起来的树成了图了,自己指向自己…???
什么情况,我又仔细地 debug 一边,最后我发现了一个很鬼畜的事实:
Node a = heap.top(); heap.pop();
Node b = heap.top(); heap.pop();
没错!就是这两行代码,每次运行的时候都返回一毛一样的地址啊!不仅每次循环是一样的,就连 a
和 b
的地址也是一样的!
为了研究一下到底是哪里出的问题,我做了一个小实验。
void TestPriorityQueue(int n = 5)
{
priority_queue<int> q;
cout << "测试 priority_queue 的地址问题" << endl;
for (int i = 0; i < 5; ++i)
{
int x;
cin >> x;
cout << "x 的地址: " << &x << endl;
q.push(x);
}
const int *last = &q.top();
while (!q.empty())
{
cout << "堆顶元素: " << q.top() << ", 地址: " << &q.top() << " ";
const int *p = &q.top();
cout << p << "; ";
cout << "指针 last: " << last << ", " << *last << endl;
//last = p;
q.pop();
}
}
运行这段代码,会得到以下结果:
测试 priority_queue 的地址问题
1
x 的地址: 0x62fee0
2
x 的地址: 0x62fee0
3
x 的地址: 0x62fee0
4
x 的地址: 0x62fee0
5
x 的地址: 0x62fee0
堆顶元素: 5, 地址: 0x712138 0x712138; 指针 last: 0x712138, 5
堆顶元素: 4, 地址: 0x712138 0x712138; 指针 last: 0x712138, 4
堆顶元素: 3, 地址: 0x712138 0x712138; 指针 last: 0x712138, 3
堆顶元素: 2, 地址: 0x712138 0x712138; 指针 last: 0x712138, 2
堆顶元素: 1, 地址: 0x712138 0x712138; 指针 last: 0x712138, 1
为啥 x
的地址都一样我以前解释过了,这里我们主要关心最后 5 行,我们会发现:
heap.top()
的地址都是一样的last
,但是它的值却始终保持和 top()
一致莫非是我对 STL 的理解有问题?我换了个容器试了试:
void TestQueue(int n = 5)
{
queue<int> q;
cout << "测试 queue 的地址问题" << endl;
for (int i = 0; i < n; ++i)
{
int x;
cin >> x;
q.push(x);
}
const int *last = &q.front();
while (!q.empty())
{
cout << "队首元素: " << q.front() << ", 地址为" << &q.front() << " ";
const int *p = &q.front();
cout << p << "; ";
cout << "队尾元素: " << q.back() << ", 地址为" << &q.back() << " ";
cout << "; 指针 last: " << last << ", " << *last << endl;
//last = p;
q.pop();
}
}
结果如下:
测试 queue 的地址问题
1 2 3 4 5
队首元素: 1, 地址为0xfa16b0 0xfa16b0; 队尾元素: 5, 地址为0xfa16c0 ; 指针 last: 0xfa16b0, 1
队首元素: 2, 地址为0xfa16b4 0xfa16b4; 队尾元素: 5, 地址为0xfa16c0 ; 指针 last: 0xfa16b0, 1
队首元素: 3, 地址为0xfa16b8 0xfa16b8; 队尾元素: 5, 地址为0xfa16c0 ; 指针 last: 0xfa16b0, 1
队首元素: 4, 地址为0xfa16bc 0xfa16bc; 队尾元素: 5, 地址为0xfa16c0 ; 指针 last: 0xfa16b0, 1
队首元素: 5, 地址为0xfa16c0 0xfa16c0; 队尾元素: 5, 地址为0xfa16c0 ; 指针 last: 0xfa16b0, 1
这里就没有问题了,可见是 priority_queue
自身的特性导致的。
其实这个问题很简单,只是我一开始没有去思考而已。
请问:优先队列是什么?是堆,具体而言是二叉堆。那二叉堆是用什么实现的?用数组,因为二叉堆是一个完全二叉树,所以很方便用数组来实现。
这不就结了嘛!为啥每次地址都一样?不就是因为堆顶元素始终都放在二叉树的根节点位置吗?根节点始终都在数组的首位元素,所以每次返回的地址都一样啊。
所以说学好数据结构还是很重要的,不然到时候除了 bug 都不知道是怎么回事。
既然这样的话,我们就需要把原来的代码改一下了,我的做法是不在存储 Node
,而直接存储 Node*
,代码如下:
#include
#include
#include
using namespace std;
struct Node
{
int val;
Node *left = nullptr;
Node *right = nullptr;
Node(int v, Node *l = nullptr, Node *r = nullptr) : val(v), left(l), right(r) {}
};
struct Cmp
{
//注意这三个 const,否则通不过编译
bool operator()(const Node *n1, const Node *n2) const
{
return n1->val > n2->val;
}
};
void Clear(Node *&root)
{
if (root == nullptr)
return;
Clear(root->left);
Clear(root->right);
delete root;
root = nullptr;
}
int main()
{
priority_queue<Node *, vector<Node *>, Cmp> heap;
int n, x;
cin >> n;
for (int i = 0; i < n; ++i)
{
cin >> x;
Node *node = new Node(x);
heap.push(node);
}
while (heap.size() > 1)
{
Node *a = heap.top();
heap.pop();
Node *b = heap.top();
heap.pop();
Node *newNode = new Node(a->val + b->val, a, b);
heap.push(newNode);
}
Node *root = heap.top();
heap.pop();
//层序遍历
queue<Node *> q;
q.push(root);
while (!q.empty())
{
Node *n = q.front();
q.pop();
cout << n->val << " ";
if (n->left && n->right)
{
q.push(n->left);
q.push(n->right);
}
}
Clear(root);
return 0;
}