Data Structures Stacks and Queues

Table of contents

3.1 stack

3.1.1 Basic concepts

3.1.2 The sequential storage structure of the stack

3.1.3 Chained storage structure of the stack 

3.2 Queue

3.2.1 Basic concepts

3.2.2 Sequential storage of queues

3.2.3 Chained storage of queues

3.3 Application of stack and queue

3.3.1 Stack bracket matching problem

3.3.2 Expression evaluation on the stack

3.3.3 Stack and recursion

3.3.4 Queue and level traversal

3.3.5 Queues and computer systems


Focus on mastering the stack entry and exit process, the legitimacy of the stack pop sequence, and the operation and characteristics of the queue

3.1 stack

3.1.1 Basic concepts

Def: A stack is a linear list that only allows insertion or deletion at one end

Top of the stack Top: also known as the end of the table, the end where the insertion and deletion operations are performed

Bottom of the stack: also known as the header, the fixed end, the end that does not allow insertion and deletion operations

·Features: operations can only be performed on the top of the stack, and access nodes follow the last in first out principle

Logical structure: the same as the linear table, still a one-to-one relationship

·Basic operation:

InitStack(&S): Initialize an empty stack S

StackEmpty(S): Determine whether a stack is empty, if the stack S is empty, return true, otherwise return false

Push(&S,x): push into the stack, if the stack s is not full, add x to make it the new top of the stack

Pop(&S,&x): Pop out the stack, if the stack s is not empty, pop the top element of the stack and return with x

GetTop(S,&x): Read the top element of the stack, if the stack s is not empty, use x to return the top element of the stack

DestroyStack(&S): Destroy the stack and release the storage space occupied by the stack s ("&" means reference call)

3.1.2 The sequential storage structure of the stack

Sequential stack: A stack that uses sequential storage is called a sequential stack, which is exactly the same as the general linear table sequential storage structure. A group of storage units with continuous addresses are used to store elements from the bottom of the stack to the top of the stack in sequence, and the top pointer is used to point to the top of the stack (or the subscript address above the top element of the stack)

Features: It is simple and convenient to use an array as a sequential stack, but the fixed size of the array is prone to overflow (overflow: continue to push when the stack is full; underflow: pop when the stack is empty, underflow can be used as an end condition)

·Implementation of sequential stack

1) Initialization: Initialize the stack top pointer S.top=-1

2) Judge the stack is empty: the stack is empty when S.top=-1, otherwise it is not empty

3) Push into the stack: when the stack is not full, the top pointer of the stack is incremented by 1 first, and then the value is sent to the top of the stack

bool Push (SqStack &S, ElemType x) {
    if (S. tope=MaxSize-1)         //栈满, 报错
        return false;
    S.data[++S.top]=x;             //指针先加1,再入栈
    return true ;
}

4) Pop out of the stack: when the stack is not empty, first take the value of the top element of the stack, and then decrement the top pointer of the stack by 1

bool Pop (SqStack &S, ElemType &x) {
    if(S. top==-1)              //栈空,报错
      return false;
    x=S.data[S.top--];          //先出栈,指针再减1
    return true;
}

5) Read the top element of the stack

bool GetTop (SqStack S, ElemType &x) {
    if(S. top==-1)                //栈空,报错
      return false;
    x=S .data[S.top];             //x记录栈顶元素
    return true ;
}

Shared stack: By utilizing the relatively unchanged position of the bottom of the stack, two sequential stacks can share a one-dimensional array, and the bottoms of the two stacks are respectively set at both ends of the shared space, and the tops of the two stacks extend toward the middle of the shared space

The stack top pointers of both stacks point to the top element of the stack. When top1=-1, stack No. 1 is empty, and when top2=Maxsize, stack No. 2 is empty; only when the two stack top pointers are adjacent (top2-top1=1), the stack is judged to be full. When the No. 1 stack is pushed into the stack, top1 is incremented by 1 before assigning a value, and when No. 2 stack is pushed into the stack, top2 is decremented by 1 before assigning a value; when the stack is popped, it is just the opposite.

