一、栈和队列的定义
1.栈
栈又名堆栈(stack),它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
2.队列
队列是一种特殊的线性表,它的特点是只能在前端(也称为队尾)进行插入操作,后端(也称为队头)进行删除操作。这种数据结构的实现使用了链表的方式,因此被称为链队列。链队列的管理通常由两个指针来实现,分别是队头指针和队尾指针。这些指针用于唯一确定每一个队列元素的位置。
二.栈的数据存储结构及基本操作。
1.栈的存储结构
typedef int DataType;
typedef struct Stack {
DataType* value;//元素
int capacity;//大小
int top;//栈顶
}ST;
2.栈的初始化
void StackInit(ST* s) {
assert(s);
s->value = NULL;
s->top = 0;
s->capacity = 0;
}
3.入栈
void StackPush(ST* s, DataType data) {
assert(s);//检查指针s是否为NULL的断言
if (s->top == s->capacity) {//检查当前顶部索引是否等于堆栈的容量
int newcapacity = s->capacity == 0 ? 4 : s->capacity * 2;//计算堆栈的新容量。如果当前容量为0,则将新容量初始化为4;否则,它会使当前容量翻倍。
int* newnode = (int*)realloc(s->value, sizeof(int) * newcapacity);
if (newnode == NULL) {//这使用realloc函数来调整s->值所指向的内存块的大小。它试图为新容量分配内存。如果分配失败,它将打印一条错误消息并从函数返回。
perror("realloc fail");
return;
}
s->value = newnode;//更新ST结构的值成员以指向新分配的内存
s->capacity = newcapacity;//更新ST结构的容量成员以反映新的容量
}
s->value[s->top] = data;//将数据参数指定给当前顶部索引处的元素
s->top++;//增顶部索引以指向堆栈中的下一个可用位置
}
4.出栈
void StackPop(ST* s) {
assert(s);//检查指针s是否为NULL的断言
assert(s->top >= 0);//此断言检查堆栈的顶部索引是否大于或等于0。如果top小于0,则表示存在下溢情况,程序将终止并显示错误消息。
s->top--;//这一行减少堆栈的顶部索引,有效地删除了顶部元素
}
5.取栈顶元素
DataType StackTop(ST* s) {
assert(s);//检查指针s是否为NULL的断言
assert(s->top >= 0);//此断言检查堆栈的顶部索引是否大于或等于0
return s->value[s->top-1];//此行返回堆栈顶部的元素。它访问索引s->top-1处的元素数组(值)
}
6.判栈空
int StackEmpty(ST* s) {
assert(s);
return s->top == 0;//此行返回比较的结果s->top==0。如果顶部索引等于0,则表示堆栈为空,并且函数返回1(true)
}
7.栈的大小
int StackSize(ST* s) {
assert(s);//检查指针s是否为NULL的断言
return s->top;//返回栈的当前大小
}
8.栈的销毁
void StackDestroy(ST* s) {
assert(s);
free(s->value);//此行使用free函数来解除分配ST结构的值成员所指向的内存块
s->value = NULL;//将设置为NUL
s->capacity = s->top = 0;//这些行将ST结构的容量和顶部成员设置为0,有效地将堆栈重置为空状态。
}
二.队列的数据存储结构及基本操作。
1.队列的数据存储结构
typedef int DataType;//将int定义为DataType
typedef struct QueueNode {
DataType val;//元素域
struct QueueNode* next;//指针域
}Qnode;
typedef struct Queue {
Qnode * front;//队列前部节点
Qnode * rear;//队列后部节点
int size;//队列中元素数量大小
}Queue;
2.队列的初始化操作
void QueueInit(Queue* q) {
assert(q);//检查指针q是否为NULL的断言
q->front = NULL;//将队列的队头指针设置为NULL
q->rear = NULL;//将队列的队尾指针设置为NULL
q->size = 0;//将队列的大小变量设置为0,表示队列不包含任何元素
}
3.队列的入队操作
void QueuePush(Queue* q, DataType x) {
assert(q);//检查指针q是否为NULL的断言
Qnode * newnode = (Qnode*)malloc(sizeof(Qnode));//使用malloc为新节点动态分配内存
if (newnode == NULL) {
perror("malloc fail");
return;
}
newnode->val = x;//将x赋给新节点
newnode->next = NULL;//将新节点的下一个指针设置为NULL
if (q->rear == NULL) {//检查队列当前是否为空。如果是,新节点将同时成为队列的前面和后面。否则,新节点将添加到现有队列的后面。
q->rear = q->front = newnode;
}
else {
q->rear->next = newnode;
q->rear = newnode;
}
q->size++;//增加队列的大小变量
}
4.队列的出队操作
void QueuePop(Queue* q) {
assert(q);//检查指针q是否为NULL
Qnode* temp = q->front->next;//创建一个临时指针temp,该指针指向队列前一个节点之后的下一个节点
free(q->front);
q->front = temp;//将队列的前队头指针更新到下一个节点,有效地删除了前端节点
if (q->front == NULL) {//检查队头指针现在是否为NULL,表示队列为空。如果删除后队列为空,则前指针和后指针都设置为NULL。
q->rear = NULL;
}
q->size--;//此行减少队列的大小变量,表示元素已被删除
}
5.取队头元素
DataType QueueFront(Queue* q) {
assert(q);
assert(q->size >= 0);
return q->front->val;//返回队头元素
}
6.取队尾元素
DataType QueueRear(Queue* q) {
assert(q);
assert(q->size >= 0);
return q->rear->val;//返回队尾的大小
}
7.队列的大小
int QueueSize(Queue* q) {
assert(q);
return q->size;//返回队列大小
}
8.判队空
bool QueueEmpty(Queue*q) {
assert(q);
return q->front == NULL;//如果队列为空返回true,不为空返回1
}
9.队列的销毁
void QueueDestroy(Queue* q) {
assert(q);
Qnode* temp = q->front;//创建一个临时指针temp,该指针最初指向队头
while (temp != NULL) {
Qnode* cur = temp->next;//创建一个临时指针cur,该指针指向队列中的下一个节点,然后再释放当前节点
free(temp);//释放为当前节点分配的内存
temp = cur;//更新临时指针以指向队列中的下一个节点,用于循环的下一次迭代
}
q->front = q->rear = NULL;//将队列的前指针和后指针都设置为NULL
q->size=0;//将队列的大小设置为0
}
三.总结
队列和栈是两种常见的数据结构,它们各自有一些优点和缺点。 队列遵循先进先出的原则,保证了元素的顺序性,先入队的元素先出队。很适合模拟现实生活中的排队行为,如任务调度、打印队列等。但是只支持在两端进行操作,限制了数据的插入和删除方式 ,且不能直接访问队列中间的元素,需要依次出队才能访问。栈遵循后进先出的原则,使得最近进入的元素最先被访问。 栈结构在递归算法的实现中具有重要作用,可以方便地保存和恢复函数调用的状态,但是栈只允许在栈顶进行插入和删除操作,限制了数据的操作方式。有些问题可能需要在数据的中间进行操作,而栈的特性使其在这些情况下不够灵活。
总体而言,选择使用队列还是栈取决于具体的应用场景和问题要求。在需要按顺序处理数据的情况下,队列可能更适合;而在需要后进先出的情况下,栈可能更合适。在某些情况下,也可以将队列和栈结合使用,以满足特定的需求。