数据结构:栈和队列

栈和队列是两种常见的数据结构,都是两种特殊的线性表。本篇文章将对栈和队列进行简要的分析。

1.栈

1.1栈的概念和结构

栈是一种特殊的线性表,与之前学过的顺序表和链表类似,但是栈有自己独有的特点:

栈是一种先进后出(Last In First Out,LIFO)的数据结构,类似于现实生活中的栈。栈有以下主要特点:

  • 只能在栈顶进行插入和删除操作。
  • 最后插入的元素首先被删除,即最先插入的元素最后被访问。
  • 只允许访问栈顶元素,不能访问其他位置的元素。
  • 栈的大小是固定的,只能存储有限数量的元素。

用图来表示栈的结构即为:

1.2栈的实现

1.2.1栈的实现的选择

栈一般可以用数组和链表来进行实现。使用数组实现比较方便,如下图:

先进入的数据摆在数组的前面,后进入的数据摆在数组的后面。入栈的时候向后添加数据,需要出栈的时候从右往左出栈(删除或导出数据)即可。

使用链表就不是非常方便,按照外面之前学过的单向链表:

我们可以一个一个数据地添加(入栈),但是如果要出栈的话就显得非常麻烦,可能还要用到双向链表,不如数组简单直接,所以我们在实现栈的时候用的是数组。在后续实现先进先出的队列时链表就显得更加便利了。

1.2.2栈的实现

我们有了实现顺序表和链表的经验,大致知道数据结构有哪些函数,以及大致的实现方法,这对其他数据结构的实现就很有帮助了。

第一步依旧是将代码分为3个文件:stack.h,stack.c,test.c .

首先我们写stack.h(定义实现栈所需要的结构体,头文件的包含以及完成函数的声明):

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


typedef int DataType;

typedef struct Stack
{
	DataType* p;
	int top;
	int Capacity;
}ST;


// 初始化和销毁
void STInit(ST* pst);
void STDestroy(ST* pst);

// 入栈  出栈
void STPush(ST* pst, DataType x);
void STPop(ST* pst);

// 取栈顶数据
DataType STTop(ST* pst);

// 判空
bool STEmpty(ST* pst);
// 获取数据个数
int STSize(ST* pst);

//打印函数
void STPrint(ST* pst);

下面我们针对stack.c中需要完成的函数逐一讲解。

(1)初始化函数。

首先要对维护栈的数据进行初始化:指针置为空,int top和int capacity均置为0.

// 初始化函数
void STInit(ST* pst)
{
	assert(pst);
	pst->p = NULL;
	pst->Capacity = 0;
	pst->top = 0;
}

(2)销毁函数同理,释放指针并且将指针置为空:

//销毁函数
void STDestroy(ST* pst)
{
	assert(pst);
	free(pst->p);
	pst->p= NULL;
	pst->top = pst->Capacity = 0;
}

(3)入栈、出栈

入栈就是数据的插入,数据插入数组非常简单,主要分为两个步骤:1.判断申请的空间是否够用,2.将数据插入数组。(用top代表栈顶数据所在数组元素的下标,每插入一个数据就将top++)

void STPush(ST* pst, DataType x)
{
	assert(pst);
	//扩容
	if (pst->Capacity == pst->top)   
{
    int NewCapacity = pst->Capacity == 0 ? 4 : 2 * pst->Capacity;
    DataType* tmp = (DataType*)realloc(pst->p, sizeof(DataType) * NewCapacity);

    pst->Capacity = NewCapacity;
    pst->p = tmp;
}
	
	
	pst->p[pst->top] = x;
	pst->top++;
}

出栈比入栈容易很多,不用进行空间的判定,直接将top--即可。

//出栈
void STPop(ST* pst) 
{
	assert(pst);
	pst->top--;
}

(4)取栈顶数据

假设我们现在已经插入了n个数据,top已经++了n次,此时top的值是n,但是栈顶的数据在数组中的下标是n-1,所以我们返回的下标应该是top-1:

// 取栈顶数据
DataType STTop(ST* pst)
{
	return pst->p[pst->top-1];
}

(5)获取数据函数和判空函数过于简单不再赘述

(6)打印函数

打印函数通过常规的遍历数组的方式即可。

//打印函数
void STPrint(ST*pst)
{
	assert(pst);
	while (pst->top)
	{
		printf("%d ", pst->p[pst->top]);
		pst->top--;
	}
}

最后简单写一个test.c测试以下效果即可:

#include"Stack.h"

int main()
{
	ST st;
	
    STInit(&st);
	STPush(&st,1);
	STPush(&st, 2);
	STPush(&st, 3);
	STPush(&st, 4);
	STPop(&st);
	STPrint(&st);
	STDestroy(&st);

	return 0;
}

