【数据结构】线性表-栈和队列


前言

栈和队列是两种十分常见且十分重要的数据结构,学到了这里那么恭喜你,你已经具备初步编写算法的能力了。深入理解栈和队列能有效提高我们的编程思维,他们虽然有自己的名字,但本质上都是线性表,只不过是带限制的线性表

一、栈(LIFO)

想象一个管子,我们往里面丢硬币,这个管子只有一个开口,也就是说当你存硬币到一定程度想拿出来用的话,最后放进去的那个是最先拿出来的,这种只能后进先出(last In First Out)的线性表我们称他为栈,简称LIFO。我们把进入元素的那一端叫做栈顶,压在底下的叫做栈底,插入元素叫入栈(压栈),删除元素叫出栈(弹栈)

(一)顺序栈

顺序栈顾名思义就是用顺序表实现的栈

存储结构

typedef int SElemType;
typedef struct{
	SElemType *base, *top;
	int stacksize;
}SqStack;

操作

1. 顺序栈的初始化

初始化S.top=S.base,top指向的是栈顶元素上的第一个空位

Status InitStack(SqStack &S){
	S.base = new SElemType[MAXSIZE];
	if(!S.base) exit(OVERFLOW);
	S.top = S.base;
	S.stacksize = MAXSIZE;
	return OK;
}
2. 判断顺序栈是否为空
Status StackEmpty(SqStack S){
	if(S.top == S.base) return TRUE;
	return FALSE:
}
3. 获取顺序栈长度
int StackLength(SqStack S){
	return S.top - S.base;
}
4. 清空顺序栈
Status ClearStack(SqStack S){
	if(S.base) S.top = S.base;
	return Ok;
}
5. 销毁顺序栈
Status DestoryStack(SqStack &S){
	if(S.base){
		delete S.base;
		S.stacksize = 0;
		S.top = S.base = NULL:
	}
	return OK;
}
6. 顺序栈的入栈

对于一个顺序栈,想要入栈,首先要考虑栈是否满了,如果没满,由于top指向的是栈顶元素上的第一个空位置,所以直接将e存入top指向的空间,随后将top+1,这两句写成一句示例如下

Status Push(SqStack &S, SElemType e){
	if(S.top - S.base == S.MAXSIZE) return ERROR:
	*S.top++ = e;
	return OK;
}
7. 顺序栈的出战

出栈时要考虑是否栈空,非空就先将top向下移一个位置使top指向要弹出的元素,随后将该元素用e带回

Status Pop(SqStack &S, SElemType e){
	if(S.top == S.base) return ERROR;
	e = *--S.top;
	return OK;
}
8. 获取栈顶元素
Status GetTop(SqStack S, SElemType e){
	if(S.top == S.base) return ERROR;
	e = *S.top;
	return OK;
}

(二)链栈

对于链栈,基于链表的栈

存储结构

typedef struct StackNode{
	SElemType data;
	StackNode *next
}*LinkStack;

操作

链栈的大多数操作和顺序栈一样,这里除要讲讲一些特别的

1. 链栈的初始化

链栈的指针指向第一个元素,只要我们在入栈时保证链表使头插法,就可以完美实现链栈,这给我们带来了极大的便利

Status InitStack(LinkStack &S){
	S = NULL;
	return OK;
}
2. 判断链栈是否为空
Status StackEmpty(LinkStack &S){
	if(S == NULL) return TRUE;
	return FALSE;
}
3. 入栈
Status Push(LinkStack &S, SElemType e){
	p = new StakcNode;
	p -> data = e;
	p -> next = s;
	s = p;
	return OK;
}
4. 出栈
Status Pop(LinkStack &S, SElemType e){
	if(S == NULL) return ERROR;
	e = s -> data;
	p = new StackNode;
	p = s;
	s = s -> next;
	delete p;
	return OK;
}

二、队列(FIFO)

想象你在肯德基买早餐,你看着前面的人一个个拿到了帕尼尼,因为他们比你早到,这种先进先出(First In First Out)的数据结构就叫做队列。(最后你也拿到了帕尼尼,因为到你出队了)

(一)顺序队列

任何顺序存储结构我时常面临溢出的风险,考虑到队列出队和入队不是一个方向,如果队尾到了数组末端,但队头又出去了一些元素,这个时候就会出现队列的假溢出,是个人都会把数据存到前面空出的位置,但是这引入了一个新问题,如何判断队满和队空,目前市面上主流的方法主要是设置一个flag来表示是空还是满,另一种是浪费一个存储空间,我们不存满数组,这样也有计算的余地,后面我们主要使用第二种方法

存储结构

一个顺序队列,我们需要一个base指针来表示存放数据的数组位置,然后两个游标分别表示队列的头和尾

