0

I'm having trouble figuring out what's wrong with my setKey function for my priority queue. I'm using a hash table to get the position of a node in the heap from its id (a string associated with the node) in constant time. The hashtable maps the id to an index in a vector of hashItems which contains a pointer to the node. But when I try getting the index from the pointer, I get a large number which leads to a segmentation fault.

I'm fairly certain that the problem is with my hashTable class like maybe I'm not handling pointers correctly.

Here is the header file for my heap class.

#ifndef _HEAP_H
#define _HEAP_H

#include <vector>
#include <string>
#include "hash.cpp"

class heap
{

public:
//=
  // heap - The constructor allocates space for the nodes of the heap
  // and the mapping (hash table) based on the specified capacity
  //
  heap(int capacity);

  //
  // insert - Inserts a new node into the binary heap
  //
  // Inserts a node with the specified id string, key,
  // and optionally a pointer.  They key is used to
  // determine the final position of the new node.
  //
  // Returns:
  //   0 on success
  //   1 if the heap is already filled to capacity
  //   2 if a node with the given id already exists (but the heap
  //     is not filled to capacity)
  //
  int insert(const std::string &id, int key, void *pv = nullptr);

  //
  // setKey - set the key of the specified node to the specified value
  //
  // I have decided that the class should provide this member function
  // instead of two separate increaseKey and decreaseKey functions.
  //
  // Returns:
  //   0 on success
  //   1 if a node with the given id does not exist
  //
  int setKey(const std::string &id, int key);



//node class 
  //an object containing an id (for the heap), a key that determines its
  //position on the heap
  //and also its position on the heap
    class node
    {
    public:
    node(std::string identi, int pass, void* pvData, int pos);
    node() = default;
        std::string id;
        int key;
        void *pData;
    int position;
    };


  private:
    int size;
    int capacity;
    std::vector<node> data;
    hashTable* map;

  public:

//fixes the heap after a key is changed or
    //a node is inserted
    void percolateUp(int posCur)
    {
    //index will be the current position of the node
    int index = posCur;
    node element = data[index];

    //while we haven't reached the top of the heap
    //and the key of the node is still smaller than that of its parent
    while(index > 1 and element.key < data[index / 2].key)
    {
      //push the parent down the heap
      data[index] = data[index / 2];
      index = index / 2;
    }
    //once the heap has been corrected
    //change the position of the node
    element.position = index;
    //set data[index] to element
    data[index] = element;
    }
    

  //fixes the heap after a key is changed
    void percolateDown(int posCur)
  {
    //index will be the current position of the node
      int index = posCur;
      node element = data[index];

    //while we haven't reached the bottom of the heap
      while(index < size + 1)
      {
        //if the key of the element is greater than that of its left child
        if(element.key > data[index * 2].key)
        {
          //push the left child up the heap
          data[index] = data[index*2];
          //take its place on the heap
          index = index *2;
        }
        //if the key of the element is greater than that of its right child
        else if(element.key > data[index * 2 + 1].key)
        {
          //push the right child up the heap
          data[index] = data[index*2 + 1];
          //take its place on the heap
          index = index *2 + 1;
        }
        //else, we found the right place for the node in the heap
        else
        {
          break;
        }
    }
    //once the heap has been corrected
    //change the position of the node
    element.position = index;
    //set data[index] to element
    data[index] = element;
  }

};

#endif //_HEAP_H


Here is my implementation of the heap class:

#include "heap.h"
#include <stdio.h>
#include <stdlib.h>


//constructor for heap object
//capacity: measure of how much space in heap is left
//size: measure of how many elements (nodes) in heap
//map is a hashTable that allows for constant time access to pointers to nodes from their ids
//creates a hashTable
heap::heap(int length)
{
    data.resize(length + 1);
    capacity = length;
    size = 0;
    map = new hashTable(2*capacity);
}

