线性表之栈与队列

文章目录

一、栈

 1.何为栈

 2.栈的模拟实现

二、队列

 1.何为队列

 2.队列的模拟实现


前言

在之前我们已经学习了线性表中的顺序表和链表,我们学习了它们的一些基本性质以及如何去实现它们。但是我们在工作时那两种数据类型还远远不够,今天我们再来学习两种线性表(操作受限的线性表),这两种线性表有时候能起到事半功倍的效果。


一、栈

1.何为栈

首先我们先来学习栈,这是一种特殊的线性表,我们规定它只能够从一端存入数据或者取出数据。并且它存储取出还有着  先进后出FILO(first in last out)或者 后进先出LIFO(last in first out) 的顺序特点。

首先我们来介绍几个栈的专用名词:

栈底:我们将栈的一端称作为栈底,我们一般不对栈底数据进行操作;

栈顶:我们将栈的一端称作为栈顶,我们一般都是在栈顶进行各种操作。我们会定义一个指针top用来表示栈顶,top一般指向最后一个有效数据的上面那个空闲空间(这个是重点);

压栈/入栈/进栈:我们将数据从栈顶存入操作叫做压栈;

出栈:我们将数据从栈顶取出操作叫做出栈;

2.栈的模拟实现

在之前,我们学习顺序表和链表两种线性表,这两种线性表分别是使用数组和链表来实现。现在我们来考虑一下栈该用啥来实现呢?

a)当我们使用数组来实现:我们可以在数组的一端作为栈顶进行插入或者删除数据,其时间复杂度都是O(1),另一端数组下标为0咱们就把它当作栈底。虽然数组大小是一开始就定义好的,但是我们可以使用增容操作(我们增容操作时一般都是成倍增加的,因此增容几次就足够了)

b)当我们使用单链表来实现:当我们对单链表的表尾作为栈顶,然后我们进行插入操作时,其时间复杂度是O(1),但是我们进行删除操作时,其时间复杂度是O(N),因为我们要从头开始遍历整个链表来找到要删除结点的前一个结点。尽管,单链表咱们可以插入一个数据申请一个结点空间,只有不容易浪费空间,但这些结点的空间并不是连续的,因此并不是很好。主要还是时间复杂度上的差异。

c)当我们使用双向链表来实现:当我们将双向链表的一端作为栈顶,另一端作为栈底时,在我们进行插入删除时时间复杂度可能是O(1)。但是,我们将栈底与栈顶换过来时时间复杂度可能就是O(N).

综上所述,我们还是使用数组来实现栈比较合适。接下来,我来用代码进行模拟实现栈,同样的我们需要创建三个文件Stack.c  , Stack.h ,text.c。

这个栈的实现与我们之前的顺序表实现基本是一样的,咱们就不做过多赘述了。

Stack.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int STDataType;
typedef struct Stack
{
	STDataType* arr;//用数组来模拟实现栈
	int capacity;//栈的空间大小
	int top;//栈顶
}ST;

//初始化
void STInit(ST* ps);

//销毁
void STDesTroy(ST* ps);

//入栈
void STPush(ST* ps, STDataType  x);

//出栈
void STPop(ST* ps);

//检查是否为空栈
bool STEmpty(ST* ps);

//读取栈顶数据
STDataType STTop(ST* ps);

//查询有效数据个数
int STSize(ST* ps);

Stack.c

#define  _CRT_SECURE_NO_WARNINGS
#include"Stack.h"


//初始化
void STInit(ST* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->top = 0;//当top=0时,即栈顶==栈底时,则表示栈空
}


//销毁
void STDesTroy(ST* ps)
{
	assert(ps);
	if (ps->arr == NULL)
	{
		free(ps->arr);
	}
	ps->capacity = ps->top =0;
}

//入栈
void STPush(ST* ps, STDataType x)
{
	assert(ps);
	//1.判断栈的空间是否足够
	if (ps->capacity == ps->top)//表示栈满了
	{
	//2.增容
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->arr, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("relloc fail!");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	//3.入栈
	ps->arr[ps->top++]= x;
}

//出栈
void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));//这里面STEmpty的参数是传递地址的,由于ps是栈的指针即地址,故直接传递过去
	ps->top--;
}


//检查是否为空栈
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;//当这个表达式成立时即为空栈,返回true

}

//读取栈顶数据
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->arr[ps->top - 1];
	//注意:咱们将top指向最后一个有效数据的上面一个空闲位置,故我们想要查询时,根据下标就是top-1,注意不能是top--
	//top--==top=top-1,这样就把栈顶改变了
}

//查询有效数据个数
int STSize(ST* ps)
{
	assert(ps);
	return ps->top;//由于数组是从0开始的,并且top指针是指向最后一个有效数据的上面一个空闲位置,所以ps->top就是有效数据的个数
}

test.c

