【C++】踩坑: 对priority_queue.top() 取址 + 哈夫曼的实现

1. 踩坑记录

今天天气好,我高高兴兴地打算用 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();

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

2. 实验+解释

2.1 实验

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

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 的理解有问题?我换了个容器试了试:

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*,代码如下:

#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;
}

猜你喜欢

转载自blog.csdn.net/frostime/article/details/94022651