//constructor for node object
//has an id which is used by the hashTable
//a key which is used to determine its position in the heap
heap::node::node(std::string identi, int pass, void* pvData, int pos)
{
    id = identi;
    key = pass;
    pData = pvData;
    position = pos;
}
//inserts into heap and adjusts based on key
int heap::insert(const std::string &id, int key, void *pv)
{
    //if the heap is almost full, return 1
    if (size == capacity - 1)
    {
        return 1;
    }
    //if the id is already in use, return 2
    if(map -> contains(id))
    {
        return 2;
    }
    //index keeps track of index of element to be inserted
    //begin by setting it to the final position in the heap
    int index = size + 1;
    //make a node based on the information given
    node element = node(id, key, pv, index);
    //set the last element in the heap to the node made
    data[index] = element;

    //call to percolateUp, adjusts the heap
    percolateUp(index);

    //make a pointer to the node
    node* ptr = &element;
    //insert the id, pointer pair to the map
    map -> insert(id, ptr);
    //increment size
    size += 1;
    //decrease capacity
    capacity -= 1;
    //return 0 on success
    return 0;
}

//changes the key of a node specified by id in the heap
//and then adjusts the heap
int heap::setKey(const std::string &id, int key)
{
    //if the node with that id is not in the map/heap
    //return 1
    if (not map -> contains(id))
    {
        return 1;
    }
    //use map to get the pointer to the node with that id
    node *element = (node*)(map -> getPointer(id));
    //prints out id, right now prints out just a new line
    std::cout <<  element -> id << std::endl;
    //sets key of the node to the given key
    element -> key = key;
    //get the current position of element and put it into index
    int index = element -> position;

    //doesn't give correct position. Gives 1823232313. Something's wrong here.

    //do percolate up
    percolateUp(index);
    //then percolate down
    //if percolateUp doesn't do anything, we need to percolateDown
    //and if it does do something, the heap is perfectly ordered
    //and percolateDown won't do anything
    percolateDown(index);

    //return 0 on success
    return 0;
}


Here is the header file for my hashTable class:

#ifndef _HASH_H
#define _HASH_H

#include <vector>
#include <string>

class hashTable {

 public:

  // The constructor initializes the hash table.
  // Uses getPrime to choose a prime number at least as large as
  // the specified size for the initial size of the hash table.
  hashTable(int size = 0);

  // Insert the specified key into the hash table.
  // If an optional pointer is provided,
  // associate that pointer with the key.
  // Returns 0 on success,
  // 1 if key already exists in hash table,
  // 2 if rehash fails.
  int insert(const std::string &key, void *pv = nullptr);

  // Check if the specified key is in the hash table.
  // If so, return true; otherwise, return false.
  bool contains(const std::string &key);

  // Get the pointer associated with the specified key.
  // If the key does not exist in the hash table, return nullptr.
  // If an optional pointer to a bool is provided,
  // set the bool to true if the key is in the hash table,
  // and set the bool to false otherwise.
  void *getPointer(const std::string &key, bool *b = nullptr);


 
 private:

  // Each item in the hash table contains:
  // key - a string used as a key.
  // isOccupied - if false, this entry is empty,
  //              and the other fields are meaningless.
  // isDeleted - if true, this item has been lazily deleted.
  // pv - a pointer related to the key;
  //      nullptr if no pointer was provided to insert.
  class hashItem {
  public:
    std::string key = "";
    bool isOccupied = false;
    bool isDeleted = false;
    void *pv = nullptr;

    hashItem() = default;
  };

  int capacity; // The current capacity of the hash table.
  int filled; // Number of occupied items in the table.
  size_t length;

  std::vector<hashItem> data; // The actual entries are here.

  // The hash function.
  int hash(const std::string &key);



  // Return a prime number at least as large as size.
  // Uses a precomputed sequence of selected prime numbers.
  static unsigned int getPrime(int size);
};

#endif //_HASH_H

And finally, here's the implementation for my hashTable class

#include "hash.h"
#include <typeinfo>
#include <stdio.h>
#include <stdlib.h>

//returns a prime number depending on size required for hash table
//supports a size of up to one million
unsigned int hashTable::getPrime(int size)
{
    if (size <= 10000) { return 12373;}
    if (size <= 20000) {return 25867;}
    if (size <= 50000) {return 51479;}
    if (size <= 100000) {return 109211;}
    if (size <= 200000) {return 202717;}
    if (size <= 400000) {return 410299;}
    if (size <= 800000) {return 803549;}
    if (size <= 1600000) {return 2001101;}
    return 3200983;
}

