数据结构栈和队列

2024-06-09 1031阅读

数据结构栈和队列

个人主页:星纭-CSDN博客

系列文章专栏:数据结构

踏上取经路,比抵达灵山更重要!一起努力一起进步!

目录

一.栈 

1.栈的介绍

2.初始化与销毁

3.入栈和出栈

4.栈顶元素,判空,求栈的元素个数

代码:

test.c

stack.h:

stack.c:

二.队列 

1.队列的介绍

2.初始化与销毁

3.插入与删除 

4.查看队头队尾元素

 5.判空与队列大小

代码:


一.栈 

1.栈的介绍

栈是一种线性数据结构,它只能在固定的一端进行插入和删除操作,这一端被称为栈顶,另一端则称为栈底。栈的特点是后进先出(Last In First Out,LIFO),即最后插入的元素最先被删除,而最先插入的元素最后被删除。

栈的基本操作有两个:入栈(push)和出栈(pop)。入栈操作将元素插入到栈顶,出栈操作将栈顶元素删除并返回。 

  1. 入栈:栈插入数据的操作也叫入栈/压栈/进栈,入数据在栈顶。
  2. 出栈:栈的删除操作也叫做出栈。出数据也在栈顶。

栈可以用来解决一些具有后效性的问题,例如表达式求值、括号匹配、函数调用等。它也可以用于实现一些常见的数据结构,例如递归函数、深度优先搜索等。 

数据结构栈和队列

栈可以使用数组或链表来实现。使用数组实现的栈称为顺序栈,使用链表实现的栈称为链式栈。无论使用哪种实现方式,栈的空间复杂度都为O(n),其中n为栈中元素的个数。 

数据结构栈和队列

栈还有一些常见的扩展操作,例如获取栈顶元素(top)、判断栈是否为空(isEmpty)、获取栈中元素个数(size)等。这些操作的时间复杂度都为O(1)。

对于顺序栈来说,又分两种情况:一种是定长的静态栈结构,一般情况下,实际中用的比较少,另一种是支持动态增长的栈。

以下的栈的实现,作者基于支持动态增长的顺序栈完成。

typedef int STDatatype;
typedef struct{
	STDatatype* a;
	int top;//栈顶
	int capacity;//栈的容量
}Stack;

为了方便对存储的数据类型进行更改,我们对int重命名一下,如果以后要存储char类型的数据,只需要更改一下int为char就可以轻松完成。 

2.初始化与销毁

//初始化
void STInit(Stack* pst);
//销毁
void STDestory(Stack* pst);

 1.初始化

void STInit(Stack* pst) {
	pst->a = NULL;
	pst->capacity = pst->top = 0;
};

因为开始的时候,这个栈中并没有任何的元素,所以我们将其的a初始化为NULL,top和capacity为0.

其实对于top的初始化是有两种情况的:

如果top指向的是栈顶的元素:

数据结构栈和队列

 比如这样的TOP就是指向栈顶的元素8,此时存放了3个数据,Top的值等于2.

可以是我们开始初始化为0,此时的TOP指向下标为0的位置,可是此时的位置上并没有元素,这里的top指向的是栈顶元素的下一个位置,并不是栈顶元素。

所以如果要使top指向栈顶元素,初始化应该为-1,而不是0.

本文采用top指向栈顶元素的下一个位置。

2.销毁

销毁其实和初始化很类似。

因为a是我们使用malloc函数动态开辟的空间,所以我们需要free释放掉a。

//初始化
void STInit(Stack* pst) {
	assert(pst);
	pst->a = NULL;
	pst->capacity = pst->top = 0;
};
//销毁
void STDestory(Stack* pst) {
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->capacity = pst->top = 0;
};

为了防止pst->a时造成野指针,需要对pst进行断言一下,避免pst是NULL.

3.入栈和出栈

 1.入栈

在入栈之前,我们需要对栈判断空间是否足够。因为top指向的是栈顶元素的下一个位置,所以当满了的条件是top等于capacity,而对于top指向的是栈顶元素而言,判满的条件是capacity = top + 1.