输出结果如下:

2.队列

2.1队列的结构和概念

队列与栈的概念和结构恰好是相反的:

队列是一种线性数据结构,用于存储一系列元素。它遵循先进先出(First In First Out,FIFO)的原则,即最先进入队列的元素最先被取出。队列支持两种基本操作:入队(enqueue)和出队(dequeue)。

  1. 入队操作:将元素添加到队列的末尾,新元素成为队列中的最后一个元素。
  2. 出队操作:从队列的头部移除并返回队列中的第一个元素,同时更新队列的头部指针。

队列的结构示意图如下:(它就像一个单向行驶的隧道,先进去的车先出去)

我们的代码最后出来的效果如下:(先进先出,先增添的那一批数据先删除)

2.2队列的实现

队列的实现与栈的实现类似。

2.2.1Queue.h

头文件Queue.h先写由链表定义的队列:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdio.h>
#include<stdbool.h>
typedef int QDataType;

typedef struct QListNode
{
	struct QListNode* next;
	QDataType val;

}QNode;

typedef struct Queue
{
	QNode* Fir;
	QNode* Las;
	int size;
}Queue;



// 初始化队列
void QueueInit(Queue* q);
//打印函数
void QueuePrint(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue*q); 
// 销毁队列
void QueueDestroy(Queue* q);

注意事项:

队列是由链表构成的。

我们这里创建了两个结构体来修饰队列:

1.QListNode 结构体是用于创造链表的

2.Queue 结构体是用来方便管理队列的(包含前指针,后指针和记录队列大小的数据,两个指针分别指向队首和队尾)。

2.2.2Queue.c

下面就是Queue.c文件代码的实现:

主要的函数已经列在头文件中,.c文件完整代码如下:

#include"queue.h"

struct Queue q;

// 初始化队列
void QueueInit(Queue* q)
{
	q->Fir = NULL;
	q->Las = NULL;
	q->size = 0;
}

//打印函数
void QueuePrint(Queue* q)
{
	assert(q);
	QNode* Qrecord = q->Fir;
	while (q->Las - q->Fir)
	{
		printf("%d ",q->Fir->val);
		q->Fir = q->Fir->next;
	}
	printf("%d ", q->Fir->val);
	q->Fir = Qrecord;
}


// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* Newnode = (QNode*)malloc(sizeof(QNode));
	Newnode->val = data;
	Newnode->next = NULL;

	//判断队列是否有元素
	if (q->Las == NULL)
	{
		q->Fir = q->Las = Newnode;
	}
	else
	{
		q->Las->next = Newnode;
		q->Las = Newnode;
        //和下面的代码一个意思
        //q->Las->next = Newnode;
		//q->Las =q->Las->next;
	}
	q->size++;
}

// 队头出队列
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->size!=0);
	//一个节点
	if (q->size == 1)
	{
		free(q->Fir);
		q->Fir = q->Las = NULL;
	}
	//多个节点
	if(q->size >1)
	{
		QNode* QTmp = q->Fir;
		q->Fir = q->Fir->next;
		free(QTmp);
	}
	q->size--;
}

// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->Fir);
	return q->Fir->val;
}


// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->Las);
	return q->Las->val;
}


// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}


// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	if (q->size == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}


// 销毁队列
void QueueDestroy(Queue* q)
{
	assert(q);
	while (q->Las - q->Fir)
	{
		QNode* tmp = q->Fir;
		q->Fir = q->Fir->next;
		free(tmp);
		tmp = NULL;
	}
	q->Fir = q->Las = NULL;
	q->size = 0;
}

2.2.3test.c

主要代码写好了之后再创建测试文件检测一下,就是开头展示效果的代码:

#include"queue.h"

int main()
{
	Queue  q;
	QueueInit(&q);
	QueuePush(&q,1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePrint(&q);
	printf("\n");
	QueuePop(&q);
	QueuePrint(&q);
	printf("\n");
	QueuePush(&q, 5);
	QueuePush(&q, 6);
	QueuePush(&q, 8);
	QueuePrint(&q);
	printf("\n");
	QueuePop(&q);
	QueuePop(&q);
	QueuePrint(&q);
	QueueDestroy(&q);
	return 0;
}

步骤:首先是添加1,2,3,4入队列,打印一行;再删除一个元素(因为1是第一个进的所以删除1),打印一行; 再添加5,6,8三个元素,打印一行;再删除两次,打印一行。最后的效果如下:


感谢观看,如有错误请批评指出

  • 23
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值