在C++中实现最小堆时出现问题

Trouble implementing min Heaps in C++

本文关键字:问题 C++ 实现      更新时间:2023-10-16

我正在尝试用C++实现一个minheap。然而,以下代码不断引发错误,例如:

heap.cpp:24:4:错误:无法在分配中将">复杂int"转换为"int">

l=2i;

^

heap.cpp:25:4:错误:无法在分配中将">复杂int"转换为"int">

r=2i+1;

^

heap.cpp:在成员函数"int heap::main()"中:

heap.cpp:47:16:错误:没有用于调用"heap::heapify(int[11],int&)"的匹配函数

heapify(a,i);
^

heap.cpp:47:16:注意:候选者是:

heap.cpp:21:5:注意:int heap::heapify(int)

int heapify(int i)//i是父索引,a[]是堆数组

^

heap.cpp:21:5:注意:候选者需要1个参数,提供2个

make:*[heap]错误1

#include <iostream>
using namespace std;
#define HEAPSIZE 10
class Heap
{
int a[HEAPSIZE+1];
Heap()
{
for (j=1;j<(HEAPISZE+1);j++)
{
cin>>a[j];
cout<<"n";
}  
}
int heapify(int i) //i is the parent index, a[] is the heap array
{
int l,r,smallest,temp;
l=2i;
r=2i+1;
if (l<11 && a[l]<a[i])
smallest=l;
else
smallest=i;
if (r<11 && a[r]<a[smallest])
smallest=r;
if (smallest != i)
{
temp = a[smallest];
a[smallest] = a[i];
a[i]=temp;
heapify(smallest);
}
}
int main()
{
int i;
for (i=1;i<=HEAPSIZE;i++)
{
heapify(a,i);
}
}
}

最终,这段代码的问题在于,它是由跳过"C++初学者"第1、2和3章的人编写的。让我们从一些基础知识开始。

#include <iostream>
using namespace std;
#define HEAPSIZE 10

在这里,我们包含了用于I/O(输入输出)的C++标头。一个良好的开端。然后,我们发布了一个指令,上面写着"将命名空间std中的所有内容放入全局命名空间"。这为您节省了一些输入,但也意味着,仔细划分为std::的数千个内容现在都可能与您想要在代码中使用的名称相冲突。这是一件坏事(TM)。尽量避免这样做。

然后我们继续使用C-ism,一个#define。有时你仍然需要在C++中这样做,但最好避免这样做。我们会回到这个问题上来。

下一个问题,至少在您发布的代码中,是对C++class的误解。

C++所基于的"C"语言具有struct的概念,用于描述数据项的集合。

struct
{
int id;
char name[64];
double wage;
};

重要的是要注意语法-尾部的";"。这是因为您可以同时描述结构和声明其类型的变量。

struct { int id; char name[64]; } earner, manager, ceo;

这声明了一个没有类型名称的结构,以及该类型的变量earnermanagerceo。分号告诉编译器我们何时完成此语句。当你需要在"}"后面加一个分号时,学习需要一段时间;通常你不会,但在结构/类定义中你会这样做。

C++为C添加了很多东西,但一个常见的误解是structclass在某种程度上完全不同。

C++最初扩展了struct的概念,允许您在结构的上下文中描述函数,允许您将成员/函数描述为privateprotectedpublic,并允许继承。

当您声明struct时,它默认为public。一个class只不过是一个以"私有"开头的struct

struct
{
int id;
char name[64];
double wage;
};
class
{
public:
int id;
char name[64];
double wage;
};

由此产生的定义都是相同的。

您的代码没有访问说明符,因此Heap类中的所有内容都是私有的。这导致的第一个也是最有问题的问题是:没有人可以调用任何函数,因为它们是私有的,只能从其他类成员调用。这包括构造函数。

class Foo { Foo () {} };
int main()
{
Foo f;
return 0;
}

上面的代码将无法编译,因为main不是Foo的成员,因此不能调用任何private

这给我们带来了另一个问题。在您发布的代码中,mainFoo成员。C++程序的入口点是main,而不是Foo::mainstd::mainFoo::bar::herp::main。只是,好的旧int main(int argc, const char* argv[])int main()

