C语言数据结构——栈和队列

目录

1、栈

1.1 栈的概念及结构

1.2 栈的基本操作

1.3 栈基本操作的实现

1.3.1 顺序表和链表的优缺点比较

1.3.2 动态顺序表实现栈的基本操作——源码

2、队列

2.1 队列的概念及结构

2.2 队列的基本操作

2.3 队列基本操作的实现

2.3.1单链表实现队列的基本操作——源码


1、栈

1.1 栈的概念及结构

概念: 

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

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。

出栈:栈的删除操作叫出栈。出数据也在栈顶。 

注意:

(1)通常称入栈(插入)、出栈(删除)的这一端为栈顶(top),另一端称为栈底(Bottom)。

(2)当线性表中没有元素时叫称为空栈。

(3)栈为后进先出(Last In First Out)的线性表,简称为LIFO表。

栈的结构: 如下图。

1.2 栈的基本操作

(1)StackInit(Stack* ps):构造一个空栈。

(2)StackEmpty(Stack* ps):判栈空。若为空栈,则返回TRUE,否则返回FALSE。

(3)StackPush(Stack* ps, StackDataType x):进栈。将元素 x 插入到栈顶。

(4)StackPop(Stack* ps):退栈。若栈非空,则将栈顶的元素删去。

(5)StackTop(Stack* ps):取栈顶的元素。若栈非空,则返回栈顶元素,但不改变栈的状态。

(6)StackSize(Stack* ps):栈的大小。也就是栈的元素个数。

(7)StackDestroy(Stack* ps):销毁栈。栈使用结束,就对栈进行销毁。

1.3 栈基本操作的实现

实现栈的基本运算选择的是顺序表呢?还是链表呢?两者都是可以实现的,但是为了选择更为优一些的方法。首先,来对比一下这两个的优缺点,再做抉择。 

1.3.1 顺序表和链表的优缺点比较

顺序表:

优点:

(1)支持随机访问(用小标访问)。需要随机访问的结构支持的算法可以很好的适用/

(2)CPU高速缓存命中率更高(不懂需要百度)。

缺点:

(1)头部中部插入删除时间效率低(原因:删除头部,还要把头部后面的全部数据往前移)。O(N)

(2)连续的物理空间,空间不够了以后需要增容。

        a.增容有一定程度消耗。

        b.为了避免频繁增容,一般我们都是按倍数去增容,用不完可能存在一定的空间浪费。

链表(带头双向循环链表):

优点:

(1)任意位置插入删除效率高。O(1)

(2)按需申请释放空间。

缺点:

(1)不支持随机访问。(不支持用下标访问)意味着:一些排序、二分查找等在这种结构不适合用。

(2)链表存储一个值,同时要存储链接指针,有一定的消耗。

(3) CPU高速缓存命中率更低(不懂需要百度)。

对比以上顺序表和链表的优缺点,基本上可以判断出顺序表实现栈更为优一些。栈的性质就是先进后出。假设顺序表和链表实现栈的结构如下图: 

 两者都是尾插和尾删。时间复杂度基本一致。链表(带头双向循环)唯一缺点就是CPU高速缓存命中率更低。基于这一缺点,这里我们是使用顺序表来实现栈的基本操作。

1.3.2 动态顺序表实现栈的基本操作——源码

 stack.h

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

typedef int StackDataType;//栈的数据类型

//顺序栈类型的定义----动态顺序表
typedef struct Stack
{
	StackDataType* data;	//栈的数据	
	int top;	//栈顶
	int capacity;	//栈的容量
}Stack;

//栈基本操作的接口函数
void StackInit(Stack* ps);//初始化栈---构造一个空栈
void StackDestroy(Stack* ps);//销毁栈
void StackPush(Stack* ps, StackDataType x);//栈顶插入一个元素---进栈
void StackPop(Stack* ps);//删除栈的元素---退栈/出栈
StackDataType StackTop(Stack* ps);//取栈顶的数据
int StackSize(Stack* ps);//栈的元素个数
bool StackEmpty(Stack* ps);//判空

