【数据结构】栈和队列

前言

本篇博客我们来探讨下数据结构中的栈和队列,两种特殊的线性表,我们来看一下它们其中的奥妙吧

💓 个人主页:小张同学zkf

⏩ 文章专栏:数据结构

      若有问题 评论区见📝

🎉欢迎大家点赞👍收藏⭐文章

​​​​​​​

目录

1.栈

1.1栈的概念及结构

1.2栈的实现 

 2.队列

2.1队列的概念及结构

2.2队列的实现

2.3循环队列


1.栈

1.1栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。 栈中的数据元素遵守后进先出 LIFO Last In First Out )的原则。
压栈 :栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶
出栈 :栈的删除操作叫做出栈。 出数据也在栈顶

 “后进先出”的原则

栈有点像弹夹的结构,同一端放入和射出


1.2栈的实现 

了解了栈的结构,我们来用代码如何实现栈那?

栈的实现一般可以使用 数组或者链表实现 ,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。

分为三个文件

stack.h文件里存放栈的结构和函数声明

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
typedef int STDataType ;
#define N 10
typedef struct Stack
{
STDataType _a [ N ];
int _top ; // 栈顶
} Stack ;
// 支持动态增长的栈
typedef int STDataType ;
typedef struct Stack
{
STDataType * _a ;
int _top ; // 栈顶
int _capacity ; // 容量
} Stack ;
// 初始化栈
void StackInit ( Stack * ps );
// 入栈
void StackPush ( Stack * ps , STDataType data );
// 出栈
void StackPop ( Stack * ps );
// 获取栈顶元素
STDataType StackTop ( Stack * ps );
// 获取栈中有效元素个数
int StackSize ( Stack * ps );
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回 0
int StackEmpty ( Stack * ps );
// 销毁栈
void StackDestroy ( Stack * ps );

 stack.c文件里是所有函数的定义

初始化函数

这个函数将栈里的所有成员初始化,这里我们要考虑一下这个栈顶top初始值是0好还是-1好

根据图的分析,我们可以得知当top初始值为-1时此刻栈顶top就是最后一个元素,若top初始值为0,此刻栈顶top元素是栈顶数据的下一个位置,所以不同的初始化代表的意义不同,这点我们要注意 

// 初始化和销毁

void STInit(ST* pst)

{

assert(pst);

pst->a = NULL;

// top指向栈顶数据的下一个位置

pst->top = 0;

// top指向栈顶数据

//pst->top = -1;

pst->capacity = 0;

}

 入栈函数

这个函数就是将数组最后一个空的位置留给新来的元素,所以当我们假设top初始值为0时,此刻的top就是新元素的下标,也是前面元素的个数和,对了在入栈时我们要用top与capacity进行比较,看是否需要扩容,这一步与顺序表当时的步骤是一样的,代码如下

void STPush(ST* pst, STDataType x)

{

assert(pst);

// 扩容

if (pst->top == pst->capacity)

{

int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;

STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));

if (tmp == NULL)

{

perror("realloc fail");

return;

}

pst->a = tmp;

pst->capacity = newcapacity;

}

pst->a[pst->top] = x;

pst->top++;

}

出栈函数

这个函数我们只需将栈顶下标减一就行,不要忘了判断栈是否为空

void STPop(ST* pst)

{

assert(pst);

assert(pst->top > 0);

pst->top--;

}

获取栈顶元素的函数

若top为0,返回下标为top-1的元素

STDataType STTop(ST* pst)

{

assert(pst);

assert(pst->top > 0);

return pst->a[pst->top - 1];

}

判空函数

看top是否变回初始化的值

bool STEmpty(ST* pst)

{

assert(pst);

return pst->top == 0;

}

总个数函数

若top为0,返回top

int STSize(ST* pst)

{

assert(pst);

return pst->top;

}

销毁函数

free掉数组,将top与capacity赋予初始值

void STDestroy(ST* pst)

{

assert(pst);

free(pst->a);

pst->a = NULL;

pst->top = pst->capacity = 0;

}

栈的代码如下

stack.h

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

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

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

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

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

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

stack.c

#include"Stack.h"

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

	pst->a = NULL;
	// top指向栈顶数据的下一个位置
	pst->top = 0;

	// top指向栈顶数据
	//pst->top = -1;

	pst->capacity = 0;
}

void STDestroy(ST* pst)
{
	assert(pst);

	free(pst->a);
	pst->a = NULL;
	pst->top = pst->capacity = 0;
}

