C语言版本数据结构04:栈和队列

23 篇文章 0 订阅
19 篇文章 0 订阅

今天我们来学习两个数据结构的新结构:栈和队列。

1.栈

①栈的概念:

我们之前学过顺序表链表,知道数据在内存中可以以顺序方式存储也可以不顺序的方式存储,它们两个各有优劣,那今天要学的这两个也是线性表,那么先来看一下栈的定义吧。

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

我们可以将栈理解为一个枪的弹夹,压子弹的时候最先被压进去的子弹一定是最后被击发出来,最后压进去的子弹一定最先被击发,这种结构就好比栈结构,那么在栈中,“压子弹”和“击发子弹”又叫做:压栈、出栈。

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

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

 ②栈的实现:

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。因为顺序表插入数据除了尾插之外的插入都是需要挪动数据的,会有性能的消耗,而栈的话插入只是在尾部,出数据也是在尾部,而且使用链表的话指针的指向也要发生改变,因为在进行出栈操作后最后一个数据就无法被找到了,所以考虑种种原因,使用数组实现栈要比用链表实现栈更加方便。

使用数组实现栈的逻辑图:

 使用链表实现栈的逻辑图:

③栈的接口实现:

栈的定义:

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;//动态开辟的数据
	int top;      //栈顶指针
	int capacity; //栈的容量
}ST;
//top要比capacity少1,因为top指向栈顶,也就是数组的最后一个元素,所以top == capacity - 1

 栈的初始化与销毁:

void StackInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;//ps->top = -1;
	ps->capacity = 0;
}
void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 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);
        //在首次开辟内存时realloc和malloc的功能相同
		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(ps->top > 0);
	return ps->a[ps->top - 1];
}

获取栈的大小:

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

判断栈是否为空:

bool StackEmpty(ST* ps)
{
    //比较暴力的方法:
	assert(ps);
    //比较温柔的方法:
	//if (ps->top == 0)
	//{
	//	return true;
	//}
	//else
	//{
	//	return false;
	//}
	return ps->top == 0;
}

2.队列:

今天要学习的第二个结构就是队列,和栈一样同为线性表,但结结构却有明显的不同。

①队列的概念:

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

所以队列和栈的不同就是栈的出栈和压栈都是在栈顶,也就是顺序表的尾部进行,而队列进队列是在队尾,而出队列则是在对头。

②队列的实现:

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。因为删除数组的第一个数据后后面的数据都要向前挪动。

 ③队列的接口实现:

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QueueNode;//创建的一个队列中的节点

typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;//因为要经常用到head和tail指针,所以用其模拟出队列的结构

队列的初始化和销毁:

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur != NULL)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

队列中元素的打印:

void QueuePrint(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

入队列:

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);
	pq->head = next;
	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)
{
	assert(pq);
	size_t count = 0;
	QueueNode* cur = pq->head;
	while (cur != NULL)
	{
		count++;
		cur = cur->next;
	}
	return count;
}

判断队列是否为空:

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;//因为队列出数据只会从队首出,所以只要判断队首是否有队列即可
}

④循环队列:

其实平时无论在考试中还是在练习中循环队列的考察也有不少,与我们讲的队列不同的是循环队列的tail->next就是head,如果head有prev指针的话那么head->prev也就是tail,其循环的结构使其形成一个圆一般。

 那么,如何计算队列中的有效数据呢?

 循环队列的接口就不再实现了,和上面的非循环的队列大同小异,只是下标略有不同,主要理解循环队列的一些特性即可。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值