《数据结构》学习笔记02:栈与队列

        本文旨在讲解基本的数据结构知识(主要是栈与队列),使用C/C++语言从零实现各种数据结构,适合于代码能力薄弱的初学者以及想要复习数据结构的读者们。笔者能力有限,所讲述的内容也无法面面俱到,如有表述错误,欢迎指正!

一、栈

1、栈的基本概念

        栈是一种特殊的线性表,其只能在表的一端进行插入和删除,这一端通常被称为栈顶,而另一端称为栈底,即只能在栈顶进行插入(入栈)和删除(出栈)。故栈又称为先进后出(FIFO)的线性表(先进栈的元素靠近栈底,后进栈的元素靠近栈顶)。

        栈有两种实现方式:顺序栈(顺序存储)和链栈(链式存储)。

2、顺序栈

(1)结构定义

        顺序栈是使用顺序存储结构实现的栈,底层使用数组实现,需要定义两个指针:base指针(栈底指针)永远指向栈底,top指针(栈顶指针)永远指向栈顶的上一个位置。

        由于入栈与出栈均在栈顶进行,所以top指针需要在入栈和出栈后移动,而base指针不变,故设定base指针为底层数组的首地址指针。结构定义代码如下:

const int MAXSIZE = 100;
typedef int SElemType;

/* 顺序栈 */
typedef struct
{
	SElemType* base;    //栈底指针
	SElemType* top;     //栈顶指针
	int length;         //栈的长度
}SqStack;
(2)初始化

        使用C++的new关键字初始化base指针,并令top指针等于base指针(栈为空时base==top)

void InitStack(SqStack& S)
{
	S.base = new SElemType[MAXSIZE];
	if (!S.base)
		return;
	S.top = S.base;
	S.length = 0;
}
(3)入栈

        入栈前需要判断栈是否为满,若满则无法入栈(top-base=MAXSIZE则栈满)。若允许入栈,则在top指针指向的位置(top指针永远指向栈顶的上一个位置)插入对应数据,并修改top指针,即top自增。

void Push(SqStack& S, SElemType e)
{
	if (Full(S))    //S.top - S.base == MAXSIZE
		return;
	*S.top = e;
	S.top++;
	//可写为:*S.top++=e;
	++S.length;
}
(4)出栈

        出栈前需要判断栈是否为空,若空则无法出栈(top==base则栈空)。若允许出栈,则删除top指针指向的数据,并修改top指针,即top自减。

int Pop(SqStack& S)
{
	if (Empty(S))      //S.top == S.base
		return -1;
	S.top--;
	int e= *S.top;
	//可写为:e=*--S.top;
	--S.length;
	return e;
}
(5)取栈顶元素

        栈顶元素是(top-1)所指向的元素,在取栈顶元素前同样需要判断是否栈为空,代码如下:

int GetTop(const SqStack& S)
{
	if (Empty(S)) 
		return -1;
	return *(S.top - 1);
}

3、链栈

(1)结构定义

        链栈是使用链式存储结构的栈,底层使用链表实现。链栈的核心结构是链栈结点,与链表结点类似,包括数据域data与指针域next,结构定义如下:

/* 链栈 */
typedef struct SNode
{
	SElemType data;
	struct SNode* next;
}SNode, * LinkStack;
(2)初始化

        链栈不需要像链表那样的头结点(因为栈的入栈出栈操作均在栈顶,插入采用头插法即可,不需要额外的头结点),故初始化只需要令链栈S(指向链栈结点的指针)为空即可。

void InitStack(LinkStack& S)
{
	S = NULL;
}
(3)入栈

        链栈的入栈采用头插法,代码如下:

void Push(LinkStack& S, SElemType e)
{
	SNode* p = new SNode;
	p->data = e;
	p->next = S;
	S = p;
}
(4)出栈

        链栈的出栈操作只需要修改S,令其指向栈顶结点的下一个结点,即S=S->next,并将原本的栈顶结点释放,代码如下:

int Pop(LinkStack& S)
{
	if (!S)
		return -1;
	int e = S->data;
	SNode* p = S;
	S = S->next;
	delete p;
	return e;
}
(5)取栈顶元素

        栈顶结点即为链栈S指针指向的结点,代码如下:

int GetTop(const LinkStack& S) 
{
	if (S != NULL)
		return S->data;
	return -1;
}

二、队列

