线性表——栈和队列

线性表——栈和队列

  • 1.栈
    • 1.1栈是什么
    • 1.2栈的实现
      • ⚽1.2.1栈的结构与初始化
      • ⚽1.2.2入栈
      • ⚽1.2.3出栈
      • ⚽1.2.4判断栈是否为空
      • ⚽1.2.5获取栈顶的元素
      • ⚽1.2.6栈元素的统计和栈的销毁
  • 2.队列
    • 2.1队列是什么
    • 2.2队列的实现
      • ⚽2.2.1队列的结构与初始化
      • ⚽2.2.2入队列
      • ⚽2.2.3出队列
      • ⚽2.2.4判断队列是否为空
      • ⚽2.2.5获取队列的元素
      • ⚽2.2.6队列元素的统计和队列的销毁
  • 一些小点

先看这里

作者:江不平
博客:江不平的博客
学如逆水行舟,不进则退
欢迎关注点赞收藏⭐️留言
❀本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步

1.栈

1.1栈是什么

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在一端进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。也叫先进后出(Last Out First In)

  • 入栈:向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;
    -出栈: 从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

线性表——栈和队列_第1张图片

1.2栈的实现

因为是特殊的线性表,所以有一些独特的接口,比如获取栈顶元素,判断栈是否为空等,下面我们来实现一下

⚽1.2.1栈的结构与初始化

栈结构,我们都在栈顶进行插入删除操作,类似尾删尾插,在这方面数组比较优,所以我们用数组来实现
栈有静态的和动态的

静态栈
#define n 10
typedef int stdatatype;
typedef struct stack
{
	stdatatype a[n];
	int top;//最后一个元素的下一个位置,就是size,
}st;

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

栈的初始化我们可以直接创建空间,也可以赋个空指针给它,将其他数据置零。
这里我置零,后面再进行扩容

void StackInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;//如果表示的是最后一个元素的位置,就初始化为-1
	ps->capacity = 0;
}

后面我们都按top表示最后一个元素位置进行实现

⚽1.2.2入栈

插入数据与顺序表相似

void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)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++;
}

⚽1.2.3出栈

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//这里要记得把StackEmpty放到前面去
	ps->top--;
}

⚽1.2.4判断栈是否为空

StackEmpty是常用的函数,在其他函数调用前要往前放,早点定义

bool StackEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}

我们在判断栈是否为空的函数时使用了布尔类型,如果栈为空则返回true,不是空则返回false。使用布尔类型,我们得先包含头文件

⚽1.2.5获取栈顶的元素

获取栈顶元素函数与出栈函数不同。出栈函数是将栈空间中栈顶的元素弹出,也就是删除销毁,获取栈顶函数是将栈顶空间保存的数据取出,并不会改变栈空间的数据存储。因此我们只需要将顺序表中top所指向的上一个空间的位置存储的数据返回即可。

STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));

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

⚽1.2.6栈元素的统计和栈的销毁

top的值就是size的值

int StackSize(ST* ps)
{
	assert(ps);

	return ps->top;
}
栈的销毁
void StackDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

2.队列

2.1队列是什么

  • 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
  • 队列的元素遵守先进先出FIFO(First In First Out)

线性表——栈和队列_第2张图片

进行插入操作的端称为队尾,进行删除操作的端称为队头

2.2队列的实现

⚽2.2.1队列的结构与初始化

队列的实现可以采用顺序表或者链表,由于队列需要使用头删,所以顺序表实现并不划算,原因在于顺序表进行头删的时候,元素需要移动覆盖队头的数据,增加了时间复杂度。用链表比较好,所以一般采用链表形式

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

typedef struct Queue
{
	//int size;
	QNode* head;
	QNode* tail;
}Queue;

队列最重要的就是队尾入数据队头出数据,为了方便操作,我们定义一头一尾两个指针

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;//写成带哨兵位的也可,但没必要,如果写的话,malloc一个结点给head和tail就可
}

⚽2.2.2入队列

两种情况,一种什么数据都没有,一种有数据

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
    //放完数据后记得更新head和tail
	if (pq->tail == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

⚽2.2.3出队列

三种情况:1.空队列2.只剩一个结点时再进行pop会导致tail为野指针3.多个结点,正常删除

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));//考虑空队列

	if (pq->head->next == NULL)//只剩一个结点情况
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else//多个结点情况
	{
	    //先保存再free
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

⚽2.2.4判断队列是否为空

记得调用empty时,将它往前挪,不然会报错:函数未定义

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;//head和tail一个为空队列就为空了,判断一个即可
}

⚽2.2.5获取队列的元素

一般分为取队头的数据和取队尾的数据,取中间的对于队列的特点来说没啥意义了

取队头的数据
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;
}

⚽2.2.6队列元素的统计和队列的销毁

元素的统计,追求效率最好可以在队列的定义时就增加变量size,进行插入删除时size加减,我们也可以遍历统计,但是效率不高,这里给出遍历的代码

int QueueSize(Queue* pq)
{
	assert(pq);
	int size = 0;
	QNode* cur = pq->head;
	while (cur)
	{
		size++;//如果觉得遍历麻烦,可以在队列结构体中定义一个size
		cur = cur->next;
	}
	return size;
}

对于链表的删除插入的操作,我们之前就说过如果怕难以连接成新的链表结构,我们可以先将数据用新的变量存储起来,再进行连接,不然就要好好考虑连接的顺序,避免出错,我建议存储起来。

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;//先存储起来
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

一些小点

  1. 栈和队列不好进行遍历,栈遍历需要不断拿栈顶的数据,当遍历结束后,栈也就空了
  2. 可以看到队列的实现与单链表是有区别的,控制变量单链表采取了二级指针,而队列没有。不用二级指针的原因(情况)1.有返回值 2.新封装成结构体(有多个指针的情况,比如这里的队列,就一个的话没必要)。

看到这里觉得还不错的老铁点赞关注一下吧
线性表——栈和队列_第3张图片

你可能感兴趣的:(数据结构,算法,c语言)