栈和队列思想架构(附代码实现+OJ题)

栈和队列

栈和队列是两种重要的线性结构,从数据结构角度看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,他们是操作受限的线性表,因此可称为限定性的数据结构。但从数据类型角度看,他们是和线性表大不相同的两类重要的抽象数据类型。

思想架构

在这里插入图片描述
如果把物质层面的人体比作数据存储的物理结构,那么精神层面的 人格则是数据存储的逻辑结构。逻辑结构是抽象的概念,它依赖于物理 结构而存在。栈和队列。这两者都属于逻辑 结构,它们的物理实现既可以利用数组,也可以利用链表来完成。

概念及结构

---------后进先出
在这里插入图片描述
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

实现
  • 数组方式实现

1、初始化(申请空间)
2、进栈(尾插)
3、出栈(尾删)
4、数据打印
5、数据销毁(内存释放)

//顺序栈
typedef struct SeqStack
{
	StackElemType *base; //
	size_t         capacity;
	int            top;
}SeqStack;

bool IsFull(SeqStack *pst)
{return pst->top >= pst->capacity;}
bool IsEmpty(SeqStack *pst)
{return pst->top == 0;}

void SeqStackInit(SeqStack *pst, int sz)
{
	assert(pst);
	pst->capacity = sz > SEQSTACK_DEFAULT_SIZE ? sz : SEQSTACK_DEFAULT_SIZE;
	pst->base = (StackElemType*)malloc(sizeof(StackElemType) * pst->capacity);
	assert(pst->base != NULL);
	pst->top = 0;
}

void SeqStackPush(SeqStack *pst, StackElemType x)
{
	assert(pst);
	if(IsFull(pst))
	{
		printf("栈空间已满,%d不能入栈\n", x);
		return;
	}
	pst->base[pst->top++] = x;
}

void SeqStackPop(SeqStack *pst)
{
	assert(pst);
	if(IsEmpty(pst))
	{
		printf("栈已空,不能出栈.\n");
		return;
	}
	pst->top--;
}

StackElemType SeqStackTop(SeqStack *pst)
{
	assert(pst && !IsEmpty(pst));
	return pst->base[pst->top-1];
}

void SeqStackShow(SeqStack *pst)
{
	assert(pst);
	for(int i=pst->top-1; i>=0; --i)
		printf("%d\n", pst->base[i]);
}

void SeqStackDestroy(SeqStack *pst)
{
	assert(pst);
	free(pst->base);
	pst->base = NULL;
	pst->capacity = pst->top = 0;
}
  • 链表方式实现

1、初始化(为链表头指针置空,这里采用无头链表)
2、进栈(头插)
3、出栈(头删)
4、数据打印
5、数据销毁(内存回收)

typedef struct LinkStackNode
{
	StackElemType data;
	struct LinkStackNode *next;
}LinkStackNode;
typedef struct LinkStack
{
	LinkStackNode *head;
}LinkStack;

void LinkStackInit(LinkStack *pst)
{
	assert(pst);
	pst->head = NULL;
}

void LinkStackPush(LinkStack *pst, StackElemType x)
{
	assert(pst);
	LinkStackNode *s = (LinkStackNode*)malloc(sizeof(LinkStackNode));
	assert(s != NULL);
	s->data = x;

	s->next = pst->head;
	pst->head = s;
}

void LinkStackPop(LinkStack *pst)
{
	assert(pst);
	if(pst->head != NULL)
	{
		LinkStackNode *p = pst->head;
		pst->head = p->next;
		free(p);
	}
}

StackElemType LinkStackTop(LinkStack *pst)
{
	assert(pst && pst->head != NULL);
	return pst->head->data;
}

void LinkStackShow(LinkStack *pst)
{
	assert(pst);
	LinkStackNode *p = pst->head;
	while(p != NULL)
	{
		printf("%d\n", p->data);
		p = p->next;
	}
}

void LinkStackDestroy(LinkStack *pst)
{
	assert(pst);
	while(pst->head != NULL)
	{
		LinkStackNode *p = pst->head;
		pst->head = p->next;
		free(p);
	}
}
对比分析

内存空间:数组形式存储是一次性开辟好空间,这样既可能出现空间不够用和空间浪费两种情况,而链式存储是采取插入一个数据开辟一个空间的存储机制,故链式存储对空间的掌控更加灵活。
时间复杂度:入栈和出栈只会影响到最后一个元 素,不涉及其他元素的整体移动,所以无论是以数组还是以链表实 现,入栈、出栈的时间复杂度都是O(1)。

队列

概念及结构

---------先进先出
在这里插入图片描述

如上图,车队驶入单行隧道,要想让车辆驶出隧道,只能按照它们驶入隧道的顺序,先驶 入的车辆先驶出,后驶入的车辆后驶出,任何车辆都无法跳过它前面的 车辆提前驶出。

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出
FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头

实现
  • 链表方式实现

1、初始化(使用front和rear指针分别维护队列的头部和尾部)
2、入队(尾插);出队(头删)
3、数据打印
4、队首队尾数据获取
5、队列摧毁(释放空间)

//链队列
typedef struct LinkQueueNode
{
	QueueElemType data;
	struct LinkQueueNode *next;
}LinkQueueNode;

typedef struct LinkQueue
{
	LinkQueueNode *head;
	LinkQueueNode *tail; //指向队尾节点
}LinkQueue;

