数据结构第六讲:栈和队列
- 1.栈
- 1.1概念与结构
- 1.2栈的实现
- 1.2.1栈的基础框架
- 1.2.2栈的初始化
- 1.2.3栈的销毁
- 1.2.4栈的插入(压栈)
- 1.2.5栈的删除(出栈)
- 1.2.6获取栈顶元素
- 1.2.7获取栈中有效数据的个数
- 2.队列
- 2.1概念与结构
- 2.2队列的实现
- 2.2.1队列的基础框架
- 2.2.2队列的初始化
- 2.2.3入队列
- 2.2.4出队列
- 2.2.5销毁队列
- 2.2.6取队列头节点数据
- 2.2.7取队列尾节点数据
- 2.2.8队列中有效元素的个数
1.栈
1.1概念与结构
栈:栈是一种特殊的线性表,只允许在固定的一端进行插入和删除操作,遵循LIFO(last in first out)的原则
压栈:栈的插入操作成为压栈/入栈/进栈,插入操作在栈顶
出栈:栈的删除成为出栈,删除操作也在栈顶
栈可以通过使用数组或链表来实现,相对而言使用数组实现较好,因为数组实现尾插的时间复杂度为O(1)
1.2栈的实现
栈的实现过程与顺序表的实现方法非常相似,所以这里就不着重阐述了
1.2.1栈的基础框架
与顺序表不同的是,这里没有用来存储有效数据的变量,变成了一个栈顶变量,大小也是有效变量的个数
typedef int StackDataType;
typedef struct Stack
{
StackDataType* arr;//使用一个指针来指向开辟的数组
int capacity;//保存数组的空间大小
int top;//指向栈顶
}Stack;
1.2.2栈的初始化
直接将变量进行初始化即可
//栈的初始化
void Init(Stack* ps)
{
assert(ps);
ps->arr = NULL;
ps->capacity = ps->top = 0;
}
1.2.3栈的销毁
直接销毁即可
//栈的销毁
void Destory(Stack* ps)
{
assert(ps);
//注意:这个要加上if进行判断,为了确保arr数组不是指向NULL
if(ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->top = 0;
}
1.2.4栈的插入(压栈)
//栈的插入
void StackPush(Stack* ps, StackDataType x)
{
assert(ps);
//因为只有栈的插入才需要开辟空间,所以开辟结点空间不必封装成一个函数
if (ps->top == ps->capacity)
{
//空间不足,需要开辟
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
StackDataType* parr = (StackDataType*)realloc(ps->arr, newcapacity * sizeof(StackDataType));
if (parr == NULL)
{
perror("malloc faile!");
exit(1);
}
ps->arr = parr;
ps->capacity = newcapacity;
}
ps->arr[ps->top++] = x;
}
1.2.5栈的删除(出栈)
//栈的删除
void StackPop(Stack* ps)
{
//栈为NULL时不能删除,栈中没有数据时不能删除
assert(ps && ps->top);
--ps->top;
}
1.2.6获取栈顶元素
//取栈顶元素
StackDataType StackPrint(Stack* ps)
{
assert(ps && ps->top);
return ps->arr[ps->top-1];
}
1.2.7获取栈中有效数据的个数
有效数据的个数就是top的大小
//获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
2.队列
2.1概念与结构
队列也是一种特殊的线性表,与栈不同的是,队列只允许在一端进行插入数据操作,在另一端进行数据删除操作,遵循的是FIFO(first in first out)原则
入队列:进行插入的一端称为队尾
出队列:进行删除的一端称为队头
队列也可以使用数组和链表两种方法完成,但是这时链表要更好一点,因为链表的删除只需要改变指针的指向即可
2.2队列的实现
2.2.1队列的基础框架
队列的实现不同于单链表,它需要两个结构体变量
//结点结构体
typedef int QueueDataType;
typedef struct QueueNode
{
//和链表一样,也需要结点进行链接
QueueDataType val;
struct QueueNode* next;
}QueueNode;
//队列结构体
typedef struct Queue
{
QueueNode* head;//指向队列的头部,方便删除数据
QueueNode* tail;//指向队列的尾部,方便插入数据
int size;//用来记录有效数据的个数
}Queue;
2.2.2队列的初始化
直接进行初始化即可
//队列初始化
void Init(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
2.2.3入队列
也就是插入队列,在队列的末尾进行插入即可
//入队列
void QueuePush(Queue* pq, QueueDataType x)
{
assert(pq);
//只有入队列需要开辟结点空间
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc faile!");
exit(1);
}
newnode->val = x;
newnode->next = NULL;
//要分情况讨论:当队列一开始没有一个结点时
if (pq->head == NULL)
{
pq->head = pq->tail = newnode;
}
else
{
//直接插入到末尾即可
//head ... tail newnode
pq->tail->next = newnode;
pq->tail = newnode;
}
pq->size++;
}
2.2.4出队列
改变头结点的指针,然后释放空间即可
//出队列
void QueuePop(Queue* pq)
{
assert(pq && pq->head);
//出队列要求从队列开头进行删除
//此时要分情况讨论:当只具有一个结点时
if (pq->head == pq->tail)
{
free(pq->head);
pq->head = pq->tail == NULL;
}
else
{
//pq->head pq->head->next
QueueNode* tmp = pq->head->next;
free(pq->head);
pq->head = tmp;
}
--pq->size;
}
2.2.5销毁队列
与链表的销毁相同,需要通过两个指针进行循环销毁
//销毁队列
void Destory(Queue* pq)
{
assert(pq && pq->head);
QueueNode* prev = pq->head;
QueueNode* next = pq->head->next;
while (prev)
{
free(prev);
prev = next;
if(next)
next = next->next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
2.2.6取队列头节点数据
头节点数据直接提取即可
//取队列头结点数据
QueueDataType QueueFront(Queue* pq)
{
assert(pq && pq->head);
return pq->head->val;
}
2.2.7取队列尾节点数据
//取队列尾节点数据
QueueDataType QueueBack(Queue* pq)
{
assert(pq && pq->head && pq->tail);
return pq->tail->val;
}
2.2.8队列中有效元素的个数
有效元素个数在结构中进行保存,直接提取即可
//队列有效元素的个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}