stack and queue

Keywords: data structure

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);
    }
}

Posted by eth0g on Sun, 26 Sep 2021 12:12:00 -0700