栈和队列~

1.栈

1.1栈的基本概念

1.1.1概念

• 线性表(linear list)是相同类型的n(n>=0)个数据元素的有限序列,若⽤L命名为线性表,则⼀般⽰
L= (a1 , a2 , ..., a**i, a**i+1 , ..., a**n)
• 栈(stack)是限定仅在⼀端进⾏插⼊或删除操作的线性表。因此,对栈来说进⾏插⼊删除数据的⼀
端称为栈顶(top),另⼀端称为栈底(bottom),不含元素的空表称为空栈。栈中的数据元素遵循后
进先出(LIFO Last In First Out)的特性。
• 栈插⼊和删除数据都在栈顶端,插⼊数据⼀般叫做⼊栈/进栈,删除数据⼀般叫做出栈。

• 现实中⼿枪弹夹和⽻⽑球桶就是⼀个栈结构。

1.1.2⼊栈出栈顺序

• 假设⼊栈顺序为a1,a2,a3,a4,a5,那么出栈顺序是什么呢?⼤家最容易想到的是所有数据都⼊以
后再出栈就是a5,a4,a3,a2,a1;实际上有多种出栈顺序,⽐如每个数据⼊栈以后⻢上出栈,所以另
⼀种出栈顺序也可以是a1,a2,a3,a4,a5;还可以是a1,a2,a3⼊栈,出a3,a2,再⼊栈a4,a5,再出栈
a5,a4,a1,也就是说出栈顺序为a3,a2,a5,a4,a1; 通过分析我们可以看到因为出栈时机的不同,可以
有很多种出栈顺序。

1.1.3栈的基本操作

• 栈是⼀种只允许在⼀端进⾏插⼊和删除的线性表,栈插⼊和删除数据都在栈顶端,插⼊数据⼀叫
做⼊栈,删除数据⼀般叫做出栈,所栈最核⼼的接⼝函数就是⼊栈和出栈函数。
//栈的初始化
void StackInit(Stack& s);
*//栈的销毁
void StackDestroy(Stack& s);
*// x
元素⼊栈
*(进栈)*
void StackPush(Stack& s, STDataType x);
*//*将栈顶元素出栈,并⽤返回栈顶元素
STDataType StackPop(Stack& s);
*//*获取栈顶元素并返回
STDataType StackTop(Stack& s);
*//*获取栈中有效元素个数
int StackSize(Stack& s);
*//*检测栈是否为空,如果是空返回真,否则返回假
bool StackEmpty(Stack& s);

1.2栈的顺序存储

栈是只允许在⼀端进⾏元素插⼊和删除操作的特殊线性表。⽽线性表有顺序和链式两种存储结构故
栈也有顺序和链式两种存储结构。

1.2.1静态顺序栈和动态顺序栈结构

静态栈结构定义 动态栈结构定义

1.2.2动态顺序栈实现

• 需要注意的是栈结构中初始化时, top=-1 还是 top=0 , top=-1 代表top指向栈顶元素,那么⼊栈时要先++top,再把⼊栈数据放到top位置。 top=0 代表top指向栈顶元素的下⼀个位置, 那么⼊栈时要先把⼊栈数据放到top位置,再++top。其他接⼝函数处理细节也要对应调整。这两种⽅式没有优劣之分。

cpp 复制代码
#pragma once
//stack.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct {
    STDataType* arr; // 指向栈数组空间的指针
    int top; // 栈顶位置
    int capacity; // 容量
}Stack;
// 栈的初始化
void StackInit(Stack* s);
// 栈的销毁
void StackDestroy(Stack* s);
// x元素⼊栈(进栈)
void StackPush(Stack* s, STDataType x);
// 将栈顶元素出栈,并⽤返回栈顶元素
STDataType StackPop(Stack* s);
// 获取栈顶元素并返回
STDataType StackTop(Stack* s);
// 获取栈中有效元素个数
int StackSize(Stack* s);
// 检测栈是否为空,如果是空返回真,否则返回假
bool StackEmpty(Stack* s);

