目录
[一. 栈](#一. 栈)
[1. 栈的概念](#1. 栈的概念)
[2. 栈的实现](#2. 栈的实现)
[2.1 栈的结构](#2.1 栈的结构)
[2.2 初始化栈](#2.2 初始化栈)
[2.3 入栈](#2.3 入栈)
[2.4 出栈](#2.4 出栈)
[2.5 获取栈顶元素](#2.5 获取栈顶元素)
[2.6 获取栈中有效个数](#2.6 获取栈中有效个数)
[2.7 判断栈是否为空](#2.7 判断栈是否为空)
[2.8 销毁栈](#2.8 销毁栈)
[二. 队列](#二. 队列)
[1. 队列的概念](#1. 队列的概念)
[2. 队列的实现](#2. 队列的实现)
[2.1 队列的结构](#2.1 队列的结构)
[2.2 队列初始化](#2.2 队列初始化)
[2.3 销毁队列](#2.3 销毁队列)
[2.4 入队列(队尾)](#2.4 入队列(队尾))
[2.5 出队列(队头)](#2.5 出队列(队头))
[2.6 获取队头数据](#2.6 获取队头数据)
[2.7 获取队尾数据](#2.7 获取队尾数据)
[2.8 获取队列节点个数](#2.8 获取队列节点个数)
[2.9 判断队列是否为空](#2.9 判断队列是否为空)
[3. 循环队列](#3. 循环队列)
[4. 编程题 设计循环队列](#4. 编程题 设计循环队列)
[三. 概念题](#三. 概念题)
一. 栈
1. 栈的概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。
进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
2. 栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些,因为数组在尾上插入数据的代价比较小。
2.1 栈的结构
- top表示栈顶数据的下一个位置。
cpptypedef int StackDataType; typedef struct Stack { StackDataType* arr; //用数组作为存放栈数据的结构 int top; //栈顶数据的下一个位置 int capacity; //容量 }Stack;
2.2 初始化栈
- 将结构数据初始化。
cppvoid StackInit(Stack* ps) { assert(ps); ps->arr = NULL; ps->top = ps->capacity = 0; }
2.3 入栈
判断扩容。
数据插入栈顶。
cppvoid StackPush(Stack* ps, StackDataType data) { assert(ps); //判断扩容 if (ps->top == ps->capacity) { int size = ps->capacity == 0 ? 4 : ps->capacity * 2; StackDataType* tmp = (StackDataType*)realloc(ps->arr, sizeof(StackDataType)*size); if (tmp == NULL) { perror("realloc"); return; } ps->arr = tmp; ps->capacity = size; } //入栈 ps->arr[ps->top++] = data; }
2.4 出栈
- 栈不为空才能出栈。
cppvoid StackPop(Stack* ps) { assert(ps); assert(!StackEmpty(ps)); ps->top--; }
2.5 获取栈顶元素
- 判断空栈
cppStackDataType StackTop(Stack* ps) { assert(ps); assert(!StackEmpty(ps)); return ps->arr[ps->top - 1]; }
2.6 获取栈中有效个数
cppint StackSize(Stack* ps) { assert(ps); return ps->top; }
2.7 判断栈是否为空
- 栈空返回真。
cppbool StackEmpty(Stack* ps) { assert(ps); return !ps->top; }
2.8 销毁栈
- 释放空间,初始数据。
cppvoid StackDestroy(Stack* ps) { assert(ps); free(ps->arr); ps->arr = NULL; ps->top = ps->capacity = 0; }
完整代码:Stack/Stack · 林宇恒/DataStructure - 码云 - 开源中国 (gitee.com)
二. 队列
1. 队列的概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。
队列具有先进先出FIFO(First In First Out)的特点。
入队列:进行插入操作,这一端称为队尾。
出队列:进行删除操作,这一端称为队头。
2. 队列的实现
队列也可以使用数组或链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
2.1 队列的结构
需要两个结构体,一个表示节点,一个表示队列整体。
节点的结构包含存放的数据和指向下一个节点的指针。
表示队列的结构包含队头指针和队尾指针还有节点的数量。
cpptypedef int QueueDataType; typedef struct QueueNode { QueueDataType data; //数据 struct QueueNode* next; //下一个节点地址 }QueueNode; typedef struct Queue { QueueNode* prev; //队头指针 QueueNode* tail; //队尾指针 size_t size; //节点个数 }Queue;
2.2 队列初始化
传入的队列指针不能为空。
将队列结构体中队头队尾指针置空,节点数量置0。
cppvoid QueueInit(Queue* q) { assert(q); q->prev = q->tail = NULL; q->size = 0; }
2.3 销毁队列
传入的队列指针不能为空。
从队头指针开始遍历释放节点直到空停下,并将队头队尾指针置空,节点数量置0。
cppvoid QueueDestroy(Queue* q) { assert(q); while (q->prev) { QueueNode* next = q->prev->next; free(q->prev); q->prev = next; } q->tail = NULL; q->size = 0; }
2.4 入队列(队尾)
传入的队列指针不能为空。
创建要入队列的节点
当队列为空时,将队头队尾指针都指向新节点,节点数量加一。
当队列不为空时,将新节点和最后一个节点连接,然后队尾指针指向新节点,节点数量加一。
cppvoid QueuePush(Queue* q, QueueDataType data) { //1. 传入的队列指针不能为空。 assert(q); //2. 创建要入队列的节点 QueueNode* new = (QueueNode*)malloc(sizeof(QueueNode)); if (new == NULL) { perror("malloc"); return; } new->data = data; new->next = NULL; //3. 当队列为空时,然后将队头队尾指针都指向新节点,节点数量加一。 if (q->size == 0) { q->prev = q->tail = new; q->size++; } //4. 当队列不为空时,将新节点和最后一个节点连接,然后队尾指针指向新节点,节点数量加一。 else { q->tail->next = new; q->tail = new; q->size++; } }
2.5 出队列(队头)
传入的队列指针不能为空。
队列不能为空。
释放第一个节点,队头指针指向第二个节点,节点数量减一。
当队列删完之后没有节点了,这种情况需要特殊处理,防止队尾指针变成野指针。
cppvoid QueuePop(Queue* q) { //1. 传入的队列指针不能为空。 assert(q); //2. 队列不能为空。 assert(q->size); //3. 释放第一个节点,队头指针指向第二个节点,节点数量减一。 QueueNode* next = q->prev->next; free(q->prev); q->prev = next; q->size--; //4. 当队列删完之后没有节点了,这种情况需要特殊处理,防止队尾指针变成野指针。 if (q->size == 0) q->tail = NULL; }
2.6 获取队头数据
队列指针不能为空。
队列不能为空。
返回队头节点存放的数据。
cppQueueDataType QueueFront(Queue* q) { //1. 队列指针不能为空。 assert(q); //2. 队列不能为空。 assert(q->size); //3. 返回队头节点存放的数据。 return q->prev->data; }
2.7 获取队尾数据
队列指针不能为空。
队列不能为空。
返回队尾节点存放的数据。
cppQueueDataType QueueBack(Queue* q) { //1. 队列指针不能为空。 assert(q); //2. 队列不能为空。 assert(q->size); //3. 返回队尾节点存放的数据。 return q->tail->data; }
2.8 获取队列节点个数
队列指针不能为空。
返回节点个数。
cppint QueueSize(Queue* q) { //1. 队列指针不能为空。 assert(q); //2. 返回节点个数。 return q->size; }
2.9 判断队列是否为空
队列指针不能为空。
根据节点数量判断。空返回真。
cppbool QueueEmpty(Queue* q) { //1. 队列指针不能为空。 assert(q); //2. 根据节点数量判断。空返回真。 return !q->size; }
完整代码: Queue/Queue · 林宇恒/DataStructure - 码云 - 开源中国 (gitee.com)
3. 循环队列
rear的位置插入数据,然后rear++。
pop一次就是front往前移一位。
front == rear 表示空。
rear+1 == front 表示满,记得取模。
这里使用数组实现,因为单链表不方便获取队尾数据。
4. 编程题 设计循环队列
cpptypedef struct { int* arr; //指向队列 int front; //队头下标 int rear; //队尾下标 int k; } MyCircularQueue; MyCircularQueue* myCircularQueueCreate(int k) { MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue)); obj->arr = (int*)malloc(sizeof(int)*(k+1)); obj->front = obj->rear = 0; obj->k = k; return obj; } bool myCircularQueueIsEmpty(MyCircularQueue* obj) { //队头下标等于队尾下标时为空。 return obj->front == obj->rear; } bool myCircularQueueIsFull(MyCircularQueue* obj) { //队尾下标加一等于队头下标时为满,超出数组范围需要取模。 return (obj->rear+1) % (obj->k+1) == obj->front; } bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) { //满了不能插入。 if(myCircularQueueIsFull(obj)) return false; else { //在队尾放数据,然后队尾加一,记得取模。 obj->arr[obj->rear] = value; obj->rear = (obj->rear+1) % (obj->k+1); return true; } } bool myCircularQueueDeQueue(MyCircularQueue* obj) { //空的不能删。 if(myCircularQueueIsEmpty(obj)) return false; else { //删除数据队头下标直接加一即可,记得取模。 obj->front = (obj->front+1) % (obj->k+1); return true; } } int myCircularQueueFront(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) return -1; return obj->arr[obj->front]; } int myCircularQueueRear(MyCircularQueue* obj) { if(myCircularQueueIsEmpty(obj)) return -1; //当rear等于0时需要特殊处理 return obj->arr[(obj->rear-1+obj->k+1)%(obj->k+1)]; } void myCircularQueueFree(MyCircularQueue* obj) { free(obj->arr); free(obj); }
三. 概念题
一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。
A 12345ABCDE
B EDCBA54321
C ABCDE12345
D 54321EDCBA
答:B
若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()A 1,4,3,2
B 2,3,4,1
C 3,1,4,2
D 3,4,2,1
答:C
循环队列的存储空间为 Q(1:100) ,初始状态为 front=rear=100 。经过一系列正常的入队与退队操作后, front=rear=99 ,则循环队列中的元素个数为( )A 1
B 2
C 99
D 0或者100
答:D
以下( )不是队列的基本运算?A 从队尾插入一个新元素
B 从队列中删除第i个元素
C 判断一个队列是否为空
D 读取队头元素的值
答:B
现有一循环队列,其队头指针为front,队尾指针为rear;循环队列长度为N。其队内有效长度为?(假设队头不存放数据)A (rear - front + N) % N + 1
B (rear - front + N) % N
C (rear - front) % (N + 1)
D (rear - front + N) % (N - 1)
答:B