栈、队列的实现讲解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

栈和队列是两种特殊的数据结构,在日常的工作中我们也会经常使用他们,这篇文章将简单的带大家了解栈和队列的实现过程。


提示:以下是本篇文章正文内容,下面案例可供参考

一、栈是什么?

        就我的理解而言,栈和数组,变量一样,是用来存储数据的容器,从作用来说,二者并无异同,但是从结构上来说,栈是由像数组,变量等东西组合而来,是进阶的版本,其特性表现为后进先出。栈的实现并不是单一的,你可以使用不同的方法实现栈,本篇文章讲的是使用数组实现栈

1、栈的特性

        为什么说栈是一种特殊的数据结构,我的理解就是其特殊的数据处理方式,它与其他的常见的数据结构不同,我们平常见到的,诸如数组,变量等,无非就是按照顺序放入或拿出数据 ,遵循先后顺序,而栈不同,栈的特殊就体现在了其处理数据时,遵循的是后入先出的顺序。

2、栈的结构

        我们根据栈的特性来选取不同的方法用以实现栈的基本结构,栈的特性是后入先出,就像是我们买的抽纸一样。我们这里使用数组来栈的数据存储,而为了更好的管理数据,也为了后面如果增加数据时,以防栈的空间不足,我们需要扩容,导致栈的指针会发生改变,这里采用结构体的方式来更新栈的空间。

//栈的存储结构

typedef int Datatype;
struct Stack
{
	Datatype* a;

}typedef ST;

3、栈的功能

一个数据结构的基本功能无非就是对数据的增加,删除,获取目标元素,而栈当然同样具有这些基本功能。

void init(ST* sp);                        //初始化栈
void Destory(ST* sp);                     //销毁
void push(ST* sp, Datatype data);        //压栈,添加数据
void pop(ST* sp);                        //出栈,删除数据
bool enpty(ST* sp);                      //判空,判断栈是否为空
Datatype Datatop(ST* sp);                //获取栈顶元素

 当然为了我们更方便的实现这些基本功能,我们需要对结构体增加一些小东西,以方便我们快速操纵数据。。。。。

typedef int Datatype;
struct Stack
{
	Datatype* a;            //存储空间
	Datatype top;           //栈顶位置,方便获取栈顶元素
	Datatype spacesize;     //栈的空间大小,方便扩容

}typedef ST;
 初始化栈

        我们这里完成了栈的基本框架了,现在我们需要开始初始化栈。

        在我们的结构体中,我们将数组的地址给以空值NULL,栈大小赋值为0;

        注意看这里的top,我们平常的思想是给以0,但是大家仔细看,如果我们给top赋值为0,那么随着数据的push,top向后挪动,这时,top所指的位置并不是栈顶位置,而是栈顶元素的下一个位置,

 入栈(push)

在上面我们已经写好了栈的基本结构了,也初始化了,现在我们来实现数据的添加,首先我们每添加一个数据,栈顶随之变化,top将随之向后挪动一位,这是功能的实现,而我们还需要考虑特殊情况,如果栈的空间不够大了,我们则需要扩容,而扩容触发的条件就是当top=spacesize,当top挪动到最后一个空间时,栈将要满,我们就要扩容。

void push(ST* sp,Datatype data)
{
	assert(sp);
	if (sp->top==sp->spacesize)
	{
		//扩容
		Datatype newspacesize =sp->spacesize==0 ? 4:sp->spacesize*2;
        //刚开始开辟空间时,栈的空间大小为0,而我们一个一个的扩容太麻烦,故而采用二倍扩容
        //但是这样的话,要求初始空间不能为0,因为原空间大小*扩容倍数!=0
		Datatype*copy =(Datatype*) realloc(sp->a, sizeof(Datatype) * newspacesize);
		if (copy==NULL)
		{
			exit(1);
			printf("realloc fail!");
		}
		sp->a = copy;
		sp->spacesize = newspacesize;
	}
	sp->a[sp->top] = data;
	++sp->top;
}

 

 出栈(pop)

出栈的时候,我们平时的想法是将空间内的数据给0,或者NULL,这里其实还有其他的选择,我们是用数组来实现栈的,所以我们访问数据时也是通过下标的方式来实现,所以,我们只需要将top往前挪动一位即可实现无法访问数据,当然这里我们也需要考虑特殊情况,当我们的栈是空的时候,我们当然不能再次删除数据,所以,这里我们使用assert来断言一下top必须大于0.

void pop(ST* sp)
{
	assert(sp);
	assert(sp->top > 0);
	--sp->top;

}

 

获取栈顶元素(datatop)

        这个功能其实是非常好实现的,我们的top记录的是栈顶元素的下一个位置,所以我们只需要返回top上一个位置的元素即可。

Datatype Datatop(ST*sp)
{
	return sp->a[sp->top-1];

}

