title: 数据结构与算法(五)栈(Stack)和队列(Queue)date: 2021-06-26 00:13:25.625 updated: 2021-06-26 14:58:31.424 url: /?p=262 categories: 数据结构与算法 tags: 数据结构与算法
栈(Stack)和队列(Queue) 栈和队列,严格意义上来说,也属于线性表,因为它们也都用于存储逻辑关系为 “一对一” 的数据。
既然栈和队列都属于线性表,根据线性表分为顺序表和链表的特点:
栈也可分为顺序栈和链表
队列也分为顺序队列和链队列
栈和队列的特点:
栈:先进后出,FILO。
队列:先进先出,FIFO。
栈 栈是一种只能从表的一端存取数据且遵循 “先进后出” 原则的线性存储结构。
栈的开口端被称为栈顶 ;封口端被称为栈底 。
向栈添加元素,叫做入栈 、进栈 或者压栈 ,英文常用 push
。
从栈中提取出指定元素,叫做出栈 或者弹栈 ;
栈是一种 “特殊” 的线性存储结构,因此栈的具体实现有以下两种方式:
顺序栈:采用顺序存储结构可以模拟栈存储数据的特点,从而实现栈存储结构;
链栈:采用链式存储结构实现栈结构;
顺序栈 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 #include <stdio.h> #include <stdlib.h> typedef struct Stack { int *data; int top; int bottom; int capacity; } stack ; stack *initStack (int num) ;void push (stack *myStack, int elem) ;int pop (stack *myStack) ;void display (stack *myStack) ;int main () { printf ("输入需要创建栈的大小:" ); int num = 0 ; scanf ("%d" , &num); stack *myStack = initStack(num); for (size_t i = 0 ; i < num; i++) { push(myStack, i * 10 ); } display(myStack); pop(myStack); pop(myStack); pop(myStack); pop(myStack); display(myStack); return -1 ; } stack *initStack (int num) { stack *myStack = (stack *)malloc (sizeof (stack )); myStack->bottom = -1 ; myStack->top = -1 ; myStack->capacity = num; myStack->data = (int *)malloc (sizeof (int ) * myStack->capacity); return myStack; } void push (stack *myStack, int elem) { if (myStack->top != myStack->capacity) { myStack->top++; (myStack->data)[myStack->top] = elem; } else { printf ("栈已满!!" ); } } int pop (stack *myStack) { if (myStack->bottom == myStack->top) { printf ("这是一个空栈" ); return -1 ; } int i = (myStack->data)[myStack->top]; printf ("弹栈的元素是:%d\n" , i); myStack->top--; return i; } void display (stack *myStack) { if (myStack->bottom == myStack->top) { printf ("这是一个空栈" ); return ; } printf ("输出栈的元素:" ); for (size_t i = 0 ; i <= myStack->top; i++) { printf ("%d " , (myStack->data)[i]); } printf ("\n" ); }
1 2 3 4 5 6 7 输入需要创建栈的大小:10 输出栈的元素:0 10 20 30 40 50 60 70 80 90 弹栈的元素是:90 弹栈的元素是:80 弹栈的元素是:70 弹栈的元素是:60 输出栈的元素:0 10 20 30 40 50
链栈 链栈,即用链表实现栈存储结构。
链栈的实现思路同顺序栈类似,顺序栈是将数顺序表(数组)的一端作为栈底,另一端为栈顶;链栈也如此,通常我们将链表的头部作为栈顶,尾部作为栈底。
将链表头部作为栈顶的一端,可以避免在实现数据 “入栈” 和 “出栈” 操作时做大量遍历链表的耗时操作。
链表的头部作为栈顶,意味着:
在实现数据”入栈”操作时,需要将数据从链表的头部插入;
在实现数据”出栈”操作时,需要删除链表头部的首元节点;
链栈实际上就是一个只能采用头插法插入或删除数据的链表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #include <stdio.h> #include <stdlib.h> typedef struct Node { int data; struct Node *next ; } node; typedef struct Stack { node *link; int top; } stack ; stack *initStack () ;void push (stack *myStack, int elem) ;int pop (stack *myStack) ;void diaplay (stack *myStack) ;int main () { stack *myStack = initStack(); for (size_t i = 0 ; i < 10 ; i++) { push(myStack, i * 10 ); } diaplay(myStack); pop(myStack); pop(myStack); pop(myStack); pop(myStack); pop(myStack); diaplay(myStack); pop(myStack); pop(myStack); pop(myStack); pop(myStack); pop(myStack); pop(myStack); diaplay(myStack); return 0 ; } stack *initStack () { stack *myStack = (stack *)malloc (sizeof (stack )); node *stackNode = (node *)malloc (sizeof (node)); stackNode->data = 0 ; stackNode->next = NULL ; myStack->link = stackNode; myStack->top = -1 ; return myStack; } void push (stack *myStack, int elem) { myStack->top++; node *stackNode = (node *)malloc (sizeof (node)); stackNode->data = elem; stackNode->next = myStack->link->next; myStack->link->next = stackNode; } int pop (stack *myStack) { if (myStack->link->next == NULL ) { printf ("空栈\n" ); return -1 ; } int i = myStack->link->data; myStack->link->next = myStack->link->next->next; myStack->top--; return i; } void diaplay (stack *myStack) { printf ("打印链栈元素:" ); if (myStack->link->next == NULL ) { printf ("空栈\n" ); return ; } node *temp = myStack->link; while (temp->next != NULL ) { temp = temp->next; printf ("%d " , temp->data); } printf ("\n" ); }
队列 队列的两端都”开口”,要求数据只能从一端进,从另一端出。
通常,称进数据的一端为 “队尾”,出数据的一端为 “队头”,数据元素进队列的过程称为 “入队”,出队列的过程称为 “出队”。
队列中数据的进出要遵循 “先进先出” 的原则。
队列存储结构的实现有以下两种方式:
顺序队列:在顺序表的基础上实现的队列结构;
链队列:在链表的基础上实现的队列结构;
顺序队列 顺序队列,即采用顺序表模拟实现的队列结构。
顺序队列简单实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #include <stdio.h> #include <stdlib.h> typedef struct { int *data; int rear; int front; int capacity; } queue ; queue *initQueue (int num) ;void initData (queue *q, int num) ;void displayQueue (queue *q) ;void enQueue (queue *q, int elem) ;int deQueue (queue *q) ;int main () { queue *myQueue = initQueue(10 ); initData(myQueue, 5 ); displayQueue(myQueue); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); displayQueue(myQueue); printf ("出队元素:%d\n" , deQueue(myQueue)); printf ("出队元素:%d\n" , deQueue(myQueue)); printf ("出队元素:%d\n" , deQueue(myQueue)); displayQueue(myQueue); return 0 ; } queue *initQueue (int num) { queue *q = (queue *)malloc (sizeof (queue )); q->capacity = num; q->data = (int *)malloc (sizeof (int ) * q->capacity); q->front = 0 ; q->rear = 0 ; return q; } void initData (queue *q, int num) { if (num > q->capacity) { printf ("注意,输入的参数不能大于队列的最大容量!" ); } for (size_t i = 0 ; i < num; i++) { *((q->data) + q->rear) = i * 10 ; q->rear++; } } void enQueue (queue *q, int elem) { printf ("入队元素:%d\n" , elem); if (q->rear - q->front == q->capacity) { printf ("栈已满" ); } *(q->data + q->rear) = elem; q->rear++; } int deQueue (queue *q) { if (q->front >= q->rear) { return -1 ; printf ("栈空" ); } int i = *(q->data + q->front); q->front++; printf ("" ); return i; } void displayQueue (queue *q) { printf ("输出队列:" ); int *data = q->data; int top = q->front; int rear = q->rear; while (top != rear) { printf ("%d " , *(data + top)); top++; } printf ("\n" ); }
输出队列:0 10 20 30 40
入队元素:1000
入队元素:1000
入队元素:1000
入队元素:1000
入队元素:1000
输出队列:0 10 20 30 40 1000 1000 1000 1000 1000
出队元素:0
出队元素:10
出队元素:20
输出队列:30 40 1000 1000 1000 1000 1000
以上顺序队列整体后移造成的影响是:
顺序队列之前的数组存储空间将无法再被使用,造成了空间浪费;
如果顺序表申请的空间不足够大,则直接造成程序中数组 a 溢出,产生溢出错误;
环状顺序队列 上面的顺序队列缺点非常致命,我们可以试着在它的基础上对其改良。
了解决以上两个问题,可以使用巧妙的方法将顺序表打造成一个环状表。
上图只是一个想象图,在真正的实现时,没必要真创建这样一种结构,我们还是使用之前的顺序表,也还是使用之前的程序,只需要对其进行一点小小的改变:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 #include <stdio.h> #include <stdlib.h> typedef struct { int *data; int rear; int front; int capacity; } queue ; queue *initQueue (int num) ;void initData (queue *q, int num) ;void displayQueue (queue *q) ;void enQueue (queue *q, int elem) ;int deQueue (queue *q) ;int main () { queue *myQueue = initQueue(10 ); initData(myQueue, 5 ); displayQueue(myQueue); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); enQueue(myQueue, 1000 ); displayQueue(myQueue); printf ("出队元素:%d\n" , deQueue(myQueue)); printf ("出队元素:%d\n" , deQueue(myQueue)); printf ("出队元素:%d\n" , deQueue(myQueue)); printf ("出队元素:%d\n" , deQueue(myQueue)); printf ("出队元素:%d\n" , deQueue(myQueue)); printf ("出队元素:%d\n" , deQueue(myQueue)); displayQueue(myQueue); return 0 ; } queue *initQueue (int num) { queue *q = (queue *)malloc (sizeof (queue )); q->capacity = num; q->data = (int *)malloc (sizeof (int ) * q->capacity); q->front = 0 ; q->rear = 0 ; return q; } void initData (queue *q, int num) { if (num > q->capacity) { printf ("注意,输入的参数不能大于队列的最大容量!" ); } for (size_t i = 0 ; i < num; i++) { *((q->data) + q->rear) = i * 10 ; q->rear++; } } void enQueue (queue *q, int elem) { if ((q->rear + 1 ) % q->capacity == q->front) { printf ("栈已满\n" ); return ; } printf ("入队元素:%d\n" , elem); *(q->data + (q->rear % q->capacity)) = elem; q->rear++; } int deQueue (queue *q) { if (q->front == q->rear % q->capacity) { return -1 ; } int i = *(q->data + q->front); q->front = (q->front + 1 ) % q->capacity; return i; } void displayQueue (queue *q) { printf ("输出队列:" ); int *data = q->data; int top = q->front; int rear = q->rear; while (top != rear) { printf ("%d " , *(data + top)); top++; } printf ("\n" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 输出队列:0 10 20 30 40 入队元素:1000 入队元素:1000 入队元素:1000 入队元素:1000 栈已满 栈已满 栈已满 栈已满 栈已满 输出队列:0 10 20 30 40 1000 1000 1000 1000 出队元素:0 出队元素:10 出队元素:20 出队元素:30 出队元素:40 出队元素:1000 输出队列:1000 1000 1000
使用此方法需要注意的是,顺序队列在判断数组是否已满时,出现下面情况:
当队列为空时,队列的头指针等于队列的尾指针;
当数组满员时,队列的头指针等于队列的尾指针;
链式队列 链式队列,简称”链队列”,即使用链表实现的队列存储结构。