数据结构-栈、队列-详解
- 1.前言
- 2.栈
- 2.1是什么
- 2.2函数实现
- struct Stack
- StackInit
- StackDestroy
- StackPush
- StackSize
- StackEmpty
- StackTop
- StackPop
- 2.3小结
- 3.队列
- 3.1是什么
- 3.2函数实现
- struct Queue
- QueueInit
- QueueDestroy
- QueueEmpty
- QueuePush
- QueuePop
- QueueFront
- QueueBack
- QueueSize
- 3.3小结
1.前言
在数据结构中,栈和队列都是一种线性表,但是,不同于顺序表和链表,栈和队列在对数据进行处理时,对数据的位置有特殊的规定。
2.栈
在操作系统中,栈指的是内存的一块空间,用于存储函数、临时变量等。
这里的栈是一种数据结构。
两个栈属于不同的学科,注意区分。
2.1是什么
栈(
stack
)是一种运算受限的线性表。限定仅在一端进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。
简单来说,对栈的操作和下图类似,都是后进去的先出来。
LIFO(Last In First Out)
2.2函数实现
struct Stack
想要实现栈,首先创造的还是一个结构体,用于存储不同类型的变量。
而,高中生物老师讲过,结构与功能相对应,这里也是相同的道理。
为了便于对栈顶的数据进行处理,创造变量记下栈顶的位置top
。
其他的类比动态顺序表就行。
typedef char StackDataType;
typedef struct Stack
{
StackDataType* a;
int top;
int capacity;
}Stack;
StackInit
栈的初始化。
void StackInit(Stack* s)
{
assert(s);
s->a = (StackDataType*)malloc(sizeof(StackDataType) * 4);
if (!s->a)
{
perror("InitStack::malloc");
return;
}
s->top = 0;
s->capacity = 4;
}
这里主要问题是top
的初始值,我选择的是0
,用于表示栈顶下一个元素的下标。
也可以是-1
,用于表示栈顶元素的下标。
当然,也可以是任意值,只要不嫌麻烦。
StackDestroy
栈的销毁。
void StackDestroy(Stack* s)
{
assert(s);
free(s->a);
s->a = NULL;
s->top = 0;
s->capacity = 0;
}
StackPush
插入新元素,即压栈。
void StackPush(Stack* s,StackDataType x)
{
assert(s);
if (s->top == s->capacity)
{
StackDataType* tmp = (StackDataType*)realloc(s->a, sizeof(StackDataType)*s->capacity * 2);
if (!tmp)
{
perror("PushStack::realloc");
return;
}
s->a = tmp;
s->capacity *= 2;
}
s->a[s->top] = x;
s->top++;
}
需注意,我的top
为栈顶下一个元素的下标,因此s->a[s->top] = x;
。
初始化不同时,此处操作应该也不同。
StackSize
元素个数。
int StackSize(Stack* s)
{
assert(s);
return s->top;
}
StackEmpty
判空。
bool StackEmpty(Stack* s)
{
assert(s);
return s->top == 0;
}
这个就比较有意思了,判空。
有人说,top == 0
不就空了吗,为什么非要写个函数。
这里还是初始化的问题,如果top
一开始是-1
呢?
因此,让初始化top
的人写个判空的函数,可减少意外发生。
StackTop
取栈顶元素,但不删除。
StackDataType StackTop(Stack* s)
{
assert(s);
assert(!StackEmpty(s));
return s->a[s->top - 1];
}
StackPop
删除栈顶元素,即出栈。
一般与StackTop
配合使用。
void StackPop(Stack* s)
{
assert(s);
assert(!StackEmpty(s));
s->top--;
}
2.3小结
嗯。
可以试试这道题:有效的括号
思路:遇到左括号,压入栈;遇到右括号,与栈顶元素匹配,然后出栈。
我写的:(注:上面栈的数据结构CV过去就行)
3.队列
3.1是什么
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。
与栈的后入先出不同,队列为先入先出:
???
3.2函数实现
struct Queue
结构与功能相对应:
- 队列在一边删除,另一边插入,使用顺序表会涉及挪动,浪费时间。因此,选择链表。
- 使用双链表固然可以,但在这里没必要,虽然,双链表提供了额外的功能,但,使用单链表已经足够满足需求。因此,使用单链表。
- 队列涉及队首和队尾的操作,因此,存储这两个结点可避免遍历链表。
于是,可以写出:
typedef int QDatatype;
typedef struct QueueNode
{
struct QueueNode* next;
QDatatype data;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
QueueInit
初始化。
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
pq->size = 0;
}
QueueDestroy
销毁。
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;
pq->size = 0;
}
QueueEmpty
判空。
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
QueuePush
入队:
void QueuePush(Queue* pq, QDatatype x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (!newnode)
{
perror("QueuePush::malloc");
return;
}
newnode->next = NULL;
newnode->data = x;
if (!QueueEmpty(pq))
{
pq->tail->next = newnode;
pq->tail = newnode;
}
else
pq->head = pq->tail = newnode;
pq->size++;
}
QueuePop
出队。
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
if (pq->head == NULL)
pq->tail = NULL;
pq->size--;
}
需注意,当变为空队列时,需把tail
也置空。
QueueFront
获取队首元素。
QDatatype QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QueueBack
获取队尾元素。
QDatatype QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
QueueSize
获取队伍长度。
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
3.3小结
最主要的问题集中于如何定义结构,当队列的结构体创建好后,其他函数实现并不复杂。
希望本篇文章对你有所帮助!并激发你进一步探索数据结构的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!
相关文章:
数据结构-顺序表-详解
数据结构-单链表-详解-1
数据结构-单链表-详解-2