//stack.c

#include"stack.h"
void StackInit(Stack* s) {
    assert(s);
    s->arr = (STDataType*)malloc(4 * sizeof(STDataType));
    if (NULL == s->arr) {
        printf("StackInit: 申请空间失败!!!\n");
        exit(-1);
    }
    // 初始化时,top = 0,表⽰top指向的是栈顶元素的下⼀个位置
    // 初始化时,top = -1,表⽰top指向的是栈顶元素
    // 两种⽅式都可以,选择哪⼀种后⾯的逻辑都对应调整即可
    s->top = 0;
    s->capacity = 4;
}
void StackDestroy(Stack* s) {
    assert(s);
    if (s->arr) {
        free(s->arr);
        s->arr = NULL;
        s->top = 0;
        s->capacity = 0;
    }
}
// x元素⼊栈(进栈)
void StackPush(Stack* s, STDataType x) {
    assert(s);
    // 满了
    if (s->top == s->capacity) {
        STDataType* tmp = (STDataType*)realloc(s->arr, sizeof(STDataType) * s -> capacity * 2);
        if (tmp == NULL) {
            printf("StackPush: 扩容空间失败!!!\n");
            exit(-1);
        }
        s->arr = tmp;
        s->capacity *= 2;
    }
    s->arr[s->top] = x;
    s->top++;
}
// 将栈顶元素出栈,并⽤返回栈顶元素
STDataType StackPop(Stack* s) {
    assert(s);
    assert(!StackEmpty(s));
    STDataType topE = s->arr[s->top - 1];
    --s->top;
    return topE;
}
// 获取栈顶元素并返回
STDataType StackTop(Stack* s) {
    assert(s);
    assert(!StackEmpty(s));
    return s->arr[s->top - 1];
}
// 获取栈中有效元素个数
int StackSize(Stack* s) {
    assert(s);
    return s->top;
}
// 检测栈是否为空,如果是空返回真,否则返回假
bool StackEmpty(Stack* s) {
    assert(s);
    return s->top == 0;
}
#include "Stack.h"
int main() {
    Stack s;
    StackInit(&s);
    // ⼊栈,检测栈顶数据元素 和 栈中有效数据元素是否正确
    StackPush(&s, 1);
    StackPush(&s, 2);
    StackPush(&s, 3);
    StackPush(&s, 4);
    StackPush(&s, 5);
    //printf("栈中元素总数为: %d\n", StackSize(&s));
    printf("栈顶元素为: %d\n", StackTop(&s));
    // 出栈
    printf("出栈:栈顶元素为: %d\n", StackPop(&s));
    printf("出栈:栈顶元素为: %d\n", StackPop(&s));
    printf("出栈:栈顶元素为: %d\n", StackPop(&s));
    printf("出栈:栈顶元素为: %d\n", StackPop(&s));
    printf("出栈:栈顶元素为: %d\n", StackPop(&s));
    // 断⾔报错
    //printf("出栈:栈顶元素为: %d\n", StackPop(&s));
    //printf("栈顶元素为: %d\n", StackTop(&s));
    StackPush(&s, 6);
    StackPush(&s, 7);
    // 出栈
    printf("出栈:栈顶元素为: %d\n", StackPop(&s));
    printf("出栈:栈顶元素为: %d\n", StackPop(&s));
    StackDestroy(&s);
    return 0;
}

1.3栈的链式存储

• 栈的链式存储,我们可以选⽤单链表结构,也可以选⽤双向链表结构。但是需要注意的是,如果⽤单链表必需⽤链表头这⼀端做栈顶,⼊栈出栈就是对应头插头删,时间复杂度为O(1) 。否则⽤链表尾做栈顶,出栈对应尾删代价太⼤了,时间复杂度为 O(N)。

