初识栈与队列

本文介绍了栈和队列这两种基本的数据结构,包括它们的定义、操作(如压入、弹出、查看栈顶/队首元素等)、存储方式(顺序栈和链式栈,以及数组/链表实现),以及在计算机科学中的应用,如函数调用管理、表达式求值等。
摘要由CSDN通过智能技术生成

初识栈与队列

在这里插入图片描述

栈的定义

栈(Stack)是一种基于先进后出(Last In, First Out,LIFO)原则的抽象数据类型(ADT)。它可以通过数组或链表等数据结构来实现。栈的定义包括以下几个关键点:

元素的存储

​ 栈中的元素按照后进先出的顺序存储。也就是说,最后放入栈的元素会被首先移除。

主要操作

​ 1.压入(Push):将一个元素添加到栈的顶部。

​ 2.弹出(Pop):移除栈顶部的元素,并返回该元素的值。

​ 3.查看栈顶元素(Top):获取栈顶部的元素,但不对栈进行修改。

​ 4.检查栈是否为空(Empty):判断栈中是否有元素。

特点

​ 1.栈是一种动态集合,大小可以根据需要动态变化。

​ 2.栈的操作只能在栈顶执行,即只能对栈顶元素进行添加或移除操作。

​ 3.栈的操作通常是高效的,时间复杂度为O(1)。

栈在计算机科学中有着广泛的应用,包括函数调用的管理(函数调用栈)、表达式求值、后缀表达式转换等。

栈的存储

​ 栈的存储结构可以使用多种方式实现,其中最常见的两种是使用数组(顺序栈)和使用链表(链式栈)。

1.顺序栈

​ 顺序栈是基于数组实现的栈。数组具有连续的内存空间,这使得顺序栈的元素在内存中是依次排列的。

​ 顺序栈需要预先分配一定大小的内存空间,当栈的容量不足时,可能需要重新分配更大的内存空间,并将原有元素复制到新的空间中。

​ 顺序栈的优点是随机访问速度快,操作简单,但容量受限制。

2.链式栈

​ 链式栈是基于链表实现的栈。链表由节点组成,每个节点包含一个元素和一个指向下一个节点的指针。

​ 链式栈不需要预先分配内存空间,可以根据需要动态地分配和释放内存。

​ 链式栈的优点是可以灵活地增加或减少元素,没有固定的容量限制,但访问某个特定位置的元素可能需要遍历链表,速度相对较慢。

无论是顺序栈还是链式栈,它们的基本操作(压入、弹出、查看栈顶元素等)都是相同的,只是在内存管理和访问速度上有所不同。在选择实现栈时,应根据具体的应用场景和需求来确定使用哪种存储结构。

栈的操作
1.顺序栈

1>存储结构

#define MAX_SIZE 100 // 定义栈的最大容量

typedef struct {
    int data[MAX_SIZE]; // 数组用于存储栈中的元素
    int top; // 栈顶指针,指向栈顶元素的位置
} SeqStack;

2>初始化栈

void initSeqStack(SeqStack *stack) {
    stack->top = -1; // 初始时栈为空,栈顶指针置为-1
}

3>判断栈是否为空

int isEmpty(SeqStack *stack) {
    return stack->top == -1;
}

4>判断栈是否已满

int isFull(SeqStack *stack) {
    return stack->top == MAX_SIZE - 1;
}

5>将元素压入栈

void push(SeqStack *stack, int item) {
    if (isFull(stack)) {
        printf("栈已满,无法执行压入操作!\n");
        return;
    }
    stack->data[++stack->top] = item; // 先将栈顶指针加1,然后将元素压入栈顶
}

6>弹出栈顶元素

int pop(SeqStack *stack) {
    if (isEmpty(stack)) {
        printf("栈已空,无法执行弹出操作!\n");
        return -1; // 返回一个特殊值表示栈为空
    }
    return stack->data[stack->top--]; // 先取出栈顶元素,然后将栈顶指针减1
}

7>查看栈顶元素

int top(SeqStack *stack) {
    if (isEmpty(stack)) {
        printf("栈已空,无栈顶元素!\n");
        return -1; // 返回一个特殊值表示栈为空
    }
    return stack->data[stack->top]; // 直接返回栈顶元素,不改变栈顶指针
}
2.链式栈

1>链表的初始化

typedef struct Node {
	int data;
	struct Node* next;
}Node;

2>栈的初始化

typedef struct Stack {
	struct Node* stackTop;
	int size;
}Stack;

