mirror of
https://git.adityakumar.xyz/dsa.git
synced 2024-11-09 13:39:43 +00:00
add stack
This commit is contained in:
parent
f2205cf799
commit
e97ef3d82f
1 changed files with 447 additions and 0 deletions
447
content/docs/dsa/stack.md
Normal file
447
content/docs/dsa/stack.md
Normal file
|
@ -0,0 +1,447 @@
|
||||||
|
---
|
||||||
|
title: "Stack"
|
||||||
|
weight: 1
|
||||||
|
# bookFlatSection: false
|
||||||
|
# bookToc: true
|
||||||
|
# bookHidden: false
|
||||||
|
# bookCollapseSection: false
|
||||||
|
# bookComments: false
|
||||||
|
# bookSearchExclude: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Stack
|
||||||
|
|
||||||
|
A stack is a fundamental abstract data type in computer science used to store a
|
||||||
|
collection of elements, with two principal operations: push (to add an element to
|
||||||
|
the top) and pop (to remove the most recently added element that was not yet
|
||||||
|
removed). This structure follows the Last In, First Out (LIFO) principle.
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
|
Stacks are widely utilized in various algorithms and programming constructs due to
|
||||||
|
their simplicity and efficiency. They are particularly useful for tasks such as:
|
||||||
|
|
||||||
|
- **Syntax parsing**: For example, during the compilation process or while
|
||||||
|
interpreting languages that use specific syntax rules like parentheses matching.
|
||||||
|
|
||||||
|
- **Backtracking problems**: Situations where you need to explore all possible
|
||||||
|
paths (like in depth-first search) and then revert to previous states as needed.
|
||||||
|
|
||||||
|
- **Function call management**: In most programming environments, function calls
|
||||||
|
are managed using a stack structure known as the "call stack." Whenever a function
|
||||||
|
is called, its state gets pushed onto the call stack; when it returns, that state
|
||||||
|
is popped off.
|
||||||
|
|
||||||
|
- **Undo mechanisms**: Any system where an action can be reversed (like in text
|
||||||
|
editors or certain applications) often uses stacks to keep track of previous
|
||||||
|
states.
|
||||||
|
|
||||||
|
- **Bracket matching and validation**: To check the correctness of nested
|
||||||
|
parentheses, brackets, or braces in source code or mathematical expressions by
|
||||||
|
ensuring they are properly balanced and ordered according to LIFO rules.
|
||||||
|
|
||||||
|
The primary operations on a stack can also include peek (to look at the top
|
||||||
|
element without removing it), isEmpty (checking if the stack is empty), size
|
||||||
|
(returning the number of elements in the stack), and others, depending on
|
||||||
|
implementation specifics.
|
||||||
|
|
||||||
|
## Algorithm
|
||||||
|
|
||||||
|
The main operations of a stack usually revolves around adding and removing items. Other helper methods can be defined such as peeking and checking for emptiness by looking at the state of the `top` pointer.
|
||||||
|
|
||||||
|
Stacks are mostly implemented using arrays or linked lists depending on the situation. If one has to go into the specifics then arrays are typically used where
|
||||||
|
|
||||||
|
1. storage is expensive,
|
||||||
|
2. the maximum entries are more or less fixed, and/or
|
||||||
|
3. performance is takes prevelance over storage.
|
||||||
|
|
||||||
|
Likewise, if the use case dictates uncertainty in maximum available storage that can be used and a little bit of performance hit is acceptable, the linked list implementation is preferred.
|
||||||
|
|
||||||
|
A typical implementation is as follows:
|
||||||
|
|
||||||
|
1. Define a Node class with at least two attributes: `data` (to store the value)
|
||||||
|
and `next` (to point to the next node).
|
||||||
|
|
||||||
|
2. Create a Stack class that manages the linked list operations for stack
|
||||||
|
functionality. It will have an instance variable called `top`, which points to the
|
||||||
|
top-most element in the stack, initially set to null since the stack is empty at
|
||||||
|
creation.
|
||||||
|
|
||||||
|
3. Implement the push operation:
|
||||||
|
|
||||||
|
- Create a new Node with the given data value.
|
||||||
|
- If the stack is currently empty (i.e., top == null), make the new node both
|
||||||
|
the `top` and its next node, pointing to itself since it's the only element in the
|
||||||
|
list at this point.
|
||||||
|
- Otherwise, set the current `top`'s next attribute to the newly created Node,
|
||||||
|
then update `top` to refer to the new Node. This effectively adds a new item on
|
||||||
|
top of the stack.
|
||||||
|
|
||||||
|
4. Implement the pop operation:
|
||||||
|
|
||||||
|
- If the stack is empty (i.e., top == null), there's nothing to remove, so you
|
||||||
|
can return an error or raise an exception.
|
||||||
|
- Otherwise, store the current `top`'s data value in a variable for later use
|
||||||
|
(the popped item).
|
||||||
|
- Update the `top` to refer to its next node and then set that previous top's
|
||||||
|
next attribute to null, effectively removing the top element from the stack.
|
||||||
|
- Return the stored popped value as output of this operation.
|
||||||
|
|
||||||
|
5. Implement the peek operation:
|
||||||
|
|
||||||
|
- Check if the stack is empty (i.e., top == null). If so, return an error or
|
||||||
|
raise an exception since there's no element to view.
|
||||||
|
- Otherwise, return the data from the current `top` node without modifying the
|
||||||
|
linked list structure. This allows you to see the value of the item at the top of
|
||||||
|
the stack.
|
||||||
|
|
||||||
|
6. Implement the isEmpty operation:
|
||||||
|
- Simply check if the `top` attribute in your Stack class is null or not, and
|
||||||
|
return true if it's null (indicating an empty stack) or false otherwise.
|
||||||
|
|
||||||
|
However, in this case, we will only look at push and pop operations. As said earlier,
|
||||||
|
implementating other operations such as peeking and checking for emptiness is trivial
|
||||||
|
as it simply involves looking at the `top` pointer and raising the desired flag.
|
||||||
|
|
||||||
|
### Pseudocode
|
||||||
|
|
||||||
|
```
|
||||||
|
Add(item)
|
||||||
|
// Push an element on the stack. Return true is successful;
|
||||||
|
// else return false. item is used as an input.
|
||||||
|
{
|
||||||
|
if (top >= n - 1) then
|
||||||
|
{
|
||||||
|
write("Stack is full");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
top := top + 1;
|
||||||
|
stack[top] := item;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove(item)
|
||||||
|
// Pop the top element form the stack. Return true if successful
|
||||||
|
// else return false. item is used as output.
|
||||||
|
{
|
||||||
|
if (top < 0) then
|
||||||
|
{
|
||||||
|
write("Stack is empty!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
item := stack[top];
|
||||||
|
top := top - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
import <print>;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
ssize_t data{};
|
||||||
|
Node *link{};
|
||||||
|
};
|
||||||
|
|
||||||
|
Node *top{nullptr};
|
||||||
|
|
||||||
|
auto add(const ssize_t &item) -> bool {
|
||||||
|
auto temp{new Node};
|
||||||
|
if (temp == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
temp->data = item;
|
||||||
|
temp->link = top;
|
||||||
|
top = temp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto remove() -> bool {
|
||||||
|
if (top == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto temp{top};
|
||||||
|
top = top->link;
|
||||||
|
delete temp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
auto success{add(54)};
|
||||||
|
if (!success) {
|
||||||
|
std::println("Out of memory");
|
||||||
|
}
|
||||||
|
success = add(45);
|
||||||
|
if (!success) {
|
||||||
|
std::println("Out of memory");
|
||||||
|
}
|
||||||
|
success = remove();
|
||||||
|
if (!success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
success = remove();
|
||||||
|
if (!success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
success = remove();
|
||||||
|
if (!success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
success = remove();
|
||||||
|
if (!success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a very basic implementation and design specifics will require you to implement
|
||||||
|
it in a slightly different way but the crux remains the same.
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
1. **Imports**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
import <print>;
|
||||||
|
```
|
||||||
|
|
||||||
|
- This line imports the `<print>` library for printing to the console, which is a feature from C++23 that includes `std::println`.
|
||||||
|
|
||||||
|
2. **Node Structure**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Node {
|
||||||
|
ssize_t data{};
|
||||||
|
Node *link{};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- This defines a structure called `Node` that represents an element in the stack.
|
||||||
|
- `ssize_t data{}`: This member holds the data of the node.
|
||||||
|
- `Node *link{}`: This is a pointer to the next node in the stack, allowing the creation of a linked list.
|
||||||
|
|
||||||
|
3. **Global Pointer**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Node *top{nullptr};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `top` is a pointer to the top node of the stack. It is initialized to `nullptr`, indicating that the stack is initially empty.
|
||||||
|
|
||||||
|
4. **`add()` Function**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto add(const ssize_t &item) -> bool {
|
||||||
|
auto temp{new Node};
|
||||||
|
if (temp == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
temp->data = item;
|
||||||
|
temp->link = top;
|
||||||
|
top = temp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- This function adds an item to the stack.
|
||||||
|
- `auto temp{new Node};`: Creates a new node and assigns it to the pointer temp.
|
||||||
|
- `if (temp == nullptr) { return false; }`: Checks if memory allocation failed (though in modern C++ with new, this check is redundant because [new throws an exception on failure](https://en.cppreference.com/w/cpp/memory/new/operator_new#Exceptions)).
|
||||||
|
- `temp->data = item;`: Assigns the input item to the data part of the new node.
|
||||||
|
- `temp->link = top;`: Links the new node to the current top node.
|
||||||
|
- `top = temp;`: Updates the top pointer to the new node.
|
||||||
|
- Returns `true` to indicate success.
|
||||||
|
|
||||||
|
5. **`remove()` Function**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
auto remove() -> bool {
|
||||||
|
if (top == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto temp{top};
|
||||||
|
top = top->link;
|
||||||
|
delete temp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- This function removes an item from the stack.
|
||||||
|
- `if (top == nullptr) { return false; }`: Checks if the stack is empty.
|
||||||
|
- `auto temp{top};`: Temporarily stores the top node.
|
||||||
|
- `top = top->link;`: Updates the top pointer to the next node in the stack.
|
||||||
|
- `delete temp;`: Deletes the old top node.
|
||||||
|
- Returns `true` to indicate success.
|
||||||
|
|
||||||
|
6. **`main()` Function**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int main() {
|
||||||
|
auto success{add(54)};
|
||||||
|
if (!success) {
|
||||||
|
std::println("Out of memory");
|
||||||
|
}
|
||||||
|
success = add(45);
|
||||||
|
if (!success) {
|
||||||
|
std::println("Out of memory");
|
||||||
|
}
|
||||||
|
success = remove();
|
||||||
|
if (!success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
success = remove();
|
||||||
|
if (!success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
success = remove();
|
||||||
|
if (!success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
success = remove();
|
||||||
|
if (!success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- The `main` function tests the stack operations.
|
||||||
|
- `auto success{add(54)};`: Attempts to add `54` to the stack and checks for success.
|
||||||
|
- If adding fails, it prints "Out of memory".
|
||||||
|
- `success = add(45);`: Attempts to add `45` to the stack and checks for success.
|
||||||
|
It then repeatedly tries to remove elements from the stack, printing "Stack empty" if the stack is empty.
|
||||||
|
|
||||||
|
## Code (using smart pointers)
|
||||||
|
|
||||||
|
Usage of raw pointers should be avoided whenever possible for safety reasons. Here is a possible implementation using smart pointers.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
import <memory>;
|
||||||
|
import <print>;
|
||||||
|
|
||||||
|
struct Node;
|
||||||
|
|
||||||
|
using node_ptr_t = std::shared_ptr<Node>;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
ssize_t data{};
|
||||||
|
std::shared_ptr<Node> link{};
|
||||||
|
|
||||||
|
Node() = default;
|
||||||
|
Node(Node &&) = default;
|
||||||
|
explicit Node(ssize_t data, node_ptr_t link)
|
||||||
|
: data(std::move(data)), link(link) {}
|
||||||
|
Node &operator=(Node &&) = default;
|
||||||
|
Node(const Node &) = delete;
|
||||||
|
Node &operator=(const Node &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
node_ptr_t top{nullptr};
|
||||||
|
|
||||||
|
auto add(const ssize_t &item) -> bool {
|
||||||
|
auto temp{std::make_shared<Node>()};
|
||||||
|
if (temp == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
temp->data = item;
|
||||||
|
temp->link = top;
|
||||||
|
top = temp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto remove() -> bool {
|
||||||
|
if (top == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto temp{top};
|
||||||
|
top = top->link;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
if (auto success{add(54)}; !success) {
|
||||||
|
std::println("Out of memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto success{add(45)}; !success) {
|
||||||
|
std::println("Out of memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto success{remove()}; !success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
if (auto success{remove()}; !success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
if (auto success{remove()}; !success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
if (auto success{remove()}; !success) {
|
||||||
|
std::println("Stack empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The implementation is mostly the same with a few notable exceptions:
|
||||||
|
|
||||||
|
- No `new` and `delete` operators as intended.
|
||||||
|
- Guard rails to prevent implicit copying.
|
||||||
|
|
||||||
|
### Explanation
|
||||||
|
|
||||||
|
1. **Forward Declaration**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Node;
|
||||||
|
```
|
||||||
|
|
||||||
|
- Forward declares the `Node` struct so it can be used in the `node_ptr_t` type definition before being fully defined.
|
||||||
|
|
||||||
|
2. **Type Definition**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using node_ptr_t = std::shared_ptr<Node>;
|
||||||
|
```
|
||||||
|
|
||||||
|
- Defines `node_ptr_t` as an alias for `std::shared_ptr<Node>`, which is a smart pointer to manage `Node` objects.
|
||||||
|
|
||||||
|
3. **`Node` Structure**
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
struct Node {
|
||||||
|
ssize_t data{};
|
||||||
|
std::shared_ptr<Node> link{};
|
||||||
|
|
||||||
|
Node() = default;
|
||||||
|
Node(Node &&) = default;
|
||||||
|
explicit Node(ssize_t data, node_ptr_t link)
|
||||||
|
: data(std::move(data)), link(link) {}
|
||||||
|
Node &operator=(Node &&) = default;
|
||||||
|
Node(const Node &) = delete;
|
||||||
|
Node &operator=(const Node &) = delete;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
- `Node` contains two members:
|
||||||
|
- `data`: Stores the value of the node.
|
||||||
|
- `link`: Points to the next node in the stack using `std::shared_ptr<Node>`.
|
||||||
|
- Constructors and assignment operators:
|
||||||
|
- Default constructor (`Node() = default`).
|
||||||
|
- Move constructor (`Node(Node &&) = default`).
|
||||||
|
- Parameterized constructor (`explicit Node(ssize_t data, node_ptr_t link)`) to initialize `data` and `link`.
|
||||||
|
- Move assignment operator (`Node &operator=(Node &&) = default`).
|
||||||
|
- Copy constructor and copy assignment operator are deleted to prevent copying (`Node(const Node &) = delete` and `Node &operator=(const Node &) = delete`).
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
```console
|
||||||
|
❯ ./main
|
||||||
|
Stack empty
|
||||||
|
Stack empty
|
||||||
|
```
|
Loading…
Reference in a new issue