数据结构代码篇2

数据结构代码篇2

线性表的结构体定义

顺序表的结构体定义

#define MAXSIZE 100 // 定义顺序表的最大长度

typedef struct {
    int data[MAXSIZE]; // 存储顺序表的数组
    int length; // 当前顺序表的长度
} SqList; // 定义顺序表的结构体类型

单链表的结构体定义

struct ListNode {
    int val; // 节点的值
    struct ListNode *next; // 指向下一个节点的指针
};

创建链表的示例

struct ListNode* head = (struct ListNode*)malloc(sizeof(struct ListNode)); // 创建头节点
head->val = 1; // 设置头节点的值
head->next = NULL; // 头节点的指针指向NULL

struct ListNode* node1 = (struct ListNode*)malloc(sizeof(struct ListNode)); // 创建第一个节点
node1->val = 2; // 设置第一个节点的值
node1->next = NULL; // 第一个节点的指针指向NULL
head->next = node1; // 头节点的指针指向第一个节点

struct ListNode* node2 = (struct ListNode*)malloc(sizeof(struct ListNode)); // 创建第二个节点
node2->val = 3; // 设置第二个节点的值
node2->next = NULL; // 第二个节点的指针指向NULL
node1->next = node2; // 第一个节点的指针指向第二个节点

双链表的结构体定义

struct ListNode {
    int val; // 节点的值
    struct ListNode *prev; // 指向前一个节点的指针
    struct ListNode *next; // 指向后一个节点的指针
};

顺序表的基本操作

初始化顺序表

int init_list(SqList *L, int maxsize) {
    L->data = (int *)malloc(maxsize * sizeof(int)); // 动态分配内存
    if (!L->data) {
        return 0; // 内存分配失败
    }
    L->length = 0; // 初始化长度为0
    L->maxsize = maxsize; // 初始化最大长度
    return 1; // 初始化成功
}

求指定元素的位置

int locate_elem(SqList *L, int elem) {
    for (int i = 0; i < L->length; i++) {
        if (L->data[i] == elem) {
            return i;
        }
    }
    return -1; // 没有找到元素
}

插入数据元素

int insert_elem(SqList *L, int pos, int elem) {
    if (pos < 1 || pos > L->length + 1) {
        return 0; // 插入位置不合法
    }
    if (L->length >= L->maxsize) {
        return 0; // 顺序表已满,无法插入
    }
    for (int i = L->length - 1; i >= pos - 1; i--) {
        L->data[i + 1] = L->data[i]; // 将pos及其后面的元素后移一位
    }
    L->data[pos - 1] = elem; // 将新元素插入到pos位置
    L->length++; // 长度加1
    return 1; // 插入成功
}

按元素查值

int search_elem(SqList *L, int elem) {
    for (int i = 0; i < L->length; i++) {
        if (L->data[i] == elem) {
            return i + 1; // 返回元素在顺序表中的位置
        }
    }
    return 0; // 没有找到元素
}

删除数据元素

int delete_elem(SqList *L, int pos) {
    if (pos < 1 || pos > L->length) {
        return 0; // 删除位置不合法
    }
    for (int i = pos; i < L->length; i++) {
        L->data[i - 1] = L->data[i]; // 将pos后面的元素前移一位
    }
    L->length--; // 长度减1
    return 1; // 删除成功
}

顺序表的元素逆置

void reverse_list(SqList *L) {
    int temp;
    for (int i = 0; i < L->length / 2; i++) {
        temp = L->data[i];
        L->data[i] = L->data[L->length - i - 1];
        L->data[L->length - i - 1] = temp;
    }
}

删除下标i-j的元素

int delete_range(SqList *L, int i, int j) {
    if (i < 1 || j > L->length || i > j) {
        return 0; // 删除范围不合法
    }
    for (int k = j + 1; k <= L->length; k++) {
        L->data[k - j + i - 2] = L->data[k - 1]; // 将j+1及其后面的元素前移j-i位
    }
    L->length -= j - i + 1; // 长度减少j-i+1
    return 1; // 删除成功
}

partition操作

将一个顺序表分成两个部分,左边的部分小于等于某个值,右边的部分大于等于某个值

int partition(SqList *L, int low, int high) {
    int pivot = L->data[low]; // 选取第一个元素作为枢轴
    while (low < high) {
        while (low < high && L->data[high] >= pivot) {
            high--;
        }
        L->data[low] = L->data[high]; // 将小于枢轴的元素移到左边
        while (low < high && L->data[low] <= pivot) {
            low++;
        }
        L->data[high] = L->data[low]; // 将大于枢轴的元素移到右边
    }
    L->data[low] = pivot; // 将枢轴放到最终位置
    return low; // 返回枢轴位置
}

单链表的基本操作

初始化单链表

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

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

LinkedList init_list() {
    LinkedList L = (LinkedList)malloc(sizeof(Node)); // 创建头结点
    L->next = NULL; // 头结点的指针域置为空
    return L;
}

int main() {
    LinkedList L = init_list(); // 初始化单链表
    return 0;
}

尾插法建立单链表

LinkedList create_list() {
    LinkedList L = (LinkedList)malloc(sizeof(Node)); // 创建头结点
    L->next = NULL; // 头结点的指针域置为空
    Node *tail = L; // 尾结点初始化为头结点
    int x;
    while (scanf("%d", &x) != EOF) { // 不断读入数据,直到输入结束
        Node *new_node = (Node*)malloc(sizeof(Node)); // 创建新结点
        new_node->data = x; // 将数据存入新结点的数据域
        new_node->next = NULL; // 新结点的指针域置为空
        tail->next = new_node; // 将新结点插入到尾结点之后
        tail = new_node; // 更新尾结点
    }
    return L;
}

头插法建立单链表

LinkedList create_list() {
    LinkedList L = (LinkedList)malloc(sizeof(Node)); // 创建头结点
    L->next = NULL; // 头结点的指针域置为空
    int x;
    while (scanf("%d", &x) != EOF) { // 不断读入数据,直到输入结束
        Node *new_node = (Node*)malloc(sizeof(Node)); // 创建新结点
        new_node->data = x; // 将数据存入新结点的数据域
        new_node->next = L->next; // 将新结点插入到头结点之后
        L->next = new_node; // 更新头结点
    }
    return L;
}

合并递增单链表

struct ListNode* mergeLists(struct ListNode* l1, struct ListNode* l2) {
    struct ListNode dummy;      // 创建虚拟头节点
    struct ListNode* tail = &dummy; // 创建尾节点指针
    while (l1 != NULL && l2 != NULL) { // 遍历两个链表
        if (l1->val < l2->val) { // 如果l1的值小于l2的值
            tail->next = l1;    // 将l1添加到新链表中
            l1 = l1->next;      // 移动l1指针到下一个节点
        } else {                // 如果l2的值小于等于l1的值
            tail->next = l2;    // 将l2添加到新链表中
            l2 = l2->next;      // 移动l2指针到下一个节点
        }
        tail = tail->next;      // 移动尾节点指针到新链表的尾部
    }
    if (l1 != NULL) {           // 如果l1还有剩余节点
        tail->next = l1;        // 将剩余节点添加到新链表的尾部
    } else {                    // 如果l2还有剩余节点
        tail->next = l2;        // 将剩余节点添加到新链表的尾部
    }
    return dummy.next;          // 返回新链表的头节点
}

将两个递增单链表合并成递减单链表

