C语言编程数据结构栈与队列的全面讲解示例教程

目录
  • 一、栈的表示和实现
    • 1栈的概念和结构
    • 2栈的初始化
    • 3压栈(栈顶插入一个数据)
    • 4出栈(栈顶删除一个数据)
    • 5取栈顶元素
    • 6取栈顶元素
    • 7判断栈是否为空
  • 二、队列的表示和实现
    • 1队列的概念及结构
    • 2队列的实现
    • 3队列初始化
    • 4入队(队尾插入一个数据)
    • 5出队(队头删除一个数据)
    • 6取队头数据
    • 7取队尾数据
    • 8计算队列中数据个数
    • 9判断队列是否为空
    • 10销毁队列
  • 总结

一、栈的表示和实现

1栈的概念和结构

栈:一种特殊的线性表(逻辑上数据是连着放的),其只允许在固定的一端进行插入和删除操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵循后进先出的原则。
压栈:栈的插入操作叫作进栈/压线/入线,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。

(图片来自比特就业课)

先入后出就类似与你装手枪弹夹,你先放入的子弹会在弹夹底部,最后放入的子弹是在弹夹顶部,你开手枪是先打出弹夹顶端的子弹。
我们这里先定义一个栈的类型:

typedef int STDataType;//方便将来如果需要其他类型的数据,可以直接修改int的类型
typedef struct Stack
{
	STDataType*a;//栈底指针
	int top;//栈顶标号
	int capacity;//容量
}ST;

本文介绍以顺序表(数组)实现栈,由此,所谓的入栈压栈也不过是顺序表的尾插尾删,如果读者想以链表实现也是可以的,方法不唯一。

2栈的初始化

关于栈的初始化:我们这里以容量大小4的栈为例,刚开始因为栈里没有数据我们用top=0标记,需要注意的是:传过来的栈底指针不能为空,开辟空间时万一没有开辟成功返回一个空指针也要丢弃。

#include<assert.h>
#include<stdlib.h>//malloc函数头文件
void StackInit(ST*ps)//栈初始化
{
	assert(ps);//防止传过来的指针是空指针
	ps->a = (STDataType)malloc(sizeof(STDataType) * 4);//malloc开辟一块空间出来,由STDataType类型进行管理
	//malloc,及后文出现的realloc、free函数详情见笔者动态内存管理文章
	if (ps->a == NULL)
	{
		printf("realloc fail\n");
		exit(-1);//终止程序
	}
	ps->capacity=4;
	//这里以开辟4个数据容量大小的栈为例,你也可以写其他数字
	//如果压栈的时候内存不够,可以在后面提到的压栈函数里面进行扩容
	ps->top = 0;//刚开始栈里没有值时,用top=0标记,后续每放入一个top++
	//关于top详细用法请往下看1.3压栈部分
}

3压栈(栈顶插入一个数据)

如上图,我们现在开辟了一块空间,a和top都在栈顶,我们往里面入一个数据1

1进入栈之后,1就是栈顶元素了,那如果我想继续入栈,就是要在top的位置放入一个数据让新放入的数据成为新的栈顶元素,然后以此类推,每次入一个元素,top++,top不是表示栈顶元素位置,而是栈顶元素下一个位置。

#include<assert.h>//assert函数头文件
#include<stdlib.h>//realloc函数头文件
void StackPush(ST*ps, STDataType x)//栈顶插入数据(入栈)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		STDataType*tmp = realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
		//realloc是在原先开辟的空间上继续往后开辟一块空间,详细见笔者动态内存文章
		//扩容一般扩2倍
		if (tmp == NULL)//扩容失败(比如内存已经不够你再开辟空间了)会返回空指针
		{
			printf("realloc fail\n");
			exit(-1);//终止程序
		}
		else
		{
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}
	ps->a[ps->top] = x;//a是一个指针,a[x]==*(a+x)
	ps->top++;
}

4出栈(栈顶删除一个数据)

出栈非常简单,我们以下图为例:

图中我们栈里已经存放了4个数据1、2、3、4,那我们现在要进行出栈操作,也就是删除4怎么办?直接top–即可

到这里肯定会有小伙伴有疑问,凭什么你top–一下就是出栈了,你4都没删除啊?解释如下:我们在1.3压栈部分就说过“top不是表示栈顶元素位置,而是栈顶元素下一个位置”,那现在我top在4这里,说明3才是真正的栈顶元素,4已经不在栈的管辖范围内了。

void StackPop(ST*ps)//栈顶删除数据(出栈)
{
	assert(ps);
	assert(ps->top > 0);//栈空了,调用Pop,直接中止程序报错
	ps->top--;
}

关于ps->top > 0,是这样的:假设你原先栈里没有有一个元素

你top–就是往下访问未知的领域了,这是非常严重的问题,所以我们这里用assert断言一下,防止栈里什么元素也没有让top标记到了未知领域。(再次强调一下:top不是表示栈顶元素位置,而是栈顶元素下一个位置)

5取栈顶元素

STDataType StackTop(ST*ps)//取栈顶数据
{
	assert(ps);
	assert(ps->top > 0);//栈空了,调StackTop,直接中止程序报错
	return ps->a[ps->top - 1];//top是栈顶元素下一个位置,top-1是栈顶元素
	//a[m]==*(a+m)
}

这里的思路和1.4几乎一样,也要防止栈里什么也没有的情况,然后正常返回栈顶元素即可,因为top是栈顶元素下一个位置,top-1是栈顶元素,然后你正常写就行,这里的
a[ps->top - 1]你也可以写成*(a+(ps->top - 1)),这个写法读者可参加笔者以前的指针文章,这里不再赘述。

6取栈顶元素

int StackSize(ST*ps)//栈的数据个数
{
	assert(ps);
	return ps->top;
}

这个就更简单了,直接返回top的值就可以了,为什么呢,我们看两张图即可
图一:

图二:

图一是压栈前,没有一个元素,top=0,图二是压栈后,top++,top=1。同样的以此类推,我们每加入1个元素,top++,每减去一个元素,top–。元素个数永远=top值,所以我们这里的函数直接返回top值即可。

7判断栈是否为空

int StackEmpty(ST*ps)//判断栈是不是空
{
	assert(ps);
	return ps->top == 0;
}

由1.6知,我们的top值是和栈里元素个数一样的,所以我们直接return ps->top == 0;即可,如果ps->top == 0这个表达式成立表达式值为1,反之为0。

二、队列的表示和实现

1队列的概念及结构

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

(图片来自比特就业课)

类似你走人工通道那样,你排在前面的,你先出去

2队列的实现

由于队列的队头出,队尾入,非常类似单链表的头删和尾插,我们这里介绍以单链表实现队列,如下图,现有3个节点的单链表:

比如现在,我要队头出一个,也就是把单链表节点第一个节点删掉(头删)

或者队尾入一个,也就是单链表的尾插

代码如下(示例):

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode*next;
	QDataType data;
}QNode;//这里和单链表定义一样,有需要的可以看一下笔者之前的单链表文章
typedef struct Queue//定义一个结构体存储头节点地址和尾节点地址,方便后面头删和尾插
{
	QNode*head;
	QNode*tail;
}Queue;

3队列初始化

代码如下(示例):

void QueueInit(Queue*pq)队列初始化,pq是一个结构体指针
{
	assert(pq);//判断pq是否是空指针
	pq->head  = NULL;
	pq->tail = NULL;
}

4入队(队尾插入一个数据)