The purpose of sharing the stack is to make more efficient use of the storage space. The spaces of the two stacks are adjusted to each other, and overflow occurs only when the entire storage space is full. The time complexity of accessing data is 0(1), so it has no effect on access efficiency. 

3.1.3 Chained storage structure of the stack 

Chain stack: A stack using chain storage is called a chain stack, which is usually implemented by a single linked list, and all operations are performed at the head of the table, so the chain stack has no head node, and the head pointer directly points to the top element of the stack

·Features: The chain stack is convenient for multiple stacks to share storage space, which improves the efficiency, and there is no overflow of the stack, which is convenient for the deletion and insertion of nodes; the operation of the chain stack is similar to the chain list, and the pop-up and stack-in are all performed at the head

·Basic operation of chain stack

1) Initialization: Construct an empty stack, and set the top pointer to empty

2) Null judgment: Lhead=NULL?

3) into the stack

4) Pop out

5) Take the top element of the stack: directly return Lhead->data

[Sequence stack and chain stack]: The time complexity of sequential stack and chain stack is the same as O(1); for space performance, the sequential stack needs to determine a fixed length in advance, which may cause the problem of wasting memory space, but its advantage is that it is very convenient to locate when accessing, while the chain stack requires each element to have a pointer field, which also increases some memory overhead, but there is no limit to the length of the stack. So if the element changes are unpredictable during the use of the stack, sometimes small and sometimes very large, then it is best to use a chain stack. Conversely, if its changes are within a controllable range, it is better to use a sequential stack.

3.2 Queue

3.2.1 Basic concepts

Def: The queue is a linear table that only allows insertion at one end (tail) and deletion (head) at the other end (think of a queue for nucleic acid), that is, a first-in-first-out linear table

· Queue basic operations:

InitQueue(&Q): Initialize the queue and construct an empty queue Q

QueueEmpty(Q): Judging that the queue is empty, if the queue Q is empty, return true, otherwise return false

EnQueue(&Q,x): Enter the queue, if the queue Q is not full, add x to make it the new tail of the queue

DeQueue (&Q,&x): Dequeue, if the queue Q is not empty, delete the head element and return with x

GetHead(Q, &x): Read the queue head element, if the queue e is not empty, then assign the queue head element to x

[Note]: Do not read any data in the middle of the stack or queue

3.2.2 Sequential storage of queues

Def: The sequential storage of the queue refers to assigning a continuous storage unit to store the elements in the queue, and setting two pointers: the queue head pointer front points to the queue head element and the queue tail pointer rear points to the next position of the queue tail element (different textbooks may have different definitions of front and rear, you can let rear point to the queue tail element, and front point to the queue head element)

·Sequential storage description type:

#define MaxSize 50         //定义队列中元素的最大个数
typedef struct{
ElemType data [MaxSize] ; //存放队列元素
int front, rear;          //队头指针和队尾指针
} SqQueue;

· Queue operations:

Initial state (queue empty): Q. front==Q. rear==0

Entering the queue operation: when the queue is not full, first send the value to the element at the end of the queue, and then add 1 to the pointer at the end of the queue

Dequeue operation: when the team is not empty, first get the value of the element at the head of the queue, and then add 1 to the pointer at the head of the queue

[Note] : When rear==maxQsize, true overflow and false overflow occur

Circular queue: For the above-mentioned true and false overflow situation, the solution is to start from the beginning when the back is full, and point the rear pointer in the above figure to the position of 0, so that the head-to-tail cycle is connected logically, that is, the table storing queue elements is logically regarded as a ring, called a circular queue, and can also be abstracted into a circular queue

Realization of circular queue: use modulo operation to realize that the rear pointer points to 0, that is, Q.rear = (Q.rear+1) % MAXQSIZE when inserting an element, so that the rear pointer can move from the last position to the first position; the same is true for the front pointer when deleting an element, Q.front = (Q.front+1) % MAXQSIZE