在C中,使用structs,因为C没有成员函数,所以您永远不会直接使用structmembers而不在其前面加上指针或成员引用,例如foo.idptr->wage。在C++中,在成员函数中,成员变量可以像局部函数变量或参数一样被引用。这可能会导致一些混乱:

class Foo
{
int a, b;
public:
void Set(int a, int b)
{
a = a;  // Erh,
b = b;  // wat???
}
};

有很多方法可以解决这个问题,但最常见的方法之一是用m_作为成员变量的前缀。

您的代码与此相冲突,显然C中的原始代码将数组传递给了heapify,并且数组位于局部变量a中。当您将a设置为成员时,保持变量名完全相同可以使您不会错过不再需要将其传递给对象的事实(事实上,您的heapify成员函数不再将数组作为指针,从而导致编译错误之一)。

我们遇到的下一个问题,还不是你问题的直接部分,是你的函数Heap()。首先,它是私有的——您使用了class,但还没有说public。但第二,你忽略了这个功能的重要性。

在C++中,每个结构/类都有一个与定义同名的隐含函数。对于CCD_ 37,这将是CCD_。这是"默认构造函数"。每当有人创建不带任何参数的Heap实例时,就会执行此函数。

这意味着它将在编译器创建短期临时堆时调用,或者在创建堆()的向量并分配新的临时堆时被调用。

这些功能有一个目的:准备对象占用的存储空间以供使用。你应该尽量避免其他工作,直到晚些时候。使用std::cin在构造函数中填充成员是你能做的最糟糕的事情之一

我们现在有了一个基础,可以开始以一种可行的方式编写代码的外壳。

最后一个更改是用类枚举替换"HEAPSIZE"。这是封装的一部分。您可以将HEAPSIZE保留为#define,但您应该在类中公开它,这样外部代码就不必依赖它,而是可以说Heap::SizeheapInstance.size()等。

#include <iostream>
#include <cstdint> // for size_t etc
#include <array> // C++11 encapsulation for arrays.
struct Heap // Because we want to start 'public' not 'private'.
{
enum { Size = 10 };
private:
std::array<int, Size> m_array; // meaningful names ftw.
public:
Heap() // default constructor, do as little as possible.
: m_array() // says 'call m_array()s default ctor'
{}
// Function to load values from an istream into this heap.
void read(std::istream& in)
{
for (size_t i = 0; i < Size; ++i)
{
in >> m_array[i];
}
return in;
}
void write(std::ostream& out)
{
for (size_t i = 0; i < Size; ++i)
{
if (i > 0)
out << ','; // separator
out << m_array[i];
}
}
int heapify(size_t index)
{
// implement your code here.
}
}; // <-- important.
int main(int argc, const char* argv[])
{
Heap myHeap; // << constructed but not populated.
myHeap.load(std::cin); // read from cin
for (size_t i = 1; i < myHeap.Size; ++i)
{
myHeap.heapify(i);
}
myHead.write(std::cout);
return 0;
}

最后,我们在您的代码中遇到了一个简单而基本的问题。C++没有隐式乘法。2i是带有后缀的数字2。它与2 * i不同。

int l = 2 * i;

您的代码还有一个特性,表明您正在混合基于0和基于1的实现。选择一个并坚持下去。

---编辑---

从技术上讲,这个:

myHeap.load(std::cin); // read from cin
for (size_t i = 1; i < myHeap.Size; ++i)
{
myHeap.heapify(i);
}

封装不良。我这样写是为了借鉴原始的代码布局,但我想指出,将构造和初始化分离的一个原因是,它可以确保初始化一切就绪。

因此,将heapify调用移到load函数中会更正确。毕竟,还有什么比添加新值、始终保持列表有序更好的时间来堆积呢。

for (size_t i = 0; i < Size; ++i)
{
in >> m_array[i];
heapify(i);
}

现在您已经简化了类api,用户不必知道内部机制。

Heap myHeap;
myHeap.load(std::cin);
myHeap.write(std::cout);