typedef int QElemType
# define MAXSIZE100
typedef struct{
	QElemType *Base;
	int front, rear;
}SqQueue

操作

1. 初始化循环队列
Status InitQueue(SqQueue &Q){
	Q.base = new QElemType[MAXSIZE];
	if(!Q.base) exit(OVERFLOW);
	Q.front = Q.rear = 0;
	return OK;
}
2. 获取循环队列长度

由于我们的线性队列是循环队列,因而不能直接用rear减去front,要对他们的差加上数组长度再取模,这样得到的才是队列的长度

int QueueLength(SqQueue Q){
	return (Q.front - Q.rear + MAXSIZE) % MAXSIZE;
}
3. 获取队头元素
QElemType GetHead(SqQueue Q){
	if(Q.front == Q.rear) return ERROR;
	return Q.base[Q.front];
}
4. 入队

因为我们留空了一个数组元素,所以(Q.rear + 1) % MAXSIZE == Q.front)就变成了判断对满的条件。在更新尾指针时,要注意位置溢出,所以要去一个模

Status EnQueue(SqQueue &Q, QElemType e){
	if((Q.rear + 1) % MAXSIZE == Q.front) return ERROR;
	Q.base[Q.rear] = e;
	Q.rear = (Q.rear + 1) % MAXSIZE;
	return OK;
}
5. 出队

(Q.rear == Q.front)是判断队空的条件

Status DeQueue(SqQueue &Q, QElemType e){
	if(Q.rear == Q.front) return ERROR;
	e = Q.base[Q.front];
	Q.front = (Q.front + 1) % MAXSIZE;
	return OK;
}
6. 清空循环队列
Status ClearQueue(SqQueue &Q){
	Q.front = Q.rear = 0;
	return OK;
}
7. 判断队列是否为空
Status QueueEmpty(SqQueue Q){
	if(Q.front == Q.rear) return TRUE;
	return FALSE:	
}

(二)链队

存储结构

链队的存储结构有些特殊,节点一个结构体,指针又要一个结构体,区别于链栈是因为链栈只需要一个指针只要保证头插法就好,而链队需要两个指针

typedef struct QNode{
	QElemType data;
	QNode *next;
}*QueuePtr;

typedef struct{
	QueuePtr front, rear;
}LinkQueue

操作

1. 链队的初始化
Status InitQueue(LinkQueue &Q){
	Q.front = Q.rear = new QNode;
	if(!Q.front) exit(OVERFLOW);
	Q.front -> next = NULL;
	return OK;
}
2. 清空链队

清空链队我们不仅需要把两个游标归零,还需要把中间各个节点占用的内存释放掉,所以通过声明两个指针p,q逐个删除节点

Status ClearQueue(LinkQueue &Q){
	QueuePtr p, q;
	Q.rear = Q.front;
	p = Q,front -> next;
	Q.front -> next = NULL;
	while(p){
		q = p;
		p = p -> next;
		delete q;
	}
	return OK;
}
3. 销毁链队
Status DestoryQueue(LinkQueue &Q){
	while(Q.front){
		Q.rear = Q.front -> next;
		delete Q.front;
		Q.front = Q.rear;
	}
	return Ok
}
4. 判断队空
Status QueueEmpty(LinkQueue Q){
	if(Q.front == Q.rear) return TRUE;
	return FALSE;
}
5. 取队头元素

front指向的是队头元素前面的头结点,也就是说front指向的是一个数据域为空的节点,他的下一个节点才是即将要出队的元素

Status GetHead(LinkQueue Q, QElemType e){
	if(Q.front == Q.rear) return ERROR;
	QueuePtr p = Q.front -> next;
	e = p -> data;
	return OK;
}
6. 入队

Q.rear是为节点的地址,通过他我们将该节点的next指针设置为s,同时我们更新Q.rear为s

Status EnQueue(LinkQueeu &Q, QElemType e){
	p = new QNode;
	if(!p) exit(OVERFLOW);
	p -> data = e;
	Q.rear -> next = s;
	Q.rear = s;
	return OK;
}
7. 出队

链队出队时,首先获取即将出队的元素地址,然后将元素的数据用e带回,接着更新Q.front的next直向出队元素p的下一个元素,最后判断出队后是否队空,如果队空了,就需要更新Q.rear为Q.front,因为此时尾游标指向了一个不存在的节点

Status DeQueeu(LinkQueue &Q, QElemType e){
	if(Q.front == Q.rear) return ERROR;
	QueuePtr p;
	p = Q.front -> next;
	e = p -> data;
	Q.front -> next = p -> next;
	if(Q.rear == p) Q.rear = Q.front;
	delete p;
	return OK;
}

跳转系列文章目录


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值