·The circular queue is empty and full: For the figure below, the empty queue and full queue are rear=front, so how to judge whether it is empty or full at this time?

 1) Set a flag, when rear=front and flag=0, the team is empty; when rear=front and flag=1, the team is full

2) Set another variable to record the number of elements in the team

3) Use one less element space. Since the rear may be larger or smaller than the front, there may be a difference of one position or a circle, which are the two situations in the figure below, so in order to solve the problem of the size of the rear and front, the condition of full queue is (rear+1) % MAXQSIZE=front; the condition of empty queue is still rear =  front

In summary, the number of elements in the queue can also be calculated as : (rear-front+ MAXQSIZE)% MAXQSIZE. With the above analysis, the basic idea of ​​​​realizing the circular queue will be

· Basic operation of circular queue

1) Initialization: Initialize the queue head and tail pointer Q.rear =Q.front=0

2) Judging the team is empty: Q.rear =Q.front

3) Enqueue

bool EnQueue (SqQueue &Q,ElemType x) {
    if((Q. rear+1) %MaxSize==Q. front) return false;  //队满则报错
    Q.data[Q. rear]=x;
    Q. rear= (Q. rear+1) MaxSize;                    //队尾指针加1取模
    return true;
}

4) Dequeue

bool DeQueue (SqQueue &Q, ElemType &X) {
    if(Q. rear==Q. front) return false;         //队空则报错
    x=Q. data[Q. front] ;
    Q. front= (Q. front+1)%MaxSize;            //队头指针加1取模
    return true;
}

3.2.3 Chained storage of queues

Def: The linked storage of the queue is called a chain queue, which is a single-linked list with both a queue head pointer and a queue tail pointer. The queue head pointer points to the queue head node, and the queue tail pointer points to the terminal node. When the queue is empty, both front and rear point to the head node

·Basic operation of chain queue

1) Initialization: establish the head node, set the queue head pointer to be empty Q.front->next=NULL

2) Empty judgment: Q.front=Q.rear

3) Enqueue: Insert a new node into the end of the chain

void EnQueue (LinkQueue &Q, ElemType x) {
    LinkNode *s= (LinkNode*) malloc (sizeof (LinkNode)) ;
    s->data=x; s->next=NULL;            //创建新结点,插入到链尾
    Q. rear->next=s;
    Q. rear=s;
}

 4) Dequeue: delete the first node of the linked list

bool DeQueue (LinkQueue &Q; ElemType &x) {
    if (Q. front--Q.rear) return false;         //空队
    LinkNode *p=Q. front->next;
    x=p->data;
    Q. front->next=p->next;
    if (Q. rear==p)
      Q. rear=Q. front;                //若原队列中只有一个结点,删除后变空
    free(p) ;
    return true;
}

3.2.4 Double-ended queue

Def: double-ended queue refers to a queue that allows both ends to enter and exit the queue, and its logical structure is still a linear structure

Output-limited double-ended queue: one end allows entry and exit, but the other end only allows double-ended entry (two inputs and one output). For double-ended queues with two inputs and one output, grasp the principle of "both sides enter and exit sequentially" when entering and exiting the queue

Input-restricted double-ended queue: one end allows entry and exit of the queue, but the other end only allows double-ended queues (two out and one in), for double-ended queues with two out and one in, grasp the principle of "in and out in sequence" when entering and leaving the queue

3.3  Application of stack and queue

3.3.1 Stack bracket matching problem

·Basic idea: Scan all characters in turn, push the left parenthesis into the stack when encountering the left parenthesis, pop the left parenthesis on the top of the stack for matching check when encountering the right parenthesis, and the following four situations occur at this time:

1) If the matching is successful, the next round of matching will be performed until all brackets are matched successfully

2) If there are parentheses that are not matched successfully, it means that the matching of these parentheses is illegal and will fail

3) If there is a right parenthesis matching, the stack is empty. At this time, the right parenthesis is single, and the parenthesis matching fails

4) When all closing parentheses are processed, there are still parentheses in the stack at this time, that is, single left parentheses, and the matching also fails

