数据结构————栈和队列

本文详细介绍了栈和队列的基本概念、定义、基本运算以及顺序和链式存储的实现方法。重点探讨了共享栈、循环队列和双端队列的特性,展示了这些数据结构在计算机科学中的重要性。
摘要由CSDN通过智能技术生成

栈和队列

目录

栈和队列

一.栈

1.栈的定义

2.栈的基本运算

3.栈的顺序存储以及基本运算的实现

4.共享栈

5.栈的链式存储以及基本运算的实现

二.队列

1.队列的定义

2.队列的基本运算

3.队列的顺序存储以及基本运算的实现

4.环形队列以及基本运算的实现

5.队列的链式存储以及基本运算的实现

6.双端队列


一.栈

1.栈的定义

        栈是一种只能在一端进行插入或删除操作的线性表。

栈顶:在表中进行插入和删除的一端。

栈底:表中的另一端就是栈底。固定的一端。

空栈:线性表中没有元素。

元素的插入被称为入栈和进栈。元素的删除被称为出栈和退栈。

 栈的特点:

        栈的主要特点就是" 先进后出 (First in last out)"(后进先出(last in first out))。即后进栈的元素先出栈。所以也叫做后进先出表。

栈的数学性质:

        n 个不同元素进栈,出栈元素不同的排列个数为   \frac{1}{n+1} C_{2n}^{n}

        

2.栈的基本运算

栈的基本操作
 InitStack(&s):  初始化栈,构造一个空栈
 DestoryStack(&s):  销毁栈,释放给栈s分配的内存空间
 StackEmpty(s):  判断栈是否为空,若栈为空,返回真,否则返回假
 Push(&s):  进栈,将元素e插入栈s中作为栈顶元素
 Pop(&s):  出栈,从栈s中删除栈顶元素,并将其赋值给e
 GetTop(s):  取栈顶元素,返回当前的栈顶元素,并将其赋值给e。

3.栈的顺序存储以及基本运算的实现

顺序栈的特点:


 栈空的条件:s->top ==-1;
 栈满的条件:s->top == Maxsize-1 ;  (data数组的最大下标)
 元素进栈:先将指针 s->top++ ;之后将元素 e 放在栈顶指针上
 元素出栈:先将元素取出  e=s->data[s->top] ;之后指针 s->top-- ;

 

顺序栈:

顺序栈的结构体:

#include <malloc.h>
#define ElemType int
#define Maxsize 50

typedef struct {
	ElemType data[Maxsize];
	int top;
}SqStack;

        从结构体定义发现,我们就会发现,栈和线性表的结构体几乎一致,线性表中定义的是 length 表示线性表的长度,而栈中定义的是 top 表示的是栈顶元素的位置。当然我们也可以通过 top 来得到栈中的元素个数。

 InitStack(&s):  初始化栈,构造一个空栈

void InitStack(SqStack*& s) {
	s = (SqStack*)malloc(sizeof(SqStack));
	s->top = -1;
}


 DestoryStack(&s):  销毁栈,释放给栈s分配的内存空间

void DestoryStack(SqStack*& s) {
	free(s);
}


 StackEmpty(s):  判断栈是否为空,若栈为空,返回真,否则返回假

bool StackEmpty(SqStack* s) {
	return (s->top == -1);
}


 Push(&s):  进栈,将元素e插入栈s中作为栈顶元素

bool Push(SqStack*& s, ElemType e) {
	if (s->top = Maxsize - 1) return false;
	s->top++;
	s->data[s->top] = e;
	return true;
}


 Pop(&s):  出栈,从栈s中删除栈顶元素,并将其赋值给e

bool Pop(SqStack*& s, ElemType& e) {
	if (s->top = -1) return false;
	e = s->data[s->top];
	s->top--;
	return true;
}


 GetTop(s):  取栈顶元素,返回当前的栈顶元素,并将其赋值给e。

	bool GetElem(SqStack* s, ElemType& e) {
		if (s->top == -1) return false;
		e = s->data[s->top];
		return true;
	}

