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

目录

一. 栈

1. 栈的概念

2. 栈的实现

2.1 栈的结构

2.2 初始化栈

2.3 入栈

2.4 出栈

2.5 获取栈顶元素

2.6 获取栈中有效个数

2.7 判断栈是否为空

2.8 销毁栈 

二. 队列

1. 队列的概念

2. 队列的实现 

2.1 队列的结构

2.2 队列初始化 

2.3 销毁队列 

2.4 入队列(队尾) 

2.5 出队列(队头) 

2.6 获取队头数据 

2.7 获取队尾数据 

2.8 获取队列节点个数 

2.9 判断队列是否为空 

3. 循环队列

4. 编程题 设计循环队列 

三. 概念题


一. 栈

1. 栈的概念

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

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

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

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

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

2. 栈的实现

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

2.1 栈的结构

1. top表示栈顶数据的下一个位置。

typedef int StackDataType;
typedef struct Stack
{
	StackDataType* arr; //用数组作为存放栈数据的结构
	int top;			//栈顶数据的下一个位置
	int capacity;		//容量
}Stack;

2.2 初始化栈

1. 将结构数据初始化。

void StackInit(Stack* ps)
{
	assert(ps);

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

2.3 入栈

1. 判断扩容。

2. 数据插入栈顶。

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. 栈不为空才能出栈。

void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

	ps->top--;
}

2.5 获取栈顶元素

1. 判断空栈

StackDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

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

2.6 获取栈中有效个数

int StackSize(Stack* ps)
{
	assert(ps);

	return ps->top;
}

2.7 判断栈是否为空

1. 栈空返回真。

bool StackEmpty(Stack* ps)
{
	assert(ps);

	return !ps->top;
}

2.8 销毁栈 

1. 释放空间,初始数据。

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. 表示队列的结构包含队头指针和队尾指针还有节点的数量。

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。

void QueueInit(Queue* q)
{
	assert(q);

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

2.3 销毁队列 

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

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

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. 当队列不为空时,将新节点和最后一个节点连接,然后队尾指针指向新节点,节点数量加一。

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. 当队列删完之后没有节点了,这种情况需要特殊处理,防止队尾指针变成野指针。

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. 返回队头节点存放的数据。

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

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

2.7 获取队尾数据 

1. 队列指针不能为空。

2. 队列不能为空。

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

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

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

2.8 获取队列节点个数 

1. 队列指针不能为空。

2. 返回节点个数。

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

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

2.9 判断队列是否为空 

1. 队列指针不能为空。

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

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)

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

  • 54
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 43
    评论
评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值