0

so i want to implement a queue data structure in c++ and use some special methods like the delete_at() putting into consideration the constraints of the queue data structure, so I made it using the dequeue method and took all the data that are not equal to the index the user want to delete and stored in array in order to enqueue it all back in but without the index that the user want to delete, however nothing gets deleted , so here is the code :

#include <list>
using namespace std;


class Queue{

private:

struct node {
   int data;
   struct node *next;
};




struct node* front = NULL;
struct node* rear = NULL;



public:

void Enqueue(int d) {

    struct node* tmp=new node;

   if (rear == NULL) {

      tmp->next = NULL;
      tmp->data = d;
      rear=tmp;
      front = rear;
   }

   else {
      rear->next = tmp;
      tmp->data = d;
      tmp->next = NULL;
      rear = tmp;
   }
}


int Dequeue() {

    struct node* tmp = front;
    int data=front->data;

   if (front == NULL) {
      return 0;
   }

   else{


   if (tmp->next != NULL) {
      tmp=front;
      front = front->next;
      delete tmp;
   }

     else {

      tmp=front;
      delete tmp;
      front = NULL;
      rear = NULL;
    }

  }
  

 return data;

   

}



void Display() {

  struct node* temp = front;

   if (front == NULL) {
      cout<<"Queue is empty"<<endl;
      return;
   }


   while (temp != NULL) {
      cout<<temp->data<<"\n";
      temp = temp->next;
   }


 }


 int Size() {

  struct node* temp = front;
  int cnt=0;

   while (temp != NULL) {
      cnt++;
      temp = temp->next;
   }

   return cnt;

 }




 void Delete_at(int index){
     int i=0;
     int ar_size=Size();
     int data_arr[ar_size-1];

     

     if(index > Size()){
        cout<<"\n"<<"Error: out of bounds !";
        return;
    }

     while(i<ar_size){
         
         if (i==(index-1)){
             Dequeue();
        
             
         }
         
         else{
             
             data_arr[i]=Dequeue();
         }
         
         i++;
    
    }
    

    
     i=0;
     
     while(i<ar_size){
         
         Enqueue(data_arr[i]);
         i++;
     }





 }
 
};



int main() {
    int i=0;
    Queue q;
    q.Enqueue(2);
    q.Enqueue(6);
    q.Enqueue(7);
    q.Enqueue(1);
    q.Enqueue(2);
    q.Enqueue(4);
    q.Delete_at(2);
    q.Display();









  return 0;
}
  • 1
    Forgetting that queues don't have random access, the proper pattern using true queue methods should be to dequeue N items, putting them in *another queue*, the skip the one you want one (just pop it, but don't push it into the new queue), *then* pop the remaining items in the source queue, pushing them into the new queue. When done, the new queue takes the place of the old queue through member swapping and you're done. That's just one algorithm; there are certainly others. But that one uses only queue methods that already exist, and doesn't use non-standard VLAs. – WhozCraig May 24 '21 at 22:20
  • i don't want to use this pattern for specific reasons! – Will Johnson May 24 '21 at 22:43
  • Wow. You managed to force your entire opening paragraph into one sentence. While that is quite a linguistic feat, it is not great for readability... – JaMiT May 24 '21 at 22:48
  • Well, then if your queue API has carte-blanche to all of this, why the array? Just start at the front of the queue and march down the linked list until you've arrived at your target, splice the prior node's next (assuming there is one), the the victim-node's next, then delete the now-excised node. Of course, consideration is (somewhat) required if you're removing the front node (which means it is synonymous to a pop). but that can be alleviated with the proper algorithm. – WhozCraig May 24 '21 at 22:54
  • I don't want to use any pointers outside the the enqueue and dequeue methods and I'm forced to do this method using only dequeque and enqueue methods again for specific technical reasons regarding the project I'm working on – Will Johnson May 24 '21 at 23:49

1 Answers1

3