4.共享栈

        如果需要使用两个类型相同的栈,会出现着这种情况,一个栈的空间已经满了,但是另一个栈的空间几乎没有使用。所以需要将这两个栈的空间结合使用起来,从而有了共享栈。

 共享栈就是利用栈中栈底元素不变的特性,两个栈共用一个一维数组空间,将两个栈中的栈底一个放在共享栈中的起始位置,一个放在共享栈中的终止位置,使两个栈顶向共享空间的中间延伸。

        共享栈的4个特性:

  • 栈空条件: 栈1空的条件是 top1 ==-1 ; 栈2空的条件是 top2==Maxsize
  • 栈满条件: top1 = top2 -1;
  • 元素 x 进栈的操作:栈1的进栈操作 top++;data[top1]=x          栈2的进栈操作 top--;data[top2]=x
  • 元素 x 出栈的操作:栈1 的出栈操作 x=data[top1];top--           栈2的出栈操作 x=data[top2]; top++

共享栈的结构体以及初始化:

// 共享栈的结构体
typedef struct {
	ElemType data[Maxsize];
	int top1;
	int top2;
}ShareStack;


// 初始化栈
void InitStack1(ShareStack*& s) {
	s = (ShareStack*)malloc(sizeof(ShareStack));
	s->top1 == -1;
	s->top2 == Maxsize;
}

        共享栈是为了更有效的利用存储空间,两个栈的空间相互调节,只有在整个存储空间被占满时才发生上溢。

存取数据的时间复杂度O(1)。所以对存取效率没有什么影响。

5.栈的链式存储以及基本运算的实现

链栈的特点:

  • 栈空的条件: s->next ==NULL
  • 栈满的条件:链栈中几乎不存在栈满的情况
  • 元素 e 进栈的操作:新建一个结点p,存放元素 e 。将结点 p 插入头结点之后。
  • 出栈的操作:取出首结点的 data 值并且将它删除

链栈的结构体:

#include <malloc.h>
#define ElemType int 


typedef struct linkNode {
	ElemType data;				// 数据域
	struct linkNode* next;		// 指针域
}LinkStNode;	

  InitStack(&s):  初始化栈,构造一个空栈

void InitStack(LinkStNode*& s) {
	s = (LinkStNode*)malloc(sizeof(LinkStNode));
	s->next = NULL;
}


 DestoryStack(&s):  销毁栈,释放给栈s分配的内存空间

void DestoryStack(LinkStNode*& s) {
	LinkStNode* pre = s;
	LinkStNode* p = s->next;
	while (p != NULL) {
		free(pre);
		pre = p;
		p = pre->next;
	}
	free(pre);
}


 StackEmpty(s):  判断栈是否为空,若栈为空,返回真,否则返回假

bool StackEmpty(LinkStNode* s) {
	return (s->next == NULL);
}


 Push(&s):  进栈,将元素e插入栈s中作为栈顶元素

bool Push(LinkStNode*& s, ElemType e) {
	LinkStNode* p;
	p = (LinkStNode*)malloc(sizeof(LinkStNode));
	p->data = e;
	p->next = s->next;
	s->next = p;
	return true;
}


 Pop(&s):  出栈,从栈s中删除栈顶元素,并将其赋值给e

bool Pop(LinkStNode*& s, ElemType& e) {
	LinkStNode* p;
	if (s->next == NULL) return false;
	p = s->next;
	e = p->data;
	s->next = p->next;
	free(p);
	return true;
}


 GetTop(s):  取栈顶元素,返回当前的栈顶元素,并将其赋值给e。

bool GetTop(LinkStNode*& s, ElemType& e) {
	if (s->next != NULL) return false;
	e = s->next->data;	// s表示的是头节点
	return true;
}

二.队列

1.队列的定义

        队列也是一种操作受限的线性表。其限制为仅允许在表的一端进行插入操作,在表的另一端进行删除操作。

队尾:进行插入的一端叫做队尾。

队头:进行删除的一端叫做对头。

