初识栈与队列
栈
栈的定义
栈(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;
}
用栈实现队列
我们以力扣上的一道题为例:
下面是代码实现:
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博客
已经到底啦!!!