Ⅳ栈和队列(C实现)

一、栈

1、栈的概念及结构

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

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

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

2、栈的实现

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

Note:

  • 数组栈有一个缺点,就是内存不够时需要增容,但是增容的消耗也还好。
  • 链式栈删除的话:如果用表尾做栈顶,尾插尾删,要设计成双向链表,否则删除数据效率低;如果用表头做栈顶,头插头删,就可以设计成单链表。

两种都可以,非要选一种的话,首推数组栈。链式栈相比于数组栈的优势只是避免了空间浪费,但除非是很需要节省空间,否则数组栈更好一些。

定义栈的结构

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
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(ST* ps) {
	assert(ps);   //给的结构体指针不能为空
	ps->a = NULL;
	ps->top = 0;  //top也可以给-1;
	              //初始化时,top给0的话,意味着top指向栈顶数据的下一位;先放数据再加一
	              //初始化时,top给-1的话,意味着top指向栈顶数据;先加一再放数据
	              //两种都可以,因为栈的实现是你自己控制的
	ps->capacity = 0;
}

入栈

void StackPush(ST* ps, STDataType x) {
	assert(ps);

	if (ps->top == ps->capacity) {//内存空间不够,扩容
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newcapacity);
		if(tmp == NULL) {
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

出栈

void StackPop(ST* ps) {
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}

获取栈顶元素

STDataType StackTop(ST* ps) {
	assert(ps);
	assert(!StackEmpty(ps));//不写这句话会有bug,不断言会产生随机值,越界访问
	//assert(ps->top > 0);
	return ps->a[ps->top - 1];
}

获取栈中有效元素个数

int StackSize(ST* ps) {
	assert(ps);
	return ps->top;
}

检测栈是否为空,如果为空返回true,如果不为空返回false

bool StackEmpty(ST* ps) {
	assert(ps);
	/*if (ps->top == 0) {
		return true;
	}
	else {
		return false;
	}*/
	//三目操作符也可以
	return ps->top == 0;
}

销毁栈

void StackDestroy(ST* ps) {
	assert(ps);
	free(ps->a);//free不是free的指针,而是free指针所指的空间
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;

}

二、队列

2.1队列的概念及结构

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

2.2队列的实现

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。需要移动结点(循环队列除外)

实现单链表只一个头指针,实现队列头插有头指针,实现队列尾删有尾指针

定义队列的结构

// 链式结构:表示队列
// 队列的结构
typedef int QDataType;
typedef struct QueueNode {
	QDataType data;
	struct QueueNode* next;
}QueueNode;

typedef struct Queue {
	QueueNode* head;
	QueueNode* tail;//单链表尾指针实现意义不大
                    //单链表尾删还是得找尾指针前的结点,故引入带头双向循环链表
	                //队列不需要尾删,只是尾插,故用尾指针更好
}Queue;

初始化队列

这里不需要二级指针,只是把结构体的地址传过去,改结构体里的指针就可以了;改指针变量才传二级指针

Note:关于函数传值调用还是传址调用 

你有单个值就定义单个值,你有多个值就定义结构体,你要改变什么就要传它的地址,你要改变一级指针就要传它的地址(二级指针),你要改变结构就要传它的结构体指针(地址),你要改变单个值就要传单个值的指针(地址) 

void QueueInit(Queue* pq) {//有些书上是Queue &pq cpp引用的语法
	assert(pq);//目前写的队列情况不可能为空,故不会出现断言失败的情况
               //为空不敢去断言,不可能随便就让程序退出
	pq->head = NULL;//不带头结点(哨兵位),因为这个对队列的实现没有价值
	pq->tail = NULL;
}

队尾入队列

void QueuePush(Queue* pq, QDataType 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* next = pq->head->next;
	free(pq->head);   //free不是free的指针,而是free指针所指的空间
	pq->head = next;

	if (pq->head == NULL) {//防止尾指针为野指针,会存在隐患
			               /取队尾数据/的接口函数会导致有隐患
		pq->tail = NULL;
	}

}

获取队列头部元素

QDataType QueueFront(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

获取队列队尾元素

QDataType QueueBack(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

获取队列中有效元素个数

int QueueSize(Queue* pq) {//有些地方会在结构体里给size,直接访问就可以
                          //但一般面试不给
	assert(pq);
	int n = 0;
	QueueNode* cur = pq->head;
	while (cur) {
		++n;
		cur = cur->next;
	}
	return n;
}

检测队列是否为空,如果为空返回true,如果非空返回false

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

销毁队列

void QueueDestroy(Queue* pq) {
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur != NULL) {//cur!=pq->tail会导致最后一个节点没删掉

		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

2.3循环队列

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

 

Note:

循环队列特点:符合先进先出;空间大小是固定的

重点:循环队列,无论是用数组实现还是链表实现,都要多开辟一个空间,就是意味着,要是一个存k个数据的循环队列,要开辟k+1个空间,否则无法实现判空和队满

数组实现   Front=rear空     (Tail+1)%(k+1)=front满

链表实现   Front=rear空        rear->next=front 满

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aspect of Twilight

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值