// 入栈  出栈
void STPush(ST* pst, STDataType x)
{
	assert(pst);

	// 扩容
	if (pst->top == pst->capacity)
	{
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		pst->a = tmp;
		pst->capacity = newcapacity;
	}

	pst->a[pst->top] = x;
	pst->top++;
}

void STPop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);

	pst->top--;
}


// 取栈顶数据
STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top > 0);

	return pst->a[pst->top - 1];
}

// 判空
bool STEmpty(ST* pst)
{
	assert(pst);

	return pst->top == 0;
}

// 获取数据个数
int STSize(ST* pst)
{
	assert(pst);

	return pst->top;
}

 text.c

#include<stdio.h>
#include<stdlib.h>
//
//int main()
//{
//	// 原地扩容
//	// 异地扩容
//	int* p1 = (int*)malloc(8);
//	printf("%p\n", p1);
//
//	int* p2 = (int*)realloc(p1, 80);
//	printf("%p\n", p2);
//
//	free(p2);
//
//
//	int i = 0;
//	int ret1 = ++i;
//
//	int ret2 = i++;
//
//
//
//	return 0;
//}

#include"Stack.h"

//int main()
//{
//	ST s;
//	STInit(&s);
//	STPush(&s, 1);
//	STPush(&s, 2);
//	STPush(&s, 3);
//	STPush(&s, 4);
//
//	printf("%d\n", STTop(&s));
//	STPop(&s);
//	printf("%d\n", STTop(&s));
//	STPop(&s);
//	STPop(&s);
//	STPop(&s);
//	STPop(&s);
//
//	//printf("%d\n", STTop(&s));
//
//	STDestroy(&s);
//
//	return 0;
//}

int main()
{
	// 入栈:1 2 3 4
	// 出栈:4 3 2 1  /  2 4 3 1
	ST s;
	STInit(&s);
	STPush(&s, 1);
	STPush(&s, 2);

	printf("%d ", STTop(&s));
	STPop(&s);

	STPush(&s, 3);
	STPush(&s, 4);

	while (!STEmpty(&s))
	{
		printf("%d ", STTop(&s));
		STPop(&s);
	}

	STDestroy(&s);
}

 2.队列

2.1队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出
FIFO(First In First Out) 入队列:进行插入操作的一端称为 队尾 出队列:进行删除操作的一端称为 队头

2.2队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

所以我们用链表的方法实现队列

还是三个文件,一个头文件,一个检测文件,一个函数定义的源文件

// 链式结构:表示队列
typedef struct QListNode
{
struct QListNode * _pNext ;
QDataType _data ;
} QNode ;
// 队列的结构
typedef struct Queue
{
QNode * _front ;
QNode * _rear ;
int size;
} Queue ;
// 初始化队列
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 );
// 检测队列是否为空,如果为空返回非零结果,如果非空返回 0
int QueueEmpty ( Queue * q );
// 销毁队列
void QueueDestroy ( Queue * q );

我们这里可以再定义一个队列的结构用来存放队头指针与队尾指针以及元素个数,方便我们避免函数参数使用过多以及避免函数的参数二级指针的使用

初始化函数

void QueueInit(Queue* pq)

{

assert(pq);

pq->phead = NULL;

pq->ptail = NULL;

pq->size = 0;

}

队尾入队列

创建一个新节点,然后尾插就行

void QueuePush(Queue* pq, QDataType x)

{

assert(pq);

QNode* newnode = (QNode*)malloc(sizeof(QNode));

if (newnode == NULL)

{

perror("malloc fail");

return;

}

newnode->next = NULL;

newnode->val = x;

if (pq->ptail == NULL)

{

pq->phead = pq->ptail = newnode;

}

else

{

pq->ptail->next = newnode;

pq->ptail = newnode;

}

pq->size++;

}

队头出队列 

我们进行链表头删操作就行,但这里注意一下,当头指针与尾指针同时指向一个元素时,也就是说队列只剩下一个元素时,再要删除,要注意将俩指针都置空,避免野指针的出现

void QueuePop(Queue* pq)

{

assert(pq);

assert(pq->size != 0);

/*QNode* next = pq->phead->next;

free(pq->phead);

pq->phead = next;

if (pq->phead == NULL)

pq->ptail = NULL;*/

// 一个节点

if (pq->phead->next == NULL)

{

free(pq->phead);

pq->phead = pq->ptail = NULL;

}

else // 多个节点

{

QNode* next = pq->phead->next;

free(pq->phead);

pq->phead = next;

}

pq->size--;

}

