Я правильно реализую алгоритм "Heapify"?
Я создаю реализацию кучи для класса компьютерной науки, и мне было интересно, может ли следующая рекурсивная функция создать кучу объекта массива, который еще не был кучей.
код выглядит следующим образом:
void Heap::Heapify(int i)
{
int temp, l, r, heapify;
l = LeftChild(i);// get the left child
r = RightChild(i);// get the right child
//if one of the children is bigger than the index
if((Data[i] < Data[l]) || (Data[i]< Data[r]))
{
//if left is the bigger child
if(Data[l] > Data[r])
{
//swap parent with left child
temp = Data[i];
Data[i] = Data[l];
Data[l] = temp;
heapify = l; // index that was swapped
}
//if right is the bigger child
else
{
//swap parent with right child
temp = Data[i];
Data[i] = Data[r];
Data[r] = temp;
heapify = r; // index that was swapped
}
// do a recursive call with the index
//that was swapped
Heapify(heapify);
}
}
Идея заключается в том, что вы видите, что данные в указанном указателе больше, чем все дети. Если это так, функция не вызывает никаких проблем. В противном случае он проверяет, какие из них самые большие (левые или правые дети), а затем свопинг с индексом. Затем heapify вызывается в индексе, где произошла замена.
по запросу ildjarn, я включаю в себя все полные определения класса и файлы реализации, чтобы помочь в ответе на мой вопрос:
здесь заголовочный файл:
#ifndef HEAP_H
#define HEAP_H
//Programmer: Christopher De Bow
//Date: november 15, 2011
class Heap
{
private:
int Data [100];
int Parent(int);
int RightChild(int);
int LeftChild(int);
void Heapify(int);
void BuildHeap();
public:
Heap();
void insert();
void HeapSort();
void ExtractMaximum();
int Maximum();
void PrintHeap();
int heapsize;
void SetData(int[]);
};
#endif
и файл реализации:
#include <iostream>
#include "Heap.h"
using namespace std;
//Programmer: Christopher De Bow
//Date: november 15, 2011
Heap::Heap()
{
int init [10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
heapsize = 10;
SetData(init);
}
int Heap::Parent(int index)
{
int Rval;
if(index%2 == 0)// if the index is even
{
Rval = ((index-1)/2);
}
else// if the index is odd
{
Rval = (index/2);
}
return Rval;
}
int Heap::RightChild(int arrplace)
{
int ret;
ret = ((2*arrplace)+2); //rightchild is index times 2 plus 2
return ret;
}
int Heap::LeftChild(int i)
{
int rval;
rval = ((2*i)+1); //leftchild is index times 2 plus 1
return rval;
}
void Heap::Heapify(int i)
{
int temp, l, r, heapify;
l = LeftChild(i); // get the left child
r = RightChild(i); // get the right child
if((l <= heapSize) && (data[l] > data[i]))
{
heapify = l;
{
else
{
heapfiy = i;
}
if((r <= heapSize) && (data[r] > data[heapify]))
{
heapify = r;
}
if(heapify != i) // one of the two child nodes has proved
{ // larger than Data[i], so interchange values
//swap parent with left child
temp = Data[i];
Data[i] = Data[heapify];
Data[heapify] = temp;
Heapify(heapify);
}
}
void Heap::BuildHeap()
{
// we do not have a heap
// we will make a heap
// by calling heapify starting at the lowest
// internal node in the heap
for(int i = heapsize; i >= 1; i--)
{
Heapify(i-1);
}
}
void Heap::insert()
{
int insert;
heapsize = (heapsize + 1);
//getting data from the user
cout<<"what data would you like to insert?"<<endl;
cin>>insert;
Data[heapsize] = insert;
BuildHeap(); //call BuildHeap on array
cout<<"done"<<endl;
}
void Heap::PrintHeap()
{
BuildHeap();
for(int count = 0; count < (heapsize-1); count++)
{
cout<<Data[count];// print out every element in heap
}
cout<<endl<<endl;
}
void Heap::HeapSort()
{
BuildHeap();
int temp;
// do this for every elem in heap:
for(int i = 0; i < heapsize; i++)
{
temp = Data[heapsize-1];
Data[heapsize-1] = Data[0];
Data[0] = temp;
heapsize--;
BuildHeap();
}
PrintHeap();
}
void Heap::ExtractMaximum()
{
BuildHeap();
//assign last thing in heap to first thing in heap
Data[0] = Data[heapsize];
heapsize --; // decrease heapsize by one
Heapify(0); // heapify from the top
}
int Heap::Maximum()
{
int Rval;
BuildHeap();// make sure we have a heap
Rval = Data[0];
return Rval; // return top thing
}
//initialize the elements in the "Data" array
void Heap::SetData(int x[])
{
for(int i = 0; i <= (heapsize); i++)
{
Data[i] = x[i];
}
}
Ответы
Ответ 1
Ваш алгоритм работает. Проблема заключается в переводе алгоритма в код. Скажем, вы объявили Data как:
int Data[7];
и вы заполняете его начальными значениями {0, 1, 2, 3, 4, 5, 6}
. Предполагая определения LeftChild(i)
и RightChild(i)
как-то вроде:
#define LeftChild(i) ((i << 1) + 1)
#define RightChild(i) ((i << 1) + 2)
то ваша функция BuildHeap()
, которая должна быть чем-то вроде:
void Heap::BuildHeap()
{
for(int i = (7 >> 1); i >= 1; i--) // in general, replace 7 with
// (sizeof(Data)/sizeof(int)), presuming
// you have an array of int's. if not,
// replace int with the relevant data type
Heapify(i-1);
}
начнет процесс Heapify
в корне нижнего правого корня. В этом случае это индекс массива 2 с левым дочерним элементом 5 и правым дочерним элементом 6. Heapify
будет правильно обмениваться 2 и 6 и рекурсивно вызывать Heapify(6)
.
Здесь все может зацепиться! В настоящее время ваше дерево выглядит следующим образом:
0
1 2
3 4 5 6
u n d e f i n e d s p a c e
так что вызов Heapify(6)
будет покорно сравнивать значения Data[6]
с Data[13]
и Data[14]
(опасностями С++ и отсутствием соблюдения границ границ массива, в отличие от Java). Очевидно, что последние два значения могут быть любыми барахлами, оставшимися в ОЗУ. Одним из решений здесь, уродливым, но рабочим патчем, является добавление 8 элементов в объявление данных и их инициализация до некоторого значения ниже любого элемента массива. Лучшее решение - добавить переменную heapSize
в ваш класс и установить ее равной длине вашего массива:
heapSize = (sizeof(Data)/sizeof(int));
Затем интегрируйте логику только для сравнения дочерних узлов, если они являются допустимыми листьями дерева. Эффективная реализация этого:
void Heap::Heapify(int i)
{
int temp, l, r, heapify;
l = LeftChild(i); // get the left child
r = RightChild(i); // get the right child
if((l <= heapSize) && (Data[l] > Data[i]))
heapify = l;
else heapfiy = i;
if((r <= heapSize) && (Data[r] > Data[heapify]))
heapify = r;
if(heapify != i) // one of the two child nodes has proved
// larger than Data[i], so interchange values
{
//swap parent with left child
temp = Data[i];
Data[i] = Data[heapify];
Data[heapify] = temp;
Heapify(heapify);
}
}
Итак, чтобы обобщить, решение так же просто, как добавление логики, чтобы убедиться, что дочерние узлы являются допустимыми листьями дерева, а ваша основная функция будет иметь что-то вроде:
Heap heap;
// initialize Data here
heap.BuildHeap();
Надеюсь, что это поможет.
Ответ 2
Нет. На дереве
1
/ \
/ \
/ \
2 3
/ \ / \
6 7 4 5
вывод будет
3
/ \
/ \
/ \
2 5
/ \ / \
6 7 4 1
который имеет несколько нарушений кучи. (Я предполагаю, что Data[l]
и Data[r]
минус бесконечность, если соответствующие дети не существуют. Для этого может потребоваться дополнительная логика.)
Что делает ваша функция, это исправить дерево, которое не может быть кучей, но левые и правые поддеревья - это кучи. Вы должны называть его на каждом node в postorder (т.е. Для я от n - 1 до 0), так что дети из я являются кучками при вызове Heapify (i).
Ответ 3
Теперь ваш код успешно создает кучу. Был только один концептуальный недостаток: остальные были ошибочными ошибками индексации. Одна фундаментальная ошибка была в BuildHeap: у вас был
for(int i = heapSize; i >= 1; i--)
{
Heapify(i-1);
}
тогда как это должно быть
for(int i = (heapSize / 2); i >= 1; i--)
{
Heapify(i-1);
}
Это действительно важно, вы должны увидеть, что Heapify всегда вызывается в корне дерева, и (это действительно круто) вы можете легко найти последний корневой корень в массиве в индексе ((heapSize/2) - 1 ) (это для С++ и Java-стиля, где первый индекс == 0). То, как было написано ваш код под названием Heapify на последнем листе дерева, который ошибочен.
Кроме этого, я добавил комментарии, чтобы отмечать ошибки "один за другим". Я поместил их влево, чтобы вы могли их легко найти. Надеюсь, вы получите представление о алгоритмах и структурах данных.: -)
Ваш заголовочный файл:
#ifndef HEAP_H
#define HEAP_H
//Programmer: Christopher De Bow
//Date: november 15, 2011
class Heap
{
private:
int Data [100];
int Parent(int);
int RightChild(int);
int LeftChild(int);
void Heapify(int);
void BuildHeap();
// SO added heapSize
int heapSize;
public:
Heap();
void insert();
void HeapSort();
void ExtractMaximum();
int Maximum();
void PrintHeap();
int heapsize;
void SetData(int[]);
};
#endif
Ваш файл cpp:
#include <iostream>
#include "Heap.h"
using namespace std;
//Programmer: Christopher De Bow
//Date: november 15, 2011
Heap::Heap()
{
int init [10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
heapSize = 10;
SetData(init);
}
int Heap::Parent(int index)
{
int Rval;
if(index%2 == 0)// if the index is even
{
Rval = ((index-1)/2);
}
else// if the index is odd
{
Rval = (index/2);
}
return Rval;
}
int Heap::RightChild(int arrplace)
{
int ret;
ret = ((2*arrplace)+2); //rightchild is index times 2 plus 2
return ret;
}
int Heap::LeftChild(int i)
{
int rval;
rval = ((2*i)+1); //leftchild is index times 2 plus 1
return rval;
}
void Heap::Heapify(int i)
{
int temp, l, r, heapify;
l = LeftChild(i); // get the left child
r = RightChild(i); // get the right child
// you have to compare the index to (heapSize - 1) because we are working
// with C++ and the first array index is 0 : l and r are direct indices
// into the array, so the maximum possible index is the heapSize'th
// element, which is at heapSize-1. this was kind of nasty as it let the
// heapify index get too large and led to a swap with memory beyond the
// last element of the array (again, C++ doesn't enforce array boundaries
// as Java does).
if((l <= (heapSize-1)) && (Data[l] > Data[i]))
heapify = l;
else
heapify = i;
// you have to compare the index to (heapSize - 1) because we are working
// with C++ and the first array index is 0 : l and r are direct indices
// into the array, so the maximum possible index is the heapSize'th
// element, which is at heapSize-1. this was kind of nasty as it let the
// heapify index get too large and led to a swap with memory beyond the
// last element of the array (again, C++ doesn't enforce array boundaries
// as Java does).
if((r <= (heapSize-1)) && (Data[r] > Data[heapify]))
heapify = r;
if(heapify != i) // one of the two child nodes has proved
{ // larger than Data[i], so interchange values
//swap parent with left child
temp = Data[i];
Data[i] = Data[heapify];
Data[heapify] = temp;
Heapify(heapify);
}
}
void Heap::BuildHeap()
{
// we do not have a heap
// we will make a heap
// by calling heapify starting at the lowest
// internal node in the heap
// i must be initialized to (heapsize/2), please see my
// post for an explanation
for(int i = heapSize/2; i >= 1; i--)
{
Heapify(i-1);
}
}
void Heap::insert()
{
int insert;
heapSize = (heapSize + 1);
//getting data from the user
cout<<"what data would you like to insert?"<<endl;
cin>>insert;
Data[heapSize] = insert;
BuildHeap(); //call BuildHeap on array
cout<<"done"<<endl;
}
void Heap::PrintHeap()
{
BuildHeap();
// the array indices are from 0 through (heapSize-1), so
// count must be less than _or equal to_ (heapSize-1). another
// way of phrasing this (which i applied in this function)
// is (count < heapSize). you'll get better boundary conditions
// with practice.
for(int count = 0; count < heapSize; count++)
{
// added an endl to the output for clarity
cout << Data[count] << endl;// print out every element in heap
}
cout<<endl<<endl;
}
void Heap::HeapSort()
{
BuildHeap();
int temp;
// do this for every elem in heap:
for(int i = 0; i < heapSize; i++)
{
temp = Data[heapSize-1];
Data[heapSize-1] = Data[0];
Data[0] = temp;
heapSize--;
BuildHeap();
}
PrintHeap();
}
void Heap::ExtractMaximum()
{
BuildHeap();
//assign last thing in heap to first thing in heap
Data[0] = Data[heapSize];
heapSize--; // decrease heapSize by one
Heapify(0); // heapify from the top
}
int Heap::Maximum()
{
int Rval;
BuildHeap();// make sure we have a heap
Rval = Data[0];
return Rval; // return top thing
}
//initialize the elements in the "Data" array
void Heap::SetData(int x[])
{
// the array indices are from 0 through (heapSize-1), so
// count must be less than _or equal to_ (heapSize-1). another
// way of phrasing this (which i applied in this function)
// is (i < heapSize). you'll get better boundary conditions
// with practice.
for(int i = 0; i < heapSize; i++)
{
Data[i] = x[i];
}
}
// basic confirmation function
int main()
{
Heap heap;
heap.PrintHeap();
return 0;
}
Ответ 4
Ваш код, как написано здесь, кажется правильным; но нет ничего подобного написанию нескольких тестовых примеров, чтобы увидеть, как это работает. Обязательно проверяйте кучу с помощью 1, 2, 3, 4 и десятков элементов. (Я ожидаю, что базовый футляр будет там, где этот фрагмент будет коротким - как он справится, когда i
не имеет детей?) Тестирование на небольших кучах должно проявляться в спешке.)
Небольшой совет для этой части:
if(Data[l] > Data[r])
{
//swap parent with left child
temp = Data[i];
Data[i] = Data[l];
Data[l] = temp;
heapify = l; // index that was swapped
}
//if right is the bigger child
else
{ //swap parent with right child
temp = Data[i];
Data[i] = Data[r];
Data[r] = temp;
heapify = r; // index that was swapped
}
Вероятно, вы можете получить четкость, указав только индекс в блоках if
:
if(Data[l] > Data[r]) {
swapme = l;
} else {
swapme = r;
}
temp = Data[i];
Data[i] = Data[swapme];
Data[swapme] = temp;
heapify = swapme;