You have a two primary problems with your code generally, and Delete_at() cannot simply call Dequeue(). As a general note, your code contains duplicated expressions that can simply be consolidated. For example, Enqueue() can be written succinctly as:

    void Enqueue(int d) {
        
        struct node *tmp = new node;
        tmp->next = nullptr;
        tmp->data = d;
        
        if (rear == nullptr) {
            front = rear = tmp;
            size = 1;
        }
        else {
            rear->next = tmp;
            rear = tmp;
            size += 1;
        }
    }

Your Dequeue() function will segfault checking front->data BEFORE checking if front == nullptr. You must check before you dereference, e.g.

    int Dequeue() {
        
        struct node *tmp = front;
        int data;
    
        if (front == nullptr) { /* (must check before dereference (front->data) */
            return 0;
        }
        
        data = front->data;
        size -= 1;
        
        if (tmp->next != nullptr) {
            front = front->next;
        }
        else {
            front = nullptr;
            rear = nullptr;
        }
        delete tmp;
        
        return data;
    }

Your Delete_at() function must remove the node at a specific index. This requires that you maintain your ->next links throughout your list, updating the prev->next before the deleted node to point to the node after the one you are deleting. You do that by iterating with both the address of the node and a pointer to node. When you reach the index to remove, you simply replace what is currently at the address of that node with the next node and delete the current, see: Linus on Understanding Pointers

    void Delete_at (size_t index) {
        
        struct node  *pnode = front,        /* pointer to node */
                    **ppnode = &front;      /* address of node */
        
        if (index >= Size()) {  /* validate with >= Size() */
            std::cerr << '\n' << "Error: out of bounds !";
            return;
        }
    
        while (index--) {                   /* loop index times */
            ppnode = &pnode->next;          /* address of next node */
            pnode = pnode->next;            /* pointer to next node */
        }
        
        *ppnode = pnode->next;      /* replace struct at address with next */
        
        delete pnode;               /* delete removed node */
        size -= 1;
    }

Your Size() function simply reduces to a "getter" function:

    size_t Size() {
    
        return size;
    }

Updating your example a bit and being mindful of Why is “using namespace std;” considered bad practice? your full code could now be:

#include <list>
#include <iostream>

class Queue{

  private:

    struct node {
        int data;
        struct node *next;
    };
    
    struct node *front = nullptr;
    struct node *rear = nullptr;
    
    size_t size;
    
  public:
    
    void Enqueue(int d) {
        
        struct node *tmp = new node;
        tmp->next = nullptr;
        tmp->data = d;
        
        if (rear == nullptr) {
            front = rear = tmp;
            size = 1;
        }
        else {
            rear->next = tmp;
            rear = tmp;
            size += 1;
        }
    }
    
    int Dequeue() {
        
        struct node *tmp = front;
        int data;
    
        if (front == nullptr) { /* (must check before dereference (front->data) */
            return 0;
        }
        
        data = front->data;
        size -= 1;
        
        if (tmp->next != nullptr) {
            front = front->next;
        }
        else {
            front = nullptr;
            rear = nullptr;
        }
        delete tmp;
        
        return data;
    }
    
    void Display() {
    
        struct node *temp = front;
    
        if (front == nullptr) {
            std::cout << "Queue is empty" << '\n';
            return;
        }
        
        while (temp != nullptr) {
            std::cout << temp->data << '\n';
            temp = temp->next;
        }
    }
    
    size_t Size() {
    
        return size;
    }
    
    void Delete_at (size_t index) {
        
        struct node  *pnode = front,        /* pointer to node */
                    **ppnode = &front;      /* address of node */
        
        if (index >= Size()) {  /* validate with >= Size() */
            std::cerr << '\n' << "Error: out of bounds !";
            return;
        }
    
        while (index--) {                   /* loop index times */
            ppnode = &pnode->next;          /* address of next node */
            pnode = pnode->next;            /* pointer to next node */
        }
        
        *ppnode = pnode->next;      /* replace struct at address with next */
        
        delete pnode;               /* delete removed node */
        size -= 1;
    }
};

int main() {
    
    Queue q;
    
    q.Enqueue(2);
    q.Enqueue(6);
    q.Enqueue(7);
    q.Enqueue(1);
    q.Enqueue(2);
    q.Enqueue(4);
    q.Display();
    
    std::cout << "\nq.Delete_at(2)\n\n";
    
    q.Delete_at(2);
    q.Display();
}