1、队列的基本概念

        队列是一种先进先出(FIFO)的线性表,只允许在一端删除(出队),在另一端插入(入队),出队的那一端称为队头,入队的那一端称为队尾。

        队列有两种实现方式:顺序结构(循环队列)和链式结构(链队列)。

2、循环队列

(1)结构定义

        循环队列是采用顺序结构的队列,采用循环队列的好处是最大限度的使用存储空间。在结构定义中,需要一个存储数据的数组的首地址指针base,以及两个指针(使用数组的下标表示)front和rear,前者始终指向队头,后者始终指向对头的后一个位置。

const int MAXSIZE = 100;
typedef int QElemType;

/* 顺序队 */
typedef struct
{
	QElemType* base;
	int front;
	int rear;
}SqQueue;
(2)初始化

        使用C++的new关键字初始化base,并指定front=rear=0(当队空时front==rear)。

void InitQueue(SqQueue& Q)
{
	Q.base = new QElemType[MAXSIZE];
	if (!Q.base)
		return;
	Q.front = Q.rear = 0;
}
(3)求队列长度

length=(Q.rear-Q.front+MAXSIZE)mod(MAXSIZE)

int Length(const SqQueue& Q)
{
	return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
(4)判断队空

Q.front==Q.rear

bool Empty(const SqQueue& Q)
{
	return Q.front == Q.rear;
}
(5)判断队满

(Q.rear+1)mod(MAXSIZE)==Q.front

bool Full(const SqQueue& Q)
{
	return (Q.rear + 1) % MAXSIZE == Q.front;
}
(6)入队

        入队前需判断是否队满,若队满则无法入队。若允许入队,由于rear指向队尾的后一个位置,故在rear指向位置插入数据,并更新rear,即rear=(rear+1)%MAXSIZE。

void EnQueue(SqQueue& Q, QElemType e)
{
	if (Full(Q))
		return;
	Q.base[Q.rear] = e;
	Q.rear = (Q.rear + 1) % MAXSIZE;
}
(7)出队

        出队前需判断是否队空,若队空则无法出队。若允许出队,则更新队头front,即front=(front+1)%MAXSIZE。

int DeQueue(SqQueue& Q)
{
	if (Empty(Q))
		return -1;
	int e = Q.base[Q.front];
	Q.front = (Q.front + 1) % MAXSIZE;
	return e;
}
(8)取队头元素

        队头元素即为front指向的元素,代码如下:

int GetFront(SqQueue Q)
{
	if (Empty(Q))
		return -1;
	return Q.base[Q.front];
}

3、链队列

(1)结构定义

        链队列是采用链式存储结构实现的队列,一个链队列需要两个分别指向队头与队尾的指针(分别称为头指针和尾指针),对于链队列,采用头结点,并让头指针始终指向头结点。

/* 链队 */
typedef struct QNode       //链队结点
{
	QElemType data;
	struct QNode* next;
}QNode;

typedef struct
{
	QNode* front;
	QNode* rear;
	int length;
}LinkQueue;             //链队
(2)初始化

        链队的front指针指向头结点(头结点不计入链队的长度),而rear指针指向最后一个结点,当链队为空时,只有一个头结点,此时front==rear。代码如下:

void InitQueue(LinkQueue& Q)
{
	Q.front = Q.rear = new QNode;
	Q.front->next = NULL;
	Q.length = 0;
}
(3)入队

        链队入队采用尾插法,所以需要设置尾指针rear(这与链栈不同,链栈的入栈操作采用头插法),插入的方法与链表类似,以下是代码实现:

void EnQueue(LinkQueue& Q, QElemType e)
{
	QNode* p = new QNode;
	p->data = e;
	p->next = NULL;
	Q.rear->next = p;
	Q.rear = p;
	++(Q.length);
}
(4)出队

        链队出队采用删除第一个结点的方式,即让头结点的下一个结点为第二个结点,并释放第一个结点的空间,具体代码如下:

int DeQueue(LinkQueue& Q)
{
	if (Empty(Q))    //Q.front == Q.rear为空
		return -1;
	QNode* p = new QNode;
	p = Q.front->next;
	int e = p->data;
	Q.front->next = p->next;
	if (Q.rear == p)
		Q.rear = Q.front;
	delete p;
	--(Q.length);
	return e;
}
(5)取队头元素

        链队队头是链队头结点的下一个结点元素,实现代码如下:

int GetFront(LinkQueue Q)
{
	if (Q.front == Q.rear)
		return -1;
	return  Q.front->next->data;
}
  • 14
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值