数据结构

白水晶大约 10 分钟数据结构链表队列

链表

什么是链表

​ 链表是一种基本的数据结构,它由一系列结点组成,每个结点都包括两个部分:数据域和指针域。数据域用于存储一个数据元素,指针域用于指向下一个结点。链表中的第一个结点称为头结点,最后一个结点称为尾结点。链表具有动态性、节省内存等优点,常用于实现栈和队列等数据结构。链表有单向链表、双向链表和循环链表等多种形式。在单向链表中,每个结点只有一个指针域,指向下一个结点;在双向链表中,每个结点有两个指针域,一个指向下一个结点,一个指向上一个结点;在循环链表中,尾结点的指针指向头结点,形成一个环。链表分为链表的插入、链表的删除、链表的修改和链表的查找等操作,需要灵活应用不同的操作方法。

怎么创建链表(C语言 单向链表)

#include <stdio.h>
#include <stdlib.h>

//链表节点定义
typedef struct ListNode {
    int val;  // 结点的值
    struct ListNode* next;  // 指向下一个结点的指针
} ListNode;

// 创建一个新结点
ListNode* createNode(int val) {
    ListNode* node = (ListNode*)malloc(sizeof(ListNode));
    node->val = val;
    node->next = NULL;
    return node;
}
// 头插法,将新结点插入链表的头部
void insertNodeAtHead(ListNode** head, int val) {
    ListNode* node = createNode(val);
    node->next = *head;
    *head = node;
}

// 尾插法,将新结点插入链表的末尾
void insertNodeAtTail(ListNode** head, int val) {
    ListNode* node = createNode(val);
    if (*head == NULL) {  // 如果链表为空,直接创建一个结点作为头结点
        *head = node;
    } else {
        ListNode* p = *head;
        while (p->next != NULL) {  // 找到最后一个结点
            p = p->next;
        }
        p->next = node;  // 在最后一个结点后面插入一个新结点
    }
}

// 删除链表中第一个值等于给定值的结点
void deleteNode(ListNode** head, int val) {
    if (*head == NULL) {
        return;
    }
    if ((*head)->val == val) {  // 如果头结点的值等于给定值,直接删除头结点
        ListNode* p = *head;
        *head = (*head)->next;
        free(p);
        return;
    }
    ListNode* prev = *head;
    ListNode* p = (*head)->next;
    while (p != NULL && p->val != val) {  // 找到第一个值等于给定值的结点
        prev = p;
        p = p->next;
    }
    if (p != NULL) {  // 如果找到了结点,删除它
        prev->next = p->next;
        free(p);
    }
}
// 打印链表所有结点的值
void printList(ListNode* head) {
    if (head == NULL) {
        printf("List is empty.\n");
    } else {
        printf("List: ");
        ListNode* p = head;
        while (p != NULL) {
            printf("%d ", p->val);
            p = p->next;
        }
        printf("\n");
    }
}

// 反转链表
ListNode* reverseList(ListNode* head) {
    ListNode* prev = NULL;  // 前驱节点
    ListNode* curr = head;  // 当前节点
    while (curr != NULL) {
        ListNode* next = curr->next;  // 记录下一节点
        curr->next = prev;  // 反转指向
        prev = curr;
        curr = next;
    }
    return prev;  // 返回新的头结点
}


// 销毁链表,释放所有结点占用的内存
void destroyList(ListNode** head) {
    ListNode* p = *head;
    while (p != NULL) {
        ListNode* next = p->next;
        free(p);
        p = next;
    }
    *head = NULL;  // 将头指针设为 NULL,表示链表已经不存在
}

// 主函数,用于测试链表的实现
int main(int argc, char **argv) {
    ListNode* head = NULL;  // 初始化链表为空
    insertNodeAtHead(&head, 1);
    insertNodeAtHead(&head, 2);
    insertNodeAtHead(&head, 3);
    printList(head);
    insertNodeAtTail(&head, 0);
    printList(head);
    deleteNode(&head, 2);
    printList(head);
    head = reverseList(head);
    printList(head);

    destroyList(&head);
    return 0;
}

双向链表

​ 双向链表(Doubly Linked List)是链表的一种扩展,除了有 next 指针指向下一个节点,还有一个 prev 指针指向前一个节点,即每个节点有两个指针,分别指向前一个节点和后一个节点。双向链表支持双向遍历,可以在链表中任意位置插入或删除元素,也可以在任意位置找到某个节点。