栈的销毁(Destory)

由于我们使用的是动态开辟的栈,所以为防止内存泄漏,我们需要将栈的空间释放掉

void Destory(ST* sp)
{
	assert(sp);
	free(sp->a);
	sp->a = NULL;
	sp->spacesize = 0;
	sp->top = 0;

}
 

小结:

栈的功能能说到底还是由我们这些开发人员来设计的,如果你觉得还想添加一点什么功能,也可以自己去实现,我这里给大家写了些其他的功能:

判空:

bool enpty(ST*sp)
{
	return sp->top == 0;

}

 

二、队列是什么

1.队列的特性

        与栈一样具有特殊之称的队列,其特性表现为数据的先进先出,类似于现实中的抽号机一样,先抽的人,先走,后抽的后走

2.队列的结构

同样,我们可以采用不同的方式来实现队列,这里我们使用的是链表的方式实现。

这里和大家详细说一下链表的优势所在,在链表中,对于队列的先进先出的特性我们可以使用头删的方式快捷实现,就算是数据插入尾部,我们也只需要使用一个尾指针记录一下就行了,但是我们使用数组的话,会很麻烦的,就单说一个数据的删除吧,你把头位置的数据删除了,后面的数据还需要向前挪动,很麻烦,所以我推荐使用链表来实现。

这里我们先设计了链表每个节点的结构,并且为了方便我们操作数据,我们将多定义一个结构体,用以存储链表的首个节点和尾节点,同时定义一个_size用于统计数据个数

#pragma once
typedef int QDataType;
// 链式结构:表示队列 
typedef struct QListNode
{
	struct QListNode* _next;    //下一个节点
	QDataType _data;            //所存储的数据
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* _front;            //头节点
	QNode* _rear;            //尾节点
	QDataType _size;          //元素个数
}Queue;

 

 3、链表的功能

        同栈的功能差不多

// 初始化队列 
void QueueInit(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空 
int QueueEmpty(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);

 

 初始化(QueueInit)

同栈一样,我们将队列的首尾指针置空,而节点的开辟我们放在push中一同处理。 

// 初始化队列 
void QueueInit(Queue* q) 
{
	q->_front = NULL;
	q->_rear = NULL;
	q->_size = 0;
}

 

入队(QueuePush)

        入队我们采取尾插的方式实现,首先我们需要开辟节点,因为我们只是初始化了控制链表的指针,现在还没有一个节点。这里我们需要注意,刚开始的时候我们的队列为空,则将开辟的地址交给记录首尾节点的指针就好,后面随着数据的push,我们的尾节点将跟着改变,而头节点也会随着pop而改变。

void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* newloca = (QNode*)malloc(sizeof(QNode));
	if (newloca == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newloca->_data = data;
	newloca->_next = NULL;
	if (q->_rear==NULL)         //如果尾指针为空,则表示这时队列为空
	{
		q->_front = q->_rear = newloca;    //直接将开辟的地址给记录首尾节点的指针
	}
	else    //队列不为空
	{
		q->_rear->_next = newloca;    //链接节点
		q->_rear = newloca;            //更新尾节点

	}
	q->_size++;
}

出队列(QueuePop) 

出队列的实现并不难,这里也没什么好讲的,我们只需要注意当队列为空时,单独拿出来判断。这里其实还有一个小细节,注意看q的值有可能是NULL,但是我们开头使用了assert断言,也就断绝了空指针的访问。

void QueuePop(Queue* q)
{	
	assert(q);
	if (q->_front==q->_rear)
	{
		q->_rear = NULL;
	}
	QNode* copy = q->_front->_next;
	free(q->_front);
	q->_front = copy;
	q->_size--;
}
获取队头元素(QueueFront) 

        由于我们是直接记录了头结点的地址,所以我们直接返回即可。

QDataType QueueFront(Queue* q)
{
	assert(q);
	return q->_front->_data;
}

获取队尾元素(QueueBack)

        尾指针也是同理,返回即可。

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

 

获取队列中有效元素个数(QueueSize)

        上面我们使用push或pop时,size也在记录着个数,返回size即可

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

检测队列是否为空(QueueEmpty)
// 检测队列是否为空
int QueueEmpty(Queue* q)
{
	assert(q);
	return q->_size;
}
销毁队列(QueueDestroy)

        这里我们直接复用了判空函数来实现

// 销毁队列 
void QueueDestroy(Queue* q)
{
	while (QueueEmpty(q))
	{
		if (q->_front == q->_rear)
		{
			q->_rear = NULL;
		}
		QNode* copy = q->_front->_next;
		free(q->_front);
		q->_size--;
		q->_front = copy;
	}
}
 
 

总结

数据结构的功能多种多样,具体要什么功能要看你自己怎么想,大家也可以自己再添加一些功能进去,好了,本篇也就讲完了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值