I'm still rather new to C++ and am having trouble implementing an add method that actually works. I've done a hash map in java, but translating it to C++ has proven to be difficult. On top of this, I have to work with constraints (such as not changing anything in the header file), and not being able to use any std library function besides std::string, and std::cout/cin.
Basically, I have to create a hash map that will end up storing usernames (as a key), and passwords (as a value). At this point the username/password combination isn't that important, since implementing a very general class is the point of the exercise.
Just trying to test my HashMap with a default constructor and adding a value ends up giving me a segmentation fault. I'm 100% sure that I'm doing something horribly wrong when trying to implement this hash table. Either I'm not properly connecting the bucket index with a node, or not initializing something correctly.
Here is the header file that I am working with:
#ifndef HASHMAP_HPP
#define HASHMAP_HPP
#include <functional>
#include <string>
class HashMap
{
public:
// Hash functions must conform to these properties:
//
// (1) Given a particular string s repeatedly, they must always
// return the same hash value.
// (2) They do not take the number of buckets into account (as they
// do not receive a parameter that tells them how many buckets
// there are). Any unsigned int value is fair game as a result.
// It will be the job of the HashMap class to reduce the results
// to the range of available bucket indices (e.g., by using the
// % operator).
typedef std::function<unsigned int(const std::string&)> HashFunction;
// This constant specifies the number of buckets that a HashMap will
// have when it is initially constructed.
static constexpr unsigned int initialBucketCount = 10;
public:
// This constructor initializes the HashMap to use whatever default
// hash function you'd like it to use. A little research online will
// yield some good ideas about how to write a good hash function for
// strings; don't just return zero or, say, the length of the string.
HashMap();
// This constructor instead initializes the HashMap to use a particular
// hash function instead of the default. (We'll use this in our unit
// tests to control the scenarios more carefully.)
HashMap(HashFunction hasher);
// The "Big Three" need to be implemented appropriately, so that HashMaps
// can be created, destroyed, copied, and assigned without leaking
// resources, interfering with one another, or causing crashes or
// undefined behavior.
HashMap(const HashMap& hm);
~HashMap();
HashMap& operator=(const HashMap& hm);
// add() takes a key and a value. If the key is not already stored in
// this HashMap, the key/value pair is added; if the key is already
// stored, the function has no effect.
//
// If adding the new key/value pair will cause the load factor of this
// HashMap to exceed 0.8, the following must happen:
//
// (1) The number of buckets should be increased by doubling it and
// adding 1 (i.e., if there were 10 buckets, increase it to
// 2 * 10 + 1 = 21).
// (2) All key/value pairs should be rehashed into their new buckets,
// important because changing the number of buckets will likely
// change which bucket a particular key hashes to (especialy if
// you're using % to determine the index of that bucket).
void add(const std::string& key, const std::string& value);
// remove() takes a key and removes it (and its associated value) from
// this HashMap if it is already present; if not, the function has no
// effect.
void remove(const std::string& key);
// contains() returns true if the given key is in this HashMap, false
// if not.
bool contains(const std::string& key) const;
// value() returns the value associated with the given key in this HashMap
// if the key is stored in this HashMap; if not, the empty string is
// returned. (Going forward, we'll discover that throwing an exception
// is a better way to handle the scenario where the key is not present,
// but we'll conquer that at a later date.)
std::string value(const std::string& key) const;
// size() returns the number of key/value pairs stored in this HashMap.
unsigned int size() const;
// bucketCount() returns the number of buckets currently allocated in
// this HashMap.
unsigned int bucketCount() const;
// loadFactor() returns the proportion of the number of key/value pairs
// to the number of buckets, a measurement of how "full" the HashMap is.
// For example, if there are 20 key/value pairs and 50 buckets, we would
// say that the load factor is 20/50 = 0.4.
double loadFactor() const;
// maxBucketSize() returns the number of key/value pairs stored in this
// HashMap's largest bucket.
unsigned int maxBucketSize() const;
private:
// This structure describes the nodes that make up the linked lists in
// each of this HashMap's buckets.
struct Node
{
std::string key;
std::string value;
Node* next;
};
// Store the hash function (either the default hash function or the one
// passed to the constructor as a parameter) in this member variable.
// When you want to hash a key, call this member variable (i.e., follow
// it with parentheses and a parameter) just like you would any other
// function.
HashFunction hasher;
// You will no doubt need to add at least a few more private members
public:
// our hash function
unsigned int hashFunc(const std::string& key) const;
private:
Node** hashTable;
// We need a variable that will always let us know what the current amount
// of buckets is. bucketCount will use this and return this variable.
unsigned int amountOfBuckets;
// we also need the number of keys currently in the hash map. This is stored here
unsigned int sz;
};
#endif // HASHMAP_HPP
And here is how I am implementing my class (HashMap.cpp):
#include "HashMap.hpp"
// default constructor will initialize Node to default values
// Create a new hash table with the initial bucket count
// Set the amount of buckets to the initial bucket count
// Set the current amount of key/value pairs to zero.
HashMap::HashMap()
: hashTable{new Node*[initialBucketCount]}, amountOfBuckets{initialBucketCount}, sz{0}
{
}
// constructor that initializes HashMap to use a different hash function other
// than the default
HashMap::HashMap(HashFunction hashFunc)
: hasher{hashFunc}, hashTable{new Node*[initialBucketCount]}, amountOfBuckets{initialBucketCount}, sz{0}
{
}
// copy constructor, initializes a new HashMap to be a copy of an existing one
HashMap::HashMap(const HashMap& hm)
// commented out for now :
{
}
// destructor: deallocate the HashMap
HashMap::~HashMap()
{
// delete something here
}
// Assignment operator that overloads equals
HashMap& HashMap::operator=(const HashMap& hm)
{
// FIX COMPILER WARNINGS, DELETE
return *this;
}
// our hash function, this is for our type def HashFunction
// pass this through the constructor
unsigned int HashMap::hashFunc(const std::string& key) const
{
unsigned int hashValue = 0; // what we end up returning
for(int i = 0; i < key.length(); i++) { // iterate through string
int letterIndex = key.at(i) - 96; // create an index for each ASCII char
// first multiply our current hashValue by a prime number
// add to the letter index, to maintain a stable result
// mod by the current amount of buckets on each iteration to prevent overflow
hashValue = (hashValue * 27 + letterIndex) % bucketCount();
}
return hashValue;
}
// add function
void HashMap::add(const std::string& key, const std::string& value)
{
// Check if key being stored matches key already in hashmap
/* BASIC ADD FUNCTION, JUST TO IMPLEMENT UNIT TESTS */
unsigned int hashVal = hashFunc(key);
//Node* prev = nullptr; // keeps track of where we are
Node* current = hashTable[hashVal]; // the place we store a data item into
while(current != nullptr) {
//prev = current; // update previous node to point to current
// this lets us move current without losing our place
current = current->next; // move current to the next node
} // stop once we find an empty node
// current should equal a nullptr
current->key = key; // set key (user)
current->value = value; // set password
current->next = nullptr; // set the next ptr to be null
}
// takes in a key (username), removes it and the value (password) associated
// with it, otherwise, it has no effect
void HashMap::remove(const std::string& key)
{
}
// returns true if given key is in hash map, otherwise returns false
// this acts as a find method
bool HashMap::contains(const std::string& key) const
{
unsigned int hashedValue = hashFunc(key); // hash the key given to get an index
if(hashTable[hashedValue] == nullptr) { // if there are no nodes at given index
return false;
} else { // there are some nodes in the hash table
// iterate through each node in the linked list
// Node* current = hashTable[hashedValue];
// start at first node (this is current)
Node* current = hashTable[hashedValue];
while(current != nullptr && current->key == key) {
current = current->next;
} // end while
if(current == nullptr) { // we reached the end of our linked list
return false; // couldn't find a value
} else { // we found the key provided
return true;
}
} // end if-else
}
// value() returns the value associated with the given key in this HashMap
// if the key is stored in this HashMap; if not, the empty string is returned.
std::string HashMap::value(const std::string& key) const
{
// HANDLES COMPILER WARNINGS, DELETE LATER
return "";
}
// size() returns the number of key/value pairs stored in this HashMap.
unsigned int HashMap::size() const
{
return sz;
}
// bucketCount() returns the number of buckets currently allocated in this HashMap.
// each bucket is an index for the array, we do not include the linked lists.
unsigned int HashMap::bucketCount() const
{
return amountOfBuckets;
}
// loadFactor() returns the proportion of the number of key/value pairs
// to the number of buckets, a measurement of how "full" the HashMap is.
// For example, if there are 20 key/value pairs and 50 buckets, we would
// say that the load factor is 20/50 = 0.4.
double HashMap::loadFactor() const
{
return sz / amountOfBuckets;
}
// maxBucketSize() returns the number of key/value pairs stored in this
// HashMap's largest bucket.
unsigned int HashMap::maxBucketSize() const
{
// HANDLE COMPILER WARNINGS, DELETE LATER
return 0;
}
The main method I am implementing right now is add. Currently I'm just trying to test the class with a main function to even see if I can add values to the map, and test to see if it recognizes whether things are being contained in the map or not. I realize that very little in the class is complete, and that the functions themselves are incomplete, however I'm just trying to test the most basic cases.
Finally, here is my main:
#include <iostream>
#include <string>
#include "HashMap.hpp"
int main()
{
// initialize test
HashMap test1;
std::cout << "TEST 1 HASHMAP OBJECT CREATED" << std::endl;
// add some values
// at this point (11/16/2014), I only have contains, add, and hashFunc
// test these methods below
// constructor doesn't quite work right
std::string key1 = "Alex";
std::string value1 = "password1";
std::string key2 = "Danielle";
std::string value2 = "password2";
std::cout << "strings have been created" << std::endl;
// add to hash map
test1.add(key1, value1);
test1.add(key2, value2);
std::cout << "Keys and values have been added to hash map" << std::endl;
// does key1 contain the word "hi"? no, should return false
std::cout << "Hash map contains word hi?: " << test1.contains("hi") << std::endl;
// does key2 contain word "Danielle"? yes, should return true
std::cout << "Hash map contains word Danielle?: " << test1.contains("Danielle") << std::endl;
return 0;
}
I am using a pre-built script to run the program after I build it. When run, I get this output:
TEST 1 HASHMAP OBJECT CREATED
strings have been created
./run: line 43: 10085 Segmentation fault (core dumped) $SCRIPT_DIR/out/bin/a.out.$WHAT_TO_RUN
Basically the segmentation fault happens during the add function. So what is actually going on with add? And how can I understand what the hash map should be doing better?
Your own comment that current should equal nullptr correctly foretells the failure at the next line:
// current should equal a nullptr
current->key = key; // set key (user)
It's generally bad practice to assume a newly allocated array will be full of nullptr.
You need to set it to all nullptr in the constructor.
There are other problems with your add() function.
Here's a stab at making it at least fit for purpose:
void HashMap::add(const std::string& key, const std::string& value)
{
// Check if key being stored matches key already in hashmap
/* BASIC ADD FUNCTION, JUST TO IMPLEMENT UNIT TESTS */
unsigned int hashVal = hashFunc(key);
Node* head=hashTable[hashVal];
Node* current = head;
if(current==nullptr){
//Nothing in this bucket so it's definitely new.
current=new Node();
current->key = key; // set key (user)
current->value = value; // set password
current->next = nullptr; // set the next ptr to be nullptr.
hashTable[hashVal]=current;
return;
}
do { //It's a do-while because the if statement above has handled current==nullptr.
if(current->key==key){
//We've found a match for the key.
//Common hash-table behavior is to overwrite the value. So let's do that.
current->value=value;
return;
}
current = current->next; // move current to the next node
} while(current != nullptr) // stop once we go past the last node.
//Finally, we found hash collisions but no match on key.
//So we add a new node and chain it to the node(s) already there.
//
//Sometimes it's a good idea to put the new one at the end or if it's likely to get looked up
//it's also a good idea to put it at the start.
//It might be a good idea to keep the collision chain sorted and insert into it accordingly.
//If we sort we can dive out of the loop above when we pass the point the key would be.
//
//However for a little example like this let's put the new node at the head.
current=new Node();
current->key = key; // set key (user)
current->value = value; // set password
current->next = head; // set the next pointer to be the old head.
hashTable[hashVal]=current;
}
PS: There's also a problem with your hash-function.
You're taking modulo the hash-table size inside the main loop.
That will only have the effect of restricting the distribution and giving a dreadful spread.
At least move it to the last line of that function:
return hashValue % bucketCount() ;
However I recommend moving it into the add function:
unsigned int hashVal = hashFunc(key); //Assume modified to not use bucketCount().
unsigned int tableIndex=hashVal % bucketCount();//Reduce hash to a valid index.
Node* head=hashTable[tableIndex]; //TODO: Do same in the other accesses to hashTable...
You could then store the full hash-value in the Node structure and use that as a stronger pre-comparison before current->key==key. If the hash-table is likely to be very full you can get a big performance gain. It depends if you want to spare the bytes for an unsigned int per Node.
If you did that and you took the tip to sort the collision chain, you would do so by hash-code and could normally avoid comparing any strings at add() new key or get() not-there and normally only once in a successful get() or overwriting add().
In your HashMap::add, you're dereferencing a nullpointer. You create an array of node pointers with size of 10 elements in your constructor, but in your code you never create any node objects and assign to the pointers in your array. So when you do:
current->key = key;
you're accessing a node-pointer that is 0.
Related
One of major drawbacks to linked lists is that the access time to elements is linear. Hashtables, on the other hand, have access in constant time. However, linked lists have constant insertion and deletion time given an adjacent node in the list. I am trying to construct a FIFO datastructure with constant access time, and constant insertion/deletion time. I came up with the following code:
unordered_map<string key, T*> hashTable;
list<T> linkedList;
T Foo;
linkedList.push_front(Foo);
hashTable.insert(pair<string, T*>("A", &Foo);
T Bar;
linkedList.push_front(Bar);
hashTable.insert(pair<string, T*>("B", &Bar);
However, this code feels like it is really dangerous. The idea was that I could use the hashtable to access any given element in constant time, and since insertion always occurs at the start of the list, and deletion from the end of the list, I can insert and delete elements in constant time. Is there anything inherently poor about the above code? If I wanted to instead store pointers to the nodes in the linkedList to have constant insertion/deletion time from any node would I just store list< T >::iterator*?
If you build a lookup table you will end up with 2 data structures holding the same data which does not make any sense.
The best thing you can do is making your linked list ordered and build a sparse lookup table in some way to select the start node to search in order to amortize some run time.
It is unclear how you want to access the list elements. From the code you provided, I assume you want the first pushed node as "A", the second pushed node as "B", etc. Then my question is what happens when we delete the first pushed node? Does the second pushed node become "A"?
If the node identifiers don't change when updating the list, your approach seems alright.
If the node identifiers has to change on list update, then here is a lightweight and limited approach:
const int MAX_ELEMENTS = 3;
vector<int> arr(MAX_ELEMENTS + 1);
int head = 0, tail = 0;
int get_size() {
if (head > tail)
return tail + ((int)arr.size() - head);
return tail - head;
}
void push(int value) {
if (get_size() == MAX_ELEMENTS) {
// TODO: handle push to full queue
cout << "FULL QUEUE\n";
return;
}
arr[tail++] = value;
if (tail == (int)arr.size()) {
tail = 0;
}
}
int pop() {
if (get_size() == 0) {
// TODO: handle access to empty queue
cout << "EMPTY QUEUE\n";
return -1;
}
int result = arr[head++];
if (head == (int)arr.size()) {
head = 0;
}
return result;
}
int get_item_at(int id) {
if (id >= get_size()) {
// TODO: index out of range
cout << "INDEX OUT OF RANGE\n";
return -1;
}
int actual_id = head + id;
if (actual_id >= (int)arr.size()) {
actual_id -= (int)arr.size();
}
return arr[actual_id];
}
The above approach will keep indices up-to-date (eg. get_item_at(0) will always return the first node in the queue). You can map ids to any suitable id you want like "A" -> 0, "B" -> 1, etc. The limitation of this solution is that you won't be able to store more than MAX_ELEMENTS in the queue.
If I wanted to instead store pointers to the nodes in the linkedList to have constant insertion/deletion time from any node would I just store list< T >::iterator*?
If identifiers must change with insertion/deletion, then it is going to take O(n) time anyways.
This idea makes sense, especially for large lists where order of insertion matters but things might be removed in the middle. What you want to do is create your own data structure:
template<typename T> class constantAccessLinkedList {
public:
void insert(const T& val) {
mData.push_back(val); //insert into rear of list, O(1).
mLookupTable.insert({val, mData.back()}); //insert iterator into map. O(1).
}
std::list<T>::iterator find(const T& val) {
return mLookupTable[val]; //O(1) lookup time for list member.
}
void delete(const T& val) {
auto iter = mLookupTable.find(val); //O(1)get list iterator
mLookupTable.erase(val); //O(1)remove from LUT.
mData.erase(iter); //O(1) erase from list.
}
private:
std::list<T> mData;
std::unordered_map<T, std::list<T>::iterator> mLookupTable;
};
The reason you can do this with list and unordered_map is because list::iterators are not invalidated on modifying the underlying container.
This will keep your data in order in the list, but will provide constant access time to iterators, and allow you to remove elements in constant time.
I'm programming an assignment for my algorithms class. I have a struct
struct Node
{
int firstPosition;
int secondPosition;
string firstColor;
string secondColor;
bool operator<(const Node& n) const {
return (firstPosition < n.secondPosition);
}
bool operator==(const Node& n) const {
return (firstPosition == n.firstPosition && secondPosition == n.secondPosition && firstColor == n.firstColor && secondColor == n.secondColor);
}
};
I have an stl map called "graph"
map<Node, vector<Node>> graph;
I place a single node in to start the building of the graph
Node startNode = {1, 1, "R", "B"};
vector<Node> emptyVector;
graph.emplace(startNode, emptyVector);
I'm essentially trying to build a graph of nodes, so for every line of input I get (class gave me an input file - sourceNode and targetNode are integers I read in, pathColor is a string I read in), I need to access each node and add other nodes to their adjacency list based on what I read in, so in a while loop for each line:
for (auto& currentNode : graph) {
// Case: sourceNode is the first position, so we make a new node with the updated first position
if (currentNode.first.firstPosition == sourceNode ) {
if (currentNode.first.secondColor == pathColor) {
Node newNode = {targetNode, currentNode.first.secondPosition, vertexColors[targetNode - 1], currentNode.first.secondColor};
graph.at(currentNode.first).emplace_back(newNode); // Put the newly created node in the current node's adjacency list
vector<Node> emptierVector;
graph.emplace(newNode, emptierVector); // Insert the newly created node into the graph
}
}
// More extra code down below
But the graph.at(currentNode.first).emplace_back(newNode); line throws "terminate called after throwing an instance of 'std::out_of_range'
what(): map::at" even though I've implemented the == operator overload and verified it works and that my data matches. I can't seem to find where my misunderstanding of the function works from documentation. Please help!
I'm expecting the map.at(key) function to return the key's associated data, but when I am passing it a struct here with the exact same data as the key, it throws an out of range instead of returning the value associated with the key. Can I not just pass a struct with the exact same data as the key? It seems to not detect as the same key value.
The problem was not the way the map.at() function works, it was that my less than operator in my struct was declared incorrectly. It compared different values from two Nodes instead of the same value.
This website helped me realize.
https://www.techiedelight.com/use-custom-objects-keys-std-map-cpp/
I have the following code which uses map to insert Nodes into mp according to a key. The class has two functions set and get to insert and access the map respectively.
struct Node {
int value;
int key;
Node(int k, int val):key(k),value(val) {};
};
class Cache {
private:
map<int, Node*> mp;
Node* tail;
Node* head;
public:
void set(int, int);
void get(int);
};
void Cache::set(int key, int value) {
Node newN = Node(key, value);
mp.insert(std::pair<int, Node*>(key, &newN));
}
void Cache::get(int key) {
auto s = this->mp.find(key);
if (s != this->mp.end()) {
Node *nHit = s->second;
std::cout << "Map key = " << s->first;
std::cout << " : Node Key = " << nHit->key;
std::cout << ", value = " << nHit->value << "\n";
}
}
A driver main function implementation is below, which takes input of 2 lines and outputs key and value.
int main() {
int i;
Cache l;
for(i = 0; i < 2; i++) {
string command;
cin >> command;
if(command == "get") {
int key;
cin >> key;
l.get(key);
}
else if(command == "set") {
int key, value;
cin >> key >> value;
l.set(key, value);
}
}
return 0;
}
Input -
set 2 3
get 2
Output -
Map key = 2 : Node Key = 32764, value = -491659096
Note - The output key and value keeps changing and are not fixed with each run.
Why and how is the key and value getting changed for the map here?
You are inserting a pointer to a function-scoped value. When the function set() exits the value newN is destroyed, and the pointer held in the map is invalid.
Either you really want a map with an instance of Node as the value; or you need to use new in set() to allocate your object, but then you also need to remember to delete it. You could use "smart" pointers such as shared_ptr or unique_ptr to help manage this lifetime - though unique_ptr won't get you any advantages over using an instance.
In C++, a piece of data is typically tied to a single location. If that location is a variable in some { block scope; }, then the variable is deleted at the closing }. For instance, the closing bracket of Cache::set deletes newN, and any references to it (including pointers in mp) no longer point anywhere.
C++ has a second option: the lifetime of some data can be controlled by a class. For instance, the int and Node* values you put in mp get deleted along with mp. So rather than storing Node* in mp, you can store Node directly, instead of its address!
The declaration of mp should be
map<int, Node*> mp;
You can insert with
void Cache::set(int key, int value) {
mp.emplace(key, Node(key, value));
}
And then in main you can have
Node &nHit = s->second;
And then you can get members of nHit with . instead of ->.
Your other alternative is to use smart pointers, as Jesper Juhl mentioned. Store std::unique_ptr<Node> in mp if you're using pointers only for polymorphism with virtual methods, and std::shared_ptr<Node> is useful if you want some of your Node objects to be used in several places and mp.
Taking the address of an object with & (e.g. to create Node*) is very useful if you want to just reference an object of known lifetime, and you want to store that reference inside of some other object of a shorter lifetime.
DO NOT use new Node(key, value) except in a smart pointer constructor, unless you're prepared to individually delete every Node* yourself, and never make mistakes.
You have to allocate Node using new. E.g.,
Node *newN = new Node(key, value);
mp.insert(std::pair<int, Node*>(key, newN));
As it is your Node is on the stack and goes out of scope when the function returns.
Hello this is the code:
template <class T> class FibonacciHeap{
public:
class Entry{
public:
// Returns the element represented by this heap entry.
T getValue(){
return mElem;
}
// Sets the element associated with this heap entry.
void setValue(T value){
mElem = value;
}
// Returns the priority of this element.
double getPriority(){
return mPriority;
}
private:
int mDegree = 0; // Number of children
bool mIsMarked = false; // Whether the node is marked
Entry mNext; // Next element in the list
Entry mPrev; // Previous element in the list
Entry mChild; // Child node, if any
Entry mParent; // Parent node, if any
T mElem; // Element being stored here
double mPriority; // Its priority
//Constructs a new Entry that holds the given element with the indicated priority.
Entry(T elem, double priority){
mNext = mPrev = this;
mElem = elem;
mPriority = priority;
}
};
...
In the Class "Entry" I want to call Entry recursively, so I can use:
First_entry.mPrev.mNext
I know this works in Java, but when I compile this in c++, I get:
error: 'FibonacciHeap<T>::Entry::mNext' has incomplete type
Does anyone know how to fix this or work around this?
Based on the variable names and initializers here, I'm assuming you're adapting my Java Fibonacci heap into C++. :-) If so, best of luck!
In Java, if you have a variable of type Entry, it acts like a C++ variable of type Entry* in that it's a pointer to another Entry object rather than an honest-to-goodness Entry object. As a result, in the definition of the Entry class, you should adjust the fields so that they're of type Entry* rather than Entry. Similarly, instead of using the . operator to select fields, you'll want to use the -> operator. So
First_entry.mPrev.mNext
would be rewritten as
First_entry->mPrev->mNext
Don't forget to explicitly initialize the Entry pointers to nullptr - Java does this automatically, which is why there are no initializers in the Java version. However, C++ gives uninitialized pointers garbage values, so make sure to give mChild and mParent an explicit nullptr value.
Read the following statement somewhere:
An additional hash table can be used to make deletion fast in
min-heap.
Question> How to combine priority_queue and unordered_map so that I can implement the idea above?
#include <queue>
#include <unordered_map>
#include <iostream>
#include <list>
using namespace std;
struct Age
{
Age(int age) : m_age(age) {}
int m_age;
};
// Hash function for Age
class HashAge {
public:
const size_t operator()(const Age &a) const {
return hash<int>()(a.m_age);
}
};
struct AgeGreater
{
bool operator()(const Age& lhs, const Age& rhs) const {
return lhs.m_age < rhs.m_age;
}
};
int main()
{
priority_queue<Age, list<Age>, AgeGreater> min_heap; // doesn't work
//priority_queue<Age, vector<Age>, AgeGreater> min_heap;
// Is this the right way to do it?
unordered_map<Age, list<Age>::iterator, HashAge > hashTable;
}
Question> I am not able to make the following work:
priority_queue<Age, list<Age>, AgeGreater> min_heap; // doesn't work
I have to use list as the container b/c the iterators of list is not affected by insertion/deletion (Iterator invalidation rules)
You can't do this with the supplied priority_queue data structure:
In a priority queue you don't know where the elements are stored, so it is hard to delete them in constant time, because you can't find the elements. But, if you maintain a hash table with the location of every element in the priority queue stored in the hash table, then you can find and remove an item quickly, although I would expect log(N) time in the worst case, not constant time. (I don't recall offhand if you get amortized constant time.)
To do this you usually need to roll your own data structures, because you have to update the hash table each time an item is moved around in the priority queue.
I have some example code that does this here:
http://code.google.com/p/hog2/source/browse/trunk/algorithms/AStarOpenClosed.h
It's based on older coding styles, but it does the job.
To illustrate:
/**
* Moves a node up the heap. Returns true if the node was moved, false otherwise.
*/
template<typename state, typename CmpKey, class dataStructure>
bool AStarOpenClosed<state, CmpKey, dataStructure>::HeapifyUp(unsigned int index)
{
if (index == 0) return false;
int parent = (index-1)/2;
CmpKey compare;
if (compare(elements[theHeap[parent]], elements[theHeap[index]]))
{
// Perform normal heap operations
unsigned int tmp = theHeap[parent];
theHeap[parent] = theHeap[index];
theHeap[index] = tmp;
// Update the element location in the hash table
elements[theHeap[parent]].openLocation = parent;
elements[theHeap[index]].openLocation = index;
HeapifyUp(parent);
return true;
}
return false;
}
Inside the if statement we do the normal heapify operations on the heap and then update the location in the hash table (openLocation) to point to the current location in the priority queue.