#include <stdio.h>
#include <stdlib.h>

// 双向链表节点的结构体
typedef struct ListNode {
    int data;
    struct ListNode *prev;
    struct ListNode *next;
} ListNode;

// 插入节点到链表头部
void insertAtHead(ListNode **head, int data) {
    ListNode *newNode = (ListNode*) malloc(sizeof(ListNode)); // 分配空间创建新节点
    newNode->data = data; // 将数据赋值给新节点
    newNode->prev = NULL;
    newNode->next = *head; // 将新节点的 next 指向链表头

    if (*head != NULL) { // 如果链表非空
        (*head)->prev = newNode; // 将原先的头节点的 prev 指向新节点
    }

    *head = newNode; // 将新节点设置为链表头
}
// 插入节点到链表尾部
void insertAtTail(ListNode **head, int data) {
    ListNode *newNode = (ListNode*) malloc(sizeof(ListNode)); // 分配空间创建新节点
    newNode->data = data; // 将数据赋值给新节点
    newNode->prev = NULL;
    newNode->next = NULL;

    if (*head == NULL) { // 如果链表为空
        *head = newNode;
        return;
    }

    ListNode *current = *head;
    while (current->next != NULL) { // 找到链表的尾节点
        current = current->next;
    }

    current->next = newNode; // 将新节点插入到尾节点之后
    newNode->prev = current;
}

// 反转链表
void reverse(ListNode **head) {
    ListNode *temp = NULL; // 中间变量保存临时节点
    ListNode *current = *head;

    while (current != NULL) {
        temp = current->prev; // 将临时节点设置为当前节点的前一个节点
        current->prev = current->next; // 将当前节点的 prev 指向 next
        current->next = temp; // 将当前节点的 next 指向 temp
        current = current->prev; // 将 current 指向下一个节点
    }

    if (temp != NULL) { // 如果链表非空
        *head = temp->prev; // 将 head 指向最后一个节点
    }
}

// 删除链表中指定元素
void delete(ListNode **head, int data) {
    if (*head == NULL) { // 如果链表为空
        return;
    }

    ListNode *current = *head; // 定义指向当前节点的指针
    ListNode *prev = NULL; // 定义指向前一个节点的指针

    while (current != NULL && current->data != data) { // 找到待删除节点
        prev = current;
        current = current->next;
    }

    if (current == NULL) { // 如果找不到待删除节点
        return;
    }

    if (prev == NULL) { // 如果需要删除的是链表头
        *head = current->next;
    } else { // 如果需要删除的不是链表头
        prev->next = current->next;
    }

    if (current->next != NULL) { // 如果需要删除节点的下一个节点非空
        current->next->prev = prev;
    }

    free(current); // 释放待删除节点
}


// 销毁链表
void destroyList(ListNode **head) {
    ListNode *current = *head; // 定义指向当前节点的指针
    ListNode *next = NULL; // 定义指向下一个节点的指针

    while (current != NULL) {
        next = current->next; // 将指向下一个节点的指针指向下一个节点
        free(current); // 释放当前节点
        current = next; // 将指向当前节点的指针指向下一个节点
    }

    *head = NULL; // 将链表头指针设置为空
}

// 打印链表
void printList(ListNode *head) {
    if (head == NULL) { // 如果链表为空
        printf("List is empty.\n");
        return;
    }

    printf("List: ");
    while (head != NULL) { // 遍历链表
        printf("%d ", head->data);
        head = head->next;
    }
    printf("\n");
}
// 测试
int main() {
    ListNode *head = NULL;

    // 插入数据
    insertAtTail(&head, 1);
    insertAtTail(&head, 2);
    insertAtTail(&head, 3);
    insertAtHead(&head, 4);
    insertAtHead(&head, 5);

    // 打印原始链表
    printf("Original ");
    printList(head);

    // 删除元素
    printf("Deleting 3.\n");
    delete(&head, 3);

    // 打印删除元素后的链表
    printf("After deletion ");
    printList(head);

    // 反转链表
    reverse(&head);

    // 打印反转后的链表
    printf("Reversed ");
    printList(head);

    // 销毁链表
    destroyList(&head);

    return 0;
}