如果选⽤双向链表,则⽤表头表尾做栈顶都可以,因为双向链表头尾插⼊删除效率都是 O(1。当
然我们完全没必要选择双向链表,因为单链表就可以⾼效实现,还省空间⼀些。双向链表没有优
势,每个结点还要多存储⼀个前驱指针,使⽤它纯粹浪费了。
• 其次我们选择单链表实现,可以带头结点,也可以不带头结点;但是这⾥⼊栈出栈就是对应头头
删,单链表带头结点时,头插头删并不会带来什么便利,所以⼀般我们选择不带头结点实现即可。

cpp 复制代码
#pragma once
// LinkStack.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
// 链式栈中存储的数据元素类型
typedef int STDataType;
// 链式栈底层单链表中结点的定义
typedef struct LinkStackNode {
    struct LinkStackNode* next;
    STDataType data;
}LSNode;

// 链式栈结构的定义
typedef struct {
    LSNode* topHead; // 栈顶结点指针
    int size; // 栈中数据个数
}LinkStack;
// 初始化链式栈s
void LinkStackInit(LinkStack* s);
// 销毁链式栈s
void LinkStackDestroy(LinkStack* s);
// x⼊栈
void LinkStackPush(LinkStack* s, STDataType x);
// 出栈,并返回栈顶元素
STDataType LinkStackPop(LinkStack* s);
// 获取栈顶元素
STDataType LinkStackTop(LinkStack* s);
// 获取栈中有效元素个数
int LinkStackSize(LinkStack* s);
// 检测栈是否为空,如果是空返回真,否则返回假
bool LinkStackEmpty(LinkStack* s);





//LinkStack.c
#include"LinkStack.h"
void LinkStackInit(LinkStack* s) {
    assert(s);
    // 这⾥可以使⽤带头结点的单链表,也可以使⽤不带头结点
    // 相对⽽⾔不带头结点更推荐⼀些,因为⼊栈出栈主要是头插头删
    // 头结点价值不⼤
    s->topHead = NULL;
    s->size = 0;
}
void LinkStackDestroy(LinkStack* s) {
    assert(s);
    LSNode* cur = s->topHead;
    while (cur) {
        LSNode* next = cur->next;
        free(cur);
        cur = next;
    }
    s->topHead = NULL;
    s->size = 0;
}
// 获取栈中有效元素个数
int LinkStackSize(LinkStack* s) {
    assert(s);
    return s->size;
}
// 检测栈是否为空,如果是空返回真,否则返回假
bool LinkStackEmpty(LinkStack* s) {
    assert(s);
    return s->topHead == NULL;
}
// x⼊栈
void LinkStackPush(LinkStack* s, STDataType x) {
    assert(s);
    // 1. 申请新结点
    LSNode* newNode = (LSNode*)malloc(sizeof(LSNode));
    if (NULL == newNode) {
        printf("LinkStackPush:申请节点失败");
        exit(-1);
    }
    newNode->data = x;
    newNode->next = s->topHead;
    s->topHead = newNode;
    s->size++;
}
// 出栈,并返回栈顶元素
// 出栈,并返回栈顶元素
STDataType LinkStackPop(LinkStack* s) {
    assert(s);
    assert(!LinkStackEmpty(s));
    STDataType top = s->topHead->data;
    LSNode* delNode = s->topHead;
    s->topHead = delNode->next;
    free(delNode);
    s->size--;
    return top;
}
// 获取栈顶元素
STDataType LinkStackTop(LinkStack* s) {
    assert(s);
    assert(!LinkStackEmpty(s));
    return s->topHead->data;
}

int main() {
    LinkStack s;
    LinkStackInit(&s);
    // ⼊栈,检测栈顶数据元素 和 栈中有效数据元素是否正确
    LinkStackPush(&s, 1);
    LinkStackPush(&s, 2);
    LinkStackPush(&s, 3);
    printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
    printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
    LinkStackPush(&s, 4);
    LinkStackPush(&s, 5);
    printf("栈中元素总数为: %d\n", LinkStackSize(&s));
    printf("栈顶元素为: %d\n", LinkStackTop(&s));
    // 出栈
    printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
    printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
    printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
    //printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
    //printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
    // 断⾔报错
    //printf("出栈:栈顶元素为: %d\n", LinkStackPop(&s));
    LinkStackDestroy(&s);
    return 0;
}

1.4栈的顺序存储和链式存储对比

• 栈的顺序存储和链式存储都可以做到⼊栈和出栈时间复杂度为 O(1) ,效率都很⾼。
• 栈的顺序存储的唯⼀问题是⼊栈时空间不够,扩容有⼀定的消耗,但是这个消耗⼏乎可以忽略不计,因为插⼊⼀定量数据才会扩容。
• 栈的顺序存储相⽐链式存储CPU⾼速缓存命中率较⾼,且没有内存碎⽚。所以栈的实现⼀般使
⽤顺序存储。

2.队列

2.1队列的基本概念

2.1.1概念

• 线性表(linear list)是相同类型的n(n>=0)个数据元素的有限序列,若⽤L命名为线性表,则⼀般⽰
L= (a1 , a2 , ..., a**i, a**i+1 , ..., a**n)
• 队列(queue)是限定在⼀端插⼊另⼀端删除的线性表。因此,对队列来说,插⼊那⼀端叫做队尾, 插⼊叫⼊队,删除那⼀端叫队头,删除叫做出队。队列中的数据元素遵循先进先出(FIFO First In
First Out)的特性。
• 队列的⼊队出队顺序就简单多了,⽆论如何操作,⼀种⼊队顺序只对应⼀种出队顺序。

2.1.2队列的基本操作

• 队列在队尾⼊数据,在对头出数据,主要⽀持的核⼼接⼝就是⼊队和出队。
*//*初始化队列
void QueueInit(LinkQueue* q);
*//*销毁队列
void QueueDestroy(LinkQueue* q);
*//*检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q);
*//*获取队列中有效元素个数
int QueueSize(LinkQueue* q);
//获取队头元素
QDataType QueueFront(LinkQueue* q);
//x
⼊队列

void EnQueue(LinkQueue* q, QDataType x);
*//*出队并返回队头数据
QDataType DeQueue(LinkQueue* q);

2.2队列的链式存储

2.2.1链式队列的结构

• 队列的链式存储,我们可以选⽤单链表结构,也可以选⽤双向链表结构。他们⼊队对应着在表插
⼊,出队对应着在表头删除。当然我们完全没必要选择双向链表,因为单链表就可以⾼效实现,还
省空间⼀些。双向链表没有优势,每个结点还要多存储⼀个前驱指针,使⽤它纯粹浪费了。
• 其次这⾥我们选择单链表实现,⼊队就是尾插,出队就是头删。可以带头结点,也可以不带头结
点,带头结点的结构略简单⼀点

cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType; // 队列中存储数据元素的类型
// 采⽤单链表实现队列,链表结点的定义
typedef struct LinkNode {
	QDataType data;
	struct LinkNode* next;
}QNode;
typedef struct {
	QNode* front;
	QNode* rear;
	int size;
}LinkQueue;
// 初始化队列
void QueueInit(LinkQueue* q);
// 销毁队列
void QueueDestroy(LinkQueue* q);
// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q);
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q);
// 获取队头元素
QDataType QueueFront(LinkQueue* q);
// 将x⼊队列
void EnQueue(LinkQueue* q, QDataType x);
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q);


