数据结构C语言篇《三》栈和队列概念,模拟函数实现,以及相关OJ面试题

栈和队列

1. 栈

数据结构C语言篇《三》栈和队列概念,模拟函数实现,以及相关OJ面试题

栈,存储货物或供旅客住宿的地方,可引申为仓库、中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈、出栈的说法。可以类比相当于吃饭,吃进去吐出来就是栈(忍着胃部强烈不适码了这句)

1.1栈的概念

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
栈的特性后进先出

注意:栈不能进行遍历操作

1.2栈的实现方法

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的
代价比较小。
数据结构C语言篇《三》栈和队列概念,模拟函数实现,以及相关OJ面试题
数据结构C语言篇《三》栈和队列概念,模拟函数实现,以及相关OJ面试题

1.3栈的模拟实现----动态内存

Stack.h

#pragma once

typedef int DataType;

typedef struct Stcak
{
	DataType *arr;
	int capacity;	//容量大小
	int size;	//有效元素个数---栈顶
}Stack; 

//栈的初始化
void StackInit(Stack *ps);

//入栈
void StackPush(Stack *ps, DataType data);

//出栈
void StackPop(Stack *ps);

//获取栈顶元素
DataType StackTop(Stack *ps);

//获取栈的大小
int StackSize(Stack *ps);

//判断栈内是否有元素
int StackEmpty(Stack *ps);

//销毁栈
void StackDestroy(Stack *ps);

void TestStack();

Stack.c

#include"Stack.h"
#include<stdio.h>
#include<assert.h>
#include<malloc.h>

//栈的初始化
void StackInit(Stack *ps)
{
	assert(ps);
	ps->arr = (DataType *)malloc(sizeof(DataType)* 3);
	if (NULL == ps->arr)	//检测空间是否申请成功
	{
		assert(0);
		return;
	}
	ps->capacity = 3;;
	ps->size = 0;
}

//检查容量
void CheckCapacity(Stack *ps)
{
	if (ps->size == ps->capacity)
	{
		ps->arr = (DataType*)realloc(ps->arr, sizeof(DataType)*ps->capacity * 2);
		if (NULL == ps->arr)	//检测空间是否申请成功
		{
			assert(0);
			return;
		}
		ps->capacity *= 2;
	}
}


//入栈
void StackPush(Stack *ps, DataType data)
{
	assert(ps);
	CheckCapacity(ps);	//扩容
	ps->arr[ps->size++] = data;
	
}

//出栈
void StackPop(Stack *ps)
{
	assert(ps);
	if (StackEmpty(ps))	//检测栈此时是否为空
		return;
	ps->size--;
}

//获取栈顶元素
DataType StackTop(Stack *ps)
{
	assert(ps && !StackEmpty(ps));
	//此处不能使用if条件判断,因为if条件判断的为合法情况
	//若是栈为空此时栈没有元素,要获取栈顶元素 则为非法情况
	//可以用assert进行判断
	//if (StackEmpty(ps))
	//	return;
	return ps->arr[ps->size - 1];
}

//获取栈的大小
int StackSize(Stack *ps)
{
	assert(ps);
	return ps->size;
}

//判断栈内是否有元素
int StackEmpty(Stack *ps)
{
	assert(ps);
	return 0 == ps->size;
}

//销毁栈
void StackDestroy(Stack *ps)
{
	assert(ps);
	if (ps->arr)
	{
		free(ps->arr);
		ps->arr = NULL;
		ps->capacity = 0;
		ps->size = 0;
	}
}

void TestStack()
{
	Stack con;
	StackInit(&con);

	StackPush(&con, 1);
	StackPush(&con, 2);
	StackPush(&con, 3);
	StackPush(&con, 4);
	StackPush(&con, 5);
	StackPush(&con, 6);
	printf("size = %d\n", StackSize(&con));
	printf("top = %d\n", StackTop(&con));

	StackPop(&con);
	StackPop(&con);
	StackPop(&con);
	printf("size = %d\n", StackSize(&con));
	printf("top = %d\n", StackTop(&con));

	StackDestroy(&con);
}

test.c

#define _CRT_SECURE_NO_WARNINGS

#include"Stack.h"

int main()
{
	TestStack();
	return 0;
}

1.4关于栈的OJ题

1.括号匹配问题 OJ

1.5逆波兰表达式

1.5.1概念

逆波兰表达式又叫做后缀表达式,逆波兰表示法是波兰逻辑学家J・卢卡西维兹(J・ Lukasiewicz)于1929年首先提出的一种表达式的表示方法 [1] 。后来,人们就把用这种表示法写出的表达式称作“逆波兰表达式”。逆波兰表达式把运算量写在前面,把算符写在后面。

1.5.2栈实现逆波兰表达式

它的优势在于只用两种简单操作,入栈和出栈就可以搞定任何普通表达式的运算。其运算方式如下:
如果当前字符为变量或者为数字,则压栈,如果是运算符,则将栈顶两个元素弹出作相应运算,结果再入栈,最后当表达式扫描完后,栈里的就是结果。

2.队列

相当于吃进去拉出来