void LinkQueueInit(LinkQueue *pq);
void LinkQueueDestroy(LinkQueue *pq);
void LinkQueueEn(LinkQueue *pq, QueueElemType x);
void LinkQueueDe(LinkQueue *pq);
void LinkQueueShow(LinkQueue *pq);
QueueElemType LinkQueueBack(LinkQueue *pq);
QueueElemType LinkQueueFront(LinkQueue *pq);

void LinkQueueInit(LinkQueue *pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

void LinkQueueEn(LinkQueue *pq, QueueElemType x)
{
	assert(pq);
	LinkQueueNode *s = (LinkQueueNode *)malloc(sizeof(LinkQueueNode));
	assert(s != NULL);
	s->data = x;
	s->next = NULL;
	if(pq->head == NULL)
		pq->head = pq->tail = s;
	else
	{
		pq->tail->next = s;
		pq->tail = s;
	}
}

void LinkQueueDe(LinkQueue *pq)
{
	assert(pq);
	if(pq->head != NULL)
	{
		LinkQueueNode *p = pq->head;
		pq->head = p->next;
		if(pq->head == NULL)
			pq->tail = NULL;
		free(p);
	}
}

void LinkQueueShow(LinkQueue *pq)
{
	assert(pq != NULL);
	LinkQueueNode *p = pq->head;
	while(p != NULL)
	{
		printf("%d<--", p->data);
		p = p->next;
	}
	printf("Nil.\n");
}

QueueElemType LinkQueueBack(LinkQueue *pq)
{
	assert(pq && pq->head);
	return pq->tail->data;
}
QueueElemType LinkQueueFront(LinkQueue *pq)
{
	assert(pq && pq->head);
	return pq->head->data;
}

void LinkQueueDestroy(LinkQueue *pq)
{
	assert(pq);
	while(pq->head != NULL)
	{
		LinkQueueNode *p = pq->head;
		pq->head = p->next;
		free(p);
	}
	pq->head = pq->tail = NULL;
}

  • 数组方式实现
    在这里插入图片描述

以插入数据容量8为例
入队:每插入一个数据,rear++;rear是要插入数据的位置
出队:每删除一个数据,front++;
问题:
如果像这样不断出队,队头(front)左边的空间 失去作用,那队列的容量岂不是越来越小了?于是便通过循环队列来避免这一问题。

- 循环队列

在这里插入图片描述

//顺序队列 --> 循环队列
#define QUEUE_DEFAULT_SIZE 8
typedef struct SeqQueue
{
	QueueElemType *base;
	size_t         capacity;
	int            front;
	int            rear;
}SeqQueue;

void SeqQueueInit(SeqQueue *pq, int sz);

void SeqQueueDestroy(SeqQueue *pq);

void SeqQueueEn(SeqQueue *pq, QueueElemType x);
void SeqQueueDe(SeqQueue *pq);
QueueElemType SeqQueueBack(SeqQueue *pq);
QueueElemType SeqQueueFront(SeqQueue *pq);
void SeqQueueShow(SeqQueue *pq);

void SeqQueueInit(SeqQueue *pq, int sz)
{
	assert(pq);
	pq->capacity = sz > QUEUE_DEFAULT_SIZE ? sz : QUEUE_DEFAULT_SIZE;
	pq->base = (QueueElemType*)malloc(sizeof(QueueElemType) * (pq->capacity+1));
	assert(pq->base != NULL);
	pq->front = pq->rear = 0;
}

void SeqQueueDestroy(SeqQueue *pq)
{
	assert(pq);
	free(pq->base);
	pq->base = NULL;
	pq->capacity = pq->front = pq->rear = 0;
}

void SeqQueueEn(SeqQueue *pq, QueueElemType x)
{
	assert(pq);
	if((pq->rear+1)%(pq->capacity+1) == pq->front)
	{
		printf("队列已满, %d 不能入队.\n", x);
		return;
	}

	pq->base[pq->rear] = x;
	pq->rear = (pq->rear+1) % (pq->capacity+1);
}
void SeqQueueDe(SeqQueue *pq)
{
	assert(pq);
	if(pq->front == pq->rear)
	{
		printf("队列已空,不能出队.\n");
		return;
	}
	pq->front = (pq->front+1) % (pq->capacity+1);
}
QueueElemType SeqQueueBack(SeqQueue *pq)
{
	assert(pq && (pq->front!=pq->rear));
	return pq->base[(pq->rear-1+pq->capacity+1) % (pq->capacity+1)];
}
QueueElemType SeqQueueFront(SeqQueue *pq)
{
	assert(pq && (pq->front!=pq->rear));
	return pq->base[pq->front];
}
void SeqQueueShow(SeqQueue *pq)
{
	assert(pq);
	for(int i=pq->front; i!=pq->rear; )
	{
		printf("%d<--", pq->base[i]);
		i = (i+1)%(pq->capacity+1);
	}
	printf("Nil.\n");
}

这样将队列循环起来即可将出队的空间再次利用,为了区分空队列和队列已满的情况,通常多余开辟一个空间,把队头下标==队尾下标作为空队列情况,而把(队尾下标+1)%数组长度 = 队头下标作为队列已满的情况。这样即可通过数组方式有效的完成队列的实现。

栈和队列面试题
  1. 括号匹配问题。OJ链接
  2. 用队列实现栈。OJ链接
  3. 用栈实现队列。OJ链接
  4. 实现一个最小栈。OJ链接
  5. 设计循环队列。OJ链接

源码仓库链接

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值