Stack* createStack() {
	Stack* myStack = (Stack*)malloc(sizeof(Stack));
	myStack->size = 0;
	myStack->stackTop = NULL;

	return myStack;
}

3>入栈

void push(Stack* myStack, int data) {
	Node* newNode = (Node*)malloc(sizeof(Node));

	newNode->data = data;
	newNode->next = myStack->stackTop;//新节点的next指针指向栈顶
	myStack->stackTop = newNode;//栈顶更新为新节点
	myStack->size++;//计数
}

4>出栈

void pop(Stack* myStack) {
	if (myStack->size == 0) {
		printf("栈为空");
	}
	else {
		Node* nextNode = myStack->stackTop->next;
		free(myStack->stackTop);
		myStack->stackTop = nextNode;
		myStack->size--;
	}
}

5>获取栈顶元素

int top(Stack* myStack) {
	if (myStack->size == 0) {
		return myStack->size;
	}

	return myStack->stackTop->data;
}

6>判断栈是否为空

int empty(Stack* myStack) {
	if (myStack->size == 0) {
		return 0;
	}
	return 1;
}

主函数

int main() {
	Stack* myStack = createStack();

	push(myStack, 1);
	push(myStack, 2);
	push(myStack, 3);

	while (empty(myStack)) {
		printf("%d ", top(myStack));
		pop(myStack);
	}
    //3 2 1
	return 0;
}

队列

在这里插入图片描述

队列的定义

队列是一种常见的数据结构,它遵循先进先出(First-In-First-Out,FIFO)的原则。这意味着最先进入队列的元素将最先被移除。队列通常比喻为排队等待服务的人群,先来的人先被服务,后来的人依次排在后面等待。

队列有两个主要操作:

1.入队(enqueue):向队列的末尾(队尾)添加一个元素。

2.出队(dequeue):从队列的头部(队首)移除一个元素。

除此之外,队列还可能包括其他操作,如:

查看队首元素(peek):获取队列的第一个元素,但不将其从队列中移除。

检查队列是否为空(isEmpty):判断队列中是否没有任何元素。

检查队列是否已满(isFull):对于固定大小的队列,可以检查队列是否已达到最大容量。

队列常见的应用包括任务调度、计算机网络数据包处理、操作系统中的进程调度等。在算法和数据结构中,队列是一种基础的数据结构,具有广泛的应用。

队列的存储

队列可以使用不同的方法进行存储,最常见的有两种:

1.数组实现

​ 使用数组作为底层存储结构。

​ 通过两个指针(队首指针和队尾指针)来标记队列的起始和结束位置。

​ 当元素入队时,将其添加到队尾指针所指向的位置,并将队尾指针向后移动。

​ 当元素出队时,从队首指针所指向的位置移除元素,并将队首指针向后移动。

​ 这种方法的优点是简单高效,但在队列长度变化较大时可能会浪费空间。

2.链表实现

​ 使用链表作为底层存储结构。

​ 每个节点包含一个元素和一个指向下一个节点的指针。

​ 队首和队尾分别由链表的头部和尾部节点表示。

​ 入队时,在链表尾部添加一个新节点,并更新队尾指针。

​ 出队时,移除链表头部的节点,并更新队首指针。

​ 这种方法可以更灵活地管理内存,但相对于数组实现可能会稍微增加一些额外的开销。

无论使用哪种方法,队列的基本操作(入队和出队)的时间复杂度都是 O(1),这使得队列成为处理先进先出数据的有效数据结构。

队列的操作
1.用数组

1>定义

typedef struct {
    int* nums;       // 用于存储队列元素的数组
    int front;       // 队首指针,指向队首元素
    int queSize;     // 尾指针,指向队尾 + 1
    int queCapacity; // 队列容量
} Queue;

2>初始化

Queue* createQueue(int capacity) {
    Queue* queue = (Queue*)malloc(sizeof(Queue));
    // 初始化数组
    queue->queCapacity = capacity;
    queue->nums = (int*)malloc(sizeof(int) * queue->queCapacity);
    queue->front = queue->queSize = 0;
    return queue;
}

3>获取队列的容量

int capacity(Queue* queue) {
    return queue->queCapacity;
}

4>获取队列的长度

int size(Queue* queue) {
    return queue->queSize;
}

5>判断队列是否为空

bool empty(Queue* queue) {
    return queue->queSize == 0;
}

6>判断队列是否为满