//LinkQueue.c
#include"LinkQueue.h"
// 初始化队列
void QueueInit(LinkQueue* q) {
	assert(q);
	// 这⾥可以实现为带头结点,也可以不带头结点,我们这⾥选择带头结点的结构
	q->front = q->rear = (QNode*)malloc(sizeof(QNode));
	if (q->front == NULL) {
		printf("QueueInit: 申请结点失败\n");
		exit(-1);
	}
	q->front->next = NULL;
	q->size = 0;
}

// 销毁队列
void QueueDestroy(LinkQueue* q) {
	assert(q);
	QNode* cur = q->front;
	while (cur) {
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->front = q->rear = NULL;
	q->size = 0;
}

// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q) {
	assert(q);
	return q->size == 0;
}
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q) {
	assert(q);
	return q->size;
}
// 获取队头元素
QDataType QueueFront(LinkQueue* q) {
	assert(q);
	assert(!QueueEmpty(q));
	return q->front->next->data;
}

// 将x⼊队列
void EnQueue(LinkQueue* q, QDataType x) {
	    
		assert(q);
		QNode* newNode = (QNode*)malloc(sizeof(QNode));
		if (newNode == NULL) {
			printf("EnQueue: 申请结点失败\n");
			exit(-1);
		}
		newNode->data = x;
		newNode->next = NULL;
		q->rear->next = newNode;
		q->rear = newNode;
		q->size++;
}
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q) {
	assert(q);
	assert(!QueueEmpty(q));
	QNode* delNode = q->front->next;
	QDataType x = delNode->data;
	// 删除第⼀个结点
	q->front->next = delNode->next;
	free(delNode);
	delNode = NULL;
	q->size--;
	// 最后⼀个结点被删掉,rear要指向哨兵位的头结点
	if (q->front->next == NULL) {
		q->rear = q->front;
	}
	return x;
}





 
// test.c
#include "LinkQueue.h"
int main() {
	LinkQueue lq;
	QueueInit(&lq);
	EnQueue(&lq, 1);
	EnQueue(&lq, 2);
	EnQueue(&lq, 3);
	EnQueue(&lq, 4);
	printf("队列数据个数%d\n", QueueSize(&lq));
	printf("出队%d\n", DeQueue(&lq));
	printf("出队%d\n", DeQueue(&lq));
	EnQueue(&lq, 5);
	EnQueue(&lq, 6);
	printf("出队%d\n", DeQueue(&lq));
	printf("出队%d\n", DeQueue(&lq));
	printf("队列数据个数%d\n", QueueSize(&lq));
	printf("出队%d\n", DeQueue(&lq));
	printf("出队%d\n", DeQueue(&lq));
	//printf("出队%d\n", DeQueue(&lq));
	QueueDestroy(&lq);
	return 0;
}