可是对于这个条件来说,当容量为0的时候,这个条件也成立,所以这里需要使用三目操作符,更改容量,以免乘以2后还是0.用tmp接收开辟的空间是为了避免开辟失败,返回NULL,导致a变成了NULL,使数据丢失。realloc函数当第一个 参数的值为NULL的时候,它的功能和malloc函数一模一样。所以我们不需要单独判断a是否为NULL.

void STPush(Stack * pst,STDatatype x){
	assert(pst);
	if (pst->top == pst->capacity) {
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDatatype* tmp = (STDatatype*)realloc(pst->a,newcapacity * sizeof(STDatatype));
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(1);
		}
		pst->a = tmp;
		pst->capacity = newcapacity;
	}
	pst->a[pst->top++] = x;
};

 注意:最后记得top+1,不要忘了。

2.出栈

对于数组而言,出栈的时候,并不需要把这个数据真的移除掉,只需要top--即可,因为这样这个元素实际上就不在这个栈中了,我们再入栈的时候,就会覆盖掉这个元素。

void STPop(Stack* pst) {
	assert(pst);
	assert(pst->top>0);
	pst->top--;
};

出栈要注意,栈空了就不能出栈了。否则top就变成负数了。

4.栈顶元素,判空,求栈的元素个数

//栈顶元素
STDatatype STTop(Stack* pst) {
	assert(pst);
	assert(pst->top>0);
	return pst->a[pst->top - 1];
};
//判空
bool IsEmpty(Stack* pst) {
	assert(pst);
	return pst->top == 0;
}
//求元素个数
int STSize(Stack* pst) {
	assert(pst);
	return pst->top;
};

这三个函数非常简单:栈定元素需要保证这个栈不能为空,也就是top要大于0,否则不对。

判空,当栈空的时候top就0,所以只需要判断top等不等于0即可。

求元素个数:因为top指向栈顶元素的下一个位置,又因为数组下标从0开始,所以top就代表元素的个数。

代码:

test.c

#define _CRT_SECURE_NO_WARNINGS 
#include"stack.h"
int main()
{
	Stack s;
	STInit(&s);
	STPush(&s,1);
	printf("%d->", STTop(&s));
	STPush(&s,2);
	printf("%d->", STTop(&s));
	STPush(&s,3);
	printf("%d->", STTop(&s));
	STPush(&s,4);
	printf("%d->", STTop(&s));
	STPush(&s,5);
	printf("%d->", STTop(&s));
	STPush(&s,6);
	printf("%d->", STTop(&s));
	printf("\n栈总共有%d个元素\n",STSize(&s));
	//打印整个栈的元素
	while (!IsEmpty(&s)) {
		printf("%d->",STTop(&s));
		STPop(&s);
	}
	printf("\n栈总共有%d个元素\n", STSize(&s));
	STDestory(&s);
    return 0;
}

输出结果: 

数据结构栈和队列

stack.h:

#pragma once
#include
#include
#include
#include
//#include
typedef int STDatatype;
typedef struct{
	STDatatype* a;
	int top;//栈顶
	int capacity;//栈的容量
}Stack;
//初始化
void STInit(Stack* pst);
//销毁
void STDestory(Stack* pst);
//入栈
void STPush(Stack* pst, STDatatype x);
//出栈
void STPop(Stack* pst);
//栈顶元素
STDatatype STTop(Stack* pst);
//判空
bool IsEmpty(Stack* pst);
//求元素个数
int STSize(Stack* pst);

stack.c:

#define _CRT_SECURE_NO_WARNINGS 
#include"stack.h"
//初始化
void STInit(Stack* pst) {
	assert(pst);
	pst->a = NULL;
	pst->capacity = pst->top = 0;
};
//销毁
void STDestory(Stack* pst) {
	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->capacity = pst->top = 0;
};
//入栈
void STPush(Stack * pst,STDatatype x){
	assert(pst);
	if (pst->top == pst->capacity) {
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDatatype* tmp = (STDatatype*)realloc(pst->a,newcapacity * sizeof(STDatatype));
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(1);
		}
		pst->a = tmp;
		pst->capacity = newcapacity;
	}
	pst->a[pst->top++] = x;
};
//出栈
void STPop(Stack* pst) {
	assert(pst);
	assert(pst->top>0);
	pst->top--;
};
//栈顶元素
STDatatype STTop(Stack* pst) {
	assert(pst);
	assert(pst->top>0);
	return pst->a[pst->top - 1];
};
//判空
bool IsEmpty(Stack* pst) {
	assert(pst);
	return pst->top == 0;
}
//求元素个数
int STSize(Stack* pst) {
	assert(pst);
	return pst->top;
};