2.1队列的概念

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,进行插入操作的一端称为队尾 出队列,进行删除操作的一端称为队头。
队列的特性先进先出

2.2队列的实现方法

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
数据结构C语言篇《三》栈和队列概念,模拟函数实现,以及相关OJ面试题

2.3队列的模拟实现----链式

Queue.h

#pragma once

typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构
typedef struct Queue
{
	QNode* front;
	QNode* rear;
}Queue;

// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);

Queue.c

#include"Queue.h"
#include<assert.h>
#include<stdio.h>
#include<malloc.h>

QNode* BuyQueueNode(QDataType data)
{
	QNode* node = (QNode *)malloc(sizeof(QNode));
	if (NULL == node)
	{
		assert(0);
		return NULL;
	}
	node->data = data;
	node->next = NULL;
	return node;
}

// 初始化队列
void QueueInit(Queue* q)
{
	assert(q);
	q->front = q->rear = BuyQueueNode(0);
}

// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	q->rear->next = BuyQueueNode(data);
	q->rear = q->rear->next;
}
// 队头出队列
void QueuePop(Queue* q)
{
	QNode *delNode = NULL;
	if (QueueEmpty(q))
		return;
	delNode = q->front->next;
	q->front->next = delNode->next;

	//如果队列中此时只有一个元素,将该元素删除后还需将rear放在front的位置
	if (delNode->next == NULL)
		q->rear = q->front;
	free(delNode);
}

// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
	assert(!QueueEmpty(q));
	return q->front->next->data;
}

// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
	assert(!QueueEmpty(q));
	return q->rear->data;
}

// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
	assert(q);
	int count = 0;
	QNode* cur = q->front->next;
	while (cur)
	{
		cur = cur->next;
		count++;
	}
	return count;
}

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	return q->front->next == NULL;
}

// 销毁队列
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* cur = q->front;
	while (cur)
	{
		q->front = cur->next;
		free(cur);
		cur = q->front;
	}
	q->front = q->rear = NULL;
}

//测试
void TestQueue()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
	QueuePush(&q, 6);
	printf("size = %d\n", QueueSize(&q));
	printf("front = %d\n", QueueFront(&q));
	printf("rear = %d\n", QueueBack(&q));

	QueuePop(&q); 
	QueuePop(&q);
	QueuePop(&q);
	printf("size = %d\n", QueueSize(&q));
	printf("front = %d\n", QueueFront(&q));
	printf("rear = %d\n", QueueBack(&q));

	QueuePop(&q);
	QueuePop(&q);
	printf("size = %d\n", QueueSize(&q));
	printf("front = %d\n", QueueFront(&q));
	printf("rear = %d\n", QueueBack(&q));


	QueuePop(&q);
	printf("size = %d\n", QueueSize(&q));

	if (QueueEmpty(&q))
	{
		printf("Empty\n");
	}
	else
	{
		printf("Error\n");
	}

	QueueDestroy(&q);
}

test.c

#include"Queue.h"

int main()
{
	TestQueue();
	system("pause");
	return 0;
}

2.4队列的数组实现----顺序方式

队列可以用数组Q[1…m]来存储,数组的上界m即是队列所容许的最大容量。在队列的运算中需设两个指针:head,队头指针,指向实际队头元素;tail,队尾指针,指向实际队尾元素的下一个位置。一般情况下,两个指针的初值设为0,这时队列为空,没有元素。数组定义Q[1…10]。Q(i) i=3,4,5,6,7,8。头指针head=2,尾指针tail=8。队列中拥有的元素个数为:L=tail-head。现要让排头的元素出队,则需将头指针加1。即head=head+1这时头指针向上移动一个位置,指向Q(3),表示Q(3)已出队。如果想让一个新元素入队,则需尾指针向上移动一个位置。即tail=tail+1这时Q(9)入队。当队尾已经处理在最上面时,即tail=10,如果还要执行入队操作,则要发生"上溢",但实际上队列中还有三个空位置,所以这种溢出称为"假溢出"。

克服假溢出的方法有两种。一种是将队列中的所有元素均向低地址区移动,显然这种方法是很浪费时间的;另一种方法是将数组存储区看成是一个首尾相接的环形区域。当存放到n地址后,下一个地址就"翻转"为1。在结构上采用这种技巧来存储的队列称为循环队列。

引入循环队列来解决假溢出
数据结构C语言篇《三》栈和队列概念,模拟函数实现,以及相关OJ面试题

2.5队列 OJ题

1.用队列模拟实现栈 OJ
2.用栈模拟实现队列 OJ
3.设计循环队列 OJ

总结与感悟

关于栈和队列的相关知识点和面试题我这里就只写这么多了吧,以后要是有想到其他的我接着慢慢补充,我初期实现就是这些,要是有不同的观点或者有不同思路的朋友欢迎大家赏光私我哈,本着相互进步的原则,希望大家能多多向我提意见,谢谢~~

栈和队列到此也基本结束了,下一章节----树开始预习,啦啦啦~

数据结构C语言篇《三》栈和队列概念,模拟函数实现,以及相关OJ面试题

上一篇:OJ题常见输入输出


下一篇:2021-04-26 CTF Misc buu oj day3 7道题