stack.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "stack.h"

void StackInit(Stack* ps)//初始化栈---构造一个空栈
{
	assert(ps);
	ps->data = NULL;
	ps->top = 0;	// top=-1,top指向栈顶的数据;top=0,top指向栈顶的下一个
	ps->capacity = 0;//栈的初始容量
}

void StackDestroy(Stack* ps)//销毁栈
{
	assert(ps);
	free(ps->data);	//释放内存
	ps->capacity = 0;
	ps->top = 0;
}

void StackPush(Stack* ps, StackDataType x)//栈顶插入一个元素---进栈
{
	assert(ps);
	if (ps->capacity == ps->top)//开辟内存及其内存空间不足扩容
	{
		ps->capacity = ps->capacity == 0 ? 6 : ps->capacity * 2;
		StackDataType* temp = (StackDataType*)realloc(ps->data, sizeof(StackDataType) * ps->capacity);
		if (temp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);//开辟失败异常退出
		}
		ps->data = temp;
	}
	ps->data[ps->top] = x;//插入元素数据
	ps->top++;//指向栈顶元素的下一个
}

void StackPop(Stack* ps)//删除栈的元素---退栈/出栈
{
	assert(ps);
	assert(!StackEmpty(ps));//栈为空就不能删除了
	ps->top--;//开始删除
}

StackDataType StackTop(Stack* ps)//取栈顶的数据
{
	assert(ps);
	assert(!StackEmpty(ps));//当栈为空时不取出数据
	return ps->data[ps->top - 1];//因为top是指向栈顶数据的下一个,所以 top-1 才能取到栈顶的元素
}

int StackSize(Stack* ps)//栈的元素个数
{
	return ps->top;
}

bool StackEmpty(Stack* ps)//判空---判断栈是否为空
{
	assert(ps);
	//if (ps->top == 0)
	//{
	//	return true;
	//}
	//else
	//{
	//	return false;
	//}

	return ps->top == 0;//当 top初始化为-1是需要写为 return ps->top==-1;

}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"

void TestStack()
{
	Stack st;
	StackInit(&st);
	StackPush(&st, 1);//插入数据
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st,5);
	//后进先出原则:5 4 3 2 1
	//栈是不能像顺序表、链表那样直接随便取数据
	//取栈的数据只有一个方式,就是需要把栈顶的数据取出来了才能取下一个,才符合栈的性质

	while (!StackEmpty(&st))
	{
		printf("%d ", StackTop(&st));//先取栈顶的数据
		//取了栈顶的数据,想要取栈顶的下一个数据,把当前的栈顶的数据 pop 掉,就是删除掉
		//才能取下一个栈的数据
		StackPop(&st);
	}

	StackDestroy(&st);//销毁栈
}

int main()
{
	TestStack();
	return 0;
}

2、队列

2.1 队列的概念及结构

概念: 

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

注意:

(1)允许删除的一端称为队头(Front)。

(2)允许插入的一端称为队尾(Back)。

(3)当队列中没有元素时称为空队列。

(4)队列亦称做先进先出(First In First Out)的线性表,简称为 FIFO 表。

 队列的结构:如下图。

2.2 队列的基本操作

(1)QueueInit(Queue* pq):置空队列。构造一个空队列。

(2)QueueEmpty(Queue* pq):判队空。若队列为空,则返回真值,否则返回假值。

(3)QueuePush(Queue* pq, QueueDataType x):将元素 x 插入队列的队尾,此操作简称为入队。

(4)QueuePop(Queue* pq):若队列非空,则删去队列的队头元素,此操作简称为出队。

(5)QueueFront(Queue* pq):取队头元素数据。若队列非空,则返回队头元素,但不改变队列的状态。

(6)QueueBack(Queue* pq):取队尾元素。若队列非空,则返回队尾元素,但不改变队列的状态。

(7)QueueSize(Queue* pq):队列元素个数。