二.队列 

1.队列的介绍

 队列是一种常见的数据结构,它遵循先进先出(FIFO)的原则,只允许在一端(队尾)进行插入数据,在另一端(队头)进行删除数据。队列有两个主要操作:入队(enqueue)和出队(dequeue)。入队将元素添加到队列的末尾,出队将队列的第一个元素删除并返回。

数据结构栈和队列

数据结构栈和队列

 队列与排队类似,有素质的人,不会插队,会从排到队尾,完成事情后,从队头离开。

所以我们,是不能访问中间元素的。

队列通常用于存储需要按照顺序处理的数据。例如,在计算机科学中,可以使用队列来实现广度优先搜索算法(BFS)。

在实现队列时,可以使用数组或链表来存储队列中的元素。数组实现的队列被称为顺序队列,链表实现的队列被称为链式队列。

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。

本文,作者采用链式队列。

队列的时间复杂度如下:

  • 入队:O(1)
  • 出队:O(1)
  • 查看队首元素:O(1)

    队列的空间复杂度为O(n),其中n是队列中的元素数量。

    typedef int QDatatype;
    typedef struct QueueNode {
    	struct QueueNode* next;
    	QDatatype val;
    }QNode;
    typedef struct Queue {
    	QNode* phead;
    	QNode* ptail;
    	int size;//节点个数
    }Queue;

    这里采用两个指针分别指向队头与队尾,为了方便,把两个指针与size存放在一个结构体中,这样传参更加的方便。

    2.初始化与销毁

    1.初始化

    最开始时,队列中是没有任何元素的。

    //初始化
    void QueueInit(Queue* pq) {
    	assert(pq);
    	pq->phead = pq->ptail = NULL;
    	pq->size = 0;
    };

    注意观察此处的参数,如果不放在一个结构体 中,那么传参的时候,就需要传递三个指针,大大地增加了使用者的操作难度。开始要断言一下,以免pq为NULL。

    2.销毁

    //销毁
    void QueueDestory(Queue* pq) {
    	assert(pq);
    	QNode* cur = pq->phead;
    	while (cur) {
    		QNode* next = cur->next;
    		free(cur);
    		cur = next;
    	}
    	pq->phead = pq->ptail = NULL;
    	pq->size = 0;
    };

    队列的销毁与单链表的销毁很相似,这里不过多介绍。

    3.插入与删除 

    1.插入

    插入数据,先malloc动态申请内存来存放这个数据。

    然后考虑当这个队列没有元素的时候,phead和ptail指针均指向NULL,这个新插入的节点,即是头节点又是尾节点,所以需要分开讨论。 

    //队尾插入数据
    void QueuePush(Queue* pq, QDatatype x)
    {
    	QNode* newnode = (QNode*)malloc(sizeof(QNode));
    	if (newnode == NULL)
    	{
    		perror("malloc fail");
    		exit(1);
    	}
    	newnode->val = x;
    	newnode->next = NULL;
    	if (pq->phead == NULL) {
    		pq->phead = pq->ptail = newnode;
    		pq->size++;
    	}
    	else {
    		pq->ptail->next = newnode;
    		pq->ptail = newnode;
    		pq->size++;
    	}
    }

    当队列为空的时候,就直接将phead与ptail指针指向newnode既可。如果队列不为空就尾插。

    2.删除

    删除数据,需要释放第一个节点即可,然后让phead指针指向下一个节点。

    可是这样写有一个问题,如果此时队列只有一个节点,如果只改变phead的话,ptail仍然指向原来的节点,这样的就会造成野指针了,所以仍然分开讨论。

    //队头删除数据
    void QueuePop(Queue* pq)
    {
    	assert(pq);
    	assert(pq->phead);
    	//一个节点
    	if (pq->phead == pq->ptail) {
    		free(pq->phead);
    		pq->phead = pq->ptail = NULL;
    	}//多个节点
    	else {
    		QNode* del = pq->phead;
    		pq->phead = pq->phead->next;
    		free(del);
    		del = NULL;
    	}
    	--pq->size;
    }

    第二个断言是为了防止队列为空仍然删除数据。

    4.查看队头队尾元素

    // 取队头和队尾的数据
    QDatatype QueueFront(Queue* pq) {
    	assert(pq);
    	assert(pq->size);
    	return pq->phead->val;
    };
    QDatatype QueueBack(Queue* pq) {
    	assert(pq);
    	assert(pq->size);
    	return pq->ptail->val;
    };

     在查看之前,首先要确定这个队列中有元素,否则不能查看

     5.判空与队列大小

    int QueueSize(Queue* pq) {
    	assert(pq);
    	return pq->size;
    };
    bool QueueEmpty(Queue* pq) {
    	assert(pq);
    	return pq->size == 0;
    };

    判空与队列大小很简单这里就不过多讲解。

    代码:

    Queue.h:

    #pragma once
    #include
    #include
    #include
    #include
    typedef int QDatatype;
    typedef struct QueueNode {
    	struct QueueNode* next;
    	QDatatype val;
    }QNode;
    typedef struct Queue {
    	QNode* phead;
    	QNode* ptail;
    	int size;
    }Queue;
    //初始化与销毁
    void QueueInit(Queue* pq);
    void QueueDestory(Queue* pq);
    //队尾插入数据/队头删除数据
    void QueuePush(Queue* pq,QDatatype x);
    void QueuePop(Queue* pq);
    // 取队头和队尾的数据
    QDatatype QueueFront(Queue* pq);
    QDatatype QueueBack(Queue* pq);
    //判空与队列大小
    int QueueSize(Queue* pq);
    bool QueueEmpty(Queue* pq);

    Queue.c:

    #define _CRT_SECURE_NO_WARNINGS 
    #include"queue.h"
    //初始化
    void QueueInit(Queue* pq) {
    	assert(pq);
    	pq->phead = pq->ptail = NULL;
    	pq->size = 0;
    };
    //销毁
    void QueueDestory(Queue* pq) {
    	assert(pq);
    	QNode* cur = pq->phead;
    	while (cur) {
    		QNode* next = cur->next;
    		free(cur);
    		cur = next;
    	}
    	pq->phead = pq->ptail = NULL;
    	pq->size = 0;
    };
    //队尾插入数据
    void QueuePush(Queue* pq, QDatatype x)
    {
    	QNode* newnode = (QNode*)malloc(sizeof(QNode));
    	if (newnode == NULL)
    	{
    		perror("malloc fail");
    		exit(1);
    	}
    	newnode->val = x;
    	newnode->next = NULL;
    	if (pq->phead == NULL) {
    		pq->phead = pq->ptail = newnode;
    		pq->size++;
    	}
    	else {
    		pq->ptail->next = newnode;
    		pq->ptail = newnode;
    		pq->size++;
    	}
    }
    //队头删除数据
    void QueuePop(Queue* pq)
    {
    	assert(pq);
    	assert(pq->phead);
    	//一个节点
    	if (pq->phead == pq->ptail) {
    		free(pq->phead);
    		pq->phead = pq->ptail = NULL;
    	}//多个节点
    	else {
    		QNode* del = pq->phead;
    		pq->phead = pq->phead->next;
    		free(del);
    		del = NULL;
    	}
    	--pq->size;
    }
    // 取队头和队尾的数据
    QDatatype QueueFront(Queue* pq) {
    	assert(pq);
    	assert(pq->size);
    	return pq->phead->val;
    };
    QDatatype QueueBack(Queue* pq) {
    	assert(pq);
    	assert(pq->size);
    	return pq->ptail->val;
    };
    int QueueSize(Queue* pq) {
    	assert(pq);
    	return pq->size;
    };
    bool QueueEmpty(Queue* pq) {
    	assert(pq);
    	return pq->size == 0;
    };
    另外扩展了解一下,实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表完成。

    数据结构栈和队列

    数据结构栈和队列

    循环队列与双端队列,下一篇文章讲解。

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]