数据结构与C

数据结构与C 知识量:8 - 24 - 99

3.1 栈><

栈的定义- 3.1.1 -

栈(stack)又名堆栈,作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。

栈具有先进后出的特性,也就是说,最后进入栈的元素会最先被弹出。

顺序栈- 3.1.2 -

顺序栈是一种基于数组实现的栈,其基本操作包括入栈、出栈、查看栈顶元素等。下面是一个简单的顺序栈及其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 函数中,首先初始化一个栈,然后依次入栈三个元素,并依次出栈三个元素,最后尝试再次出栈一个元素,此时会输出错误信息。

链栈- 3.1.3 -

链栈是一种特殊的栈,它使用链表来实现。链栈的节点包括数据域和指针域,其中指针域指向下一个节点。链栈具有先进后出的特性,即最后进入栈的元素会最先被弹出。

以下是链栈的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 函数中,首先初始化一个栈,然后依次入栈三个元素,并依次出栈三个元素,最后尝试再次出栈一个元素,此时会输出错误信息。

栈与递归的实现- 3.1.4 -

栈的一个非常重要的应用就是在程序设计语言中实现递归。递归是指在定义自身的同时又出现对自身的引用。如果一个函数在其定义体内直接调用自己,则称其为直接递归函数;如果一个函数经过一系列中间调用语句,通过其他函数间接调用自己,则称其为间接递归函数。

下面的示例演示一个简单的递归函数,以及如何使用栈来保存和恢复程序的执行上下文。

首先,是如何使用递归实现一个计算阶乘的函数。阶乘是一个数学函数,通常表示为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部分的代码。