取队头函数,取队尾函数,取元素个数

分别返回队头指针指向的数据,返回队尾指针指向的数据,返回元素个数就行了,别忘了判断指针是否指向有效数据

QDataType QueueFront(Queue* pq)

{

assert(pq);

assert(pq->phead);

return pq->phead->val;

}

QDataType QueueBack(Queue* pq)

{

assert(pq);

assert(pq->ptail);

return pq->ptail->val;

}

int QueueSize(Queue* pq)

{

assert(pq);

return pq->size;

}

判空函数

若size为0,则此刻队列是空

bool QueueEmpty(Queue* pq)

{

assert(pq);

return pq->size == 0;

}

 销毁函数

用一个临时变量进行while循环遍历,遍历完,别忘了将指针置空,数据清为0

void QueueDestroy(Queue* pq)

{

assert(pq);

QNode* cur = pq->phead;

while (cur)

{

QNode* next = cur->next;

free(cur);

cur = next;

}

pq->phead = pq->ptail = NULL;

pq->size = 0;

}

以下是队列的所有代码

queue.h

#pragma once

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

typedef int QDataType;

typedef struct QueueNode
{
    struct QueueNode* next;
    QDataType val;
}QNode;

typedef struct Queue
{
    QNode* phead;
    QNode* ptail;
    int size;
}Queue;

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);

// 队尾插入
void QueuePush(Queue* pq, QDataType x);
// 队头删除
void QueuePop(Queue* pq);

// 取队头和队尾的数据
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);

int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

队尾插入
//void QueuePush(QNode** pphead, QNode** pptail, QDataType x);
队头删除
//void QueuePop(QNode** pphead, QNode** pptail);

queue.c

#include"Queue.h"

void QueueInit(Queue* pq)
{
    assert(pq);
    pq->phead = NULL;
    pq->ptail = NULL;
    pq->size = 0;
}

void QueueDestroy(Queue* pq)
{
    assert(pq);

    QNode* cur = pq->phead;
    while (cur)
    {
        QNode* next = cur->next;
        free(cur);

        cur = next;
    }

    pq->phead = pq->ptail = NULL;
    pq->size = 0;
}

// 队尾插入
void QueuePush(Queue* pq, QDataType x)
{
    assert(pq);

    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL)
    {
        perror("malloc fail");
        return;
    }

    newnode->next = NULL;
    newnode->val = x;

    if (pq->ptail == NULL)
    {
        pq->phead = pq->ptail = newnode;
    }
    else
    {
        pq->ptail->next = newnode;
        pq->ptail = newnode;
    }

    pq->size++;
}

// 队头删除
void QueuePop(Queue* pq)
{
    assert(pq);
    assert(pq->size != 0);

    /*QNode* next = pq->phead->next;
    free(pq->phead);
    pq->phead = next;

    if (pq->phead == NULL)
        pq->ptail = NULL;*/

    // 一个节点
    if (pq->phead->next == NULL)
    {
        free(pq->phead);
        pq->phead = pq->ptail = NULL;
    }
    else // 多个节点
    {
        QNode* next = pq->phead->next;
        free(pq->phead);
        pq->phead = next;
    }

    pq->size--;
}

QDataType QueueFront(Queue* pq)
{
    assert(pq);
    assert(pq->phead);

    return pq->phead->val;
}

QDataType QueueBack(Queue* pq)
{
    assert(pq);
    assert(pq->ptail);

    return pq->ptail->val;
}


int QueueSize(Queue* pq)
{
    assert(pq);

    return pq->size;
}

bool QueueEmpty(Queue* pq)
{
    assert(pq);

    return pq->size == 0;
}

test.c

#include"Queue.h"

int main()

{

Queue q;

QueueInit(&q);

QueuePush(&q, 1);

QueuePush(&q, 2);

printf("%d ", QueueFront(&q));

QueuePop(&q);

QueuePush(&q, 3);

QueuePush(&q, 4);

while (!QueueEmpty(&q))

{

printf("%d ", QueueFront(&q));

QueuePop(&q);

}

printf("\n");

return 0;

}


2.3循环队列

另外扩展了解一下,实际中我们有时还会使用一种队列叫循环队列。如后期操作系统中生产者消费者模型时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。


结束语 

栈和队列这一部分根据前面顺序表和链表的总结还是比较简单的,下片博客我们来探讨一下这部分的有关习题,这部分有点意思哦,那下篇博客见

OK,感谢观看!!!

评论 68
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值