int isQueueFull(Queue* queue){
    if (queue->queSize == queue->queCapacity) {  
        return 1;  
    }  
    return 0;

7>访问队首元素

int peek(Queue* queue) {
    assert(size(queue) != 0);
    return queue->nums[queue->front];//队列的大小不为零,返回队首元素
}

8>入队

void push(Queue* queue, int num) {
    if (size(queue) == capacity(queue)) {
        printf("队列已满\r\n");
        return;
    }
    // 计算队尾指针,指向队尾索引 + 1
    // 通过取余操作实现 rear 超过数组的长度时,回到数组的开头。
    int rear = (queue->front + queue->queSize) % queue->queCapacity;
    // 将 num 添加至队尾
    queue->nums[rear] = num;
    queue->queSize++;
}

9>出队

int pop(Queue* queue) {  
    if (empty(queue)) {  
        printf("队列为空\n");  
        return -1; 
    }  
    int num = peek(queue);  // 保存出队元素
    queue->front = (queue->front + 1) % queue->queCapacity;  
    queue->queSize--;  
    return num;  // 返回出队的元素
}
2.用链表

1>初始化

typedef struct Node {
	int data;
	struct Node* next;
}Node;

typedef struct Queue {
	struct Node* frontNode;
	struct Node* tailNode;
	int size;
}Queue;

Queue* createQueue() {
	Queue* myQueue = (Queue*)malloc(sizeof(Queue));
	myQueue->frontNode = myQueue->tailNode = NULL;
	myQueue->size = 0;

	return myQueue;
}

2>入队

void push(Queue* myQueue,int data) {
	Node* newNode = createNode(data);
	if (myQueue->size == 0) {
		myQueue->frontNode = myQueue->tailNode = newNode;
	} else {
		myQueue->tailNode->next = newNode;
		myQueue->tailNode = newNode;
	}		
	myQueue->size++;
}

3>出队

void pop(Queue* myQueue) {
	if (myQueue->size == 0) {
		printf("队列为空");
		system("pause");
		return;
	} else {
		Node* newNode = myQueue->frontNode->next;// 指向队首的下一个节点
		free(myQueue->frontNode);// 释放队列的头节点的内存
		myQueue->frontNode = newNode;//队列的头节点指针指向下一个节点
		myQueue->size--;
	}
}

4>获取队首元素

int front(Queue* myQueue){
	if (myQueue->size == 0) {
		printf("队列为空");
		system("pause");
		return 0;
	}
	return myQueue->frontNode->data;
}

5>判断队是否为空

int empty(Queue* myQueue) {
	if (myQueue->size == 0) {
		return 0;
	} else{
		return 1;
	}
}

6>主函数

int main() {
	Queue* myQueue = createQueue();
    
	push(myQueue, 1);
	push(myQueue, 2);
	push(myQueue, 3);

	while (empty(myQueue)) {
		printf("%d ", front(myQueue));
		pop(myQueue);
	}
	return 0;
}

用栈实现队列

我们以力扣上的一道题为例:

232. 用栈实现队列
在这里插入图片描述

下面是代码实现:

typedef struct MyQueue{
    int stackIntop,stackOuttop;
    int stackIn[100],stackOut[100];
} MyQueue;


MyQueue* myQueueCreate() {
    MyQueue* queue=(MyQueue*)malloc(sizeof(MyQueue));
    queue->stackIntop=0;
    queue->stackOuttop=0;
    return queue;
}

void myQueuePush(MyQueue* obj, int x) {
    obj->stackIn[(obj->stackIntop)++]=x;
}

int myQueuePop(MyQueue* obj) {
    int inTop=obj->stackIntop;
    int outTop=obj->stackOuttop;
    if(outTop==0){
        while(inTop>0){
            obj->stackOut[outTop++]=obj->stackIn[--inTop];
        }
    }
    int top=obj->stackOut[--(outTop)];
    while(outTop>0){
        obj->stackIn[inTop++]=obj->stackOut[--outTop];
    }
    obj->stackIntop=inTop;
    obj->stackOuttop=outTop;
    return top;
}

int myQueuePeek(MyQueue* obj) {
    return obj->stackIn[0];
}

bool myQueueEmpty(MyQueue* obj) {
    if(obj->stackIntop==0&&obj->stackOuttop==0)
        return true;
    return false;
}

void myQueueFree(MyQueue* obj) {
    obj->stackIntop = 0;
    obj->stackOuttop = 0;
}

特别鸣谢:栈和队列初探-CSDN博客

代码随想录 (programmercarl.com)

已经到底啦!!!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值