mirror of
https://git.adityakumar.xyz/dsa.git
synced 2024-11-21 17:42:53 +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