#pragma once

#include <stdio.h>

#include <stdlib.h>

#include <assert.h>

#include <stdbool.h>

typedef int QDataType; // 队列中存储数据元素的类型

// 采⽤单链表实现队列,链表结点的定义

typedef struct LinkNode {

QDataType data;

struct LinkNode* next;

}QNode;

typedef struct {

QNode* front;

QNode* rear;

int size;

}LinkQueue;

// 初始化队列

void QueueInit(LinkQueue* q);

// 销毁队列

void QueueDestroy(LinkQueue* q);

// 检测队列是否为空,空返回真,否则返回假

bool QueueEmpty(LinkQueue* q);

// 获取队列中有效元素个数

int QueueSize(LinkQueue* q);

// 获取队头元素

QDataType QueueFront(LinkQueue* q);

// 将x⼊队列

void EnQueue(LinkQueue* q, QDataType x);

// 出队并返回队头数据

QDataType DeQueue(LinkQueue* q);

//LinkQueue.c

#include"LinkQueue.h"

// 初始化队列

void QueueInit(LinkQueue* q) {

assert(q);

// 这⾥可以实现为带头结点,也可以不带头结点,我们这⾥选择带头结点的结构

q->front = q->rear = (QNode*)malloc(sizeof(QNode));

if (q->front == NULL) {

printf("QueueInit: 申请结点失败\n");

exit(-1);

}

q->front->next = NULL;

q->size = 0;

}

// 销毁队列