Example Use/Output

$ ./bin/queue_delete_at
2
6
7
1
2
4

q.Delete_at(2)

2
6
1
2
4

Look things over and let me know if you have further questions.

Edit With Additional Constraints From Comments

Per you comments, you have constraints of only being able to use Dequeue() and Enqueue() in Delete_at() and no pointers, etc... You can do that, but understand it will be horribly inefficient compared to simply removing the node at the index. You will essentially have to save (Dequeue()) your entire queue data in an allocated block of memory, omitting the index to remove. You will then need to iterate over all saved values calling Enqueue() to repopulated your list.

You can do that as:

    void Delete_at (size_t index) {
        
        if (index >= Size()) {  /* validate with >= Size() */
            std::cerr << '\n' << "Error: out of bounds !";
            return;
        }
        
        size_t nelem = Size();
        int *arr = new int [nelem],
            n = 0;
        
        for (size_t i = 0; i < nelem; i++) {
            int tmp = Dequeue();
            if (i != index && tmp)
                arr[n++] = tmp;
        }
        
        for (size_t i = 0; i < (size_t)n; i++)
            Enqueue (arr[i]);
        
        delete[] arr;
    }

(same output)

For a less readable more C++'ized presentation, you can replace the first loop with:

        for (int i = 0, j = Dequeue(); j; i++, j = Dequeue())
            if (static_cast<size_t>(i) != index)
                arr[n++] = j;

It would be nice to have utilized at least a separate list pointer, so you could build the new list while simultaneously deleting the old, but your class/struct isn't setup to use additional pointers. So you are basically left with buffering all values except the index to remove and then recreating your queue.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Hi there , thanks for bieng helpful but there are certain constraints that I'm forced to deal with that is regarding the entire project that I'm working on and i won't be able to share them here , however I'm forced to do the Delete_at() method using only the dequeque and enqueue methods any attempt to use pointers outside of those will break those constraints , another thing is that i can't simply use the other method of copying into another queue again for specific reasons so I'm forced to deal with it without making another queue and that's another constraint – Will Johnson May 25 '21 at 00:00
  • If you read the link about pointers, you will know that leaves you to use the "naive" approach where you will need to maintain separate pointers for the `previous`, `current` and `next` node. Since you must use only `Dequeue()` and `Enqueue()` you will have to save the `data` value for every node while calling `Dequeue()` on every node until your `at` value is reached. You then need to `Enqueue()` the saved `data` values and every node in the list that remains with `int n; while ((n = Dequeue())) { Enqueue(n); }` That provides a roadmap -- but is it horribly inefficient. – David C. Rankin May 25 '21 at 00:13
  • I did something very simlar to that b in the code above , but instead of dequeuing first i made it in a way that would work with indexes , so if i reached the index i want to delete i will dequeue but not save the data returned into the array else i will dequeue then save but i can't seem to know what is causing the problem , and by the way i know this is a horrible way of doing things but there are very strict project requirements that force me to walk this path – Will Johnson May 25 '21 at 00:30
  • Give me a second and I'll take a look at it. – David C. Rankin May 25 '21 at 00:31
  • Take your time ! – Will Johnson May 25 '21 at 00:41
  • i'm curious about why it only worked when we made a separate counter `n` and did not work when we made a unified counter `i` and both are initialized with the sam number `0` ? – Will Johnson May 25 '21 at 12:15
  • Because you will assign (advance array index) only when the counter is NOT equal to the index to remove. If you just use a loop counter, you will have an uninitialized value in the middle of your array where `i == index` because assignment is skipped in that case. – David C. Rankin May 25 '21 at 17:20
  • can i communicate with you privately ? I wanna share something but not here – Will Johnson May 28 '21 at 15:58
  • @WillJohnson drankinatty at gmail will do. I'll check this afternoon. – David C. Rankin May 28 '21 at 18:01
  • Yes, I was trying to avoid writing it in a way it could be scraped from this site `:)` (it would be nice if you delete) – David C. Rankin May 28 '21 at 22:57