​ 栈(Stack)是一种后进先出(Last In First Out,LIFO)的数据结构。它的特点是只能在栈顶进行操作,也就是说先进入栈底的元素会被压在栈顶元素的后面,只有栈顶元素才能被操作和访问。栈可以用数组或链表实现。常用的操作有 push(入栈)、pop(出栈)和 top(获取栈顶元素)。以计算器为例,当输入一个表达式时,需要利用栈来进行计算。具体实现是:当遇到数字时,将其入栈;当遇到符号时,从栈中弹出两个数进行计算,再将结果入栈;最后栈顶的元素即为表达式的结果。栈也可以用在函数调用中。每当调用一个函数时,都会将其参数和返回地址压入栈中,函数执行结束后再弹出栈顶元素,将控制权返回给调用者。在操作系统中,栈被用来处理程序的调用、中断和异常等。当程序运行遇到中断或异常时,当前执行的指令和寄存器状态会被保存在栈中,处理完中断或异常后,再从栈中恢复状态并继续执行。栈是一种非常常用的数据结构,被广泛应用于编译器、操作系统、数据库等领域中的算法和数据结构。

#include <stdio.h>
#include <stdlib.h>

// 定义栈的节点类型
typedef struct Node {
    int data;
    struct Node *next;
} Node;

// 定义链栈的结构体
typedef struct LinkedListStack {
    Node *top;
} LinkedListStack;

// 创建一个空链栈并返回栈顶指针
LinkedListStack* createStack() {
    LinkedListStack *s = (LinkedListStack*) malloc(sizeof(LinkedListStack)); // 分配空间创建链栈
    s->top = NULL; // 链栈的栈顶初始为NULL
    return s;
}

// 判断链栈是否为空
int isEmpty(LinkedListStack *s) {
    return s->top == NULL;
}

// 入栈操作
void push(LinkedListStack *s, int data) {
    Node *newNode = (Node*) malloc(sizeof(Node)); // 创建一个新节点
    newNode->data = data; // 节点存储数据
    newNode->next = s->top; // 将新节点插入到链栈的栈顶
    s->top = newNode; // 更新链栈的栈顶指针
    printf("%d pushed to stack.\n", data);
}

// 出栈操作
int pop(LinkedListStack *s) {
    if (isEmpty(s)) { // 如果栈为空,无法出栈
        printf("Stack is empty. Cannot pop from stack.\n");
        return -1;
    }
    int data = s->top->data; // 取出栈顶元素
    Node *temp = s->top; // 用临时指针存储栈顶节点
    s->top = s->top->next; // 更新链栈的栈顶指针
    free(temp); // 释放栈顶的节点空间
    printf("%d popped from stack.\n", data);
    return data;
}

// 获取栈顶元素
int peek(LinkedListStack *s) {
    if (isEmpty(s)) { // 如果栈为空,栈顶无法取出
        printf("Stack is empty. Cannot peek from stack.\n");
        return -1;
    }
    return s->top->data;
}

// 遍历链栈并打印元素
void printStack(LinkedListStack *s) {
    if (isEmpty(s)) { // 如果栈为空
        printf("Stack is empty.\n");
        return;
    }
    printf("Stack: ");
    Node *temp = s->top; // 用临时指针遍历链栈
    while (temp != NULL) {
        printf("%d ", temp->data);
        temp = temp->next;
    }
    printf("\n");
}

// 测试
int main() {
    LinkedListStack *s = createStack(); // 创建一个空链栈

    push(s, 1); // 入栈元素 1
    push(s, 2); // 入栈元素 2
    push(s, 3); // 入栈元素 3
    printStack(s); // 打印链栈

    pop(s); // 出栈元素
    printStack(s); // 打印链栈

    printf("Peeking from stack: %d\n", peek(s)); // 获取栈顶元素

    pop(s); // 出栈元素
    pop(s); // 出栈元素
    pop(s); // 尝试出栈元素,但是栈已经为空

    // 释放链栈的空间
    free(s);

    return 0;
}

栈的应用,表达式求值

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

typedef struct
{
    int top;        // 栈顶指针
    int capacity;   // 栈的容量
    int *data;      // 存储数据的数组
} Stack;

void init(Stack *s, int capacity)
{
    s->top = -1;        // 初始化栈顶指针
    s->capacity = capacity;
    s->data = (int *)malloc(sizeof(int) * capacity);    // 分配存储数据的数组
}

void push(Stack *s, int value)
{
    if (s->top == s->capacity - 1)      // 栈已满
        return;

    s->data[++s->top] = value;          // 栈顶指针加一,将数据压入栈中
}

