1. Definition of stack
A stack is a linear table that can only be inserted and deleted at the end of the table.
We call the end that allows insertion and deletion as the top of the stack, the other end as the bottom of the stack, and the stack without any data elements as an empty stack. Stack is also called last in first out linear table, or LIFO structure for short.
To understand the definition of stack, you should pay attention to:
First of all, it is a linear table, that is, finding elements has a linear relationship, that is, the predecessor successor relationship. But it is a special linear table. In the definition, insert and delete operations are performed at the end of the linear table. Here, the end of the table refers to the top of the stack, not the bottom of the stack.
Its special feature is that it limits the insertion and deletion positions of this linear table. It always only inserts and deletes at the top of the stack.
This makes: the bottom of the stack is fixed, and the first to enter the stack can only be at the bottom of the stack.
Stack insertion is called stack entry, also known as stack pressing and stack entry. A similar bullet enters the magazine, as shown in the following figure on the left.
The deletion of the stack is called out of the stack, and some are also called bounce stack. It is like the bullet out of the clip, as shown in the following figure on the right.
In software applications, stack, a last in first out data structure, is very common. For example, when browsing a web page, no matter what browser has a "back" key, you can click it and load the browsed web pages in reverse order.
Stack in and stack out changes
So, can the first element on the stack only be the last element on the stack?
A: not necessarily. It depends on what happens. The stack limits the insertion and deletion positions of the linear table, and does not limit the entry and exit time of elements. In other words, when not all elements are put into the stack, the elements that have been put into the stack in advance can also be put out of the stack, as long as it is guaranteed that the elements at the top of the stack are put out of the stack.
For example, if we now have three integer numeric elements 1, 2 and 3 stacked in turn, what order will they be out of the stack?
- The first type: 1, 2 and 3 in, and then 3, 2 and 1 out. This is the simplest and best understood one. The stack order is 3, 2 and 1.
- The second type: 1 in, 1 out, 2 in, 2 out, 3 in, 3 out. That is, one in and one out. The order of stack out is 1, 2 and 3.
- The third type: 1 in, 2 in, 2 out, 1 out, 3 in, 3 out. The stack order is 2, 1 and 3.
- The fourth type: 1 in, 1 out, 2 in, 3 in, 3 out, 2 out. The stack order is 1, 3 and 2.
- The fifth type: 1 in, 2 in, 2 out, 3 in, 3 out, 1 out. The stack order is 2, 3 and 1.
Is it possible to stack in the order of 3, 1 and 2?
The answer is definitely not. Because 3 is out of the stack first, it means that 3 has been in the stack. Now that 3 has been in the stack, it means that 1 and 2 have been in the stack. At this time, 2 must be above 1, which is closer to the top of the stack. Then the stack can only be 3, 2 and 1. Otherwise, it does not meet the requirements of 1, 2 and 3 in turn. Therefore, 1:2 will not be out of the stack first at this time.
2. Abstract data type of stack
ADT Stack( stack) Data The elements of the same linear table have the same type, and the adjacent meta elements have precursor and successor relationships. Operation InitStack(*S):Initialize the operation and create an empty stack s; DestroyStack(*S):If the stack exists, destroy it; ClearStack(*S):Empty the stack; StackEmpty(S):If the stack is empty, return true,Otherwise return false; GetTop(S,*e):If the stack exists and is not empty, use e return s Stack top element of; Push(*S,e):If stack s Existing, insert new element e To stack s And become the top element of the stack; Pop(*S,*e):Delete stack s The top element of the stack, and e Return its value; StackLength(S):Return stack s Number of elements; endADT
Because the stack itself is a linear table, the sequential storage and chain storage of linear tables are also applicable to the stack.
3. Sequential storage structure of stack and its implementation
Since the stack is a special case of a linear table, the sequential storage of the stack is actually a simplification of the sequential storage of the linear table, which is called the sequential stack for short. The linear table is implemented by an array. For a linear table that can only be inserted and deleted at one end of the stack, which end of the array is better as the stack item and the bottom of the stack?
Yes, yes, it's better to use the end with subscript 0 as the bottom of the stack, because the first element exists at the bottom of the stack and the change is the smallest, so let it be the bottom of the stack.
We define a top variable to indicate the position of the top element of the stack in the array. Top is like the cursor of the vernier caliper in physics. As shown in the figure below, it can move back and forth, which means that the top of the stack item can become larger and smaller, but in any case, the cursor cannot exceed the length of the ruler. Similarly, if the length of the storage stack is StackSize, the top position of the stack must be less than StackSize. When the stack is stored In the case of an element, top is equal to 0, so the judgment condition of empty stack is usually set as top is equal to - 1.
Stack structure definition:
typedef int SElemType; //The type of SElemType depends on the actual situation. Here it is int. /*Sequential stack structure*/ typedef struct { SElemType data[MAXSIZE]; int top; //Stack top pointer }SqStack;
If there is a stack and StackSize is 5, the normal stack, empty stack and full stack are shown in the figure below.
Sequential storage structure of stack -- stack operation
For stack insertion, i.e. stack entry, as shown in the following figure:
The code is as follows:
Status Push(SqStack *S,SElemType e) { if(S->top == MAXSIZE-1) //Stack full { return ERROR; } S->top++; //Stack top pointer plus 1 S->data[s->top]=e; //Assign the newly inserted element to the stack top space return OK; }
Sequential storage structure of stack -- out of stack operation
Stack out operation pop, the code is as follows:
/* If the stack is not empty, delete the top element of s, return its value with e, and return OK; otherwise, return ERROR */ Status Pop(SqStack *S,SElemType *e) { if(S->top == -1) return ERROR; *e = S->data[S->top]; S->top--; return OK; }
4. Two stack shared space
If we have two stacks of the same type, we open up array space for each of them. If the first stack is full, it will overflow when we enter the stack, and the other stack has a lot of free storage space. At this time, we can use an array to store two stacks and make full use of the memory space occupied by this array.
The implementation is shown in the figure below. The array has two endpoints and two stacks have two stack bottoms. The bottom of one stack is the beginning of the array, that is, the subscript is 0, and the other stack is the end of the array, that is, the subscript is n-1 of the array length. In this way, if elements are added to the two stacks, the points at both ends extend to the middle.
The key idea is: they are at both ends of the array and close to the middle. top1 and top2 are stack item pointers of stack 1 and stack 2. It is conceivable that the two stacks can be used all the time as long as they don't meet.
It can be analyzed from here that when stack 1 is empty, it is when top1 is equal to - 1; when top2 is equal to n, it is when stack 2 is empty, and when the stack is full?
Consider the extreme case. If stack 2 is empty, stack 1 is full when top1 of stack 1 is equal to n- 1. On the contrary, when stack 1 is empty, stack 2 is full when top2 is equal to 0. But more often, when the two stacks meet, that is, when the difference between the two pointers is 1, that is, top1 + 1 == top2 (when top2 is the next element of top1 in the array), the stack is full.
The code of the shared space structure of the two stacks is as follows:
typedef struct { SElemType data[MAXSIZE]; int top1; //Stack 1 stack top pointer int top2; //Stack 2 stack top pointer }SqDoubleStack;
For the push method of the shared space between two stacks, in addition to inserting the element value parameter, we also need to have a stack number parameter stackNumber of stack 1 or stack 2. The code of the inserted element is as follows:
Status Push(SqDoubleStack *S,SElemType e,int stackNumber) { if(s->top1+1==S->top2) //The stack is full and cannot push new elements return ERROR; if(stackNumber==1) //There are elements in stack 1 S->data[++S->top1]=e; //If it is stack 1, first top1+1 and then assign a value to the array element else if(stackNumber==2) //There are elements in stack 2 S->data[--S->top2]=e; //If it is stack 2, first top2-1 and then assign a value to the array element return OK; }
Since it has been determined whether the stack is full at the beginning of the code, the following top1+1 or
top2-1 does not worry about overflow.
For the pop method of shared space between two stacks, the parameter is only the parameter stackNumber of stack 1 and stack 2. The code is as follows:
/*If the stack is not empty, delete the top element of S, return its value with e, and return 0K; Otherwise, ERROR is returned */ Status Pop(SqDoubleStack *S , SELemType *e, int stackNumber) { if(stackNumber==1) { if(s->top1==-1) /*Description stack 1 is empty and overflows*/ return ERROR; *e = s->data[s->top1--]; /* Remove the top element of stack 1 from the stack */ } else if(stackNumber==2) { if(s->top2==MAXSIZE) /*Description stack 2 is empty and overflows*/ return ERROR; *e=s->data[s->top2++] //Remove the top element of stack 2 from the stack } return OK; }
In fact, using such a data structure is usually when the space requirements of two stacks have an opposite relationship, that is, when one stack grows, the other stack is shortening. In this way, the use of two stacks of shared space storage method is of great significance. Otherwise, both stacks keep growing, and it will soon overflow because the stack is full.
Note: the two stacks should have the same data type.
5. Chain storage structure of stack and its implementation
The chain storage structure of stack is called chain stack for short.
The top of the stack is used for insertion and deletion, and the stack has a top pointer; Then put the top of the stack on the head of the single linked list (as shown in the figure below). In addition, there is already a top of the stack on the head. The common head nodes in the single linked list will lose their meaning. Generally, the head nodes are not required for the chain stack.
For the chain stack, the stack is basically not full, unless there is no available space in the memory. If it does happen, the computer operating system is facing a crash, not whether the chain stack overflows.
But for the empty stack, the original definition of the linked list is that the head pointer points to null, so the emptiness of the chain stack is actually when top=NULL.
The structure code of the chain stack is as follows:
/*Chain stack structure*/ typedef struct StackNode { SElemType data; struct StackNode *next; }}StackNode,*L inkStackPtr; typedef struct { LinkStackPtr top; int count; //count is the length of the stack (total number of elements) }LinkStack;
Chain storage structure of stack -- stack operation
For the stack push operation of the chain stack, assume that when the element value is a new node of e, s and top are the stack top pointers, as shown in the following diagram:
/* Insert element e as the new stack top element */ Status Push(LinkStack *S,SElemType e) { LinkStackPtr p=(LinkStackPtr)malloc(sizeof(StackNode)); p->data=e; p->next=S->top; //Assign the current stack top element to the direct successor of the new node, as shown in Figure 1 above S->top=s; //Assign the new node p to the stack top pointer, as shown in Figure 2 above S->count++; return OK; }
Chain storage structure of stack -- out of stack operation
Stack out operation of chain stack: use variable p to store the top node to be deleted, move the top pointer down one bit, and finally release P.
// If the stack is not empty, delete the top element of the stack, return its value with e, and return OK; otherwise, return ERROR Status Pop(LinkStack *S,SElemType *e) { LinkStackPtr p; if(StackEmpty(*S)) return ERROR; *e=S->top->data; p=S->top; //Assign the stack top pointer to p, as shown in Figure 3 above S->top=S->top->next; //Move the stack top pointer back one bit and point to the next node, as shown in Figure 4 above free(p); S->count--; return OK; }
The push and pop operations of the chain stack are very simple, without any cyclic operation, and the time complexity is O(1).
Compare the sequence stack and chain stack. They have the same time complexity, both of which are 0 (1). For spatial performance, the sequential stack needs to be determined in advance - a fixed length may waste memory space, but its advantage is that it is easy to locate during access, 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. Therefore, the difference between them is the same as that discussed in the linear table. If the element change during the use of the stack is unpredictable, sometimes small and sometimes very large, it is better to use the chain stack. On the contrary, if its change is within the controllable range, it is recommended to use the sequential stack.
6. Function of stack
The introduction of stack simplifies the problem of programming, divides different levels of attention, reduces the scope of thinking, and focuses more on the core of the problem we want to solve. For example, the array used in the linear table sequential storage structure, because it disperses energy to consider the details such as the increase or decrease of the subscript of the array, it masks the essence of the problem.
So now many high-level languages, such as Java, C# and so on, encapsulate the Stack structure. You can directly use the push and pop methods of Stack without paying attention to its implementation details, which is very convenient.
Stack application -- recursion
Let's start with a classic recursive example: Fibonacci sequence:
Fei Lao said that if rabbits have the ability to reproduce two months after birth, a pair of rabbits can give birth to a pair of little rabbits every month. Assuming that all rabbits do not die, how many pairs of rabbits can be bred in a year?
Let's take a pair of newly born rabbits for analysis: in the first month, the rabbits had no reproductive ability, so they were still a pair; Two months later, two pairs of rabbits were born; Three months later, the old rabbit gave birth to another pair. Because the little rabbit has no reproductive ability, there are three pairs in total... And so on. You can list the following table:
It can be seen that in the sequence of rabbit logarithms, the sum of the first two terms is equal to the next term.
Defined by mathematical function:
//Fibonacci's recursive function int Fbi(int i) { if(i<2) return i==0?0:1; return Fbi(i-1)+Fbi(i-2); } //Print out the first 20 Fibonacci numbers int main() { int i; printf("Recursive display of Fibonacci sequence:\n"); for(i=0;i<20;i++) printf("%d",Fbi(i)); return 0; }
//Iterative implementation prints out the first 20 items of Fibonacci sequence int main() { int i; int a[40]; printf("The iteration shows the Fibonacci sequence:\n"); a[0]=0; a[1]=1; printf("%d ",a[0]); printf("%d ",a[1]); for(i = 2;i < 40;i++) { a[i] = a[i-1] + a[i-2]; printf("%d ",a[i]); } }
Definition of recursion
In high-level languages, calling yourself or other functions is not essential. We call a function that directly calls itself or indirectly calls itself through a series of statements as a recursive function.
Of course, the biggest fear of writing recursive programs is to fall into endless recursion. Therefore, each recursive definition must have at least one condition. When it is satisfied, the recursion will no longer proceed, that is, it will no longer refer to itself, but return the value to exit.
For example, in the example just now, there is always a recursion that will make I < 2, so that the statement of rturn i can be executed without further recursion.
Two codes to implement Fibonacci are compared. The difference between iteration and recursion is that iteration uses loop structure and recursion uses selection structure. Recursion can make the structure of the program clearer, more concise and easier to understand, so as to reduce the time to read the code. However, a large number of recursive calls will create a copy of the function, which will consume a lot of time and memory. Iteration does not require repeated function calls and additional memory. Therefore, we should choose different code implementation methods according to different situations.
7. Application of stack -- evaluation of four arithmetic expressions
8. Definition of queue
A queue is a linear table that only allows insertion at one end and deletion at the other end.
Queue is a first in first out linear table, called FIFO for short. The end that allows insertion is called the end of the queue, and the end that allows deletion is called the opposite end.
Abstract data type of queue
ADT queue(Queue) Data Same linear table. Elements have the same type,Adjacent elements have precursor and successor relationships. Operation InitQueue (*Q) :Initialization operation,establish- -Empty queues Q. DestroyQueue (*Q) :If queue Q existence,Destroy it. ClearQueue (*Q) :Queue Q Empty. QueueEmpty (Q) :If queue Q Empty,return true,Otherwise return false. GetHead (Q, *e) :If queue Q Exists and is not empty,use e Return queue Q Team head element. EnQueue (*Q, e) :If queue Q existence,Insert new element e To queue e And become the tail element. DeQueue (*Q,*e) :Delete queue Q Squadron head element,Combined use e Returns its value. QueueLength (Q) :Return queue e Number of elements. endADT
#include "stdio.h" #include "stdlib.h" #include "math.h" #include "time.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 20 / * initial allocation of storage space*/ typedef int Status; typedef int QElemType; /* QElemType The type depends on the actual situation. It is assumed to be int */ /* Sequential storage structure of circular queue */ typedef struct { QElemType data[MAXSIZE]; int front; /* Head pointer */ int rear; /* Tail pointer, if the queue is not empty, points to the next position of the tail element of the queue */ }SqQueue; /* Initialize an empty queue Q */ Status InitQueue(SqQueue *Q) { Q->front=0; Q->rear=0; return OK; } /* Clear Q to empty queue */ Status ClearQueue(SqQueue *Q) { Q->front=Q->rear=0; return OK; } /* If queue Q is an empty queue, it returns TRUE; otherwise, it returns FALSE */ Status QueueEmpty(SqQueue Q) { if(Q.front==Q.rear) /* Flag for empty queue */ return TRUE; else return FALSE; } /* Returns the number of elements of Q, that is, the current length of the queue */ int QueueLength(SqQueue Q) { return (Q.rear-Q.front+MAXSIZE)%MAXSIZE; } /* If the queue is not empty, use e to return the queue header element of Q and return OK; otherwise, return ERROR */ Status GetHead(SqQueue Q,QElemType *e) { if(Q.front==Q.rear) /* queue is empty */ return ERROR; *e=Q.data[Q.front]; return OK; } /* If the queue is not full, insert a new tail element with element e as Q */ Status EnQueue(SqQueue *Q,QElemType e) { if ((Q->rear+1)%MAXSIZE == Q->front) /* Queue full judgment */ return ERROR; Q->data[Q->rear]=e; /* Assign the element e to the end of the queue */ Q->rear=(Q->rear+1)%MAXSIZE;/* rear Move the pointer back one position, */ /* If to the end, go to the array header */ return OK; } /* If the queue is not empty, delete the queue header element in Q and return its value with e */ Status DeQueue(SqQueue *Q,QElemType *e) { if (Q->front == Q->rear) /* Judgment of empty queue */ return ERROR; *e=Q->data[Q->front]; /* Assign the team head element to e */ Q->front=(Q->front+1)%MAXSIZE; /* front Move the pointer back one position, */ /* If to the end, go to the array header */ return OK; } Status visit(QElemType c) { printf("%d ",c); return OK; } /* Each element in queue Q is output from the head of the queue to the tail of the queue */ Status QueueTraverse(SqQueue Q) { int i; i=Q.front; while((i+Q.front)!=Q.rear) { visit(Q.data[i]); i=(i+1)%MAXSIZE; } printf("\n"); return OK; }
/* Chain storage structure of queue */ #include "stdio.h" #include "stdlib.h" #include "math.h" #include "time.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 20 / * initial allocation of storage space*/ typedef int Status; typedef int QElemType; /* QElemType The type depends on the actual situation. It is assumed to be int */ typedef struct QNode /* Node structure */ { QElemType data; struct QNode *next; }QNode,*QueuePtr; typedef struct /* Linked list structure of queue */ { QueuePtr front,rear; /* Head and tail pointer */ }LinkQueue; /* Construct an empty queue Q */ Status InitQueue(LinkQueue *Q) { Q->front=Q->rear=(QueuePtr)malloc(sizeof(QNode)); if(!Q->front) exit(OVERFLOW); Q->front->next=NULL; return OK; } /* Destroy queue Q */ Status DestroyQueue(LinkQueue *Q) { while(Q->front) { Q->rear=Q->front->next; free(Q->front); Q->front=Q->rear; } return OK; } /* Clear Q to empty queue */ Status ClearQueue(LinkQueue *Q) { QueuePtr p,q; Q->rear=Q->front; p=Q->front->next; Q->front->next=NULL; while(p) { q=p; p=p->next; free(q); } return OK; } /* If Q is an empty queue, it returns TRUE; otherwise, it returns FALSE */ Status QueueEmpty(LinkQueue Q) { if(Q.front==Q.rear) return TRUE; else return FALSE; } /* Find the length of the queue */ int QueueLength(LinkQueue Q) { int i=0; QueuePtr p; p=Q.front; while(Q.rear!=p) { i++; p=p->next; } return i; } /* If the queue is not empty, use e to return the queue header element of Q and return OK; otherwise, return ERROR */ Status GetHead(LinkQueue Q,QElemType *e) { QueuePtr p; if(Q.front==Q.rear) return ERROR; p=Q.front->next; *e=p->data; return OK; } /* Insert a new tail element with element e as Q */ Status EnQueue(LinkQueue *Q,QElemType e) { QueuePtr s=(QueuePtr)malloc(sizeof(QNode)); if(!s) /* Storage allocation failed */ exit(OVERFLOW); s->data=e; s->next=NULL; Q->rear->next=s; /* Assign the new node s with element e to the successor of the original team tail node, as shown in figure ① */ Q->rear=s; /* Set the current s as the end of the queue node, and the rear points to s, as shown in figure ② */ return OK; } /* If the queue is not empty, delete the queue header element of Q, return its value with e, and return OK; otherwise, return ERROR */ Status DeQueue(LinkQueue *Q,QElemType *e) { QueuePtr p; if(Q->front==Q->rear) return ERROR; p=Q->front->next; /* Temporarily save the queue head node to be deleted to p, as shown in figure ① */ *e=p->data; /* Assign the value of the queue head node to be deleted to e */ Q->front->next=p->next;/* Assign the successor p - > next of the original team's head node to the successor of the head node, as shown in figure ② */ if(Q->rear==p) /* If the head of the queue is the tail of the queue, after deletion, point the rear to the head node, as shown in figure ③ */ Q->rear=Q->front; free(p); return OK; } Status visit(QElemType c) { printf("%d ",c); return OK; } /* Each element in queue Q is output from the head of the queue to the tail of the queue */ Status QueueTraverse(LinkQueue Q) { QueuePtr p; p=Q.front->next; while(p) { visit(p->data); p=p->next; } printf("\n"); return OK; }
Circular queue
Insufficient queue sequential storage:
We assume that a queue has n elements, then the sequentially stored queue needs to establish an array greater than N, and store all the elements of the queue in the first n units of the array. The end with an array subscript of 0 is the queue head. The so-called queue entry operation is actually adding an element at the end of the queue without moving any elements. Therefore, the time complexity is 0 (1), as shown in the following figure.
Different from the stack, the queue elements are dequeued at the queue head, that is, the subscript is 0, which means that. All elements in the column have to move forward, and the area ensures that the head of the reverse column, that is, the position of the subscript River, is not empty. At this time, the time complexity is 0(n), as shown in the figure below.
But sometimes I think, why do you have to move all the way out of the queue? If we do not restrict the condition that the elements of the queue must be stored in the first n units of the array, the out of queue performance will be greatly increased. In other words, the team head does not need to be in the position with subscript 0.
In order to avoid that when there is only one element, the queue head and tail coincide, which makes the processing troublesome, two pointers are introduced. The front pointer points to the queue head element and the rear pointer points to the next position of the queue tail element. In this way, when the front is equal to Frear, the queue is not an empty queue.
Suppose it is an array with length of 5. The initial state and empty queue are shown in the following figure on the left. The front and rear pointers point to the position with subscript 0. Then join the queue a, a, a, a, font pointer still points to the position with subscript 0, while the rear pointer points to the position with subscript 4, as shown in the following figure on the right.
When leaving the queue a1 and a2, the front pointer points to the position with subscript 2, and the rear remains unchanged, as shown in the figure below on the left. When entering the queue a5 again, the front pointer remains unchanged, and the rear pointer moves outside the array. Huh? Outside the array, where will that be? As shown in the following figure on the right.
The problem goes beyond that. Assuming that the total number of this queue does not exceed 5, but at present, if we continue to join the queue, because the element at the end of the array has been occupied, and then add it back, there will be an error that the array is out of bounds. In fact, our queue is still idle where the subscript is and. We call this phenomenon "false overflow".
Definition of circular queue
The solution to false overflow is to start from the beginning when the back is full, that is, a loop connected from beginning to end. We call this sequential storage structure of queue head to tail as circular queue.
Continue with the example just now. The rear in the figure above can be changed to point to the position with subscript 0, so that the pointer will not be unclear, as shown in the figure below.
Then join the team a6, place it at the subscript 0, and the rear pointer points to the subscript 1, as shown in the figure below on the left. If you join the queue a again, the rear pointer coincides with the front pointer and points to the position with subscript 2, as shown in the following figure on the right.
- At this time, the problem arises again. We just said that when the queue is empty, front equals rear. Now when the queue is full, front equals rear. So how to judge whether the queue is empty or full?
- The first method is to set a flag variable flag. When front =rear and flag = 0, the queue is empty, and when front =rear and fiag= 1, the queue is full.
- ■ the second method is that when the queue is empty, the condition is front = rear. When the queue is full, we modify the condition to reserve an element space. That is, when the queue is full, there is another free cell in the array. For example, as shown in the figure on the left, we think the queue is full, that is, we don't allow the situation in the figure on the right.
Let's focus on the second method. Since Frear may be larger than front or smaller than font, although they are full when they differ by only one position, they may also differ by a whole circle. Therefore, if the maximum size of the queue is QueueSize, the condition that the queue is full is (rear + 1)% QueueSize = = front (the purpose of taking the module "%" is to integrate the size of rear and front). For example, in the above example, QueueSize=5, front=O, and rear=4, (4+1)%5= σ, So the queue is full at this time. For another example, in the upper right figure, front = 2 and rear =1. (1 + 1)% 5 = 2, so the queue is also full at this time. For the following figure, front = 2 and rear= 0, (0+1%5 =1, 1 ≠ 2), so the queue is not full at this time.
In addition, when rear > fon, that is, figure 1 and Figure 2 in the following figure, the length of the queue is rear front
When rear < font, t, as shown in Figure 3 above and figure 3 below, the queue length is divided into two segments, one is
QueueSire - font, and the other segment is 0+rer. When added together, the queue length is
rear - front + QueueSizeo.
Therefore, the general formula for calculating the queue length is:
(rear- front + QueueSize)%QueueSize
9. Chain storage structure of queue and its implementation
The chain storage structure of queue is actually a single chain list of linear table, but it can only go in at the beginning and out at the end. We call it chain queue for short.
For the convenience of operation, we point the queue head pointer to the head node of the chain queue and the queue tail pointer to the terminal node, as shown in the following figure:
Empty queue means that both front and rear point to the head node, as shown in the following figure:
The structure of the chain queue is:
typedef int QElemType; //The type of QElemType depends on the actual situation. Here, it is assumed to be int typedef struct QNode //Node structure { QElemType data; struct QNode *Next; }QNode,*QueuePtr; typedef struct //Linked list structure of queue { QueuePtr front, rear; //Head of team, tail of team pointer }LinkQueue;
Chained storage structure of queue -- queue operation
When joining a queue, you actually insert a node at the end of the linked list, as shown in the following figure:
Its code is as follows:
//Insert a new tail element with element e as Q Status EnQueue(LinkQueue *Q,QElemType e) { QueuePtr s=(QueuePtr)malloc(sizeof(QNode)); if(!s) //Storage allocation failed exit(OVERFLOW); s->data = e; s->next = NULL; Q->rear->next = s; //Assign the new node s with element e to the successor of the original team tail node, as shown in Figure 1 above Q->rear = s; //Set the current s as the tail node, and the rear point to s, as shown in Figure 2 above return OK; }
Chained storage structure of queue - out of queue operation
During the out of queue operation, the successor node of the head node is out of the queue. Change the successor node of the head node to the node behind it. If there is only - one element left in the linked list except the head node, it is necessary to point the rear to the head node, as shown in the figure below.
The code is as follows:
/*If the queue is not empty, delete the queue header element of Q, return its value with e, and return 0K; otherwise, return ERROR */ Status DeQueue(LinkQueue *Q, QELemType *e) { QueuePtr p; if(Q->front==Q->rear) return ERROR; p=Q->f ront- ->next ;/*Temporarily save the queue head node to be deleted to p, as shown in figure ①*/ *e=p->data;/* .Assign the value of the queue head node to be deleted to e */ Q->front- >next=p->next; /*The successor of the original team head node p->next if(Q->rear==p) Q- >rear=Q->front; free(p); return 0K; }
The comparison between circular queue and chain queue can be considered from two aspects. In terms of time, their basic operations are constant time, that is, they are all 0 () However, the circular queue applies for space in advance and does not release during use. For the chain queue, there will be some time overhead for each application and release node. If the queue is in and out frequently, there are still slight differences between the two. For space, the circular queue must have a fixed length, so there is a problem of the number of storage elements and waste of space. The chain queue does not have this problem. Although it needs a pointer field and will incur some space overhead, it is also acceptable. Therefore, the chain queue is more flexible in space.
In general, when the maximum queue length can be determined, it is recommended to use circular queue. If you can't estimate the queue length, use chain queue.
Summary review
- A stack is a linear table that is restricted to insert and delete operations only at the end of the table.
- A queue is a linear table that only allows insertion at one end and deletion at the other.
They can all be realized by the sequential storage structure of linear table, but they all have some disadvantages of sequential storage, so they have their own skills to solve this problem.
For the stack, if it is two stacks with the same data type, the two ends of the array can be used as the bottom of the stack to make the two stacks share data, which can maximize the space of the array.
Columns. Thus, each queue head and queue location can be changed cyclically in the array. If the time loss of mobile data is solved, the original insertion column will be obtained. If the time loss of mobile data is solved, the time complexity of original insertion and deletion will become 0 (1).
They can also be realized through the chain storage structure, which is basically the same as the linear table in principle, as shown in the following figure: