stack
What is the stack
A stack is a table that restricts the insertion (push) and deletion (pop) of elements to only one position. This position is the end of the table, which is called the top of the stack.
When you first understand the stack, you can understand it as a special array. Elements are added and deleted at the top of the stack. The stack is similar to a magazine. The last loaded elements are taken out first, which is also the LIFO principle followed by the stack.
Advantages of stack
The operation of stack is constant time, and in a very fast constant time. On some machines, push and pop can be written as a machine instruction. Modern computers take stack operation as a part of its instructions. Therefore, stack is the most basic data structure after relaying arrays in computer science.
Implementation of stack
Several basic functions of stack:
- push: adds an element to the top of the stack and returns whether it was successfully added
- pop: deletes the element at the top of the stack, and returns whether the deletion was successful
- peek: return stack top element
- isEmpty: judge whether the current stack is empty
- isFull: judge whether the current stack is full
- size: returns the number of elements in the stack
- Clear: clear stack
Stack can be realized by sequential storage and chain storage.
Sequential storage of stack
- Definition of stack
typedef struct arrayStack { int data[MaxSize]; // An array of storage elements int top; //Stack top pointer }ArrayStack;
In order to facilitate understanding, the data storage type here is int. generally, we give int an alias, so as to realize simple polymorphism. When we need to change the int type stack to other type stacks, we only need to change the type of alias.
- push: adds an element to the top of the stack and returns whether it was successfully added
bool push(ArrayStack *arrayStack, int element) { if (arrayStack.top == MaxSize - 1) { // Stack full return false; } arrayStack.data[arrayStack.top] = element; // Add to stack arrayStack.top++; //Stack top pointer + 1 return true; }
- pop: deletes the element at the top of the stack, and returns whether the deletion was successful
bool pop(ArrayStack *arrayStack) { if(arrayStack.top == 0) { // Stack empty return false; } arrayStack.top--; //Stack top pointer - 1 arrayStack.data[arrayStack.top] = 0; //Reset stack top element return true; }
- peek: return stack top element
int peek(ArrayStack *arrayStack) { if(arrayStack.top == 0) { //Stack empty return 0; } return arrayStack.data[arrayStack.top - 1]; //Return stack top element }
- isEmpty: judge whether the current stack is empty
BOOL isEmpty(ArrayStack *arrayStack) { if(arrayStack.top == 0) { //Stack empty return 0; } else { //Stack is not empty return 1; } }
- isFull: judge whether the current stack is full
BOOL isFull(ArrayStack *arrayStack) { if(arrayStack.top == MaxSize - 1) { //Stack full return 1; } else { //Stack not full return 0; } }
- size: returns the number of elements in the stack
int size(ArrayStack *arrayStack) { return arrayStack.top; //Return stack top pointer }
- Clear: clear stack
void clear(ArrayStack *arrayStack) { int n = arrayStack.top; while(n) { //Reset elements in stack arrayStack.data[n - 1] = 0; n--; } arrayStack.top = 0; //Reset stack top pointer }
Chain storage of stack
- Definition of stack
typedef struct linkedStack { int data; //Data domain struct linkedStack *next; //Pointer field }LinkedStack;
- push: add elements to the top of the stack
void push(LinkStack *top, int element) { LinkedStack *node = (LinkedStack)malloc(sizeof(LinkedStack)); //Create a new node node->data = element; //Assign value to node data field node->next = top->next; //Stack nodes top->next = node; }
Note: the stack in the form of linked list cannot be full, so push does not need to judge the stack full
- pop: deletes the element at the top of the stack and returns whether the deletion was successful
bool pop(LinkedStack *top) { if(top->next == NULL) { //Stack empty judgment return false; } LinkedStack *node = top->next; //Mark pop node top->next = node->next; //Delete this node free(node); return true;
- peek: return stack top element
int pop(LinkedStack *top) { if(top->next == NULL) { //Stack empty judgment return 0; } return top->next->data;
- isEmpty: judge whether the current stack is empty
BOOL isEmpty(LinkedStack *top) { if(top->next == NULL) { //Stack empty return 0; } else { //Stack is not empty return 1; } }
- size: returns the number of elements in the stack
int size(LinkedStack *top) { int cnt = 0; //Counter while(top->next != NULL) { //Let the counter + 1 for each node cnt++; top = top->next; } return cnt; }
- Clear: clear the stack to free up memory space
void clear(LinkedStack *top) { LinkedStack *front = top; //Set front and back nodes LinkedStack *rear; while (front) { //Determine whether to move to the end of the team rear = front; front = front->next; free(rear); } }
queue
What is the queue
Queue, also known as queue, is a linear list of first in first out (FIFO). In specific applications, it is usually implemented by linked list or array. Queue can only be inserted at the back end and deleted at the front end.
Queue, as the name suggests, is just like queue. The first in and first out of the queue, and the elements in the team cannot jump in the queue or quit halfway.
Implementation of queue
Queues can also be represented by sequential storage and chain storage.
Sequential storage of queues
- Structure definition
typedef struct { int data[MaxSize]; //Storing queue elements in a static array int front; //Queue head pointer int rear; //Tail pointer }ArrayQueue;
We follow the practice of sequential stack storage and add header and tail pointers. In the initial state, we point the header and tail pointers to 0. In the process of operating the queue, we ensure that the header pointer points to the subscript of the header and the tail pointer points to the next position of the tail.
With these rules in place, we perform 5 queue entries and 5 queue exits for a queue with a length of 6:
Considering how to judge whether the queue is full in this case, it is easy to think of whether the tail pointer is equal to MaxSize - 1. However, there is an obvious problem in this way. When we enter the queue for 5 times and then exit the queue for 5 times for a queue with MaxSize = 6, our tail pointer is equal to MaxSize - 1, but our queue is actually empty and the memory space is not fully utilized. To solve this problem We adopt the logical structure of circular queue.
Physically, we still use a continuous array to store. Logically, the end of our array and the beginning of the array are continuous. When the end of the queue pointer of the circular queue reaches the end of the array, it will return to the starting position of the array, realizing the reuse of memory.
It should be noted that when moving the queue head and tail pointers, we need to take the modulus of MaxSize to achieve correct movement. For example, for a queue with MaxSize = 8, when the queue head pointer reaches 7, add elements to join the queue. At this time, the position of the queue head pointer should be (7 + 1) %8 = 0, return to the position where the array subscript is 0. Therefore, the operation of moving the queue head and tail pointers should be:
front->front = (front->front + 1) % MaxSize;
front->rear = (front->rear + 1) % MaxSize;
The following is the core function of circular queue.
- initQueue: queue initialization
void initQueue(ArrayQueue *queue){ queue->front = queue->rear = 0; //Resetting the head and tail pointers completes the initialization of the queue }
- isEmpty: judge whether the queue is empty
bool isEmpty(ArrayQueue *queue){ if (queue.rear == queue.front) { //If the tail pointer and the head pointer coincide, the team is empty return true; } else { return false; } }
- enQueue: adds an element to the queue and returns whether it has been successfully queued
int enQueue(ArrayQueue *queue, int element) { if((queue->rear + 1) % MaxSize == queue->front) { //Team full return false; } queue->data[queue->rear] = element; //Insert element at the end of the queue queue->rear = (queue->rear + 1) % MaxSize; //Move tail pointer return true; }
- deQueue: causes the end of the queue element to exit the queue and returns whether it has been successfully dequeued
int deQueue(ArrayQueue *queue) { if (isEmpty(queue)) { //Team air return false; } queue->front = (queue->front + 1) % MaxSize; //Move queue head pointer return true; }
Chained storage of queues
- Structure definition
typedef struct queueNode { //Chain queue node int data; struct queueNode *next; }QueueNode; typedef struct { Queue *front; //Queue head pointer Queue *rear; //Tail pointer }LinkedQueue;
- initQueue: queue initialization
void initQueue(LinkedQueue *queue) { queue->front = (LinkedQueue *)malloc(sizeof(LinkedQueue)); queue->rear = (LinkedQueue *)malloc(sizeof(LinkedQueue)); queue->front->next = NULL; //Initialize queue header pointer queue->rear->next = NULL; //Initialize end of queue pointer }
- isEmpty: judge whether the queue is empty
bool isEmpty(LinkedQueue *queue) { if (queue.rear == queue.front) { //If the tail pointer and the head pointer coincide, the team is empty return true; } else { return false; } }
- enQueue: add elements to the queue
void enQueue(LinkedQueue *queue, int element) { QueueNode *node = (QueueNode *)malloc(sizeof(QueueNode)); //Create node node->data = element; //Node initialization node->next = NULL; queue->rear->next = node; //Insert node at the end of the queue queue->rear = node; //Move tail pointer }
- deQueue: causes the end of the queue element to exit the queue and returns whether it has been successfully dequeued
bool deQueue(LinkedQueue *queue) { if (isEmpty(queue)) { //Team air return false; } QueueNode *node = queue->front->next; //Mark the element to be dequeued queue->front->next = node->next; //Move queue head pointer if (queue->rear == node) { //If the last element is out of the queue, the end of queue pointer needs to be modified queue->rear == queue->front; } free(node); return true; }
deQueue has special treatment for the last element out of the queue, because when we delete the last node, the tail pointer points to a piece of memory that has been destroyed. When the queue is empty, the head and tail pointers should be the same. Therefore, we need to make the tail pointer equal to the head pointer after deleting the last node.
Judge the condition of the last element: whether the next of the head node (queue.front) is the tail node.
- Destroy queue: destroy the chain storage queue to free up memory space
void destroyQueue(LinkedQueue *queue) { QueueNode *front = queue->front; //Set front and back nodes QueueNode *rear; while (front) { //Determine whether to move to the end of the team rear = front; front = front->next; free(rear); } }