int pop(Stack *s)
{
    if (s->top == -1)       // 栈为空
        return 0;

    return s->data[s->top--];       // 返回栈顶元素,并将栈顶指针减一
}

int peek(Stack *s)
{
    if (s->top == -1)       // 栈为空
        return 0;

    return s->data[s->top];        // 返回栈顶元素
}

int is_empty(Stack *s)
{
    return s->top == -1;        // 判断栈是否为空
}

int is_operator(char c)
{
    return c == '+' || c == '-' || c == '*' || c == '/';    // 判断是否为运算符
}

int get_operator_priority(char c)
{
    if (c == '+' || c == '-')
        return 1;
    else if (c == '*' || c == '/')
        return 2;
    else
        return 0;
}

int evaluate_expression(char *exp)
{
    Stack operand_stack;
    Stack operator_stack;
    init(&operand_stack, strlen(exp));
    init(&operator_stack, strlen(exp));
    int i;
    for (i = 0; exp[i] != '\0'; i++)    // 遍历表达式
    {
        if (isdigit(exp[i]))       // 如果是数字,直接压入操作数栈中
        {
            int operand = 0;
            while (isdigit(exp[i]))
            {
                operand = operand * 10 + (exp[i] - '0');
                i++;
            }
            push(&operand_stack, operand);
            i--;    // 因为循环中会自动加一,所以此处减一
        }
        else if (exp[i] == '(')     // 如果是左括号,直接压入操作符栈中
        {
            push(&operator_stack, exp[i]);
        }
        else if (exp[i] == ')')     // 如果是右括号,弹出操作符栈中的元素,直到遇到左括号为止,然后将运算结果压入操作数栈中
        {
            while (!is_empty(&operator_stack) && peek(&operator_stack) != '(')
            {
                char operator = pop(&operator_stack);
                int right_operand = pop(&operand_stack);
                int left_operand = pop(&operand_stack);
                switch (operator)
                {
                    case '+':
                        push(&operand_stack, left_operand + right_operand);
                        break;
                    case '-':
                        push(&operand_stack, left_operand - right_operand);
                        break;
                    case '*':
                        push(&operand_stack, left_operand * right_operand);
                        break;
                    case '/':
                        push(&operand_stack, left_operand / right_operand);
                        break;
                }
            }
            pop(&operator_stack);   // 把左括号弹出
        }
        else if (is_operator(exp[i]))       // 如果是运算符,将其与操作符栈中的元素进行比较,判断是否需要进行运算
        {
            while (!is_empty(&operator_stack) && peek(&operator_stack) != '(' && get_operator_priority(exp[i]) <= get_operator_priority(peek(&operator_stack)))
            {
                char operator = pop(&operator_stack);
                int right_operand = pop(&operand_stack);
                int left_operand = pop(&operand_stack);
                switch (operator)
                {
                    case '+':
                        push(&operand_stack, left_operand + right_operand);
                        break;
                    case '-':
                        push(&operand_stack, left_operand - right_operand);
                        break;
                    case '*':
                        push(&operand_stack, left_operand * right_operand);
                        break;
                    case '/':
                        push(&operand_stack, left_operand / right_operand);
                        break;
                }
            }
            push(&operator_stack, exp[i]);  // 将当前运算符压入操作符栈中
        }
    }
    while (!is_empty(&operator_stack))     // 虽然表达式已经遍历结束,但操作符栈中可能还有元素,需要进行弹出和运算
    {
        char operator = pop(&operator_stack);
        int right_operand = pop(&operand_stack);
        int left_operand = pop(&operand_stack);
        switch (operator)
        {
            case '+':
                push(&operand_stack, left_operand + right_operand);
                break;
            case '-':
                push(&operand_stack, left_operand - right_operand);
                break;
            case '*':
                push(&operand_stack, left_operand * right_operand);
                break;
            case '/':
                push(&operand_stack, left_operand / right_operand);
                break;
        }
    }
    int result = pop(&operand_stack);   // 最终结果是栈中唯一的元素,即操作数栈的栈顶元素
    free(operand_stack.data);       // 释放操作数栈和操作符栈占用的空间
    free(operator_stack.data);
    return result;
}

int main()
{
    char exp[100];
    printf("请输入一个表达式:\n");
    scanf("%s", exp);
    int result = evaluate_expression(exp);
    printf("表达式的结果是:%d\n", result);
    return 0;
}
上次编辑于:
贡献者: wucq@infogo