【数据结构】详细介绍栈和队列,解析栈和队列每一处细节

目录

[一. 栈](#一. 栈)

[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. 栈的概念

  1. 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。

  2. 进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。

  3. 栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

  4. 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

  5. 出栈:栈的删除操作叫做出栈。出数据也在栈顶。

2. 栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些,因为数组在尾上插入数据的代价比较小。

2.1 栈的结构

  1. top表示栈顶数据的下一个位置。
cpp 复制代码
typedef int StackDataType;
typedef struct Stack
{
	StackDataType* arr; //用数组作为存放栈数据的结构
	int top;			//栈顶数据的下一个位置
	int capacity;		//容量
}Stack;

2.2 初始化栈

  1. 将结构数据初始化。
cpp 复制代码
void StackInit(Stack* ps)
{
	assert(ps);

	ps->arr = NULL;
	ps->top = ps->capacity = 0;
}

2.3 入栈

  1. 判断扩容。

  2. 数据插入栈顶。

cpp 复制代码
void 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 出栈

  1. 栈不为空才能出栈。
cpp 复制代码
void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	ps->top--;
}

2.5 获取栈顶元素

  1. 判断空栈
cpp 复制代码
StackDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	return ps->arr[ps->top - 1];
}

2.6 获取栈中有效个数

cpp 复制代码
int StackSize(Stack* ps)
{
	assert(ps);

	return ps->top;
}

2.7 判断栈是否为空

  1. 栈空返回真。
cpp 复制代码
bool StackEmpty(Stack* ps)
{
	assert(ps);

	return !ps->top;
}

2.8 销毁栈

  1. 释放空间,初始数据。
cpp 复制代码
void StackDestroy(Stack* ps)
{
	assert(ps);

	free(ps->arr);
	ps->arr = NULL;
	ps->top = ps->capacity = 0;
}

完整代码:Stack/Stack · 林宇恒/DataStructure - 码云 - 开源中国 (gitee.com)


二. 队列

1. 队列的概念

  1. 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表。

  2. 队列具有先进先出FIFO(First In First Out)的特点。

  3. 入队列:进行插入操作,这一端称为队尾。

  4. 出队列:进行删除操作,这一端称为队头。

2. 队列的实现

队列也可以使用数组或链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

2.1 队列的结构

  1. 需要两个结构体,一个表示节点,一个表示队列整体。

  2. 节点的结构包含存放的数据和指向下一个节点的指针。

  3. 表示队列的结构包含队头指针和队尾指针还有节点的数量。

cpp 复制代码
typedef int QueueDataType;
typedef struct QueueNode
{
	QueueDataType data;     //数据
	struct QueueNode* next; //下一个节点地址
}QueueNode;

typedef struct Queue
{
	QueueNode* prev; //队头指针
	QueueNode* tail; //队尾指针
	size_t size;     //节点个数
}Queue;

2.2 队列初始化

  1. 传入的队列指针不能为空。

  2. 将队列结构体中队头队尾指针置空,节点数量置0。

cpp 复制代码
void QueueInit(Queue* q)
{
	assert(q);

	q->prev = q->tail = NULL;
	q->size = 0;
}

2.3 销毁队列

  1. 传入的队列指针不能为空。

  2. 从队头指针开始遍历释放节点直到空停下,并将队头队尾指针置空,节点数量置0。

cpp 复制代码
void 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 入队列(队尾)

  1. 传入的队列指针不能为空。

  2. 创建要入队列的节点

  3. 当队列为空时,将队头队尾指针都指向新节点,节点数量加一。

  4. 当队列不为空时,将新节点和最后一个节点连接,然后队尾指针指向新节点,节点数量加一。

cpp 复制代码
void 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 出队列(队头)

  1. 传入的队列指针不能为空。

  2. 队列不能为空。

  3. 释放第一个节点,队头指针指向第二个节点,节点数量减一。

  4. 当队列删完之后没有节点了,这种情况需要特殊处理,防止队尾指针变成野指针。

cpp 复制代码
void 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 获取队头数据

  1. 队列指针不能为空。

  2. 队列不能为空。

  3. 返回队头节点存放的数据。

cpp 复制代码
QueueDataType QueueFront(Queue* q)
{
	//1. 队列指针不能为空。
	assert(q);
	//2. 队列不能为空。
	assert(q->size);

	//3. 返回队头节点存放的数据。
	return q->prev->data;
}

2.7 获取队尾数据

  1. 队列指针不能为空。

  2. 队列不能为空。

  3. 返回队尾节点存放的数据。

cpp 复制代码
QueueDataType QueueBack(Queue* q)
{
	//1. 队列指针不能为空。
	assert(q);
	//2. 队列不能为空。
	assert(q->size);

	//3. 返回队尾节点存放的数据。
	return q->tail->data;
}

2.8 获取队列节点个数

  1. 队列指针不能为空。

  2. 返回节点个数。

cpp 复制代码
int QueueSize(Queue* q)
{
	//1. 队列指针不能为空。
	assert(q);

	//2. 返回节点个数。
	return q->size;
}

2.9 判断队列是否为空

  1. 队列指针不能为空。

  2. 根据节点数量判断。空返回真。

cpp 复制代码
bool QueueEmpty(Queue* q)
{
	//1. 队列指针不能为空。
	assert(q);

	//2. 根据节点数量判断。空返回真。
	return !q->size;
}

完整代码: Queue/Queue · 林宇恒/DataStructure - 码云 - 开源中国 (gitee.com)

3. 循环队列

  1. rear的位置插入数据,然后rear++。

  2. pop一次就是front往前移一位。

  3. front == rear 表示空。

  4. rear+1 == front 表示满,记得取模。

  5. 这里使用数组实现,因为单链表不方便获取队尾数据。

4. 编程题 设计循环队列

链接:. - 力扣(LeetCode)

cpp 复制代码
typedef 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

相关推荐
古月居GYH4 分钟前
在C++上实现反射用法
java·开发语言·c++
Betty’s Sweet7 分钟前
[C++]:IO流
c++·文件·fstream·sstream·iostream
祁思妙想12 分钟前
10.《滑动窗口篇》---②长度最小的子数组(中等)
leetcode·哈希算法
敲上瘾21 分钟前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
福大大架构师每日一题23 分钟前
文心一言 VS 讯飞星火 VS chatgpt (396)-- 算法导论25.2 1题
算法·文心一言
不会写代码的ys27 分钟前
【类与对象】--对象之舞,类之华章,共绘C++之美
c++
兵哥工控29 分钟前
MFC工控项目实例三十二模拟量校正值添加修改删除
c++·mfc
EterNity_TiMe_38 分钟前
【论文复现】(CLIP)文本也能和图像配对
python·学习·算法·性能优化·数据分析·clip
长弓聊编程39 分钟前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++
陌小呆^O^43 分钟前
Cmakelist.txt之win-c-udp-client
c语言·开发语言·udp