【C++】巨坑: 对priority_queue取址

1. 踩坑记录

今天天气好,我高高兴兴地打算用 priority_queue 实现一个哈夫曼树,我是这么做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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 一边,最后我发现了一个很鬼畜的事实:

1
2
Node a = heap.top(); heap.pop();
Node b = heap.top(); heap.pop();

没错!就是这两行代码,每次运行的时候都返回一毛一样的地址啊!不仅每次循环是一样的,就连 ab 的地址也是一样的!

2. 实验+解释

2.1 实验

为了研究一下到底是哪里出的问题,我做了一个小实验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 行,我们会发现:

  1. 每次 heap.top() 的地址都是一样的
  2. 尽管我们没有改变 last,但是它的值却始终保持和 top() 一致

莫非是我对 STL 的理解有问题?我换了个容器试了试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 自身的特性导致的。

2.2 解释

其实这个问题很简单,只是我一开始没有去思考而已。

请问:优先队列是什么?是堆,具体而言是二叉堆。那二叉堆是用什么实现的?用数组,因为二叉堆是一个完全二叉树,所以很方便用数组来实现。

这不就结了嘛!为啥每次地址都一样?不就是因为堆顶元素始终都放在二叉树的根节点位置吗?根节点始终都在数组的首位元素,所以每次返回的地址都一样啊。

所以说学好数据结构还是很重要的,不然到时候除了 bug 都不知道是怎么回事。

3. 修改我的哈夫曼

既然这样的话,我们就需要把原来的代码改一下了,我的做法是不在存储 Node,而直接存储 Node*,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
#include <queue>
#include <functional>
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;
}