(8)QueueDestroy(Queue* pq):销毁队列。

2.3 队列基本操作的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。出一个队头还需要把后面的元素往前移,时间复杂度O(N)。基于顺序表的这一缺点,这里采用单链表实现队列。
入队:

出队:

2.3.1单链表实现队列的基本操作——源码

queue.h 

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

typedef int QueueDataType;//队列元素的数据类型

//链表队列类型定义
typedef struct QueueNode
{
	struct QueueNode* next;
	QueueDataType data;
}QueueNode;

typedef struct Queue
{
	QueueNode* head;	//队列头指针
	QueueNode* tail;	//队列尾指针

}Queue;

//队列基本操作的函数接口
void QueueInit(Queue* pq);//初始化队列--置空队列。构造一个空队列。
void QueueDestroy(Queue* pq);//销毁队列
void QueuePush(Queue* pq, QueueDataType x);//向队尾插入一个元素---入队列
void QueuePop(Queue* pq);//删除队头元素
QueueDataType QueueFront(Queue* pq);//取队头元素
QueueDataType QueueBack(Queue* pq);//取队尾元素
int QueueSize(Queue* pq);//队列元素个数
bool QueueEmpty(Queue* pq);//判空--判断队列是否为空

 queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"queue.h"

void QueueInit(Queue* pq)//初始化队列--置空队列。构造一个空队列。
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}

void QueueDestroy(Queue* pq)//销毁队列
{
	assert(pq);
	QueueNode* current = pq->head;//记录头指针
	while (current)
	{
		QueueNode* next = current->next;//记录头指针的下一个结点的指针
		free(current);
		current = next;
	}
	pq->head = pq->tail = NULL;//释放结束将头尾指针置空
}

void QueuePush(Queue* pq, QueueDataType x)//向队尾插入一个元素---入队列
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	newnode->data = x;
	newnode->next = NULL;
	//尾插
	if (pq->head == NULL)//第一次入队
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;//更新尾指针
	}
}

void QueuePop(Queue* pq)//删除队头元素
{
	assert(pq);
	assert(!QueueEmpty(pq));
	//开始头删
	QueueNode* headNext = pq->head->next;
	free(pq->head);
	pq->head = headNext;
	if (pq->head == NULL)//当把队列删空了,不仅要把 head 置空,还要把 tail 置空
	{
		pq->tail = NULL;
	}
}

QueueDataType QueueFront(Queue* pq)//取队头元素
{
	assert(pq);
	assert(!QueueEmpty(pq));//队列非空就取
	return pq->head->data;
}

QueueDataType QueueBack(Queue* pq)//取队尾元素
{
	assert(pq);
	assert(!QueueEmpty(pq));//队列非空就取
	return pq->tail->data;
}

int QueueSize(Queue* pq)//队列元素个数
{
	//遍历队列,进行计数,返回出结果
	assert(pq);
	int count = 0;
	QueueNode* current = pq->head;
	while (current)
	{
		++count;
		current = current->next;
	}

	return count;
}

bool QueueEmpty(Queue* pq)//判空--判断队列是否为空
{
	assert(pq);
	return pq->head == NULL;//队列为空就为真,否则为假
}

 test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"queue.h"

void TestQueue()
{
	Queue qu;
	QueueInit(&qu);
	QueuePush(&qu, 1);//入队
	QueuePush(&qu, 2);
	QueuePush(&qu, 3);
	QueuePush(&qu, 4);
	QueuePush(&qu, 5);

	//出队-----先进先出----1 2 3 4 5
	//取队头的元素,想要取出队头的下一个元素,将队头 Pop,才能取下一个
	while (!QueueEmpty(&qu))
	{
		QueueDataType front = QueueFront(&qu);
		printf("%d ", front);
		QueuePop(&qu);
	}
	printf("\n");
	
	QueueDestroy(&qu);//销毁队列
}

int main()
{
	TestQueue();
	return 0;
}
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值