void QueueDestroy(LinkQueue* q) {

assert(q);

QNode* cur = q->front;

while (cur) {

QNode* next = cur->next;

free(cur);

cur = next;

}

q->front = q->rear = NULL;

q->size = 0;

}

// 检测队列是否为空,空返回真,否则返回假

bool QueueEmpty(LinkQueue* q) {

assert(q);

return q->size == 0;

}

// 获取队列中有效元素个数

int QueueSize(LinkQueue* q) {

assert(q);

return q->size;

}

// 获取队头元素

QDataType QueueFront(LinkQueue* q) {

assert(q);

assert(!QueueEmpty(q));

return q->front->next->data;

}

// 将x⼊队列

void EnQueue(LinkQueue* q, QDataType x) {

assert(q);

QNode* newNode = (QNode*)malloc(sizeof(QNode));

if (newNode == NULL) {

printf("EnQueue: 申请结点失败\n");

exit(-1);

}

newNode->data = x;

newNode->next = NULL;

q->rear->next = newNode;

q->rear = newNode;

q->size++;

}

// 出队并返回队头数据

QDataType DeQueue(LinkQueue* q) {

assert(q);

assert(!QueueEmpty(q));

QNode* delNode = q->front->next;

QDataType x = delNode->data;

// 删除第⼀个结点

q->front->next = delNode->next;

free(delNode);

delNode = NULL;

q->size--;

// 最后⼀个结点被删掉,rear要指向哨兵位的头结点

if (q->front->next == NULL) {

q->rear = q->front;

}

return x;

}

// test.c

#include "LinkQueue.h"

int main() {

LinkQueue lq;

QueueInit(&lq);

EnQueue(&lq, 1);

EnQueue(&lq, 2);

EnQueue(&lq, 3);

EnQueue(&lq, 4);

printf("队列数据个数%d\n", QueueSize(&lq));

printf("出队%d\n", DeQueue(&lq));

printf("出队%d\n", DeQueue(&lq));

EnQueue(&lq, 5);

EnQueue(&lq, 6);

printf("出队%d\n", DeQueue(&lq));

printf("出队%d\n", DeQueue(&lq));

printf("队列数据个数%d\n", QueueSize(&lq));

printf("出队%d\n", DeQueue(&lq));

printf("出队%d\n", DeQueue(&lq));

//printf("出队%d\n", DeQueue(&lq));

QueueDestroy(&lq);

return 0;

}

相关推荐
wear工程师1 小时前
ThreadLocal 在线程池里为什么会串数据:别只会答内存泄漏
java·后端
审小匠OpenCPAi1 小时前
银行流水核查怎么自动化?单边匹配、双向勾稽与图聚类异常检测的工程对比
java·前端·人工智能·python·审计
Hrain-AI1 小时前
2026 企业 AI 编程智能体实战:Codex 与 Claude Code
开发语言·人工智能·kotlin
码上上班1 小时前
docker课程
java·docker·容器
腻害兔1 小时前
【若依项目-产品经理视角】RuoYi-Vue-Pro 源码拆解:CRM 客户关系模块深度解析——从线索到回款,一套完整的 B2B 销售闭环是怎么搭的?
java·前端·javascript·vue.js·产品经理·ai编程
wdfk_prog2 小时前
嵌入式面试真题第 13 题:单硬件 Timer 下的高精度多路软件定时器架构设计
c语言·开发语言·面试·职场和发展·嵌入式
oort1232 小时前
吃上了自家的细糠,还挺丝滑,用起来手感还行,OortCloud发布新版AI编程平台,下载 OortCodex,Token多,免费薅
大数据·开发语言·人工智能·ai编程
达子6662 小时前
第9章_HarmonyOs图解 用Java开发UI
java·ui·harmonyos
slandarer2 小时前
MATLAB | 泰勒图绘制,支持各种角度范围
开发语言·数学建模·matlab·nature·顶刊·泰勒图