void MergeList(LinkList *La,LinkList *Lb)
{
    //合并2个递增有序链表(带头结点),并使合并后的链表递减排列
    Linklist *r,*pa=La->next,*pb=Lb->next;//分别是表La和Lb的工作指针
    La->next=NULL;//La作为结果链表的头指针,先将结果链表初始化为空
    while(pa&&pb){ //当两链表均不空时循环
        if(pa->data<=pb->data){
            r=pa->next; //r暂存pa的后继节点指针
            pa->next=La->next;//将比较之后的较小结点的next指针指向结果链表的头结点后的第一个结点
            La->next=pa; //将结果链表的头结点的next指针指向新来的"小"结点
            pa=r;//恢复pa为当前待比较节点
        }else{
            r=pb->next; //r暂存pa的后继节点指针
            pb->next=La->next;
            La->next=pb; //将pa节点与链于结点链表中,同时逆置(头插法)
            pb=r;//恢复pa为当前待比较节点
        }
        if(pa)
            pb=pa;//通常会剩一个链表非空,处理剩下的部分
        while(pb){ //处理剩下的一个链表
            r=pb->next; //r暂存pa的后继节点指针
            pb->next=La->next;
            La->next=pb; //将pa节点与链于结点链表中,同时逆置(头插法)
            pb=r;//恢复pa为当前待比较节点
        }
    free(Lb);
}

查找元素并删除

void delete_node(LinkedList L, int x) {
    Node *p = L->next, *pre = L; // 分别指向第一个结点和头结点
    while (p != NULL) { // 遍历单链表
        if (p->data == x) { // 如果找到了要删除的结点
            pre->next = p->next; // 将前驱结点的指针域指向后继结点
            free(p); // 释放要删除的结点的内存空间
            return; // 结束函数
        }
        pre = p; // 更新前驱结点
        p = p->next; // 指向下一个结点
    }
    printf("未找到要删除的结点!\n"); // 如果遍历完整个单链表都没有找到要删除的结点,则输出提示信息
}

删除递增单链表的重复元素

void delete_duplicate(LinkedList L) {
    Node *p = L->next, *q; // 分别指向第一个结点和下一个结点
    while (p != NULL) { // 遍历单链表
        q = p->next; // 指向下一个结点
        while (q != NULL && q->data == p->data) { // 如果下一个结点的值与当前结点的值相同
            Node *temp = q; // 保存下一个结点的指针
            q = q->next; // 指向下一个结点
            free(temp); // 释放重复结点的内存空间
        }
        p->next = q; // 将当前结点的指针域指向下一个不重复的结点
        p = q; // 指向下一个结点
    }
}

删除单链表中的最小值

void delete_min(LinkedList L) {
    Node *p = L->next, *pre = L, *min_p = p, *min_pre = pre; // 分别指向第一个结点和头结点,min_p和min_pre分别指向最小值结点和其前驱结点
    while (p != NULL) { // 遍历单链表
        if (p->data < min_p->data) { // 如果找到了比当前最小值更小的结点
            min_p = p; // 更新最小值结点
            min_pre = pre; // 更新最小值结点的前驱结点
        }
        pre = p; // 更新前驱结点
        p = p->next; // 指向下一个结点
    }
    min_pre->next = min_p->next; // 将最小值结点的前驱结点的指针域指向最小值结点的后继结点
    free(min_p); // 释放最小值结点的内存空间
}

单链表的原地逆置

void reverseList(struct ListNode **head) {
    if (*head == NULL || (*head)->next == NULL) { // 如果链表为空或只有一个节点,直接返回
        return;
    }
    struct ListNode *prev = NULL, *curr = *head, *next = NULL;
    while (curr != NULL) { // 遍历链表,将每个节点的后继指针指向前一个节点
        next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    *head = prev; // 将头指针指向逆置后的链表的头节点
}

可运行测试实例

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

struct ListNode {
    int val; // 链表节点的值
    struct ListNode *next; // 链表节点的后继指针
};

// 创建一个包含n个随机整数的链表
struct ListNode* createList(int n) {
    struct ListNode *head = NULL, *tail = NULL;
    for (int i = 0; i < n; i++) {
        struct ListNode *node = (struct ListNode*)malloc(sizeof(struct ListNode)); // 创建一个新的链表节点
        node->val = rand() % 100; // 随机生成一个0~99的整数作为节点的值
        node->next = NULL;
        if (tail == NULL) { // 如果链表为空,将头指针和尾指针都指向新节点
            head = tail = node;
        } else { // 否则将新节点插入到链表尾部
            tail->next = node;
            tail = node;
        }
    }
    return head; // 返回链表头指针
}

// 打印链表中的元素
void printList(struct ListNode *head) {
    while (head != NULL) {
        printf("%d ", head->val); // 打印当前节点的值
        head = head->next; // 将指针指向下一个节点
    }
    printf("\n");
}

// 就地逆置链表
void reverseList(struct ListNode **head) {
    if (*head == NULL || (*head)->next == NULL) { // 如果链表为空或只有一个节点,直接返回
        return;
    }
    struct ListNode *prev = NULL, *curr = *head, *next = NULL;
    while (curr != NULL) { // 遍历链表,将每个节点的后继指针指向前一个节点
        next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    *head = prev; // 将头指针指向逆置后的链表的头节点
}

int main() {
    srand(time(NULL));
    struct ListNode *l1 = createList(5); // 创建一个包含5个随机整数的链表
    printf("Original list: ");
    printList(l1); // 打印原始链表的元素
    reverseList(&l1); // 就地逆置链表
    printf("Reversed list: ");
    printList(l1); // 打印逆置后的链表的元素
    return 0;
}

判断链表是否有环

bool hasCycle(struct ListNode *head) {
    if (head == NULL || head->next == NULL) { // 如果链表为空或只有一个节点,直接返回false
        return false;
    }
    struct ListNode *slow = head, *fast = head->next;
    while (fast != NULL && fast->next != NULL) { // 遍历链表,判断是否存在环
        if (slow == fast) {
            return true;
        }
        slow = slow->next;
        fast = fast->next->next;
    }
    return false;
}

算法思想:

  1. 使用快慢指针fast和slow来遍历链表。

  2. fast指针每次移动2步,slow指针每次移动1步。

  3. 在遍历链表的过程中,如果fast和slow指针相遇,说明链表有环。

  4. 为了避免特殊情况,先判断了链表为空或只有1个节点的边界情况。

  5. 初始化slow为head,fast为head->next,fast指针领先slow一个节点。

  6. while循环的条件仅判断fast,因为fast会先到达尾部。

  7. 如果fast和slow相遇,直接return true表示有环。

  8. 如果fast遍历完还没与slow相遇,说明无环,return false。

  9. 这样,通过快慢指针的速度差,可以节省遍历链表的次数,很巧妙地判断链表是否有环。

可运行的实例

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

struct ListNode {
    int val;
    struct ListNode *next;
};

struct ListNode *createList(int n) {
    struct ListNode *head = NULL, *tail = NULL;
    for (int i = 0; i < n; i++) {
        struct ListNode *node = (struct ListNode *)malloc(sizeof(struct ListNode));
        node->val = i + 1;
        node->next = NULL;
        if (head == NULL) {
            head = node;
            tail = node;
        } else {
            tail->next = node;
            tail = node;
        }
    }
    tail->next = head->next->next; // 第3个节点和第5个节点之间存在环
    return head;                   // 如果不想存在环,在上一行加入//即可
}

int hasCycle(struct ListNode *head) {
    if (head == NULL || head->next == NULL) { // 如果链表为空或只有一个节点,直接返回false
        return 0;
    }
    struct ListNode *slow = head, *fast = head->next;
    while (fast != NULL && fast->next != NULL) { // 遍历链表,判断是否存在环
        if (slow == fast) {
            return 1;
        }
        slow = slow->next;
        fast = fast->next->next;
    }
    return 0;
}

int main() {
    struct ListNode *head = createList(5); // 创建一个包含5个节点的链表,其中第3个节点和第5个节点之间存在环
    if (hasCycle(head)) {
        printf("链表中存在环\n");
    } else {
        printf("链表中不存在环\n");
    }
    return 0;
}

逆序打印单链表中的节点

void print_list_reverse(LinkedList L) {
    if (L->next != NULL) { // 如果单链表不为空
        print_list_reverse(L->next); // 递归输出单链表中除头结点之外的结点
        printf("%d ", L->next->data); // 输出当前结点的数据域
    }
}

查找链表中倒数第k个节点

Node* find_kth_to_tail(LinkedList L, int k) {
    if (L->next == NULL || k <= 0) { // 如果单链表为空或 k 小于等于 0,则返回 NULL
        return NULL;
    }
    Node *p = L->next, *q = L->next; // p 和 q 都指向第一个结点
    while (k > 1 && q != NULL) { // q 先走 k - 1 步
        q = q->next;
        k--;
    }
    if (q == NULL) { // 如果 q 走到了 NULL,则说明 k 大于单链表的长度,返回 NULL
        return NULL;
    }
    while (q->next != NULL) { // p 和 q 同时向后走,直到 q 走到了最后一个结点
        p = p->next;
        q = q->next;
    }
    return p; // 返回倒数第 k 个结点
}

单链表节点的删除

struct ListNode* p = head;
struct ListNode* q = NULL;
int i = 1;
while (p != NULL && i < pos) {
    q = p;
    p = p->next;
    i++;
}
if (p == NULL) {
    printf("删除位置无效\n");
    return;
}
if (q == NULL) {
    head = p->next; // 删除头节点
} else {
    q->next = p->next; // 删除中间节点
}
free(p); // 释放要删除的节点的内存

双链表的基本操作

双链表插入节点

void insert_node(LinkedList L, int pos, int x) {
    Node *p = L->next; // 指向第一个结点
    int i = 1;
    while (p != NULL && i < pos) { // 遍历双链表,找到要插入的位置
        p = p->next;
        i++;
    }
    if (p == NULL) { // 如果插入位置无效,则输出错误信息并返回
        printf("插入位置无效\n");
        return;
    }
    Node *new_node = (Node*)malloc(sizeof(Node)); // 创建新结点
    new_node->data = x; // 将数据存入新结点的数据域
    new_node->prev = p; // 将新结点的前一个节点指针指向前一个节点
    new_node->next = p->next; // 将新结点的后一个节点指针指向后一个节点
    if (p->next != NULL) { // 如果后一个节点存在,则将后一个节点的前一个节点指针指向新节点
        p->next->prev = new_node;
    }
    p->next = new_node; // 将前一个节点的后一个节点指针指向新节点
}

双链表删除节点

void delete_node(LinkedList L, int x) {
    Node *p = L->next; // 指向第一个结点
    while (p != NULL && p->data != x) { // 遍历双链表,查找值为 x 的结点
        p = p->next;
    }
    if (p == NULL) { // 如果没找到该结点,则输出错误信息并返回
        printf("未找到该结点\n");
        return;
    }
    p->prev->next = p->next; // 将前一个节点的后一个节点指针指向后一个节点
    if (p->next != NULL) { // 如果后一个节点存在,则将后一个节点的前一个节点指针指向前一个节点
        p->next->prev = p->prev;
    }
    free(p); // 释放被删除的结点
}

栈和队列的结构体定义

顺序栈的定义

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

typedef struct Stack {
    int data[MAXSIZE]; // 栈的数据域,使用静态数组实现
    int top; // 栈顶指针
} Stack;

链栈节点定义

typedef struct StackNode {
    int data; // 数据域
    struct StackNode *next; // 指向下一个节点的指针
} StackNode;

顺序队列定义

#define MAXSIZE 100 // 定义队列的最大容量

typedef struct Queue {
    int data[MAXSIZE]; // 队列的数据域,使用静态数组实现
    int front; // 队头指针
    int rear; // 队尾指针
} Queue;

链队定义

typedef struct QueueNode {
    int data; // 数据域
    struct QueueNode *next; // 指向下一个节点的指针
} QueueNode;

typedef struct Queue {
    QueueNode *front; // 队头指针
    QueueNode *rear; // 队尾指针
} Queue;

顺序栈道基本操作

顺序栈的初始化

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

typedef struct Stack {
    int data[MAXSIZE]; // 栈的数据域,使用静态数组实现
    int top; // 栈顶指针
} Stack;

void init_stack(Stack *S) {
    S->top = -1; // 初始化栈顶指针为 -1,表示栈为空
}

判断栈空

int is_empty(Stack *S) {
    return S->top == -1; // 如果栈顶指针为 -1,表示栈为空,返回 1,否则返回 0
}

进栈出栈

void push(Stack *S, int x) {
    if (S->top == MAXSIZE - 1) { // 如果栈已满,则输出错误信息并返回
        printf("栈已满\n");
        return;
    }
    S->top++; // 栈顶指针加 1
    S->data[S->top] = x; // 将数据存入栈顶指针所指向的位置
}

int pop(Stack *S) {
    if (S->top == -1) { // 如果栈为空,则输出错误信息并返回
        printf("栈为空\n");
        return -1;
    }
    int x = S->data[S->top]; // 取出栈顶元素
    S->top--; // 栈顶指针减 1
    return x; // 返回栈顶元素
}

链栈的基本操作

链栈的初始化

typedef struct StackNode {
    int data; // 数据域
    struct StackNode *next; // 指向下一个节点的指针
} StackNode;

typedef struct Stack {
    StackNode *top; // 栈顶指针
} Stack;

void init_stack(Stack *S) {
    S->top = NULL; // 初始化栈顶指针为空,表示栈为空
}

判断栈空

typedef struct StackNode {
    int data; // 数据域
    struct StackNode *next; // 指向下一个节点的指针
} StackNode;

typedef struct Stack {
    StackNode *top; // 栈顶指针
} Stack;

int is_empty(Stack *S) {
    return S->top == NULL; // 如果栈顶指针为空,表示栈为空,返回 1,否则返回 0
}

进栈代码

typedef struct StackNode {
    int data; // 数据域
    struct StackNode *next; // 指向下一个节点的指针
} StackNode;

typedef struct Stack {
    StackNode *top; // 栈顶指针
} Stack;

void push(Stack *S, int x) {
    StackNode *new_node = (StackNode*)malloc(sizeof(StackNode)); // 创建新结点
    new_node->data = x; // 将数据存入新结点的数据域
    new_node->next = S->top; // 将新结点插入到栈顶之后
    S->top = new_node; // 更新栈顶指针
}

出栈代码

typedef struct StackNode {
    int data; // 数据域
    struct StackNode *next; // 指向下一个节点的指针
} StackNode;

typedef struct Stack {
    StackNode *top; // 栈顶指针
} Stack;

int pop(Stack *S) {
    if (S->top == NULL) { // 如果栈为空,则输出错误信息并返回
        printf("栈为空\n");
        return -1;
    }
    int x = S->top->data; // 取出栈顶元素
    StackNode *temp = S->top; // 保存栈顶指针
    S->top = S->top->next; // 栈顶指针指向下一个节点
    free(temp); // 释放原栈顶节点的内存
    return x; // 返回栈顶元素
}

顺序队列的基本操作

顺序队列的初始化

#define MAXSIZE 100 // 定义队列的最大容量

typedef struct Queue {
    int data[MAXSIZE]; // 队列的数据域,使用静态数组实现
    int front; // 队头指针
    int rear; // 队尾指针
} Queue;

void init_queue(Queue *Q) {
    Q->front = Q->rear = 0; // 初始化队头指针和队尾指针为 0,表示队列为空
}

判断队空

#define MAXSIZE 100 // 定义队列的最大容量

typedef struct Queue {
    int data[MAXSIZE]; // 队列的数据域,使用静态数组实现
    int front; // 队头指针
    int rear; // 队尾指针
} Queue;

int is_empty(Queue *Q) {
    return Q->front == Q->rear; // 如果队头指针等于队尾指针,表示队列为空,返回 1,否则返回 0
}

进队算法

#define MAXSIZE 100 // 定义队列的最大容量

typedef struct Queue {
    int data[MAXSIZE]; // 队列的数据域,使用静态数组实现
    int front; // 队头指针
    int rear; // 队尾指针
} Queue;

void enqueue(Queue *Q, int x) {
    if (Q->rear == MAXSIZE) { // 如果队列已满,则输出错误信息并返回
        printf("队列已满\n");
        return;
    }
    Q->data[Q->rear] = x; // 将数据存入队尾指针所指向的位置
    Q->rear++; // 队尾指针加 1
}

出队算法

#define MAXSIZE 100 // 定义队列的最大容量

typedef struct Queue {
    int data[MAXSIZE]; // 队列的数据域,使用静态数组实现
    int front; // 队头指针
    int rear; // 队尾指针
} Queue;

int dequeue(Queue *Q) {
    if (Q->front == Q->rear) { // 如果队列为空,则输出错误信息并返回
        printf("队列为空\n");
        return -1;
    }
    int x = Q->data[Q->front]; // 取出队头元素
    Q->front++; // 队头指针加 1
    return x; // 返回队头元素
}

链队的基本操作

链队的初始化

typedef struct QueueNode {
    int data; // 数据域
    struct QueueNode *next; // 指向下一个节点的指针
} QueueNode;

typedef struct Queue {
    QueueNode *front; // 队头指针
    QueueNode *rear; // 队尾指针
} Queue;

void init_queue(Queue *Q) {
    Q->front = Q->rear = NULL; // 初始化队头指针和队尾指针为空,表示队列为空
}

判断队空算法

typedef struct QueueNode {
    int data; // 数据域
    struct QueueNode *next; // 指向下一个节点的指针
} QueueNode;

typedef struct Queue {
    QueueNode *front; // 队头指针
    QueueNode *rear; // 队尾指针
} Queue;

int is_empty(Queue *Q) {
    return Q->front == NULL; // 如果队头指针为空,表示队列为空,返回 1,否则返回 0
}

入队算法

typedef struct QueueNode {
    int data; // 数据域
    struct QueueNode *next; // 指向下一个节点的指针
} QueueNode;

typedef struct Queue {
    QueueNode *front; // 队头指针
    QueueNode *rear; // 队尾指针
} Queue;

void enqueue(Queue *Q, int x) {
    QueueNode *new_node = (QueueNode*)malloc(sizeof(QueueNode)); // 创建新结点
    new_node->data = x; // 将数据存入新结点的数据域
    new_node->next = NULL; // 新结点的指针域置为空
    if (Q->front == NULL) { // 如果队列为空,则将新结点作为队头结点
        Q->front = new_node;
    } else { // 否则将新结点插入到队尾结点之后
        Q->rear->next = new_node;
    }
    Q->rear = new_node; // 更新队尾指针
}

出队算法

typedef struct QueueNode {
    int data; // 数据域
    struct QueueNode *next; // 指向下一个节点的指针
} QueueNode;

typedef struct Queue {
    QueueNode *front; // 队头指针
    QueueNode *rear; // 队尾指针
} Queue;

int dequeue(Queue *Q) {
    if (Q->front == NULL) { // 如果队列为空,则输出错误信息并返回
        printf("队列为空\n");
        return -1;
    }
    QueueNode *temp = Q->front; // 保存队头结点
    int x = temp->data; // 取出队头元素
    Q->front = temp->next; // 将头结点指向下一个结点
    if (Q->front == NULL) { // 如果队列中只有一个元素,则更新队尾指针
        Q->rear = NULL;
    }
    free(temp); // 释放原队头结点的内存
    return x; // 返回队头元素
}

串的结构体定义

定长顺序存储

#define MAXSIZE 100 // 定义串的最大长度

typedef struct {
    char str[MAXSIZE]; // 存储串的字符数组
    int length; // 串的长度
} String;

变长分配存储

typedef struct {
    char *str; // 指向存储串的字符数组的指针
    int length; // 串的长度
    int capacity; // 串的容量
} String;

串的模式匹配算法

简单模式匹配算法

int simple_match(char *s, char *p) {
    int i = 0, j = 0;
    while (s[i] != '\0' && p[j] != '\0') { // 两个字符串都没有遍历完
        if (s[i] == p[j]) { // 当前字符匹配成功
            i++;
            j++;
        } else { // 当前字符匹配失败,回溯
            i = i - j + 1;
            j = 0;
        }
    }
    if (p[j] == '\0') { // 如果模式串已经遍历完,说明匹配成功
        return i - j;
    } else { // 否则匹配失败
        return -1;
    }
}

KMP求next数组代码

void get_next(char *p, int *next) {
    int i = 0, j = -1;
    next[0] = -1;
    while (p[i] != '\0') {
        if (j == -1 || p[i] == p[j]) {
            i++;
            j++;
            next[i] = j;
        } else {
            j = next[j];
        }
    }
}

KMP求next_val数组代码

void get_nextval(char *p, int *nextval) {
    int i = 0, j = -1;
    nextval[0] = -1;
    while (p[i] != '\0') {
        if (j == -1 || p[i] == p[j]) {
            i++;
            j++;
            if (p[i] != p[j]) {
                nextval[i] = j;
            } else {
                nextval[i] = nextval[j];
            }
        } else {
            j = nextval[j];
        }
    }
}

KMP算法

void get_next(char* p, int* next) {
    int len = strlen(p);
    next[0] = -1;
    int i = 0, j = -1;
    while (i < len - 1) {
        if (j == -1 || p[i] == p[j]) {
            i++;
            j++;
            next[i] = j;
        } else {
            j = next[j];
        }
    }
}

int kmp_match(char* s, char* p, int* next) {
    int i = 0, j = 0;
    int s_len = strlen(s), p_len = strlen(p);
    while (i < s_len && j < p_len) {
        if (j == -1 || s[i] == p[j]) {
            i++;
            j++;
        } else {
            j = next[j];
        }
    }
    if (p[j] == '\0') {
        return i - j;
    } else {
        return -1;
    }
}

可实际运行的代码和测试用例

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

void get_next(char* p, int* next) {
    int len = strlen(p);
    next[0] = -1;
    int i = 0, j = -1;
    while (i < len - 1) {
        if (j == -1 || p[i] == p[j]) {
            i++;
            j++;
            next[i] = j;
        } else {
            j = next[j];
        }
    }
}

int kmp_match(char* s, char* p, int* next) {
    int i = 0, j = 0;
    int s_len = strlen(s), p_len = strlen(p);
    while (i < s_len && j < p_len) {
        if (j == -1 || s[i] == p[j]) {
            i++;
            j++;
        } else {
            j = next[j];
        }
    }
    if (p[j] == '\0') {
        return i - j;
    } else {
        return -1;
    }
}

int main() {
    char s[] = "hello world";
    char p[] = "world";
    int next[strlen(p)];
    get_next(p, next);
    int pos = kmp_match(s, p, next);
    printf("%d\n", pos);

    char s2[] = "hello world";
    char p2[] = "abc";
    int next2[strlen(p2)];
    get_next(p2, next2);
    int pos2 = kmp_match(s2, p2, next2);
    printf("%d\n", pos2);
    return 0;
}

二叉树

二叉树的节点定义

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

线索二叉树的节点定义

typedef struct TBTNode{
  char data;
  int ltag, rtag;
  struct TBTNode *lchild;
  struct TBTNode *rchild;
}TBTNode;

二叉树的遍历算法

先序遍历(递归)

void preorderTraversal(struct TreeNode* root) {
    if (root == NULL) {
        return;
    }
    printf("%d ", root->val); // 访问根节点
    preorderTraversal(root->left); // 递归遍历左子树
    preorderTraversal(root->right); // 递归遍历右子树
}

实际可运行的例子

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

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

struct TreeNode* createTree() {
    struct TreeNode *root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->val = 1;
    
    root->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->val = 2;
    
    root->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->right->val = 3;
    root->right->left = NULL;
    root->right->right = NULL;
    
    root->left->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->left->val = 4;
    root->left->left->left = NULL;
    root->left->left->right = NULL;
    
    root->left->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->right->val = 5;
    root->left->right->left = NULL;
    root->left->right->right = NULL;
    
    return root;
}

void preorder(struct TreeNode* root) {
    if (root == NULL) {
        return;
    }
    printf("%d ", root->val);
    preorder(root->left);
    preorder(root->right);
}

int main() {
    struct TreeNode *root = createTree();
    preorder(root);
    return 0;
}

先序遍历(非递归)

void preorderN(BTNode *bt) {
    BTNode *Stack[maxSize];
    BTNode *p;
    int top = -1;  // 定义人工栈
        
    if (bt != NULL) {
        Stack[++top] = bt;  // 根节点入栈
 
        while (top != -1) {  // 判断不空
            p = Stack[top--];  // 出栈 并完成一次访问
            printf("%c\n", p->data);
            if (p->rchild != NULL)   // 记得,先序遍历一定是先右孩子,再左孩子
                Stack[++top] = p->rchild;
            if (p->lchild != NULL) 
                Stack[++top] = p->lchild;
        }
    }
}

实际可运行的例子

#include <stdio.h>
#include <stdlib.h>
#define maxSize 100

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

void preorderN(struct TreeNode *bt) {
    struct TreeNode *stack[maxSize];
    struct TreeNode *p;
    int top = -1;  // 定义人工栈
        
    if (bt != NULL) {
        stack[++top] = bt;  // 根节点入栈
 
        while (top != -1) {  // 判断不空
            p = stack[top--];  // 出栈 并完成一次访问
            printf("%d ", p->val);
            if (p->right != NULL)   // 先将右子树入栈
                stack[++top] = p->right;
            if (p->left != NULL)   // 再将左子树入栈
                stack[++top] = p->left;
        }
    }
}

struct TreeNode* createTree() {
    struct TreeNode *root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->val = 1;
    
    root->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->val = 2;
    
    root->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->right->val = 3;
    root->right->left = NULL;
    root->right->right = NULL;
    
    root->left->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->left->val = 4;
    root->left->left->left = NULL;
    root->left->left->right = NULL;
    
    root->left->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->right->val = 5;
    root->left->right->left = NULL;
    root->left->right->right = NULL;
    
    return root;
}
int main() {
    struct TreeNode *root = createTree();
    printf("\n非递归先序遍历结果:\n");
    preorderN(root);
    return 0;
}

中序遍历(递归)

void inorderTraversal(struct TreeNode* root) {
    if (root == NULL) {
        return;
    }
    inorderTraversal(root->left);
    printf("%d ", root->val);
    inorderTraversal(root->right);
}

后序遍历(递归)

void postorderTraversal(struct TreeNode* root) {
    if (root == NULL) {
        return;
    }
    postorderTraversal(root->left);
    postorderTraversal(root->right);
    printf("%d ", root->val);
}

后序遍历(非递归)

void postorderN(struct TreeNode *bt) {
    struct TreeNode *stack[maxSize];
    struct TreeNode *p = bt;
    struct TreeNode *r = NULL;  // 记录上一个访问的节点
    int top = -1;  // 定义人工栈

    while (p != NULL || top != -1) {
        if (p != NULL) {
            stack[++top] = p;  // 当前节点入栈
            p = p->left;  // 遍历左子树
        } else {
            p = stack[top];  // 取出栈顶元素
            if (p->right == NULL || p->right == r) {  // 如果右子树为空或者已经访问过
                printf("%d ", p->val);  // 访问当前节点
                r = p;  // 记录上一个访问的节点
                top--;  // 出栈
                p = NULL;  // 避免重复访问
            } else {
                p = p->right;  // 遍历右子树
            }
        }
    }
}

实际可运行的代码

#include <stdio.h>
#include <stdlib.h>
#define maxSize 100

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

void postorderN(struct TreeNode *bt) {
    struct TreeNode *stack[maxSize];
    struct TreeNode *p = bt;
    struct TreeNode *r = NULL;  // 记录上一个访问的节点
    int top = -1;  // 定义人工栈

    while (p != NULL || top != -1) {
        if (p != NULL) {
            stack[++top] = p;  // 当前节点入栈
            p = p->left;  // 遍历左子树
        } else {
            p = stack[top];  // 取出栈顶元素
            if (p->right == NULL || p->right == r) {  // 如果右子树为空或者已经访问过
                printf("%d ", p->val);  // 访问当前节点
                r = p;  // 记录上一个访问的节点
                top--;  // 出栈
                p = NULL;  // 避免重复访问
            } else {
                p = p->right;  // 遍历右子树
            }
        }
    }
}

struct TreeNode* createTree() {
    struct TreeNode *root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->val = 1;
    
    root->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->val = 2;
    
    root->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->right->val = 3;
    root->right->left = NULL;
    root->right->right = NULL;
    
    root->left->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->left->val = 4;
    root->left->left->left = NULL;
    root->left->left->right = NULL;
    
    root->left->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->right->val = 5;
    root->left->right->left = NULL;
    root->left->right->right = NULL;
    
    return root;
}
int main() {
    struct TreeNode *root = createTree();
    printf("\n非递归后序遍历结果:\n");
    postorderN(root);
    return 0;
}

层序遍历

void levelOrder(struct TreeNode *root) {
    if (root == NULL) { // 如果根节点为空,直接返回
        return;
    }
    struct TreeNode *queue[maxSize];
    int front = 0, rear = 0;
    queue[rear++] = root; // 将根节点入队
    while (front < rear) { // 当队列不为空时,执行以下操作
        struct TreeNode *node = queue[front++]; // 取出队头元素
        printf("%d ", node->val); // 访问该节点
        if (node->left != NULL) { // 如果该节点有左子节点,将其左子节点入队
            queue[rear++] = node->left;
        }
        if (node->right != NULL) { // 如果该节点有右子节点,将其右子节点入队
            queue[rear++] = node->right;
        }
    }
}

实际可运行的代码

#include <stdio.h>
#include <stdlib.h>
#define maxSize 100

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

void levelOrder(struct TreeNode *root) {
    if (root == NULL) { // 如果根节点为空,直接返回
        return;
    }
    struct TreeNode *queue[maxSize];
    int front = 0, rear = 0;
    queue[rear++] = root; // 将根节点入队
    while (front < rear) { // 当队列不为空时,执行以下操作
        struct TreeNode *node = queue[front++]; // 取出队头元素
        printf("%d ", node->val); // 访问该节点
        if (node->left != NULL) { // 如果该节点有左子节点,将其左子节点入队
            queue[rear++] = node->left;
        }
        if (node->right != NULL) { // 如果该节点有右子节点,将其右子节点入队
            queue[rear++] = node->right;
        }
    }
}

struct TreeNode* createTree() {
    struct TreeNode *root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->val = 1;
    
    root->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->val = 2;
    
    root->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->right->val = 3;
    root->right->left = NULL;
    root->right->right = NULL;
    
    root->left->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->left->val = 4;
    root->left->left->left = NULL;
    root->left->left->right = NULL;
    
    root->left->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->right->val = 5;
    root->left->right->left = NULL;
    root->left->right->right = NULL;
    
    return root;
}
int main() {
    struct TreeNode *root = createTree();
    printf("\n层序遍历结果:\n");
    levelOrder(root);
    return 0;
}

二叉树的其他代码

递归求WPL

int getWPL(struct TreeNode *root, int depth) {
    if (root == NULL) { // 如果节点为空,返回0
        return 0;
    }
    if (root->left == NULL && root->right == NULL) { // 如果节点是叶子节点,返回带权路径长度
        return depth * root->val;
    }
    // 如果节点不是叶子节点,递归计算左子树和右子树的WPL,并相加返回
    return getWPL(root->left, depth + 1) + getWPL(root->right, depth + 1);
}

转化成中缀表达式

      +
     / \
    *   -
   / \ / \
  a  b c  d
#include <stdio.h>
#include <stdlib.h>

struct TreeNode {
    char val;
    struct TreeNode *left;
    struct TreeNode *right;
};

void inorderTraversal(struct TreeNode *root) {
    if (root == NULL) { // 如果节点为空,返回
        return;
    }
    if (root->left != NULL || root->right != NULL) { // 如果节点是操作符节点
        printf("("); // 输出左括号
    }
    inorderTraversal(root->left); // 递归遍历左子树
    printf("%c", root->val); // 输出节点的值
    inorderTraversal(root->right); // 递归遍历右子树
    if (root->left != NULL || root->right != NULL) { // 如果节点是操作符节点
        printf(")"); // 输出右括号
    }
}

int main() {
    struct TreeNode *root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->val = '+';
    root->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->val = '*';
    root->left->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->left->val = 'a';
    root->left->left->left = NULL;
    root->left->left->right = NULL;
    root->left->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->left->right->val = 'b';
    root->left->right->left = NULL;
    root->left->right->right = NULL;
    root->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->right->val = '-';
    root->right->left = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->right->left->val = 'c';
    root->right->left->left = NULL;
    root->right->left->right = NULL;
    root->right->right = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->right->right->val = 'd';
    root->right->right->left = NULL;
    root->right->right->right = NULL;

    inorderTraversal(root); // 输出中缀表达式

    return 0;
}

图的存储结构

邻接矩阵的节点定义

#define maxSize 100 // 定义邻接矩阵的最大大小

typedef struct {
    int no; // 顶点编号
    char info; // 顶点的其他信息,这里默认为char型。
} VertexType; // 顶点类型

typedef struct {
    int edges[maxSize][maxSize]; // 有权图则将int改为float
    int n, e; // 分别为定点数和边数
    VertexType vex[maxSize]; // 存放节点信息
} MGraph; // 图的邻接矩阵类型

邻接表的节点定义

#define maxSize 100 // 定义邻接表的最大大小

typedef struct ArcNode {
    int adjvex; // 该边所指向的节点的位置
    struct ArcNode *nextarc; // 指向下一条边的指针
    int info; // 该边的相关信息(如权值)
} ArcNode;

typedef struct {
    char data; // 定点信息
    ArcNode *firstarc; // 指向第一条边的指针
} VNode;

typedef struct {
    VNode adjlist[maxSize]; // 邻接表
    int n, e; // 定点数和边数
} AGraph; // 图的邻接表类型

图的遍历方式

深度优先搜索算法(DFS)

在图中的深度优先搜索算法思想是:

1、从一个起始节点出发,首先访问该节点,并标记该节点已访问。

2、查找该节点的所有邻接点,将末访问过的邻接点按某种顺序排列到一个栈中。

3、取栈顶元素作为当前访问节点,重复步骤1和2直到栈为空。

4、如果还有未访问的节点则选择一个未访问节点作为起始节点,重复步骤1、2、3,直至图中所有节点都被访问。

5、通过栈来维护当前节点信息,指导下一步搜索顺序。

其关键思路是:

•递归访问起始节点的所有未访问邻接点

•邻接点入栈 Waiting遍历

•当前节点访问结束后,从栈顶取出下一个节点

•利用栈来维护节点顺序

•重复上述步骤直至所有节点访问结束

通过栈保存节点访问顺序,实现系统有序地深度优先遍历图的所有节点。

可运行的代码

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

#define maxSize 100 // 定义邻接表的最大大小

typedef struct ArcNode {
    int adjvex; // 该边所指向的节点的位置
    struct ArcNode *nextarc; // 指向下一条边的指针
    int info; // 该边的相关信息(如权值)
} ArcNode;

typedef struct {
    char data; // 定点信息
    ArcNode *firstarc; // 指向第一条边的指针
} VNode;

typedef struct {
    VNode adjlist[maxSize]; // 邻接表
    int n, e; // 定点数和边数
} AGraph; // 图的邻接表类型

int visit[maxSize]; // 定义一个全局数组,用于标记节点是否被访问过

void dfs(AGraph *G, int v) {
    ArcNode *p;
    visit[v] = 1; // 标记节点v已被访问
    printf("%d ", v); // 输出节点v的值
    p = G->adjlist[v].firstarc; // 让p指向顶点v的第一条边

    while (p != NULL) {
        if (visit[p->adjvex] == 0) { // 如果节点未被访问,则递归遍历该节点
            dfs(G, p->adjvex);
        }
        p = p->nextarc; // 继续遍历下一条边
    }
}

int main() {
    AGraph G;
    int i, j, k;

    G.n = 6; // 定点数为6
    G.e = 7; // 边数为7

    // 初始化邻接表
    for (i = 0; i < G.n; i++) {
        G.adjlist[i].data = i;
        G.adjlist[i].firstarc = NULL;
    }

    // 构造邻接表
    int edges[7][2] = {{0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 4}, {3, 5}, {4, 5}};
    for (k = 0; k < G.e; k++) {
        i = edges[k][0];
        j = edges[k][1];

        // 头插法插入边
        ArcNode *p = (ArcNode *)malloc(sizeof(ArcNode));
        p->adjvex = j;
        p->nextarc = G.adjlist[i].firstarc;
        G.adjlist[i].firstarc = p;

        // 无向图需要插入反向边
        p = (ArcNode *)malloc(sizeof(ArcNode));
        p->adjvex = i;
        p->nextarc = G.adjlist[j].firstarc;
        G.adjlist[j].firstarc = p;
    }

    // 初始化visit数组
    for (i = 0; i < G.n; i++) {
        visit[i] = 0;
    }

    printf("深度优先搜索结果:");
    dfs(&G, 0); // 从节点0开始深度优先搜索

    return 0;
}

这个测试用例中,我们构造了一个有6个节点、7条边的无向图,并从节点0开始进行深度优先搜索。你可以根据需要修改节点数、 边数和边的连接关系,来测试不同的情况。

广度优先搜索算法(BFS)

图的广度优先搜索算法思想是:

1.选择一个起点,访问该节点,并标记已访问。

2.遍历该节点的所有邻接点,将末访问的邻接点入队。

3.从队头取出一个节点,作为当前访问节点。

4.对当前访问节点进行步骤1和2的处理。

5.重复步骤3和4,直到队列为空。

6.如果还有未访问节点,回到步骤1,直至图中所有节点都被访问。

7.通过队列来维护访问顺序。

其关键思路是:

•起点入队,标记已访问

•当前节点访问结束后,其邻接点入队

•从队头取出下一个节点继续处理

•利用队列来维护和指导节点访问顺序

•重复上述步骤直到遍历完图中所有节点

广度优先搜索利用队列机制,实现按层次遍历图的所有节点。

相比深度优先搜索的递归思路,广度优先搜索通过队列层次遍历节点

可运行的代码

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

#define maxSize 100 // 定义邻接表的最大大小

typedef struct ArcNode {
    int adjvex; // 该边所指向的节点的位置
    struct ArcNode *nextarc; // 指向下一条边的指针
    int info; // 该边的相关信息(如权值)
} ArcNode;

typedef struct {
    char data; // 定点信息
    ArcNode *firstarc; // 指向第一条边的指针
} VNode;

typedef struct {
    VNode adjlist[maxSize]; // 邻接表
    int n, e; // 定点数和边数
} AGraph; // 图的邻接表类型

int visit[maxSize]; // 定义一个全局数组,用于标记节点是否被访问过

void bfs(AGraph *G, int v) {
    int queue[maxSize]; // 定义一个队列
    int front = 0, rear = 0; // 定义队列的头和尾指针
    int w;
    ArcNode *p;

    printf("%d ", v); // 输出节点v的值
    visit[v] = 1; // 标记节点v已被访问
    queue[rear++] = v; // 将节点v入队

    while (front != rear) { // 队列不为空时循环
        w = queue[front++]; // 取出队头元素
        p = G->adjlist[w].firstarc; // 让p指向顶点w的第一条边

        while (p != NULL) {
            if (visit[p->adjvex] == 0) { // 如果节点未被访问,则输出节点的值并标记为已访问
                printf("%d ", p->adjvex);
                visit[p->adjvex] = 1;
                queue[rear++] = p->adjvex; // 将节点入队
            }
            p = p->nextarc; // 继续遍历下一条边
        }
    }
}

int main() {
    AGraph G;
    int i, j, k;

    G.n = 6; // 定点数为6
    G.e = 7; // 边数为7

    // 初始化邻接表
    for (i = 0; i < G.n; i++) {
        G.adjlist[i].data = i;
        G.adjlist[i].firstarc = NULL;
    }

    // 构造邻接表
    int edges[7][2] = {{0, 1}, {0, 2}, {1, 3}, {1, 4}, {2, 4}, {3, 5}, {4, 5}};
    for (k = 0; k < G.e; k++) {
        i = edges[k][0];
        j = edges[k][1];

        // 头插法插入边
        ArcNode *p = (ArcNode *)malloc(sizeof(ArcNode));
        p->adjvex = j;
        p->nextarc = G.adjlist[i].firstarc;
        G.adjlist[i].firstarc = p;

        // 无向图需要插入反向边
        p = (ArcNode *)malloc(sizeof(ArcNode));
        p->adjvex = i;
        p->nextarc = G.adjlist[j].firstarc;
        G.adjlist[j].firstarc = p;
    }

    // 初始化visit数组
    for (i = 0; i < G.n; i++) {
        visit[i] = 0;
    }

    printf("广度优先搜索结果:");
    bfs(&G, 0); // 从节点0开始广度优先搜索

    return 0;
}

最小代价生成树

Prim算法

最小生成树Prim算法的步骤如下:

  1. 选取任意一个结点作为起始结点,标记为已访问。

  2. 从已访问的结点中找到到其它所有末访问结点的距离中最小的一条边,连接这个末访问结点,标记为已访问。

  3. 重复步骤2,直到所有结点都被标记为已访问。

  4. 此时得到的边集合构成了最小生成树。

Prim算法的基本思想是从一个结点开始,不断添加与已访问结点集相连的最小权值的边,直到遍历完所有结点,即可得到最小生成树。

Prim算法的时间复杂度为O(n^2),其中n为图中的结点数。常见的优化方法是使用优先队列存储边信息,可以将时间复杂度降低为 ◎(mlogn),其中m为图中的边数。

可运行的代码

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

#define MAXN 1000

int n; // 节点数
int m; // 边数
int graph[MAXN][MAXN]; // 邻接矩阵
int visited[MAXN]; // 标记数组
int dist[MAXN]; // 距离数组

// Prim算法  
int prim() {
  int i, j, res = 0;

  for (i = 1; i <= n; i++) {
    visited[i] = 0;
    dist[i] = INT_MAX;
  }
  dist[1] = 0;

  for (i = 1; i <= n; i++) {
    int u = 0, minDist = INT_MAX;
    for (j = 1; j <= n; j++) {
      if (!visited[j] && dist[j] < minDist) {
        u = j;
        minDist = dist[j];
      }
    }
    visited[u] = 1;
    res += dist[u];

    for (j = 1; j <= n; j++) {
      if (!visited[j] && graph[u][j] < dist[j]) {
        dist[j] = graph[u][j];
      }
    }
  }
  return res;
}

int main() {
  // 读入节点数边集  
  scanf("%d %d", &n, &m);
  int i, j;
  for (i = 1; i <= n; i++) {
    for (j = 1; j <= n; j++) {
      graph[i][j] = INT_MAX;
    }
  }
  for (i = 0; i < m; i++) {
    int u, v, w;
    scanf("%d %d %d", &u, &v, &w);
    graph[u][v] = graph[v][u] = w;
  }
  
  printf("%d\n", prim());

  return 0;
}

你可以按照下面格式输入图的数据进行测试

6 9
1 2 2
1 3 1
1 4 4
1 5 5
1 6 1
2 3 4
3 4 2
4 5 3
5 6 1

其中第一行是结点数和边数,接下来的m行是边的信息,每行包含三个整数山、vw,表示一条人u到v的边,边权为w。这个测试用例的最小生成树的边权和为7。

Kruskal算法

Kruskal算法是一种用于求解最小生成树的贪心算法。它的基本思想是将所有边按照权值从小到大排序,然后依次加入到生成树中, 如果加入一条边会形成环,则不加入这条边。直到生成树中有n-1条边为止,此时生成树就是原图的最小生成树。

Kruskal算法的具体实现步骤如下:

  1. 将所有边按照权值从小到大排序。

  2. 初始化一个并查集,每个节点的父节点都是它本身。

  3. 依次遍历所有边,如果这条边的两个端点不在同一个连通块中,则将这条边加入生成树中,并将这两个端点所在的连通块合并。

  4. 重复步骤3,直到生成树中有八-1条边为止。

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

#define MAXN 1000

struct Edge {
  int u, v, w;
};

int n; // 节点数
int m; // 边数
int parent[MAXN]; // 并查集数组
struct Edge edges[MAXN * MAXN]; // 边集数组

int find(int x) {
  if (parent[x] != x) {
    parent[x] = find(parent[x]); 
  }
  return parent[x];
}

void merge(int x, int y) {
  int pX = find(x);
  int pY = find(y);
  if (pX != pY) {
    parent[pX] = pY;
  }
}

// Kruskal算法  
int kruskal() {
  int i, j, res = 0, count = 0;

  for (i = 1; i <= n; i++) {
    parent[i] = i; 
  }
  
  for (i = 0; i < m; i++) {
    for (j = i + 1; j < m; j++) {
      if (edges[i].w > edges[j].w) {
        struct Edge tmp = edges[i];
        edges[i] = edges[j];
        edges[j] = tmp;
      }
    }
  }

  for (i = 0; i < m; i++) {
    if (find(edges[i].u) != find(edges[i].v)) {
      merge(edges[i].u, edges[i].v);
      res += edges[i].w;
      count++;
      if (count == n - 1) break;
    }
  }
  return res;
}

int main() {
  // 读入节点数边集  
  scanf("%d %d", &n, &m);
  int i;
  for (i = 0; i < m; i++) {
    scanf("%d %d %d", &edges[i].u, &edges[i].v, &edges[i].w);
  }
  
  printf("%d\n", kruskal());

  return 0;
}

你可以按照下面格式输入图的数据进行测试

6 9
1 2 2
1 3 1
1 4 4
1 5 5
1 6 1
2 3 4
3 4 2
4 5 3
5 6 1

其中第一行是结点数和边数,接下来的m行是边的信息,每行包含三个整数山v、w,表示一条从心到v的边,边权为w。这个测试用例的最小生成树的边权和为7。

最短路径算法

Dijkstra算法

Dikstra算法是一种用于求解单源最短路径的贪心算法。它的基本思想是从源节点开始,依次找到距离源节点最近的节点,并以此更新其他节点的距离。具体实现步骤如下:

  1. 初始化距离数组dist和标记数组visited,将源节点的距离设为0,其他节点的距离设为无穷大,标记数组visited中所有元泰都为0.

  2. 从未标记的节点中找到距离源节点最近的节点u,将节点u标记为已访问。

  3. 遍历节点u的所有邻居节点v,如果节点v未被访问过且从源节点到节点v的距离比当前记录的距离更短,则更新节点v的距离。

  4. 重复步骤2和步骤3,直到所有节点都被访问过或者没有未标记的节点。

在实现过程中,可以使用一个优先队列来存储末标记的节点,每次从队列中取出距离源节点最近的节点进行更新。这样可以减少遍历的节点数,提高算法效率。

Dikstra算法的时问复杂度为O(n^2)),如果使用优先队列实现,则时间复杂度可以降为O(mlogn),其中n为节点数,m为边数。

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

#define MAXN 1000

int n; // 节点数
int m; // 边数
int graph[MAXN][MAXN]; // 邻接矩阵
int visited[MAXN]; // 标记数组
int dist[MAXN]; // 距离数组

// Dijkstra算法  
void dijkstra(int s) {
  int i, j;

  for (i = 1; i <= n; i++) {
    visited[i] = 0;
    dist[i] = INT_MAX;
  }
  dist[s] = 0;

  for (i = 1; i <= n; i++) {
    int u = 0, minDist = INT_MAX;
    for (j = 1; j <= n; j++) {
      if (!visited[j] && dist[j] < minDist) {
        u = j;
        minDist = dist[j];
      }
    }
    visited[u] = 1;

    for (j = 1; j <= n; j++) {
      if (!visited[j] && graph[u][j] != INT_MAX && dist[u] + graph[u][j] < dist[j]) {
        dist[j] = dist[u] + graph[u][j];
      }
    }
  }
}

int main() {
  // 读入节点数边集  
  scanf("%d %d", &n, &m);
  int i, j;
  for (i = 1; i <= n; i++) {
    for (j = 1; j <= n; j++) {
      graph[i][j] = INT_MAX;
    }
  }
  for (i = 0; i < m; i++) {
    int u, v, w;
    scanf("%d %d %d", &u, &v, &w);
    graph[u][v] = w;
  }
  
  dijkstra(1);

  for (i = 1; i <= n; i++) {
    printf("%d ", dist[i]);
  }
  printf("\n");

  return 0;
}

输入测试:

6 9
1 2 2
1 3 1
1 4 4
1 5 5
1 6 1
2 3 4
3 4 2
4 5 3
5 6 1

排序算法

快速排序

快速排序的核心思想是分治法,其算法思想可以概括为:

  1. 从数组中选择一个基准值(pivot,通常选择第一个元素或者最后一个元素。

  2. 通过一趟排序,将数组分成两个部分,左子数组小于基准值,右子数组大于基准值。这个过程称为分割(partition)。

  3. 对左右两个子数组递归进行快速排序。

  4. 送归结束条件是子数组大小为1,则直接返回。

  5. 递归调用完毕后,整个数组排序完成。

其关键在于分割探作,快速找到数组的中间位置pivot,左边都比pivot小,右边都比pivot大。

快速排序通过递月进行分治,每次只需要排序子数组,这保证了它的快速性平均时间复杂度为O(nlogn),最坏为O(1^2),空问复杂度O(logn)。是原地排序算法之一。

void quicksort(int arr[], int left, int right) {
    if (left >= right) { // 如果左指针大于等于右指针,返回
        return;
    }

    int i = left, j = right, pivot = arr[left]; // 定义左指针、右指针和基准值
    while (i < j) {
        while (i < j && arr[j] >= pivot) { // 从右往左找第一个小于基准值的元素
            j--;
        }
        if (i < j) { // 如果找到了,将该元素赋值给左指针所指的位置
            arr[i] = arr[j];
        }

        while (i < j && arr[i] <= pivot) { // 从左往右找第一个大于基准值的元素
            i++;
        }
        if (i < j) { // 如果找到了,将该元素赋值给右指针所指的位置
            arr[j] = arr[i];
        }
    }

    arr[i] = pivot; // 将基准值放到正确的位置上

    quicksort(arr, left, i - 1); // 递归排序左半部分
    quicksort(arr, i + 1, right); // 递归排序右半部分
}

两路快排

两路快排 (Dual-Pivot QuickSort) 是快速排序算法的一种变体,它使用两个 pivot 元素来将数组分成三部分,分别是小于第一个pivot 元素、大于第一个pivot 元素且小于第二个pivot 元素、大于第二个pivot 元素的部分。这个算法的时间复杂度为 O(n log n),空间复杂度为 O(log n)。

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

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void dualPivotQuickSort(int arr[], int left, int right) {
    if (left >= right) {
        return;
    }
    if (arr[left] > arr[right]) {
        swap(&arr[left], &arr[right]);
    }
    int p = arr[left], q = arr[right];
    int i = left + 1, j = right - 1, k = left + 1;
    while (k <= j) {
        if (arr[k] < p) {
            swap(&arr[k], &arr[i]);
            i++;
        } else if (arr[k] >= q) {
            while (arr[j] > q && k < j) {
                j--;
            }
            swap(&arr[k], &arr[j]);
            j--;
            if (arr[k] < p) {
                swap(&arr[k], &arr[i]);
                i++;
            }
        }
        k++;
    }
    i--;
    j++;
    swap(&arr[left], &arr[i]);
    swap(&arr[right], &arr[j]);
    dualPivotQuickSort(arr, left, i - 1);
    dualPivotQuickSort(arr, i + 1, j - 1);
    dualPivotQuickSort(arr, j + 1, right);
}

int main() {
    int arr[] = {5, 3, 8, 4, 2, 7, 1, 10, 6, 9};
    int n = sizeof(arr) / sizeof(arr[0]);
    dualPivotQuickSort(arr, 0, n - 1);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

三路快排

三路快排 (Three-Way QuickSort) 是快速排序算法的一种变体,它使用三个 pivot 元素来将数组分成四部分,分别是小于第一个pivot 元素、等于第一个pivot 元素、大于第一个pivot 元素且小于第二个 pivot 元素、大于第二个pivot 元素且小于等于第三个pivot 元素、大于第三个pivot 元素的部分。这个算法的时间复杂度为 O(n logn),空问复杂度为 O(log n)。

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

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void dualPivotQuickSort(int arr[], int left, int right) {
    if (left >= right) {
        return;
    }
    if (arr[left] > arr[right]) {
        swap(&arr[left], &arr[right]);
    }
    int p = arr[left], q = arr[right];
    int i = left + 1, j = right - 1, k = left + 1;
    while (k <= j) {
        if (arr[k] < p) {
            swap(&arr[k], &arr[i]);
            i++;
        } else if (arr[k] >= q) {
            while (arr[j] > q && k < j) {
                j--;
            }
            swap(&arr[k], &arr[j]);
            j--;
            if (arr[k] < p) {
                swap(&arr[k], &arr[i]);
                i++;
            }
        }
        k++;
    }
    i--;
    j++;
    swap(&arr[left], &arr[i]);
    swap(&arr[right], &arr[j]);
    dualPivotQuickSort(arr, left, i - 1);
    dualPivotQuickSort(arr, i + 1, j - 1);
    dualPivotQuickSort(arr, j + 1, right);
}

int main() {
    int arr[] = {5, 3, 8, 4, 2, 7, 1, 10, 6, 9};
    int n = sizeof(arr) / sizeof(arr[0]);
    dualPivotQuickSort(arr, 0, n - 1);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

归并排序

归并排序的算法思想可以归纳为:

1.把长度为n的输入序列分成两个长度为n/2的子序列;

2.对这两个子序列分别采用归并排序;

3.将两个排序好的子序列合并成一个最终的排序序列。

其核心思路是分治法:

​ 分解;递归地将当前序列均分为两个子序列,直到不能再分解(子序列大小为1)。

​ 解決;通过合井操作将两个排序好的子序列合并成一个排序序列。

​ 合并;重复地合并排序好的子序列,直到最终只有一个排序完毕的序列。

归并排序稳定、时间复杂度为O(nlogn),需要额外O(n)的空间复杂度用于merge操作。

void merge(int arr[], int left, int mid, int right) {
    int i = left, j = mid + 1, k = 0;
    int temp[right - left + 1]; // 定义临时数组

    while (i <= mid && j <= right) { // 将左右两部分按顺序合并到临时数组中
        if (arr[i] <= arr[j]) {
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
        }
    }

    while (i <= mid) { // 将左边剩余的部分复制到临时数组中
        temp[k++] = arr[i++];
    }

    while (j <= right) { // 将右边剩余的部分复制到临时数组中
        temp[k++] = arr[j++];
    }

    for (i = left, k = 0; i <= right; i++, k++) { // 将临时数组中的元素复制回原数组中
        arr[i] = temp[k];
    }
}

void merge_sort(int arr[], int left, int right) {
    if (left >= right) { // 如果左指针大于等于右指针,直接返回
        return;
    }

    int mid = (left + right) / 2; // 计算中间位置

    merge_sort(arr, left, mid); // 递归处理左边的子数组
    merge_sort(arr, mid + 1, right); // 递归处理右边的子数组

    merge(arr, left, mid, right); // 合并左右两部分
}

冒泡排序

void bubble_sort(int arr[], int n) {
    int i, j, temp;

    for (i = 0; i < n - 1; i++) { // 外层循环控制排序轮数
        for (j = 0; j < n - i - 1; j++) { // 内层循环控制每轮比较次数
            if (arr[j] > arr[j + 1]) { // 如果前一个数大于后一个数,交换它们的位置
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

折半插入排序

void binary_insertion_sort(int arr[], int n) {
    int i, j, left, right, mid, temp;

    for (i = 1; i < n; i++) { // 外层循环控制插入次数
        temp = arr[i]; // 取出待插入的数
        left = 0; // 初始化左指针
        right = i - 1; // 初始化右指针

        while (left <= right) { // 折半查找插入位置
            mid = (left + right) / 2;
            if (arr[mid] > temp) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        for (j = i - 1; j >= left; j--) { // 将插入位置后面的数依次后移
            arr[j + 1] = arr[j];
        }

        arr[left] = temp; // 将待插入的数插入到正确位置
    }
}

直接插入排序

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

void insertionSort(int arr[], int n) {
    for (int i = 1; i < n; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

int main() {
    int arr[] = {5, 3, 8, 4, 2, 7, 1, 10, 6, 9};
    int n = sizeof(arr) / sizeof(arr[0]);
    insertionSort(arr, n);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

希尔排序

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

void shellSort(int arr[], int n) {
    for (int gap = n / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < n; i++) {
            int temp = arr[i];
            int j;
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
}

int main() {
    int arr[] = {5, 3, 8, 4, 2, 7, 1, 10, 6, 9};
    int n = sizeof(arr) / sizeof(arr[0]);
    shellSort(arr, n);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

简单选择排序

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

void selectionSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex != i) {
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

int main() {
    int arr[] = {5, 3, 8, 4, 2, 7, 1, 10, 6, 9};
    int n = sizeof(arr) / sizeof(arr[0]);
    selectionSort(arr, n);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

堆排序

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

void shiftdown(int arr[], int n, int i) {
    int largest = i;
    int l = 2 * i + 1;
    int r = 2 * i + 2;
    if (l < n && arr[l] > arr[largest]) {
        largest = l;
    }
    if (r < n && arr[r] > arr[largest]) {
        largest = r;
    }
    if (largest != i) {
        int temp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = temp;
        shiftdown(arr, n, largest);
    }
}

void heapSort(int arr[], int n) {
    for (int i = n / 2 - 1; i >= 0; i--) {
        shiftdown(arr, n, i);
    }
    for (int i = n - 1; i >= 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        shiftdown(arr, i, 0);
    }
}

int main() {
    int arr[] = {5, 3, 8, 4, 2, 7, 1, 10, 6, 9};
    int n = sizeof(arr) / sizeof(arr[0]);
    heapSort(arr, n);
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    system("pause");
    return 0;
}

二分查找

二分查找的基本思想是:将有序数列分成两部分,取中问位置的数与目标数进行比较,如果中间位置的数等于目标数,返回中问位置;如果中间位置的数小于目标数,将左指针移到中间位置的右边;如果中间位置的数大于目标数,将右指针移到中间位置的左边。在实现过程中,可以使用一个循环来控制左右指针的移动,直到找到目标数或者左指针大于右指针为止.

#include <stdio.h>

int binary_search(int arr[], int n, int target) {
    int left = 0, right = n - 1, mid;

    while (left <= right) { // 循环条件为左指针小于等于右指针
        mid = (left + right) / 2; // 取中间位置
        if (arr[mid] == target) { // 如果中间位置的数等于目标数,返回中间位置
            return mid;
        } else if (arr[mid] < target) { // 如果中间位置的数小于目标数,将左指针移到中间位置的右边
            left = mid + 1;
        } else { // 如果中间位置的数大于目标数,将右指针移到中间位置的左边
            right = mid - 1;
        }
    }

    return -1; // 如果没有找到目标数,返回-1
}

int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8};
    int n = sizeof(arr) / sizeof(arr[0]);
    int target = 5;

    int index = binary_search(arr, n, target);

    if (index == -1) {
        printf("没有找到目标数\n");
    } else {
        printf("目标数的下标为:%d\n", index);
    }

    return 0;
}

二叉排序树

节点定义

struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
};

查找算法

struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
};

struct TreeNode* searchBST(struct TreeNode* root, int val) {
    if (root == NULL || root->val == val) {
        return root;
    }
    if (root->val > val) {
        return searchBST(root->left, val);
    } else {
        return searchBST(root->right, val);
    }
}

插入算法

struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
};

struct TreeNode* insertBST(struct TreeNode* root, int val) {
    if (root == NULL) {
        root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
        root->val = val;
        root->left = NULL;
        root->right = NULL;
        return root;
    }
    if (root->val > val) {
        root->left = insertBST(root->left, val);
    } else {
        root->right = insertBST(root->right, val);
    }
    return root;
}

构造算法

struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
};

struct TreeNode* insertBST(struct TreeNode* root, int val) {
    if (root == NULL) {
        root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
        root->val = val;
        root->left = NULL;
        root->right = NULL;
        return root;
    }
    if (root->val > val) {
        root->left = insertBST(root->left, val);
    } else {
        root->right = insertBST(root->right, val);
    }
    return root;
}

struct TreeNode* constructBST(int arr[], int n) {
    struct TreeNode* root = NULL;
    for (int i = 0; i < n; i++) {
        root = insertBST(root, arr[i]);
    }
    return root;
}
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心如止水Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值