数据结构与C 知识量:8 - 24 - 99
栈(stack)又名堆栈,作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。
栈具有先进后出的特性,也就是说,最后进入栈的元素会最先被弹出。
顺序栈是一种基于数组实现的栈,其基本操作包括入栈、出栈、查看栈顶元素等。下面是一个简单的顺序栈及其C语言实现的示例:
#include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 // 栈的最大容量 typedef struct { int data[MAX_SIZE]; // 存储数据的数组 int top; // 栈顶指针 } Stack; // 初始化栈 void initStack(Stack* s) { s->top = -1; } // 判断栈是否为空 int isEmpty(Stack* s) { return s->top == -1; } // 判断栈是否已满 int isFull(Stack* s) { return s->top == MAX_SIZE - 1; } // 入栈操作 void push(Stack* s, int value) { if (isFull(s)) { printf("Error: stack is full\n"); return; } s->data[++s->top] = value; } // 出栈操作 int pop(Stack* s) { if (isEmpty(s)) { printf("Error: stack is empty\n"); return -1; } return s->data[s->top--]; } // 查看栈顶元素 int peek(Stack* s) { if (isEmpty(s)) { printf("Error: stack is empty\n"); return -1; } return s->data[s->top]; } int main() { Stack s; initStack(&s); push(&s, 1); push(&s, 2); push(&s, 3); printf("%d\n", pop(&s)); // 输出 3 printf("%d\n", pop(&s)); // 输出 2 printf("%d\n", pop(&s)); // 输出 1 printf("%d\n", pop(&s)); // 输出 Error: stack is empty,返回值 -1 return 0; }
在这个示例中,定义了一个结构体 Stack,其中包含一个数组 data 和一个 top 指针,用于指向栈顶元素。initStack 函数用于初始化栈,将 top 指针设为 -1 表示栈为空。isEmpty 和 isFull 函数分别用于判断栈是否为空或已满。push 和 pop 函数分别用于入栈和出栈操作,其中 push 函数在入栈前先判断栈是否已满,pop 函数在出栈前先判断栈是否为空。peek 函数用于查看栈顶元素,但不会将其弹出。在 main 函数中,首先初始化一个栈,然后依次入栈三个元素,并依次出栈三个元素,最后尝试再次出栈一个元素,此时会输出错误信息。
链栈是一种特殊的栈,它使用链表来实现。链栈的节点包括数据域和指针域,其中指针域指向下一个节点。链栈具有先进后出的特性,即最后进入栈的元素会最先被弹出。
以下是链栈的C语言实现示例:
#include <stdio.h> #include <stdlib.h> // 定义链栈节点结构体 typedef struct Node { int data; // 数据域 struct Node* next; // 指针域 } Node; // 定义链栈结构体 typedef struct Stack { Node* top; // 栈顶指针 } Stack; // 初始化栈 void initStack(Stack* s) { s->top = NULL; } // 判断栈是否为空 int isEmpty(Stack* s) { return s->top == NULL; } // 入栈操作 void push(Stack* s, int value) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = value; newNode->next = s->top; s->top = newNode; } // 出栈操作 int pop(Stack* s) { if (isEmpty(s)) { printf("Error: stack is empty\n"); return -1; } Node* temp = s->top; int value = temp->data; s->top = temp->next; free(temp); return value; } int main() { Stack s; initStack(&s); push(&s, 1); push(&s, 2); push(&s, 3); printf("%d\n", pop(&s)); // 输出 3 printf("%d\n", pop(&s)); // 输出 2 printf("%d\n", pop(&s)); // 输出 1 printf("%d\n", pop(&s)); // 输出 Error: stack is empty,返回值 -1 return 0; }
在这个示例中,定义了结构体 Node 和 Stack,其中 Node 表示链栈的节点,Stack 表示链栈。initStack 函数用于初始化栈,将 top 指针设为 NULL 表示栈为空。isEmpty 函数用于判断栈是否为空。push 函数用于入栈操作,它创建一个新的节点,将数据存储在其中,并将新节点的指针指向栈顶节点,最后更新栈顶指针。pop 函数用于出栈操作,它先判断栈是否为空,然后将栈顶节点的数据返回,并将栈顶指针指向下一个节点,最后释放被弹出节点的内存。在 main 函数中,首先初始化一个栈,然后依次入栈三个元素,并依次出栈三个元素,最后尝试再次出栈一个元素,此时会输出错误信息。
栈的一个非常重要的应用就是在程序设计语言中实现递归。递归是指在定义自身的同时又出现对自身的引用。如果一个函数在其定义体内直接调用自己,则称其为直接递归函数;如果一个函数经过一系列中间调用语句,通过其他函数间接调用自己,则称其为间接递归函数。
下面的示例演示一个简单的递归函数,以及如何使用栈来保存和恢复程序的执行上下文。
首先,是如何使用递归实现一个计算阶乘的函数。阶乘是一个数学函数,通常表示为n!,n的阶乘定义为所有小于等于n,大于0的正整数的乘积。例如,5! = 5 * 4 * 3 * 2 * 1 = 120。
#include <stdio.h> // 递归函数计算阶乘 int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } } int main() { int number; printf("Enter a number: "); scanf("%d", &number); printf("%d! = %d\n", number, factorial(number)); return 0; }
接下来,将使用C语言的setjmp.h库来演示如何使用栈来保存和恢复程序的执行上下文。setjmp.h库提供了一种在C语言中实现异常处理的方法。可以使用setjmp函数来保存当前的执行上下文(包括堆栈和CPU寄存器等),而longjmp函数则可以恢复之前保存的执行上下文。
示例如下:
#include <stdio.h> #include <setjmp.h> jmp_buf env; void second() { printf("second\n"); longjmp(env,1); } void first() { second(); printf("first\n"); } int main() { if ( ! setjmp(env) ) { first(); } else { printf("main\n"); // 在这里被调用,因为first()中的second()调用了longjmp()。 } return 0; }
在这个示例中,setjmp函数在main函数中被调用,并将当前的执行上下文保存到env变量中。然后,first函数被调用,然后second函数被调用,second函数调用longjmp函数来恢复之前保存的执行上下文。因为longjmp函数被调用,所以if语句的条件变为false,程序执行else部分的代码。
Copyright © 2017-Now pnotes.cn. All Rights Reserved.
编程学习笔记 保留所有权利
MARK:3.0.0.20240214.P35
From 2017.2.6