【问题标题】:STL Priority queue with custom comparator not working as expected具有自定义比较器的 STL 优先级队列未按预期工作
【发布时间】:2021-06-18 16:14:29
【问题描述】:

我正在尝试使用自定义运算符实现优先级队列。该算法试图找到要完成的最小增量,以便数组中没有两个相邻元素的绝对差 > 1。
为此,我得到数组中的最大元素“x”并将其邻居修改为 x-1,然后对其他元素重复相同的操作
代码如下:

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

int arr[100], visited[100];
int sizeOfArray;
struct comp
{
public:
    bool operator() (int x1, int x2) {
    return arr[x1] < arr[x2];
    }
};  

int main(){
    cin>>sizeOfArray;
    priority_queue<int, vector<int>, comp> priorityQue;
    for(int i = 0; i < sizeOfArray; i++) {
        cin>>arr[i];
        priorityQue.push(i);
        visited[i]=0;
    }
    while(!priorityQue.empty()) {
        int index = priorityQue.top();
        priorityQue.pop();
        if(visited[index])   
            continue;
        visited[index]=1;

        cout<<"Currently at index: "<<index<<endl;

        int currentElement = arr[index];
        int dx[] = {-1, 1};    // left and right neighbours
        for(int k = 0; k < 2; k++) {
            int nextIndex = index + dx[k];
            if( nextIndex >= 0 && nextIndex < sizeOfArray && 
                (currentElement - arr[nextIndex]) > 1 ) 
            {
                arr[nextIndex] = currentElement - 1;
                cout<<"Modifying index :"<<nextIndex<<endl;
                cout<<"New Array is: ";
                // print array 
                for(int z=0;z<sizeOfArray;z++)
                    cout<<arr[z]<<" ";
                cout<<endl;
                
                priorityQue.push(nextIndex);
                cout<<"Pushing index "<<nextIndex<<" to queue"<<endl;
            }
        }
    }
    return 0;
}

对于输入:

4
4 1 1 0

输出是:

当前位于索引:0
修改索引:1
新数组是:4 3 1 0
将索引 1 推送到队列
目前在索引:2
目前在索引:1
修改索引:2
新数组是:4 3 2 0
将索引 2 推送到队列
目前处于索引:3

我发现优先级队列没有按照比较器应有的方式提取最大元素。访问索引 0 后,数组变为 4 3 1 0 因此索引 1 应该是下一个,但在这种情况下索引 2 被拾取。 我错过了什么??

【问题讨论】:

  • typedef long long ll; -- 不要这样做 -- 不需要这样的宏。 C++ 有int64_t——它准确地描述了类型。然后是这个#include&lt;bits/stdc++.h&gt;——包括正确的头文件,而不是这个。
  • 并且不要使用逗号运算符将表达式“链接”成单个语句,而是使用单独的语句。还要尽量避免使用全局变量和简短的非描述性名称。所有这些都会使您的代码更难阅读、理解、维护和调试。
  • @Someprogrammerdude 已编辑

标签: c++ algorithm stl priority-queue max-heap


【解决方案1】:

您在将项目放入队列后对其进行修改,并且仍然是该队列的一部分。优先级队列不支持这并产生未指定的结果。 C++ STL 不提供可更新的优先级队列。

但是,看到您的用例,我怀疑这是竞争性算法编程。这个用例有不错的选择。 我不建议将它们用于生产代码(至少在没有适当抽象的情况下不会。

第一个“正确”的替代方法是使用std::set&lt;std::pair&lt;...&gt;&gt;。您的代码将保持非常相似,但有一些重要的区别:

  • 您不需要自定义比较器,而是依赖配对比较(您需要使用 std::greater 作为比较器,以便将最大的项目放在“顶部”,
  • 您将把{a[index], index} 作为这个集合的元素,
  • 您将使用.begin() 而不是.top()
  • 在更新它们的值之前,您需要.erase() 项目,并使用新值再次插入它们。

我相信上面的复杂性与优先级队列的标准实现相同。尽管更新看起来很慢,但我们只执行了两次 O(log n) 操作 - 这与堆结构中的实际更新具有相同的复杂性。

您可以像示例中那样使用间接比较器来实现它。通常它会起作用,但您仍然需要围绕更新的擦除和插入流程。此外,您还需要比较比较器中的索引,以使具有相同优先级的项目不同,从而删除正确的项目。

此外,我们还可以在许多常见情况下使用另一个技巧,例如 Dijkstra 或 Prim 的算法。在这些情况下,我们只会将优先级更新为更高(更低的值)。在这种情况下,我们可以忽略擦除项目,而只是添加重复项。这是因为单个查询/更新的时间复杂度变为O(log n^2) = O(2 log n) = O(log n)。内存复杂度增加,但这通常不是问题。

在最后一种情况下,您可以使用其他容器来满足您的喜好,std::priority_queue&lt;std::pair&lt;...&gt;&gt;std::multimap&lt;...&gt; 都可以很好地工作。但在所有这些中,您需要将优先级作为您插入的项目的一部分,而不是通过间接比较器使用它。

作为附录,这是您的代码,经过更改后可以按预期工作:

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

int arr[100], visited[100];
int sizeOfArray;


int main(){
    cin>>sizeOfArray;
    priority_queue<std::pair<int, int>> priorityQue;
    for(int i = 0; i < sizeOfArray; i++) {
        cin>>arr[i];
        priorityQue.push({arr[i], i});
        visited[i]=0;
    }
    while(!priorityQue.empty()) {
        int index = priorityQue.top().second;
        priorityQue.pop();
        if(visited[index])   
            continue;
        visited[index]=1;

        cout<<"Currently at index: "<<index<<endl;

        int currentElement = arr[index];
        int dx[] = {-1, 1};    // left and right neighbours
        for(int k = 0; k < 2; k++) {
            int nextIndex = index + dx[k];
            if( nextIndex >= 0 && nextIndex < sizeOfArray && 
                (currentElement - arr[nextIndex]) > 1 ) 
            {
                arr[nextIndex] = currentElement - 1;
                cout<<"Modifying index :"<<nextIndex<<endl;
                cout<<"New Array is: ";
                // print array 
                for(int z=0;z<sizeOfArray;z++)
                    cout<<arr[z]<<" ";
                cout<<endl;
                
                priorityQue.push({arr[nextIndex], nextIndex});
                cout<<"Pushing index "<<nextIndex<<" to queue"<<endl;
            }
        }
    }
    return 0;
}

【讨论】:

  • In such case we can ignore erasing items, and instead just add duplicates. 这正是我正在做的事情,我没有修改队列,我只是添加重复索引,其优先级由comp 函数决定。
  • 但是您的比较器使用全局 arr[] 数组。因此,当您第二次插入键时,您正在修改已经在其中的键的值 - 这会导致不可预知的行为。
  • 我添加了工作代码,以明确没有此问题的解决方案的外观。
  • 我不认为 key 的值会发生变化。根据thispriority queue' 容器与我定义的数组是分开的。
  • key的“值”是arr[index]的值。这是用于比较的值,并且该值会发生变化。我相信优先队列是作为堆实现的。在第一个pop() 优先级队列之后包含1 1 0。索引 2 的 1 很可能位于根中,而索引 1 和 3 位于其下方。当您更新 arr[1] = 3 并再次插入 1 时,只会更新新元素的位置。它可能附加在旧 1 下方。但是由于您更新了旧 1 的值,因此新 1 永远不会超过它,并且永远不会针对 2 进行检查。在 arr[1] = 3 之后,堆处于无效状态
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-15
  • 1970-01-01
相关资源
最近更新 更多