·Algorithm implementation:

3.3.2 Expression evaluation on the stack

· Three arithmetic expressions

1) Infix expression: the operator is between the two operands (a+bc*d)

2) Prefix expression: the operator is in front of the two operands (-+ab*cd)

3) Postfix expression: the operator follows the two operands (ab+cd*-)

· Conversion between expressions

1) infix → suffix

①Determine the operation order of each operator in the expression (the operation order is not unique, and the corresponding expression is not unique)

② Select operators in order, and form new operands in the order of various expressions

③ If there are still unprocessed operators, continue to execute ②

[Note]: During the conversion process, it is best to follow the principle of left priority to ensure that the manual calculation is consistent with the calculation, that is, as long as the operator on the left can be calculated first, the operator on the left will be calculated first

Stack implementation: Initialize a stack to save operators whose operation order cannot be determined temporarily. Processing each element from left to right may encounter three situations:

① Encountered operands. Join the suffix expression directly.

②Encountered a delimiter. When “(” is encountered, it is directly pushed onto the stack; when “)” is encountered, the operators in the stack will be popped one by one and added to the suffix expression until “(” is popped up. Note: “(” is not added to the suffix expression.

③Encountered operators. Pop all operators in the stack whose priority is higher than or equal to the current operator in turn, and add them to the postfix expression. If it encounters "(" or the stack is empty, it will stop. Then put the current operator on the stack.

2) infix → prefix

The method is the same as 1), but note that the principle of right priority is followed at this time, that is, as long as the operator on the right can be calculated first, the operator on the right will be calculated first

· Calculation and realization of expressions

1) Suffix expression: scan from left to right, and whenever an operator is encountered, the two nearest operands in front of the operator will perform the corresponding operation and combine them into one operand

Stack implementation:

① Scan the next element from left to right until all elements are processed

②If the operand is scanned, push it into the stack, and return to ①; otherwise, execute ③

③If the operator is scanned , two elements on the top of the stack will be popped up, and the result of the corresponding operation will be pushed back to the top of the stack, returning to ①

2) Prefix expression: scan from right to left, and every time an operator is encountered, the two operands following the operation will perform the corresponding operation and combine them into one operand

Stack implementation: For prefix expressions, you only need to scan from right to left in the process of ①

3) Infix expressions:

Stack implementation: convert infix to suffix, and then use suffix expression to evaluate

Initialize two stacks, operand stack and operator stack

②If the operand is scanned, push it into the operand stack

If an operator or delimiter is scanned, it will be pushed into the operator stack according to the logic of "infix to suffix" (the operator will also be popped up during the period, and each time an operator is popped up, it will pop up the top elements of the two operand stacks to perform the corresponding operation, and the result of the operation will be pushed back to the operand stack)

3.3.3 Stack and recursion

·Function call: In the process of function calling, the last called function is always the first to be executed. What is realized by using the stack is that the last stored on the stack is called first. In this process, the stack will store the function call return address, actual parameters and local variables

Recursive call: When calling recursively, the function call stack can be called a recursive working stack. Every time a layer of recursion is entered, the information required for the recursive call is pushed onto the top of the stack. Every time a layer of recursion is exited, the corresponding information is popped from the top of the stack.

Disadvantages: low efficiency, and too many layers of recursion will cause stack overflow; it may also contain a lot of repeated calculations

3.3.4 Queue and level traversal

[Hierarchical traversal of binary tree]:

[Breadth-first traversal of graphs]: the same reason, abbreviated

3.3.5 Queues and computer systems

When multiple processes compete for limited system resources, first-come-first-serve is a common method, and this method happens to be implemented with queues

Allocation of CPU resources: ready process queue, processes wait for the CPU in the order of first come, first come

·Print data buffer: the buffer here can be implemented with a queue to store the data to be printed, which can alleviate the problem of the speed mismatch between the host and the printer

Guess you like

Origin blog.csdn.net/weixin_46516647/article/details/127165251