#define  _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
void STtest()
{
	ST st;//定义一个栈
	STInit(&st);
	//入栈4个数字
	STPush(&st, 1);
	STPush(&st, 2);
	STPush(&st, 3);
	STPush(&st, 4);
	//printf("size:%d\n", STTop(&st));
	//STPop(&st);
	//printf("size:%d\n", STTop(&st));
	//STDesTroy(&st);
	//printf("size:%d", STTop(&st));
	//编写一个循环将栈中的所有数据打印出来,注意只能一个一个从栈顶出来,出来一个打印一个直到栈空
	while(!STEmpty(&st))
	{
		STDataType data = STTop(&st);
		printf("%d ", data);
		STPop(&st);
	}
}

int main()
{

	STtest();
	return 0;
}

二、队列

1.何为队列

队列与栈一样,也是一种操作受限的线性表。它只能在一端存入数据,一端取出数据。它有着先进先出FIFO(first in last out)的特点。其实咱们只要稍微联想一下就行,我们平时排队的时候,是不是第一个来的排队首,然后队首的第一个人来进行操作,后面的人只能排到队尾,只能等前面的人都搞完才能轮到他。我们也来介绍一下关于队列的几个名词:

队头:我们把队列的一端当作为队头,我们只能在这一端进行插入操作;

队尾:我们把队列的另一端当作为队尾,我们只能在这一端进行删除操作;

入队:我们将数据存入队列的操作叫做入队。这个操作只能够在队头进行;

出队:我们将数据取出队列的操作叫做出队。这个操作只能在队尾进行。

2.队列的模拟实现

这个队列我们该用什么来进行实现呢?如果使用数组来实现的话,由于数组一端是不变的,我们无法对那一端进行操作,因此数组这个不行。然后,我们可以使用单链表来实现。链表中有头插尾插,头删尾删等操作,但是,尾插操作中,其时间复杂度是O(n),于是我们可以创建两个指针,一个头指针head,一个尾指针tail,那么我们尾删,头删的时间复杂度都是O(1)。接下来我们用代码来实现队列,同样我们用三个文件Queue.c,Queue.h,test.c来进行实现

Queue.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int QDatatype;
//定义队列结点
typedef struct QueueNode
{
	QDatatype data;
	struct QueueNode* next;
}QNode;

//定义队列
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

//初始化
void QInit(Queue* pq);

//销毁
void QDetroy(Queue* pq);

//判断队列是否为空
bool QEmepty(Queue* pq);

//入队列
void QPush(Queue* pq,QDatatype x);

//出队列
void QPop(Queue* pq);

//读队头数据
QDatatype QFront(Queue* pq);

//读队尾数据
QDatatype QBack(Queue* pq);

//队列有效数据个数
int	 QSize(Queue* pq);

Queue.c

#define  _CRT_SECURE_NO_WARNINGS
#include"Queue.h"

//初始化
void QInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

//销毁
void QDetroy(Queue* pq)
{
	assert(pq);
	assert(!QEmepty(pq));
	QNode* pcur = pq->head;
	while (pcur)
	{
		QNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;

}

//判断队列是否为空
bool QEmepty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL && pq->tail == NULL;
}

//入队列(队尾入)
void QPush(Queue* pq, QDatatype x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	//将新的结点初始化一下
	newnode->data = x;
	newnode->next = NULL;
	//队列为空
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = pq->tail->next;
	}
	pq->size++;
}

//出队列(队头出)
void QPop(Queue* pq)
{
	assert(pq);
	assert(!QEmepty(pq));
	//当只有一个结点时
	if (pq->head == pq->tail)
	{
		free(pq->head);
		//将尾指针也置为NULL,否则就成为野指针了
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* NEXT = pq->head->next;
		free(pq->head);
		pq->head = NEXT;
	}

	pq->size--;
}

//读队头数据
QDatatype QFront(Queue* pq)
{
	assert(pq);
	assert(!QEmepty(pq));
	return pq->head->data;
}


//读队尾数据
QDatatype QBack(Queue* pq)
{
	assert(pq);
	assert(!QEmepty(pq));
	return pq->tail->data;
}

//队列有效数据个数
int	 QSize(Queue* pq)
{
	assert(pq);
	assert(!QEmepty(pq));
	return pq->size;
}

test.c

#define  _CRT_SECURE_NO_WARNINGS
#include"Queue.h"

void Queuetest()
{
	Queue q;
	QInit(&q);
	QPush(&q, 1);
	QPush(&q, 2);
	QPush(&q, 3);
	QPush(&q, 4);
	
	printf("head: %d\n", QFront(&q));
	printf("tail: %d\n", QBack(&q));

}

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

总结

这次我们学习的这两种线性表是两种操作受限的线性表,但是它们两个的实现方式是基于之前的顺序表和链表,因此,我们不仅要学习现在的,也要熟练掌握之前所学过的。有句老话怎么说来着:温故而知新,可以为师矣。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值