向队列中插入新元素叫做入队或者进队,从队列中删除元素叫做出队和离队。

队列的特点: 

  队列的主要特点就是" 先进先出 (First in First out)"(后进后出(last in last out))。即先进队列的元素先出队列。所以也叫做先进先出表。

2.队列的基本运算

  • InitQueue(&Q):初始化队列,构造一个空队列Q
  • QueueEmpty(Q): 判断队列空,若队列 Q 为空返回 true,否则返回 false
  • EnQueue(&Q,x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾。
  • DeQueue(&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回
  • DestoryQueue(&Q):销毁队列,并释放队列空间。

3.队列的顺序存储以及基本运算的实现

顺序队列的特点:

  • 对空的条件:q->front == q->rear。
  • 队满的条件:q->rear == Maxsize-1
  • 元素 e 进队的操作:先将 q->rear++;然后将元素 e 放在数组的 rear 位置。
  • 出队的操作:先将 q->front++;之后取出 data 数组中 front 位置的元素。 

顺序队的结构体:

typedef struct {
	ElemType data[Maxsize];
	int front;			// 队头指针(队头删除元素)
	int rear;			// 队尾指针(队尾添加元素)
}SqQueue;

InitQueue(&Q):初始化队列,构造一个空队列Q

void InitQueue(SqQueue*& q) {
	q = (SqQueue*)malloc(sizeof(SqQueue));
	q->front = q->rear = -1;
}

DestoryQueue(&Q):销毁队列,并释放队列空间。

void DestoryQueue(SqQueue*& q) {
	free(q);
}

QueueEmpty(Q): 判断队列空,若队列 Q 为空返回 true,否则返回 false

bool QueueEmpty(SqQueue* q) {
	return (q->front == q->rear);
}

EnQueue(&Q,x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾。

bool EnQueue(SqQueue*& q, ElemType e) {
	if (q->rear == Maxsize - 1) return false;
	q->rear++;
	q->data[q->rear] = e;
	return true;
}

DeQueue(&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回

bool deQueue(SqQueue*& q, ElemType& e) {
	if (q->front == q->rear) return false;
	q->front++;
	e = q->data[q->front];
	return true;
}

        实现基本运算之后,我们根据顺序队列的两个条件可能会出现问题。队满的条件时 :q->rear == Maxsize-1;但是我们发现进队出队是两个伪指针在控制着,如果进队时 rear 不断后移,一直到 Maxsize-1 的下标出,但是在这过程中 font 也是一直在增加,使得元素一直出队。这就导致就算 rear== Maxsize-1;我们这个队列也不是满的,从而造成空间的浪费,这就是假溢出。

使用循环队列来解决假溢出的问题

4.循环队列以及基本运算实现

循环队列:

        在假溢出时 rear ==Maxsize-1时;我们可以发现另外一端还有很多空闲的位置,所以我们将数组的前端位置和后端位置连接起来,类似于循环链表,首尾相连。形成一个循环数组。

当首尾相连之后,当队尾指针 rear==Maxsize-1时;如果需要继续存储元素的话,那么就需要放在另一端的空位置上了。

队头指针 front 循环增1:front =(front+1)%Maxsize

队尾指针 rear 循环增1:rear = (rear+1)%Maxsize

循环队列的特点:

  • 初始时:q->front = q->rear=0;
  • 队头指针+1:q->front=(q->front+1)%Maxsize
  • 队尾指针+1:q->rear=(q->rear+1)%Maxsize
  • 队列长度:(rear--front+Maxsize)%Maxsize

队空条件:q->rear==q->front

对满条件:q->front=(q->rear+1)%Maxsize

InitQueue(&Q):初始化队列,构造一个空队列Q

void InitQueue(SqQueue*& q) {
    q = (SqQueue*)malloc(sizeof(SqQueue));
	q->front = q->rear = 0;
}

QueueEmpty(Q): 判断队列空,若队列 Q 为空返回 true,否则返回 false

bool QueueEmpty(SqQueue*& q) {
	return (q->front == q->rear);
}

DestoryQueue(&Q):销毁队列,并释放队列空间。

void DestoryQueue(SqQueue*& q) {
	free(q);
}

EnQueue(&Q,x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾。

bool EnQueue(SqQueue*& q, ElemType e) {
	if (q->front ==(q->rear + 1) % Maxsize) return false;
	q->rear = (q->rear + 1) % Maxsize;
	q->data[q->rear] = e;
	return true;
}

DeQueue(&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回

bool deQueue(SqQueue*& q, ElemType& e) {
	if (q->front == q->rear) return false;
	q->front=(q->front+1)%Maxsize;
	e = q->data[q->front];
	

5.队列的链式存储以及基本运算的实现

        队列中的数据元素的逻辑关系呈线性关系,所以队列可以像线性表那样采用链式存储结构。采用链式存储的队列叫做链队。

链队的特点:

  • 队空的条件:q->rear == NULL(q->front ==NULL)
  • 当队中只有一个数据元素时:q->rear == q->front 
  • 队满的条件:不考虑
  • 元素 e 进栈的操作:新建一个结点p,存放元素 e 。将结点 p 插入头结点之后。
  • 出栈的操作:取出首结点的 data 值并且将它删除

链队的结构体:

// 链栈的数据结点的结构体
typedef struct qnode {		
	ElemType data;
	struct qnode* next;
}DataNode;

// 链栈的头节点
typedef struct {
	DataNode* front;	// 指向队首结点
	DataNode* rear;		// 指向队尾结点
}LinkQueue;

InitQueue(&Q):初始化队列,构造一个空队列Q

void InitQueue(LinkQueue*& q) {
	q = (LinkQueue*)malloc(sizeof(LinkQueue));
	q->front = q->rear = NULL;
}

QueueEmpty(Q): 判断队列空,若队列 Q 为空返回 true,否则返回 false

bool QueueEmpty(LinkQueue *q){
	return (q->front == NULL);
}

EnQueue(&Q,x):入队,若队列 Q 未满,将 x 加入,使之成为新的队尾。

bool EnQueue(LinkQueue*& q,ElemType e) {
	DataNode* p;
	p = (DataNode*)malloc(sizeof(DataNode));		// 创建新节点并分配空间
	p->data = e;
	p->next = NULL;
	if (q->rear == NULL)			// 链栈为空的时候
		q->front = q->rear = p;
	else {
		q->rear->next = p;			// 将 p 结点放在 q->rear 的后面
		q->rear = p;				// 将 队尾结点指向 p
	}
}

DeQueue(&Q,&x):出队,若队列 Q 非空,删除队头元素,并用 x 返回

bool DeQueue(LinkQueue*& q,ElemType &e) {
	DataNode* p;
	if (q->rear == NULL) return false;
	p = q->front;		// 将需要删除的元素指向 p;
	if (q->front == q->rear)			// 当链表中只有一个元素
		q->front = q->rear = NULL;
	else {
		q->front = q->front->next;	
	}
	e = p->data;
	free(p);
	return true;
}

DestoryQueue(&Q):销毁队列,并释放队列空间。

void DestoryQueue(LinkQueue*& q) {
	DataNode* pre = q->front;	// pre指向链队的首结点
	DataNode* p;
	if (pre != NULL) {
		p = pre->next;
		while (p != NULL) {
			free(pre);
			pre = p;
			p = pre->next;
		}
		free(pre);
	}
	free(q);
}

6.双端队列

        双端队列指的是允许两端都可以进行删除和插入的队列。

双端队列的特点:

  • 前端进的元素排列在队列中的后端进的元素的前面,后端进的元素排列在前端进的元素的后面。
  • 无论是前端还是后端出队,先出的元素排列在后出的元素的前面。

输出受限的双端队列:

        允许一段进行插入和删除,但在另一端只允许插入的双端队列称为输出受限的双端队列。

输入受限的双端队列:

        允许一段进行插入和删除,但在另一端只允许删除的双端队列称为输入受限的双端队列。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值