// constructor for hashTable class
//takes initial size input and makes size of table from getPrime
//sets capacity to be initially the size of the hash table
hashTable::hashTable(int size)
{
    int num = getPrime(size);
    length = num;
    data.resize(num);
    capacity = length;
    filled = 0;
}



//hash function
//uses linear probing in the event of a collision
//which is where the function goes from the index
//where the initial hash sent the string to and 
//goes down the table until it either finds a free space
//or it finds the hashitem with that key value
int hashTable::hash(const std::string &key)
{
    int hashVal = 0;
    for(char ch: key)
    {
        hashVal = 37*hashVal + ch;
    }
    int i = 0;

    if (data[hashVal % length].isOccupied)
    {
        while( data[(hashVal + i) % length].isOccupied and 
            (data[(hashVal + i)% length].key).compare(key) != 0)
        {
            i += 1;
        }
    }
    return (hashVal + i) % length;
}

//insert function inserts key into position given by hash function
//changes filled and capacity number
//returns 0 on success, 1 when the key has already been inserted
//and 2 if there was a memory allocation error in the rehash function
//if the loading size exceeds 0.5, it rehashes
//this means that at any given time, the hashtable only uses a 1/3 of its actual size
//which is necessary in order to decrease the likelihood of collisions
int hashTable::insert(const std::string &key, void *pv)
{
    int index = hash(key);
    if ((data[index].key).compare(key) == 0)
    {
        return 1;
    }
    data[index] = hashItem();
    data[index].isOccupied = true;
    data[index].key = key;
    data[index].pv = pv;
    filled += 1;
    capacity -= 1;
    if (filled/capacity > 0.5)
    {
       return 2;
    }
    return 0;
}


//checks if key is in hash table
bool hashTable::contains(const std::string &key)
{
    return (data[hash(key)]).isOccupied and not((data[hash(key)]).isDeleted);
}

//retrieves pointer in hashItem from hashtable
//also writes to a boolean pointer whether or not key is in the table
//returns nullptr if not in the table
void* hashTable::getPointer(const std::string &key, bool *b)
{
    bool con = contains(key);
    b = &con;
    if(not con)
    {
        return nullptr;
    }
    return data[hash(key)].pv;
}


  • Unrelated (probably): `#include "heap.cpp"` is probably an unforced error. Prefer to include heap.h and then compile and link "heap.cpp" – user4581301 Oct 15 '21 at 20:03
  • Also probably unrelated: `_HASH_H` is an illegal identifier. Usually breaking the [underscore usage rules](https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) results in bizarre compiler errors, but sometimes things line up just wrong and hit you with a nigh-inscrutable runtime error instead. – user4581301 Oct 15 '21 at 20:06
  • 1
    Problem: You have about 700 lines of code here. Practically no one is going to want to wade through that for free. I recommend constructing a [mre] (MRE) and if making the MRE doesn't lead to you finding and fixing the mistake, it often does, update the question to include the MRE. – user4581301 Oct 15 '21 at 20:11
  • Sorry about that! I cut out all of the functions that weren't needed to insert into the heap and set the keys. Please let me know if you get any compile time errors. – NaiveCoder Oct 15 '21 at 20:42
  • To be honest, I couldn't compile it before. `hashTable::rehash` had a variable named `key` that seemed to spring out of nowhere. Have you confirmed that the reduced code accurately reproduces the error you had before? As in can I compile it, run it, and get the same crash? If not, you have not made a MRE. – user4581301 Oct 15 '21 at 21:07
  • I'm not sure why the original version didn't compile for you but I checked with the reduced version and it should be good. It produces the same segmentation error when setKey is called and inserts properly too. – NaiveCoder Oct 15 '21 at 21:34
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community Oct 16 '21 at 14:01
  • Found out the problem. I wasn't using the new operator to make a pointer to the node so I think the object the pointer was pointing to might have been deleted once the insert function was done. But the object in the vector never got deleted which meant my insert function would work as normal. – NaiveCoder Oct 16 '21 at 18:23

0 Answers0