Skip to content

Commit 0067539

Browse files
authored
Create README.md
1 parent 8019d8b commit 0067539

File tree

1 file changed

+383
-0
lines changed
  • 16 - Queue Data Structure Problems/01 - Linear Queue Problems/11 - Implement k Queues in an Single Array

1 file changed

+383
-0
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
<h1 align='center'>Implement - K - Queues - In an - Single - Array</h1>
2+
3+
## Problem Statement
4+
5+
**Problem URL :** [Impelement k Queues in an Single Array](https://www.geeksforgeeks.org/efficiently-implement-k-queues-single-array/)
6+
7+
You are given **`k`** queues and an array of size **`n`**. The challenge is to implement these **`k`** queues efficiently using a **single array** of size **`n`**. The program should support standard queue operations, namely:
8+
1. **Enqueue (push an element into a specific queue)**.
9+
2. **Dequeue (remove an element from a specific queue)**.
10+
11+
The goal is to:
12+
- **Efficiently manage multiple queues** using only one array.
13+
- Ensure that the space in the array is reused optimally when items are dequeued.
14+
15+
This is also called the **k-queue problem** where we need to use a single array to store elements of multiple queues.
16+
17+
### Beginner's Approach:
18+
19+
A beginner DSA student can break down the problem as follows:
20+
1. **Understanding the problem**: Instead of creating separate arrays for each queue, you need to use a single array to manage multiple queues. This is space-efficient.
21+
2. **Front and Rear management**: Each queue has its own `front` and `rear` to keep track of where elements are added and removed.
22+
3. **Array space reuse**: When an element is dequeued, the space it used can be recycled and made available for future enqueue operations.
23+
24+
### Key Ideas:
25+
- We maintain an array to store the actual elements.
26+
- We use an additional `front` and `rear` array for each queue.
27+
- A `next` array helps link the different spaces in the single array, similar to how nodes are linked in a linked list.
28+
- A `freeSpot` variable keeps track of the next available free space in the array.
29+
30+
### Approach:
31+
1. **Initialization**: Start with an array that stores the queue elements, a `front[]` and `rear[]` array to keep track of each queue's front and rear. Also, maintain a `next[]` array to point to the next free space or the next element in the queue.
32+
2. **Enqueue**:
33+
- Check if there's a free space using `freeSpot`.
34+
- Insert the element at the free space and update the necessary pointers (update `rear[]`, `front[]`, and `next[]` arrays).
35+
3. **Dequeue**:
36+
- Check if the queue is empty by checking `front[]`.
37+
- Remove the front element from the queue, update the pointers, and recycle the space by updating `freeSpot`.
38+
## Problem Solution
39+
```cpp
40+
#include <iostream>
41+
using namespace std;
42+
43+
class kQueue{
44+
public:
45+
int *arr;
46+
int k;
47+
int n;
48+
int *front;
49+
int *rear;
50+
int freeSpot;
51+
int *next;
52+
53+
kQueue(int n, int k){
54+
this -> n = n;
55+
this -> k = k;
56+
arr = new int[n];
57+
front = new int[k];
58+
rear = new int[k];
59+
next = new int[n];
60+
freeSpot = 0;
61+
62+
for(int i = 0; i < k; i++){
63+
front[i] = -1;
64+
rear [i] = -1;
65+
}
66+
67+
for(int i = 0; i < n-1; i++){
68+
next[i] = i + 1;
69+
}
70+
next[n-1] = -1;
71+
}
72+
73+
void enQueue(int data, int qn){
74+
// overflow
75+
if(freeSpot == -1){
76+
cout << "No empty space is available" << endl;
77+
return;
78+
}
79+
80+
// find first free index;
81+
int index = freeSpot;
82+
83+
// update freeSpot;
84+
freeSpot = next[index];
85+
86+
// check wheather first element
87+
if(front[qn-1] == -1){
88+
front[qn-1] = index;
89+
}else{
90+
// link new element to the prev element
91+
next[rear[qn-1]] = index;
92+
}
93+
94+
// update next;
95+
next[index] = -1;
96+
97+
// update rear;
98+
rear[qn-1] = index;
99+
100+
// push element;
101+
arr[index] = data;
102+
103+
}
104+
105+
int deQueue(int qn){
106+
// underflow
107+
if(front[qn-1] == -1){
108+
cout << "Queue underflow" <<endl;
109+
return -1;
110+
}
111+
112+
// find index to pop
113+
int index = front[qn-1];
114+
115+
// update front pointer
116+
front[qn-1] = next[index];
117+
118+
// next freeSpot
119+
next[index] = freeSpot;
120+
freeSpot = index;
121+
122+
return arr[index];
123+
}
124+
};
125+
126+
int main() {
127+
kQueue q(10, 3);
128+
129+
q.enQueue(10, 1);
130+
q.enQueue(15, 1);
131+
q.enQueue(20, 2);
132+
q.enQueue(25, 1);
133+
q.enQueue(30, 2);
134+
q.enQueue(35, 3);
135+
136+
cout << "Element poped from q1 : " << q.deQueue(1) <<endl;
137+
cout << "Element poped from q1 : " << q.deQueue(1) <<endl;
138+
cout << "Element poped from q2 : " << q.deQueue(2) <<endl;
139+
cout << "Element poped from q3 : " << q.deQueue(3) <<endl;
140+
return 0;
141+
}
142+
```
143+
144+
## Problem Solution Explanation
145+
Here's a detailed explanation of the source code, walking through it step by step:
146+
147+
### 1. **Class and Member Variables**
148+
149+
```cpp
150+
class kQueue{
151+
public:
152+
int *arr; // Array to store elements for all the queues
153+
int k; // Number of queues
154+
int n; // Total size of the array (capacity)
155+
int *front; // Array to store front indices of all queues
156+
int *rear; // Array to store rear indices of all queues
157+
int freeSpot; // Index of the next free spot in the array
158+
int *next; // Array to manage the next free index or the next element in a queue
159+
```
160+
161+
This declares the class `kQueue` and the member variables. Here’s what each variable does:
162+
- **`arr[]`**: Stores the actual elements for all the queues.
163+
- **`k`**: The number of queues.
164+
- **`n`**: The total size of the array.
165+
- **`front[]`**: Keeps track of the front (starting) index of each queue.
166+
- **`rear[]`**: Keeps track of the rear (last) index of each queue.
167+
- **`freeSpot`**: Index of the next available free spot in the array.
168+
- **`next[]`**: This helps manage both free spots and the linked structure of queues.
169+
170+
### 2. **Constructor**
171+
172+
```cpp
173+
kQueue(int n, int k){
174+
this -> n = n;
175+
this -> k = k;
176+
arr = new int[n];
177+
front = new int[k];
178+
rear = new int[k];
179+
next = new int[n];
180+
freeSpot = 0;
181+
182+
for(int i = 0; i < k; i++){
183+
front[i] = -1;
184+
rear[i] = -1;
185+
}
186+
187+
for(int i = 0; i < n-1; i++){
188+
next[i] = i + 1;
189+
}
190+
next[n-1] = -1;
191+
}
192+
```
193+
194+
Here’s what happens:
195+
- **`this -> n = n` and `this -> k = k`**: Initialize the total size (`n`) and number of queues (`k`).
196+
- **`arr = new int[n]`**: Allocate memory for the array that holds all elements.
197+
- **`front[]` and `rear[]`**: Arrays to hold front and rear indices for each queue. Initially, each queue is empty, so both are set to `-1`.
198+
- **`next[]`**: Helps track the next free spot and link elements within queues. Initially, all slots are free and linked to the next one in sequence (`next[i] = i + 1`). For example, `next[0] = 1`, `next[1] = 2`, and so on. The last index (`next[n-1] = -1`) indicates that there are no more free spots.
199+
200+
**Example Setup**:
201+
Let’s say `n = 10` (array size) and `k = 3` (three queues):
202+
- Initially:
203+
- `arr[] = { _, _, _, _, _, _, _, _, _, _ }` (empty array)
204+
- `front[] = { -1, -1, -1 }` (no front indices yet)
205+
- `rear[] = { -1, -1, -1 }` (no rear indices yet)
206+
- `next[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, -1 }` (linked free spots)
207+
- `freeSpot = 0` (next free spot is index 0)
208+
209+
### 3. **Enqueue Operation**
210+
211+
```cpp
212+
void enQueue(int data, int qn){
213+
if(freeSpot == -1){
214+
cout << "No empty space is available" << endl;
215+
return;
216+
}
217+
218+
int index = freeSpot; // Get the free index
219+
freeSpot = next[index]; // Update freeSpot to the next available spot
220+
221+
if(front[qn-1] == -1){ // If the queue is empty
222+
front[qn-1] = index; // Set front of the queue to this index
223+
} else {
224+
next[rear[qn-1]] = index; // Link the new element to the previous rear
225+
}
226+
227+
next[index] = -1; // Set next of the new element to -1 (end of queue)
228+
rear[qn-1] = index; // Update rear to the new index
229+
arr[index] = data; // Add the element to the array
230+
}
231+
```
232+
233+
- **Check for overflow**: If `freeSpot == -1`, there are no free spots left.
234+
- **Allocate a free spot**: `index = freeSpot` gives us the current free index. `freeSpot = next[index]` updates the free spot to the next available one.
235+
- **Add the element**:
236+
- If the queue is empty (`front[qn-1] == -1`), set the front of the queue to this index.
237+
- Otherwise, link the new element to the existing queue by updating `next[rear[qn-1]] = index`.
238+
- **Set `next[index] = -1`** to mark the new element as the last element of the queue.
239+
- **Update `rear[qn-1] = index`** to point to the newly added element.
240+
- **Store the data**: Finally, `arr[index] = data` stores the new element in the array.
241+
242+
**Example**:
243+
Let’s enqueue `10` into Queue 1 (`qn = 1`):
244+
- **Before enqueue**:
245+
- `freeSpot = 0`
246+
- `front[] = { -1, -1, -1 }`
247+
- `rear[] = { -1, -1, -1 }`
248+
- **Steps**:
249+
- Free spot is 0 (`index = 0`).
250+
- Set `freeSpot = next[0] = 1` (next free spot is 1).
251+
- Queue 1 is empty, so `front[0] = 0`.
252+
- Set `next[0] = -1` and `rear[0] = 0`.
253+
- Store `arr[0] = 10`.
254+
255+
The array looks like this after enqueuing:
256+
- `arr[] = { 10, _, _, _, _, _, _, _, _, _ }`
257+
- `front[] = { 0, -1, -1 }`
258+
- `rear[] = { 0, -1, -1 }`
259+
- `freeSpot = 1`
260+
261+
### 4. **Dequeue Operation**
262+
263+
```cpp
264+
int deQueue(int qn){
265+
if(front[qn-1] == -1){
266+
cout << "Queue underflow" << endl;
267+
return -1;
268+
}
269+
270+
int index = front[qn-1]; // Get the front index
271+
front[qn-1] = next[index]; // Update the front to the next element
272+
273+
next[index] = freeSpot; // Add the current index to the free list
274+
freeSpot = index; // Update freeSpot to this newly freed index
275+
276+
return arr[index]; // Return the dequeued element
277+
}
278+
```
279+
280+
- **Check for underflow**: If `front[qn-1] == -1`, the queue is empty.
281+
- **Dequeue the front element**: Save the front index (`index = front[qn-1]`), then update the front to the next element (`front[qn-1] = next[index]`).
282+
- **Free the current spot**: Add the dequeued spot to the list of free spots by setting `next[index] = freeSpot`, and update `freeSpot = index`.
283+
- **Return the dequeued element**: Finally, return the element stored in `arr[index]`.
284+
285+
**Example**:
286+
Let’s dequeue an element from Queue 1:
287+
- **Before dequeue**:
288+
- `front[0] = 0` (points to index 0 where `10` is stored).
289+
- `rear[0] = 0`.
290+
- **Steps**:
291+
- Dequeue `arr[0] = 10`.
292+
- Set `front[0] = next[0] = -1` (Queue 1 becomes empty).
293+
- Set `next[0] = freeSpot = 1` (index 0 is now free).
294+
- Update `freeSpot = 0`.
295+
296+
The array after dequeue:
297+
- `arr[] = { 10, _, _, _, _, _, _, _, _, _ }` (element is still in the array but not part of the queue)
298+
- `front[] = { -1, -1, -1 }`
299+
- `rear[] = { -1, -1, -1 }`
300+
- `freeSpot = 0`
301+
302+
### 5. **Main Function Example**
303+
304+
```cpp
305+
int main() {
306+
kQueue q(10, 3);
307+
308+
q.enQueue(10, 1); // Enqueue 10 to Queue 1
309+
q.enQueue(15, 1); // Enqueue 15 to Queue 1
310+
q.enQueue(20, 2); // Enqueue 20 to Queue 2
311+
q.enQueue(25, 1); // Enqueue 25 to Queue 1
312+
q.enQueue(30, 2); // Enqueue 30 to Queue 2
313+
q.enQueue(35, 3); // Enqueue 35 to Queue 3
314+
315+
cout << "Element poped from
316+
317+
queue 1 is " << q.deQueue(1) << endl;
318+
cout << "Element poped from queue 2 is " << q.deQueue(2) << endl;
319+
cout << "Element poped from queue 3 is " << q.deQueue(3) << endl;
320+
}
321+
```
322+
323+
- Enqueues 10, 15, and 25 into Queue 1, 20 and 30 into Queue 2, and 35 into Queue 3.
324+
- Dequeues elements from Queue 1, Queue 2, and Queue 3 and prints them.
325+
326+
### Time and Space Complexities of the `kQueue` Implementation:
327+
328+
#### 1. **Space Complexity:**
329+
330+
Let’s break down the space usage:
331+
332+
- **`arr[]`**: The array stores the elements of all the queues, and its size is `n` (the total capacity of all queues combined).
333+
- **`front[]` and `rear[]`**: Two arrays of size `k` (the number of queues) to store the front and rear indices of each queue.
334+
- **`next[]`**: An array of size `n` to manage free spots and links between elements within the queues.
335+
- **Miscellaneous**: A few integer variables (`n`, `k`, `freeSpot`), which take constant space.
336+
337+
**Space complexity** is therefore:
338+
\[
339+
\text{Space Complexity} = O(n) + O(k) + O(n) = O(n + k)
340+
\]
341+
Where:
342+
- `O(n)` is for storing the elements in `arr[]` and `next[]`.
343+
- `O(k)` is for the `front[]` and `rear[]` arrays, which store the indices for each of the `k` queues.
344+
345+
#### 2. **Time Complexity:**
346+
347+
The two primary operations, `enQueue` and `deQueue`, are both constant time operations due to the efficient usage of the `next[]` array and index tracking.
348+
349+
##### **a. `enQueue(int data, int qn)`**
350+
351+
1. **Checking for available space** (`if(freeSpot == -1)`): This is a constant time check, `O(1)`.
352+
2. **Allocating space**: Fetching a free spot and updating the `freeSpot` pointer is constant time (`O(1)`).
353+
3. **Updating the `front[]` and `rear[]` arrays**: Setting front or rear indices and updating the `next[]` array takes constant time, `O(1)`.
354+
4. **Storing the data in `arr[]`**: Simply involves assigning a value to the array, which is constant time, `O(1)`.
355+
356+
Thus, the overall **time complexity of the `enQueue` operation** is:
357+
\[
358+
O(1)
359+
\]
360+
361+
##### **b. `deQueue(int qn)`**
362+
363+
1. **Checking for underflow** (`if(front[qn-1] == -1)`): This is a constant time check, `O(1)`.
364+
2. **Fetching the front element**: Retrieving the element and updating the `front[]` pointer takes constant time, `O(1)`.
365+
3. **Releasing the space**: Adding the index back to the list of free spots (by updating `freeSpot` and `next[]`) is done in constant time, `O(1)`.
366+
367+
Thus, the overall **time complexity of the `deQueue` operation** is:
368+
\[
369+
O(1)
370+
\]
371+
372+
### Summary of Complexities:
373+
374+
- **Time Complexity**:
375+
- **`enQueue`**: `O(1)`
376+
- **`deQueue`**: `O(1)`
377+
378+
- **Space Complexity**: `O(n + k)`, where `n` is the total capacity of the array for all queues, and `k` is the number of queues.
379+
380+
This implementation is highly efficient in terms of time complexity, as both enqueue and dequeue operations take constant time (`O(1)`), making it ideal for real-time applications where multiple queues need to be managed in a memory-efficient manner.
381+
382+
383+

0 commit comments

Comments
 (0)