void QueuePush(Queue*pq, QDataType x)//入队(队尾入)
{
	assert(pq);
	QNode*newnode = (QNode*)malloc(sizeof(QNode));//开辟出一块空间给newnode
	if (newnode == NULL)//有可能剩余内存不够开辟空间,malloc开辟失败会返回空指针
	{
		printf("开辟空间失败\n");
		exit(-1);//退出程序
	}
	newnode->data = x;
	newnode->next = NULL;
	if (pq->tail == NULL)//原先队列里没有任何数据,头和尾指针都指向NULL
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

关于下面if这段代码解释如下:

    if (pq->tail == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}

原先队列里没有任何数据,头和尾指针都指向NULL

当你往队列里面放了一个数据,头和尾指针是指向新的数据(头和尾指针仍指向一个)

如果你想再加1个节点,你首先得把两节点连上,也就是pq->tail->next = newnode;(没接上之前tail还指向第一个节点),接上之后tail指针指向第二节点

5出队(队头删除一个数据)

void QueuePop(Queue*pq)//出队(队头出)
{
	assert(pq);
	assert(pq->head);//要出队,队里也要有数据可以出
	//原队列只有1个数据
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	//原队列有多个数据
	else
	{
		QNode*next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

关于出队,这里要分2种情况讨论:

1.原队列只有1个数据

只有一个数据的时候,头和尾指针都指向1节点,他们的next是空指针,所以我们以这个条件为判断。又因为要出队(队头删除一个数据),因为只有一个节点,也就是把这个节点给删掉,我们用free函数释放掉head指向的空间。释放完,那块空间已经还给内存了,这时你的头和尾指针就不能指向那块空间了,我们用空指针赋值。
2.原队列有多个数据

我们现在要出队(队头删除一个数据),也就是把1节点删掉,我们先创建一个变量next找到2节点位置,然后free掉1节点的空间

1节点空间free掉之后,把next(指向2节点)赋给head,让头指针指向2节点。

6取队头数据

QDataType QueueFront(Queue*pq)//取队头数据
{
	assert(pq);
	assert(pq->head);
	return pq->head->data;
}

7取队尾数据

QDataType QueueBack(Queue*pq)//取队尾数据
{
	assert(pq);
	assert(pq->head);
	return pq->tail->data;
}

8计算队列中数据个数

int QueueSize(Queue*pq)//队内数据个数
{
	assert(pq);
	int size = 0;
	QNode*cur = pq->head;
	while (cur)//cur!=NULL进行循环,NULL是遍历完tail之后出现的
	{
		size++;
		cur = cur->next;
	}
	return size;
}

9判断队列是否为空

bool QueueEmpty(Queue*pq)
{
	assert(pq);
	return pq->head == NULL;
}

这一般是作为一个辅助函数来使用,bool 就是用来判断真假的数据类型,如果表达式:pq->head == NULL成立返回true,反之返回false

10销毁队列

void QueueDestory(Queue*pq)//队列销毁
{
	assert(pq);
	QNode*cur = pq->head;
	while (cur)//cur!=NULL进行循环,NULL是遍历完tail之后出现的
	{
		QNode*next = cur->next;
		free(cur);//free是释放指向的空间,指针还是在的
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

如上图,我们创建一个变量cur并用head指针赋值。然后找到第二个节点,用cur->next标记,标记完我们释放掉cur(释放的第一个节点空间,指针还是在的),释放完,cur开始遍历第二个节点,也就是我们先前用next标记的空间。。。剩下的以此类推。cur!=NULL进行循环,NULL是遍历完tail之后出现的,当循环不进行说明已经遍历完了尾指针指向的节点,头和尾节点已经不需要了,我们用空指针赋值。

总结

本文介绍了栈和队列的相关原理及各个接口函数,内容较多,知识点量大,希望读者耐心学习,相信你一定会有所收获,祝读者学习愉快~更多关于C语言数据结构栈与队列的资料请关注我们其它相关文章!

(0)

相关推荐

  • C语言基于考研的栈和队列

    目录 栈 栈的基本操作 三角矩阵 总结 栈 栈的基本操作 InitStack(&S):初始化 StackEmpty(S):判空,空则true,非空则false Push(&S,x):入栈 Pop(&S,&x):出栈,并用x返回元素内容 GetTop(S,&x):读栈顶元素 DestroyStack(&S):销毁并释放空间 栈是一种受限的线性表,只允许在一端操作 栈若只能在栈顶操作,则只可能上溢 采用非递归方式重写递归时,不一定要用栈,比如菲波那切数列只要用循

  • 深入浅析C语言中堆栈和队列

    1.堆和栈 (1)数据结构的堆和栈 堆栈是两种数据结构. 栈(栈像装数据的桶或箱子):是一种具有后进先出性质的数据结构,也就是说后存放的先取,先存放的后取.这就如同要取出放在箱子里面底下的东西(放入的比较早的物体),首先要移开压在它上面的物体(放入的比较晚的物体). 堆(堆像一棵倒过来的树):是一种经过排序的树形数据结构,每个结点都有一个值.通常所说的堆的数据结构,是指二叉堆.堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆.由于堆的这个特性,常用来实现优先队列,堆的存取是随意,

  • C语言数据结构链表队列的实现

    C语言数据结构链表队列的实现 1.写在前面 队列是一种和栈相反的,遵循先进先出原则的线性表. 本代码是严蔚敏教授的数据结构书上面的伪代码的C语言实现代码. 分解代码没有包含在内的代码如下: #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 typedef int QElemtype; typedef int status; 2.代码分解 2.1对队列和节点的结构定义 typedef struct

  • C语言编程数据结构的栈和队列

    目录 栈 数组实现 标题全部代码 Stack_array.c Stack_array.h 初始化数组栈 满栈后扩容 是否为空栈 压栈和退栈 链表实现 stack_chain.h stack_chain.c 整个压栈流程 整个弹栈流程 出栈情况 队列 队列的实现 queue_chain.h queue_chain.c 一个结构体类型用于维护这个队列 概念流程图 入队列的实现 出队列的实现 是否空队 栈 栈是一种以后进先出为顺序对对象进行添加或删除的数据结构 对栈进行形象记忆就像是桌子上的一堆书或一

  • C语言编程数据结构栈与队列的全面讲解示例教程

    目录 一.栈的表示和实现 1栈的概念和结构 2栈的初始化 3压栈(栈顶插入一个数据) 4出栈(栈顶删除一个数据) 5取栈顶元素 6取栈顶元素 7判断栈是否为空 二.队列的表示和实现 1队列的概念及结构 2队列的实现 3队列初始化 4入队(队尾插入一个数据) 5出队(队头删除一个数据) 6取队头数据 7取队尾数据 8计算队列中数据个数 9判断队列是否为空 10销毁队列 总结 一.栈的表示和实现 1栈的概念和结构 栈:一种特殊的线性表(逻辑上数据是连着放的),其只允许在固定的一端进行插入和删除操作.

  • Java语言实现数据结构栈代码详解

    近来复习数据结构,自己动手实现了栈.栈是一种限制插入和删除只能在一个位置上的表.最基本的操作是进栈和出栈,因此,又被叫作"先进后出"表. 首先了解下栈的概念: 栈是限定仅在表头进行插入和删除操作的线性表.有时又叫LIFO(后进先出表).要搞清楚这个概念,首先要明白"栈"原来的意思,如此才能把握本质. "栈"者,存储货物或供旅客住宿的地方,可引申为仓库.中转站,所以引入到计算机领域里,就是指数据暂时存储的地方,所以才有进栈.出栈的说法. 实现方式是

  • C语言 表、栈和队列详解及实例代码

    C语言 表.栈和队列详解 表ADT 形如A1,A2,A3-An的表,这个表的大小为n,而大小为0的表称为空表,非空表中,Ai+1后继Ai,Ai-1前驱Ai,表ADT的相关操有PrintList打印表中的元素:CreateEmpty创建一个空表:Find返回关键字首次出现的位置:Insert和Delete从表的某个位置插入和删除某个关键字. 对表的所有操作都可以通过使用数组来实现,但在这里使用链表的方式来实现.链表(linked list)由一系列不必在内存中相连的结构组成,每个结构均含有元素和指

  • C语言编程数据结构线性表之顺序表和链表原理分析

    目录 线性表的定义和特点 线性结构的特点 线性表 顺序存储 顺序表的元素类型定义 顺序表的增删查改 初始化顺序表 扩容顺序表 尾插法增加元素 头插法 任意位置删除 任意位置添加 线性表的链式存储 数据域与指针域 初始化链表 尾插法增加链表结点 头插法添加链表结点 打印链表 任意位置的删除 双向链表 测试双向链表(主函数) 初始化双向链表 头插法插入元素 尾插法插入元素 尾删法删除结点 头删法删除结点 doubly-Linked list.c文件 doubly-Linkedlist.h 线性表的定

  • C语言 浅谈栈与队列的定义与操作

    目录 栈的定义 栈的实现 前置 初始化栈 栈的销毁 栈的插入 出栈的操作 取栈顶元素 栈的大小 队列的定义 队列的基本操作 队列的初始化 队列的销毁 队列的插入 队列的删除 队列的判空 取出队头元素 取出队尾元素 队列的大小 栈的定义 栈同样是一种线性表,它的特性是插入元素必须从后面插入,删除元素也是从后面删除,进行数据删除和插入的一端称为栈顶,另一端是栈底. 压栈-就是插入元素 出栈-就是删除元素 它可以用数组实现也可以用链表实现 但是用数组实现更好,因为链表的插入和删除要进行遍历,而数组可以

  • C语言分别实现栈和队列详解流程

    目录 什么是栈 栈的结构图示 栈的实现 创建栈的结构体 初始化栈 入栈 出栈 获取栈顶元素 获取栈中有效元素个数 检测栈是否为空 栈的销毁 什么是队列? 队列的实现 创建队列结构体 初始化队列 队尾入队列 队头出队列 获取队列头部元素 获取队列尾部元素 获取队列中元素个数 检测队列是否为空 销毁队列 什么是栈 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作.进行数据插入和删除的一端称为栈顶,另一端称为栈底.栈中的数据元素遵守先进后出LIFO(Last In First Out

  • C语言函数声明以及函数原型超详细讲解示例

    C语言代码由上到下依次执行,原则上函数定义要出现在函数调用之前,否则就会报错.但在实际开发中,经常会在函数定义之前使用它们,这个时候就需要提前声明. 所谓声明(Declaration),就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上. 函数声明的格式非常简单,相当于去掉函数定义中的函数体,并在最后加上分号;,如下所示: dataType functionName( dataType1 param1, dataType2 param2 ... ); 也

  • C语言编程数据结构带头双向循环链表全面详解

    目录 前言 一.什么是带头循环双向链表 二.链表初始化 三.链表接口函数 1.尾插 2.头插 3.头删 4.尾删 5.任意位置插入数据 6.任意位置删除数据 四.打印链表 总结 前言 上一篇数据结构专栏:C语言数据结构单链表接口函数全面讲解教程 我们介绍了单链表的各个接口函数,大家可能会发现单链表存在一些缺陷:比如它一个节点要存储数据+下一个节点地址,占用的空间要远多于顺序表:并且由于单链表是无法从后往前找的,如果你想进行尾删这样的操作,你必须从第一个节点往后找,你的时间复杂度一定是O(n).

  • Swift算法之栈和队列的实现方法示例

    一.概述 栈和队列在数据结构中是比较重要的一个数据结构. 其实对于栈和队列并不需要太深入的介绍,栈和队列的核心内容是栈是先进后出.队列是先进先出.在实际开发中有些场景也可能会用到,比如 APP 中用户可以撤销操作,比如下棋 APP 中的悔棋操作,返回上一步就是先进后出(后进先出),也就是栈的特性. 比如在售票 APP 中,为先下订单的用户先出票,就需要用到队列.当然这两个只是在简单场景下的情况,实际开发中情况可能更复杂,比如售票 APP 为会员用户优先出票等. 接下来就通过 Swift 去实现栈

随机推荐