Skip to content

可运行版本 · C

每个文件均可独立编译运行(gcc 文件名.c -o out && ./out),带 main 测试用例,并实现了多道统考真题。建议亲手跑一遍验证边界条件。

文件一览

序号内容文件
01线性表 · 顺序表01_线性表_顺序表.c
02线性表 · 单链表02_线性表_单链表.c
03栈与队列03_栈与队列.c
04二叉树04_二叉树.c
05排序算法05_排序算法.c
06串匹配与查找06_串匹配与查找.c
07图算法07_图算法.c

01 线性表 · 顺序表

c
/*
 * ============================================================
 *   408 考研数据结构 —— 顺序表 (可编译运行版)
 *   文件: 01_线性表_顺序表.c
 *   编译: gcc 01_线性表_顺序表.c -o sqlist && sqlist
 * ============================================================
 *
 *   本文件包含:
 *     1. 顺序表的定义与初始化
 *     2. 插入、删除操作 (考研必背)
 *     3. 按值/按序号查找
 *     4. ★ 真题经典: 数组循环左移 p 位 (2010 年真题)
 *     5. ★ 真题经典: 两个有序数组求中位数 (2011 年真题)
 *     6. ★ 真题经典: 寻找主元素 (2013 年真题)
 *
 *   注意: 教材中数组下标一般从 0 开始, 逻辑位序从 1 开始。
 * ============================================================
 */

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

#define MaxSize 50
typedef int ElemType;

/* ---- 顺序表结构体定义 ---- */
typedef struct {
    ElemType data[MaxSize]; /* 用静态数组存放数据元素 */
    int length;             /* 当前长度 */
} SqList;


/* ========== 基础操作 ========== */

/* 初始化: 将长度置零即可 */
void InitList(SqList *L) {
    L->length = 0;
}

/* 插入: 在第 i 个位置(1-based)插入元素 e
 *   - 要把第 i 个及之后的元素全部后移一位
 *   - 时间复杂度: 最好 O(1), 最坏 O(n), 平均 O(n)
 */
int ListInsert(SqList *L, int i, ElemType e) {
    if (i < 1 || i > L->length + 1) return 0;
    if (L->length >= MaxSize) return 0;

    int j;
    for (j = L->length; j >= i; j--)  /* 从最后一个元素开始后移 */
        L->data[j] = L->data[j - 1];

    L->data[i - 1] = e;  /* 位序 i 对应下标 i-1 */
    L->length++;
    return 1;
}

/* 删除: 删除第 i 个位置(1-based)的元素, 用 e 带回
 *   - 要把第 i+1 个及之后的元素全部前移一位
 */
int ListDelete(SqList *L, int i, ElemType *e) {
    if (i < 1 || i > L->length) return 0;

    *e = L->data[i - 1];
    int j;
    for (j = i; j < L->length; j++)
        L->data[j - 1] = L->data[j];

    L->length--;
    return 1;
}

/* 按值查找: 返回第一个等于 e 的元素的位序(1-based), 找不到返回 0 */
int LocateElem(SqList L, ElemType e) {
    int i;
    for (i = 0; i < L.length; i++)
        if (L.data[i] == e) return i + 1;
    return 0;
}

/* 打印顺序表 */
void PrintList(SqList L) {
    int i;
    printf("[ ");
    for (i = 0; i < L.length; i++)
        printf("%d ", L.data[i]);
    printf("] (length=%d)\n", L.length);
}


/* ========== 408 真题经典算法 ========== */

/*
 * ★ 2010 年真题: 将数组 a[0..n-1] 循环左移 p 位
 *   思路: 三次逆置
 *     1. 逆置 a[0..p-1]
 *     2. 逆置 a[p..n-1]
 *     3. 逆置 a[0..n-1]
 *   时间 O(n), 空间 O(1)
 */
void Reverse(int a[], int left, int right) {
    int temp;
    while (left < right) {
        temp = a[left];
        a[left] = a[right];
        a[right] = temp;
        left++;
        right--;
    }
}

void LeftShift(int a[], int n, int p) {
    p = p % n; /* 防止 p >= n 的情况 */
    if (p == 0) return;
    Reverse(a, 0, p - 1);     /* 步骤1: 逆置前 p 个 */
    Reverse(a, p, n - 1);     /* 步骤2: 逆置后 n-p 个 */
    Reverse(a, 0, n - 1);     /* 步骤3: 整体逆置 */
}

/*
 * ★ 2011 年真题: 两个等长有序序列 A,B 各含 n 个元素, 求合并后的中位数
 *   O(n) 解法: 双指针归并计数
 *     两个指针分别指向 A 和 B 的起始位置,
 *     每次移动较小者的指针, 当总共移动了 n-1 次时,
 *     下一个较小者就是中位数。
 */
int FindMedian(int A[], int B[], int n) {
    int ia = 0, ib = 0;
    int count;
    int last = 0; /* 记录上一个被选中的值 */

    for (count = 0; count < n; count++) {
        if (ia < n && (ib >= n || A[ia] <= B[ib])) {
            last = A[ia];
            ia++;
        } else {
            last = B[ib];
            ib++;
        }
    }
    return last;  /* 第 n 个就是中位数 */
}

/*
 * ★ 2013 年真题: 找主元素
 *   若数组 A[0..n-1] 中有某个元素出现次数 > n/2, 则称其为主元素
 *   返回主元素值, 若不存在返回 -1
 *
 *   经典 O(n) 做法: 摩尔投票法 (Boyer-Moore Voting)
 *     - 第一遍扫描: 用一个候选者 candidate 和计数器 count
 *       遇到相同的 count++, 不同的 count--
 *       count 归零时换人。最后剩下的就是候选者。
 *     - 第二遍扫描: 验证候选者出现次数是否真的 > n/2
 */
int FindMainElement(int A[], int n) {
    int candidate = A[0];
    int count = 1;
    int i;

    /* 第一遍: 找候选者 */
    for (i = 1; i < n; i++) {
        if (A[i] == candidate)
            count++;
        else {
            count--;
            if (count == 0) {
                candidate = A[i];
                count = 1;
            }
        }
    }

    /* 第二遍: 验证 */
    count = 0;
    for (i = 0; i < n; i++)
        if (A[i] == candidate) count++;

    if (count > n / 2)
        return candidate;
    else
        return -1;
}


/* ========== main: 全部演示 ========== */
int main() {
    printf("===== 408 顺序表 可运行演示 =====\n\n");

    /* --- 基础操作演示 --- */
    SqList L;
    InitList(&L);
    ListInsert(&L, 1, 10);
    ListInsert(&L, 2, 20);
    ListInsert(&L, 3, 30);
    ListInsert(&L, 2, 15); /* 在第2位插入15 */
    printf("插入后: ");
    PrintList(L); /* 预期: [10 15 20 30] */

    ElemType del;
    ListDelete(&L, 3, &del);
    printf("删除第3位(值=%d)后: ", del);
    PrintList(L); /* 预期: [10 15 30] */

    int pos = LocateElem(L, 15);
    printf("元素 15 在位序 %d\n\n", pos);

    /* --- 2010: 循环左移 --- */
    printf("--- 2010 真题: 循环左移 ---\n");
    int arr1[] = {0, 1, 2, 3, 4, 5, 6, 7};
    int n1 = 8, p = 3;
    printf("原数组: ");
    int i;
    for (i = 0; i < n1; i++) printf("%d ", arr1[i]);
    LeftShift(arr1, n1, p);
    printf("\n左移 %d 位: ", p);
    for (i = 0; i < n1; i++) printf("%d ", arr1[i]);
    printf("\n\n");

    /* --- 2011: 两有序数组求中位数 --- */
    printf("--- 2011 真题: 两有序数组求中位数 ---\n");
    int A[] = {1, 3, 5, 7, 9};
    int B[] = {2, 4, 6, 8, 10};
    int median = FindMedian(A, B, 5);
    printf("A = {1,3,5,7,9}, B = {2,4,6,8,10}\n");
    printf("中位数 = %d\n\n", median); /* 预期: 5 */

    /* --- 2013: 主元素 --- */
    printf("--- 2013 真题: 主元素 ---\n");
    int C[] = {0, 5, 5, 3, 5, 1, 5, 5};
    int mainElem = FindMainElement(C, 8);
    printf("数组 = {0,5,5,3,5,1,5,5}\n");
    printf("主元素 = %d\n\n", mainElem); /* 预期: 5 */

    int D[] = {0, 5, 5, 3, 5, 1, 2, 7};
    mainElem = FindMainElement(D, 8);
    printf("数组 = {0,5,5,3,5,1,2,7}\n");
    printf("主元素 = %d (不存在则 -1)\n", mainElem);

    return 0;
}

02 线性表 · 单链表

c
/*
 * ============================================================
 *   408 考研数据结构 —— 单链表 (可编译运行版)
 *   文件: 02_线性表_单链表.c
 *   编译: gcc 02_线性表_单链表.c -o linklist && linklist
 * ============================================================
 *
 *   本文件包含:
 *     1. 单链表的定义 (带头结点)
 *     2. 头插法 / 尾插法建表 (两种最基本的创建方式)
 *     3. 按序号 / 按值查找
 *     4. 插入 / 删除结点
 *     5. ★ 高频考点: 链表原地逆置 (头插法逆置)
 *     6. ★ 高频考点: 快慢指针找中间结点
 *     7. ★ 课后题: 删除链表中所有值为 x 的结点
 *     8. ★ 课后题: 链表排序 (直接插入排序思路)
 *
 *   教材惯例: 单链表带头结点, 头结点不存数据,
 *             L->next 指向第一个真实的数据结点。
 * ============================================================
 */

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

typedef int ElemType;

/* ---- 单链表结构体定义 ---- */
typedef struct LNode {
    ElemType data;
    struct LNode *next;
} LNode, *LinkList;
/* LinkList 等价于 LNode *, 用 LinkList 强调"这是整个链表"  */
/* 用 LNode * 强调"这是某个结点的指针"                     */


/* ========== 基础操作 ========== */

/* 初始化: 创建一个空的带头结点链表 */
LinkList InitList() {
    LinkList L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    return L;
}

/* 头插法建表: 每次把新结点插到头结点后面
 *   结果: 数据顺序与输入顺序相反
 *   (考点: 常用于 "链表逆置" 的核心技巧)
 */
LinkList CreateList_Head(int arr[], int n) {
    LinkList L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    int i;
    for (i = 0; i < n; i++) {
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = arr[i];
        s->next = L->next;  /* 新结点指向原来的第一个 */
        L->next = s;         /* 头结点指向新结点 */
    }
    return L;
}

/* 尾插法建表: 每次把新结点接到表尾
 *   结果: 数据顺序与输入顺序相同
 *   (需要一个尾指针 r 跟踪表尾)
 */
LinkList CreateList_Tail(int arr[], int n) {
    LinkList L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    LNode *r = L; /* r 始终指向当前的尾结点 */
    int i;
    for (i = 0; i < n; i++) {
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = arr[i];
        r->next = s;
        r = s;
    }
    r->next = NULL; /* 千万别忘: 最后尾结点的 next 置空! */
    return L;
}

/* 按序号查找: 返回第 i 个结点的指针 (1-based) */
LNode *GetElem(LinkList L, int i) {
    if (i < 0) return NULL;
    if (i == 0) return L;  /* 第 0 个是头结点 */

    LNode *p = L->next;
    int j = 1;
    while (p != NULL && j < i) {
        p = p->next;
        j++;
    }
    return p; /* 如果 i 超出长度, p 会是 NULL */
}

/* 按值查找: 返回第一个值为 e 的结点指针 */
LNode *LocateElem(LinkList L, ElemType e) {
    LNode *p = L->next;
    while (p != NULL && p->data != e)
        p = p->next;
    return p; /* 找不到返回 NULL */
}

/* 在第 i 个位置插入值为 e 的新结点 */
int ListInsert(LinkList L, int i, ElemType e) {
    LNode *p = GetElem(L, i - 1); /* 找前驱 */
    if (p == NULL) return 0;

    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next; /* 新结点指向原第 i 个 */
    p->next = s;        /* 前驱指向新结点 */
    return 1;
}

/* 删除第 i 个结点, 被删除的值通过 e 带回 */
int ListDelete(LinkList L, int i, ElemType *e) {
    LNode *p = GetElem(L, i - 1); /* 找前驱 */
    if (p == NULL || p->next == NULL) return 0;

    LNode *q = p->next;   /* q 指向要删除的结点 */
    *e = q->data;
    p->next = q->next;    /* 前驱跳过被删结点 */
    free(q);               /* 释放空间 */
    return 1;
}

/* 求表长 */
int ListLength(LinkList L) {
    int len = 0;
    LNode *p = L->next;
    while (p != NULL) {
        len++;
        p = p->next;
    }
    return len;
}

/* 打印链表 */
void PrintList(LinkList L) {
    LNode *p = L->next;
    printf("Head -> ");
    while (p != NULL) {
        printf("%d -> ", p->data);
        p = p->next;
    }
    printf("NULL\n");
}


/* ========== 高频考点算法 ========== */

/*
 * ★ 链表原地逆置 (不申请新结点)
 *   方法: 将原链表的结点逐一摘下, 用头插法重新挂到头结点后面
 *   时间 O(n), 空间 O(1)
 */
void ReverseList(LinkList L) {
    LNode *p = L->next;  /* p: 工作指针, 指向待处理结点 */
    LNode *r;              /* r: 保存 p 的后继, 防止断链 */
    L->next = NULL;        /* 先把头结点摘出来, 变成空链表 */

    while (p != NULL) {
        r = p->next;       /* 1. 保存后继 */
        p->next = L->next; /* 2. 头插: p 指向当前的第一个 */
        L->next = p;       /* 3. 头结点指向 p */
        p = r;             /* 4. p 前进到下一个待处理结点 */
    }
}

/*
 * ★ 快慢指针找中间结点
 *   快指针每次走 2 步, 慢指针每次走 1 步
 *   当快指针走到底时, 慢指针恰好在中间
 */
LNode *FindMiddle(LinkList L) {
    LNode *slow = L->next;
    LNode *fast = L->next;
    while (fast != NULL && fast->next != NULL) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

/*
 * ★ 删除链表中所有值为 x 的结点
 *   技巧: 用前驱指针判断后继的值
 */
void DeleteAllX(LinkList L, ElemType x) {
    LNode *p = L; /* p 是当前结点的前驱 */
    LNode *q;
    while (p->next != NULL) {
        if (p->next->data == x) {
            q = p->next;
            p->next = q->next;
            free(q);
            /* 注意: 这里不移动 p, 因为删除后 p->next 变了 */
        } else {
            p = p->next;
        }
    }
}

/*
 * ★ 链表排序 (直接插入排序)
 *   思路: 摘下原链头, 逐个插入到一个新的有序链表中
 */
void SortList(LinkList L) {
    if (L->next == NULL || L->next->next == NULL) return;

    LNode *p = L->next->next; /* 从第二个结点开始 */
    L->next->next = NULL;      /* 有序链表初始只有第一个结点 */

    while (p != NULL) {
        LNode *r = p->next;   /* 保存后继 */

        /* 在有序部分中找插入位置: 找到第一个 > p->data 的前驱 */
        LNode *q = L;
        while (q->next != NULL && q->next->data < p->data)
            q = q->next;

        /* 插入 */
        p->next = q->next;
        q->next = p;

        p = r; /* 处理下一个 */
    }
}


/* ========== main: 全部演示 ========== */
int main() {
    printf("===== 408 单链表 可运行演示 =====\n\n");

    /* 尾插法建表 */
    int data[] = {3, 1, 4, 1, 5, 9, 2, 6};
    LinkList L = CreateList_Tail(data, 8);
    printf("尾插法建表: ");
    PrintList(L);

    /* 头插法建表 (验证顺序反转) */
    int data2[] = {1, 2, 3};
    LinkList L2 = CreateList_Head(data2, 3);
    printf("头插法建表 {1,2,3}: ");
    PrintList(L2); /* 预期: 3 -> 2 -> 1 */

    /* 插入与删除 */
    ListInsert(L, 1, 99);
    printf("\n在第1位插入99: ");
    PrintList(L);

    ElemType del;
    ListDelete(L, 1, &del);
    printf("删除第1位(%d): ", del);
    PrintList(L);

    /* 链表逆置 */
    printf("\n--- 链表逆置 ---\n");
    printf("逆置前: ");
    PrintList(L);
    ReverseList(L);
    printf("逆置后: ");
    PrintList(L);

    /* 快慢指针找中间 */
    printf("\n--- 快慢指针找中间 ---\n");
    LNode *mid = FindMiddle(L);
    if (mid) printf("中间结点值 = %d\n", mid->data);

    /* 删除所有值为 1 的结点 */
    printf("\n--- 删除所有值为 1 的结点 ---\n");
    printf("删除前: ");
    PrintList(L);
    DeleteAllX(L, 1);
    printf("删除后: ");
    PrintList(L);

    /* 链表排序 */
    printf("\n--- 链表排序 ---\n");
    int data3[] = {5, 3, 8, 1, 2, 7};
    LinkList L3 = CreateList_Tail(data3, 6);
    printf("排序前: ");
    PrintList(L3);
    SortList(L3);
    printf("排序后: ");
    PrintList(L3);

    return 0;
}

03 栈与队列

c
/*
 * ============================================================
 *   408 考研数据结构 —— 栈与队列 (可编译运行版)
 *   文件: 03_栈与队列.c
 *   编译: gcc 03_栈与队列.c -o stack_queue && stack_queue
 * ============================================================
 *
 *   本文件包含:
 *     1. 顺序栈: 初始化 / 进栈 / 出栈 / 判空
 *     2. 循环队列 (牺牲一个单元法): 初始化 / 入队 / 出队 / 判空
 *     3. ★ 栈的经典应用: 括号匹配
 *     4. ★ 栈的经典应用: 十进制转二进制
 *     5. ★ 栈的经典应用: 后缀表达式求值
 *
 *   考研重点:
 *     - 顺序栈 top 初始为 -1, 先加后存
 *     - 循环队列的 3 种判空判满方案 (本文件展示最常考的牺牲一个单元法)
 * ============================================================
 */

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

#define MaxSize 50
typedef int ElemType;

/* ========== 顺序栈 ========== */
typedef struct {
    ElemType data[MaxSize];
    int top; /* 栈顶指针, 初始化为 -1 */
} SqStack;

void InitStack(SqStack *S) { S->top = -1; }
int StackEmpty(SqStack *S) { return S->top == -1; }

int Push(SqStack *S, ElemType x) {
    if (S->top == MaxSize - 1) return 0; /* 栈满 */
    S->data[++S->top] = x;  /* 先加指针, 再存数据 */
    return 1;
}

int Pop(SqStack *S, ElemType *x) {
    if (S->top == -1) return 0; /* 栈空 */
    *x = S->data[S->top--]; /* 先取数据, 再减指针 */
    return 1;
}

int GetTop(SqStack *S, ElemType *x) {
    if (S->top == -1) return 0;
    *x = S->data[S->top]; /* 只看不取 */
    return 1;
}


/* ========== 循环队列 (牺牲一个单元法) ========== */
typedef struct {
    ElemType data[MaxSize];
    int front, rear;
} SqQueue;

void InitQueue(SqQueue *Q) { Q->front = Q->rear = 0; }
int QueueEmpty(SqQueue *Q) { return Q->front == Q->rear; }

/* 判满: rear 的下一个位置是 front 就满了 */
int EnQueue(SqQueue *Q, ElemType x) {
    if ((Q->rear + 1) % MaxSize == Q->front) return 0;
    Q->data[Q->rear] = x;
    Q->rear = (Q->rear + 1) % MaxSize;
    return 1;
}

int DeQueue(SqQueue *Q, ElemType *x) {
    if (Q->front == Q->rear) return 0;
    *x = Q->data[Q->front];
    Q->front = (Q->front + 1) % MaxSize;
    return 1;
}


/* ========== 栈的经典应用 ========== */

/*
 * ★ 括号匹配算法
 *   遇到左括号入栈, 遇到右括号检查栈顶是否匹配
 *   最终栈必须为空才算匹配成功
 */
int BracketMatch(const char *str) {
    SqStack S;
    InitStack(&S);
    int i;
    char ch;

    for (i = 0; str[i] != '\0'; i++) {
        if (str[i] == '(' || str[i] == '[' || str[i] == '{') {
            Push(&S, str[i]); /* 左括号入栈 */
        } else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {
            if (StackEmpty(&S)) return 0; /* 没有左括号可配 */
            Pop(&S, (ElemType *)&ch);

            /* 检查对称性 */
            if (str[i] == ')' && ch != '(') return 0;
            if (str[i] == ']' && ch != '[') return 0;
            if (str[i] == '}' && ch != '{') return 0;
        }
    }
    return StackEmpty(&S); /* 最终栈为空才行 */
}

/*
 * ★ 十进制转二进制
 *   反复除以 2 取余入栈, 然后弹栈得到结果
 */
void DecToBin(int n) {
    SqStack S;
    InitStack(&S);
    ElemType rem;

    if (n == 0) { printf("0"); return; }
    while (n > 0) {
        Push(&S, n % 2);
        n = n / 2;
    }
    while (!StackEmpty(&S)) {
        Pop(&S, &rem);
        printf("%d", rem);
    }
}

/*
 * ★ 后缀表达式求值 (操作数为个位数, 简化演示)
 *   规则: 遇到数字入栈, 遇到运算符弹出两个操作数计算后压回
 */
int EvalPostfix(const char *expr) {
    SqStack S;
    InitStack(&S);
    int i;
    ElemType a, b, result;

    for (i = 0; expr[i] != '\0'; i++) {
        if (expr[i] >= '0' && expr[i] <= '9') {
            Push(&S, expr[i] - '0');
        } else if (expr[i] == ' ') {
            continue; /* 跳过空格 */
        } else {
            Pop(&S, &b); /* 注意: 先弹出的是右操作数 */
            Pop(&S, &a);
            switch (expr[i]) {
                case '+': result = a + b; break;
                case '-': result = a - b; break;
                case '*': result = a * b; break;
                case '/': result = a / b; break;
                default: result = 0;
            }
            Push(&S, result);
        }
    }
    Pop(&S, &result);
    return result;
}


/* ========== main 演示 ========== */
int main() {
    printf("===== 408 栈与队列 可运行演示 =====\n\n");

    /* --- 顺序栈基础 --- */
    printf("--- 顺序栈 ---\n");
    SqStack S;
    InitStack(&S);
    Push(&S, 10);
    Push(&S, 20);
    Push(&S, 30);
    ElemType val;
    GetTop(&S, &val);
    printf("栈顶: %d\n", val);
    Pop(&S, &val);
    printf("弹出: %d, 新栈顶: ", val);
    GetTop(&S, &val);
    printf("%d\n\n", val);

    /* --- 循环队列基础 --- */
    printf("--- 循环队列 ---\n");
    SqQueue Q;
    InitQueue(&Q);
    EnQueue(&Q, 1);
    EnQueue(&Q, 2);
    EnQueue(&Q, 3);
    DeQueue(&Q, &val);
    printf("出队: %d\n", val);
    DeQueue(&Q, &val);
    printf("出队: %d\n\n", val);

    /* --- 括号匹配 --- */
    printf("--- 括号匹配 ---\n");
    printf("\"(a+b)*[c-d]\" 匹配? %s\n",
           BracketMatch("(a+b)*[c-d]") ? "是" : "否");
    printf("\"(a+b]*c\"     匹配? %s\n",
           BracketMatch("(a+b]*c") ? "是" : "否");
    printf("\"((())\"        匹配? %s\n\n",
           BracketMatch("((())") ? "是" : "否");

    /* --- 十进制转二进制 --- */
    printf("--- 十进制转二进制 ---\n");
    printf("42 -> ");
    DecToBin(42);
    printf("\n13 -> ");
    DecToBin(13);
    printf("\n\n");

    /* --- 后缀表达式求值 --- */
    printf("--- 后缀表达式求值 ---\n");
    /* "3 4 + 5 *" 即 (3+4)*5 = 35 */
    int res = EvalPostfix("34+5*");
    printf("后缀 \"34+5*\" = %d (即(3+4)*5)\n", res);
    /* "5 1 2 + 4 * + 3 -" 即 5+((1+2)*4)-3 = 14 */
    res = EvalPostfix("512+4*+3-");
    printf("后缀 \"512+4*+3-\" = %d (即5+(1+2)*4-3)\n", res);

    return 0;
}

04 二叉树

c
/*
 * ============================================================
 *   408 考研数据结构 —— 二叉树 (可编译运行版)
 *   文件: 04_二叉树.c
 *   编译: gcc 04_二叉树.c -o btree && btree
 * ============================================================
 *
 *   本文件包含:
 *     1. 二叉链表存储结构的定义与手工建树
 *     2. 先序/中序/后序 递归遍历
 *     3. ★ 中序非递归遍历 (栈实现, 考研大题重点)
 *     4. ★ 层序遍历 (队列实现, BFS 的原型)
 *     5. ★ 求二叉树深度 (递归)
 *     6. ★ 统计叶子结点个数
 *     7. ★ 2014 真题: 求 WPL (带权路径长度)
 *     8. ★ 判断是否是完全二叉树
 *
 *   手工建树说明:
 *     为了让你真正运行起来看到效果, 本文件手工构造一棵树:
 *          1
 *        /   \
 *       2     3
 *      / \   / \
 *     4   5 6   7
 * ============================================================
 */

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

typedef int ElemType;

/* ---- 二叉树链式存储定义 ---- */
typedef struct BiTNode {
    ElemType data;
    struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;

/* 辅助: 创建新结点 */
BiTNode *NewNode(ElemType val) {
    BiTNode *p = (BiTNode *)malloc(sizeof(BiTNode));
    p->data = val;
    p->lchild = p->rchild = NULL;
    return p;
}


/* ========== 递归遍历 ========== */

void PreOrder(BiTree T) {
    if (T != NULL) {
        printf("%d ", T->data);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

void InOrder(BiTree T) {
    if (T != NULL) {
        InOrder(T->lchild);
        printf("%d ", T->data);
        InOrder(T->rchild);
    }
}

void PostOrder(BiTree T) {
    if (T != NULL) {
        PostOrder(T->lchild);
        PostOrder(T->rchild);
        printf("%d ", T->data);
    }
}


/* ========== 非递归中序遍历 (大题重点) ========== */
/*
 * 算法思路:
 *   1. 从根结点开始, 一路向左走到底, 沿途入栈
 *   2. 走到空了, 弹栈, 访问该结点
 *   3. 再转向该结点的右子树, 重复步骤 1
 *   循环条件: 当前指针不空 或 栈不空
 */
void InOrderNonRecursive(BiTree T) {
    BiTNode *stack[50];
    int top = -1;
    BiTNode *p = T;

    while (p != NULL || top != -1) {
        if (p != NULL) {
            stack[++top] = p;  /* 入栈 */
            p = p->lchild;     /* 一路向左 */
        } else {
            p = stack[top--];  /* 出栈 */
            printf("%d ", p->data); /* 访问 */
            p = p->rchild;     /* 转向右子树 */
        }
    }
}


/* ========== 层序遍历 (BFS, 队列实现) ========== */
void LevelOrder(BiTree T) {
    if (T == NULL) return;

    BiTNode *queue[50];
    int front = 0, rear = 0;

    queue[rear++] = T; /* 根入队 */

    while (front != rear) {
        BiTNode *p = queue[front++]; /* 出队 */
        printf("%d ", p->data);

        if (p->lchild != NULL) queue[rear++] = p->lchild;
        if (p->rchild != NULL) queue[rear++] = p->rchild;
    }
}


/* ========== 求二叉树深度 ========== */
int TreeDepth(BiTree T) {
    if (T == NULL) return 0;
    int ld = TreeDepth(T->lchild);
    int rd = TreeDepth(T->rchild);
    return (ld > rd ? ld : rd) + 1;
}


/* ========== 统计叶子结点个数 ========== */
int CountLeaves(BiTree T) {
    if (T == NULL) return 0;
    if (T->lchild == NULL && T->rchild == NULL)
        return 1; /* 没有孩子就是叶子 */
    return CountLeaves(T->lchild) + CountLeaves(T->rchild);
}


/* ========== 2014 真题: 求 WPL ========== */
/*
 * WPL = 所有叶子结点的 (权值 × 深度) 之和
 * 递归: 如果是叶子就返回 weight*depth, 否则递归左右子树
 * 注意: 这里 depth 从 0 开始 (根在第 0 层)
 */
int CalcWPL(BiTree T, int depth) {
    if (T == NULL) return 0;
    if (T->lchild == NULL && T->rchild == NULL)
        return T->data * depth;
    return CalcWPL(T->lchild, depth + 1) +
           CalcWPL(T->rchild, depth + 1);
}


/* ========== 判断是否为完全二叉树 ========== */
/*
 * 层序遍历,遇到空结点后不应再出现非空结点
 */
int IsComplete(BiTree T) {
    if (T == NULL) return 1;

    BiTNode *queue[50];
    int front = 0, rear = 0;
    int reachedNull = 0; /* 标记是否遇到过空 */

    queue[rear++] = T;
    while (front != rear) {
        BiTNode *p = queue[front++];

        if (p == NULL) {
            reachedNull = 1;
        } else {
            if (reachedNull) return 0; /* 空之后又遇到非空, 不是完全二叉树 */
            queue[rear++] = p->lchild; /* 即使是空也入队 */
            queue[rear++] = p->rchild;
        }
    }
    return 1;
}


/* ========== main 演示 ========== */
int main() {
    printf("===== 408 二叉树 可运行演示 =====\n\n");

    /* 手工建立一棵二叉树:
     *         1
     *       /   \
     *      2     3
     *     / \   / \
     *    4   5 6   7
     */
    BiTNode *n1 = NewNode(1);
    BiTNode *n2 = NewNode(2);
    BiTNode *n3 = NewNode(3);
    BiTNode *n4 = NewNode(4);
    BiTNode *n5 = NewNode(5);
    BiTNode *n6 = NewNode(6);
    BiTNode *n7 = NewNode(7);
    n1->lchild = n2; n1->rchild = n3;
    n2->lchild = n4; n2->rchild = n5;
    n3->lchild = n6; n3->rchild = n7;

    printf("先序遍历: "); PreOrder(n1); printf("\n");
    printf("中序遍历: "); InOrder(n1); printf("\n");
    printf("后序遍历: "); PostOrder(n1); printf("\n");

    printf("\n中序非递归: "); InOrderNonRecursive(n1); printf("\n");
    printf("层序遍历:   "); LevelOrder(n1); printf("\n");

    printf("\n树的深度 = %d\n", TreeDepth(n1));
    printf("叶子结点数 = %d\n", CountLeaves(n1));

    /* WPL: 叶子是 4(深度2), 5(深度2), 6(深度2), 7(深度2) */
    printf("\nWPL = %d (叶子权×深度之和)\n", CalcWPL(n1, 0));

    printf("是否完全二叉树? %s\n", IsComplete(n1) ? "是" : "否");

    /* 构造一棵不完全的树测试 */
    BiTNode *m1 = NewNode(1);
    BiTNode *m2 = NewNode(2);
    BiTNode *m3 = NewNode(3);
    m1->lchild = m2; m1->rchild = m3;
    m3->rchild = NewNode(4); /* 右子树有右孩子, 左子树无 -> 不完全 */
    printf("不完全二叉树判断? %s\n", IsComplete(m1) ? "是" : "否");

    return 0;
}

05 排序算法

c
/*
 * ============================================================
 *   408 考研数据结构 —— 排序算法全家桶 (可编译运行版)
 *   文件: 05_排序算法.c
 *   编译: gcc 05_排序算法.c -o sort && sort
 * ============================================================
 *
 *   本文件一次性将 408 考纲要求的全部排序算法集齐并可运行:
 *     1. 直接插入排序
 *     2. 折半插入排序
 *     3. 希尔排序
 *     4. 冒泡排序
 *     5. ★ 快速排序 (含 Partition 核心及第 k 小应用)
 *     6. 简单选择排序
 *     7. ★ 堆排序 (大顶堆建堆 + 排序, 下标从 1 开始)
 *     8. ★ 归并排序
 *
 *   每个算法都带完整的测试, 一运行就能看到排序前后的对比。
 *
 *   注意: 堆排序中, 教材惯例 A[0] 作为暂存单元不存数据,
 *         有效元素从 A[1] 到 A[n]。
 * ============================================================
 */

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

/* 打印数组辅助函数 */
void PrintArray(int A[], int n) {
    int i;
    for (i = 0; i < n; i++) printf("%d ", A[i]);
    printf("\n");
}

/* 拷贝数组 (每次测试前恢复原始数据) */
void CopyArray(int src[], int dst[], int n) {
    int i;
    for (i = 0; i < n; i++) dst[i] = src[i];
}

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


/* ===========================================
 *  1. 直接插入排序
 *     将 A[i] 插入到前面已排好的 A[0..i-1] 中
 *     最好 O(n), 最坏 O(n^2), 稳定
 * =========================================== */
void InsertSort(int A[], int n) {
    int i, j, temp;
    for (i = 1; i < n; i++) {
        if (A[i] < A[i - 1]) {
            temp = A[i];
            for (j = i - 1; j >= 0 && temp < A[j]; j--)
                A[j + 1] = A[j]; /* 一边找一边后移 */
            A[j + 1] = temp;
        }
    }
}


/* ===========================================
 *  2. 折半插入排序
 *     查找阶段用二分, 但移动次数不变, 总体仍 O(n^2)
 * =========================================== */
void BinaryInsertSort(int A[], int n) {
    int i, j, low, high, mid, temp;
    for (i = 1; i < n; i++) {
        temp = A[i];
        low = 0; high = i - 1;
        while (low <= high) {
            mid = (low + high) / 2;
            if (A[mid] > temp) high = mid - 1;
            else low = mid + 1;
        }
        /* high+1 就是插入位置 */
        for (j = i - 1; j >= high + 1; j--)
            A[j + 1] = A[j];
        A[high + 1] = temp;
    }
}


/* ===========================================
 *  3. 希尔排序
 *     增量序列: dk = n/2, n/4, ..., 1
 *     不稳定, 约 O(n^1.3)
 * =========================================== */
void ShellSort(int A[], int n) {
    int dk, i, j, temp;
    for (dk = n / 2; dk >= 1; dk /= 2) {
        for (i = dk; i < n; i++) {
            if (A[i] < A[i - dk]) {
                temp = A[i];
                for (j = i - dk; j >= 0 && temp < A[j]; j -= dk)
                    A[j + dk] = A[j];
                A[j + dk] = temp;
            }
        }
    }
}


/* ===========================================
 *  4. 冒泡排序
 *     最好 O(n), 最坏 O(n^2), 稳定
 *     每趟把一个最小/最大的元素冒到最终位置
 * =========================================== */
void BubbleSort(int A[], int n) {
    int i, j, flag;
    for (i = 0; i < n - 1; i++) {
        flag = 0;
        for (j = n - 1; j > i; j--) {
            if (A[j - 1] > A[j]) {
                swap(&A[j - 1], &A[j]);
                flag = 1;
            }
        }
        if (flag == 0) return; /* 本趟无交换, 已经有序 */
    }
}


/* ===========================================
 *  5. 快速排序
 *     核心: Partition 划分函数
 *     平均 O(nlogn), 最坏 O(n^2), 不稳定
 * =========================================== */
int Partition(int A[], int low, int high) {
    int pivot = A[low]; /* 取第一个作为枢轴 */
    while (low < high) {
        while (low < high && A[high] >= pivot) high--;
        A[low] = A[high]; /* 小的换到左边 */
        while (low < high && A[low] <= pivot) low++;
        A[high] = A[low]; /* 大的换到右边 */
    }
    A[low] = pivot; /* 枢轴就位 */
    return low;
}

void QuickSort(int A[], int low, int high) {
    if (low < high) {
        int pos = Partition(A, low, high);
        QuickSort(A, low, pos - 1);
        QuickSort(A, pos + 1, high);
    }
}


/* ===========================================
 *  6. 简单选择排序
 *     每趟从未排序部分选最小的, 放到已排序末尾
 *     时间始终 O(n^2), 不稳定
 * =========================================== */
void SelectSort(int A[], int n) {
    int i, j, min;
    for (i = 0; i < n - 1; i++) {
        min = i;
        for (j = i + 1; j < n; j++)
            if (A[j] < A[min]) min = j;
        if (min != i) swap(&A[i], &A[min]);
    }
}


/* ===========================================
 *  7. 堆排序 (下标从 1 开始, A[0] 作暂存单元)
 *     建堆 O(n), 排序 O(nlogn), 空间 O(1), 不稳定
 * =========================================== */
void AdjustDown(int A[], int k, int len) {
    int i;
    A[0] = A[k]; /* 暂存 */
    for (i = 2 * k; i <= len; i *= 2) {
        if (i < len && A[i] < A[i + 1]) i++; /* 找较大的孩子 */
        if (A[0] >= A[i]) break; /* 根比孩子大, 到位 */
        A[k] = A[i]; /* 大孩子上浮 */
        k = i;
    }
    A[k] = A[0]; /* 放到最终位置 */
}

void BuildMaxHeap(int A[], int len) {
    int i;
    for (i = len / 2; i > 0; i--) /* 从最后一个非叶结点开始 */
        AdjustDown(A, i, len);
}

void HeapSort(int A[], int len) {
    BuildMaxHeap(A, len);      /* 建初始大顶堆 */
    int i;
    for (i = len; i > 1; i--) {
        swap(&A[1], &A[i]);    /* 堆顶(最大)与末尾交换 */
        AdjustDown(A, 1, i - 1); /* 剩余元素重新调整 */
    }
}


/* ===========================================
 *  8. 归并排序
 *     辅助数组 B 开在外面, 避免反复分配
 *     时间总是 O(nlogn), 空间 O(n), 稳定
 * =========================================== */
int B_merge[50]; /* 辅助数组 */

void Merge(int A[], int low, int mid, int high) {
    int i, j, k;
    for (k = low; k <= high; k++) B_merge[k] = A[k]; /* 复制 */

    for (i = low, j = mid + 1, k = low; i <= mid && j <= high; k++) {
        if (B_merge[i] <= B_merge[j])
            A[k] = B_merge[i++];
        else
            A[k] = B_merge[j++];
    }
    while (i <= mid)  A[k++] = B_merge[i++];
    while (j <= high) A[k++] = B_merge[j++];
}

void MergeSort(int A[], int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2;
        MergeSort(A, low, mid);
        MergeSort(A, mid + 1, high);
        Merge(A, low, mid, high);
    }
}


/* ========== main: 全部排序对比演示 ========== */
int main() {
    int original[] = {49, 38, 65, 97, 76, 13, 27, 49};
    int n = 8;
    int A[8];

    printf("===== 408 八大排序 可运行演示 =====\n");
    printf("原始数组: "); PrintArray(original, n);
    printf("\n");

    CopyArray(original, A, n);
    InsertSort(A, n);
    printf("直接插入: "); PrintArray(A, n);

    CopyArray(original, A, n);
    BinaryInsertSort(A, n);
    printf("折半插入: "); PrintArray(A, n);

    CopyArray(original, A, n);
    ShellSort(A, n);
    printf("希尔排序: "); PrintArray(A, n);

    CopyArray(original, A, n);
    BubbleSort(A, n);
    printf("冒泡排序: "); PrintArray(A, n);

    CopyArray(original, A, n);
    QuickSort(A, 0, n - 1);
    printf("快速排序: "); PrintArray(A, n);

    CopyArray(original, A, n);
    SelectSort(A, n);
    printf("选择排序: "); PrintArray(A, n);

    /* 堆排序: A[0] 做暂存, 数据从 A[1] 开始 */
    printf("\n--- 堆排序 (下标从1开始) ---\n");
    int H[] = {0, 49, 38, 65, 97, 76, 13, 27, 49}; /* H[0]不用 */
    printf("排序前: ");
    int i;
    for (i = 1; i <= 8; i++) printf("%d ", H[i]);
    printf("\n");
    HeapSort(H, 8);
    printf("排序后: ");
    for (i = 1; i <= 8; i++) printf("%d ", H[i]);
    printf("\n");

    printf("\n--- 归并排序 ---\n");
    CopyArray(original, A, n);
    printf("排序前: "); PrintArray(A, n);
    MergeSort(A, 0, n - 1);
    printf("排序后: "); PrintArray(A, n);

    return 0;
}

06 串匹配与查找

c
/*
 * ============================================================
 *   408 考研数据结构 —— KMP 串匹配与查找 (可编译运行版)
 *   文件: 06_串匹配与查找.c
 *   编译: gcc 06_串匹配与查找.c -o kmp && kmp
 * ============================================================
 *
 *   本文件包含:
 *     1. ★ KMP 算法: get_next 求 next 数组
 *     2. ★ KMP 算法: get_nextval 求 nextval 数组
 *     3. ★ KMP 主匹配函数 Index_KMP
 *     4. 折半查找 (非递归)
 *
 *   重要说明:
 *     教材中字符串下标从 1 开始, ch[0] 存长度或不用。
 *     本文件为了可运行性, 同时展示了:
 *       - 教材 1-based 风格 (考试用这个)
 *       - C 语言 0-based 风格 (理解用)
 * ============================================================
 */

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

/* ========== 1-based KMP (严格教材风格, 用于考试) ========== */

/*
 *  get_next: 从模式串 T 推导出 next 数组
 *
 *  next[j] 的含义:
 *    当 T[j] 与主串失配时, j 应该回退到 next[j] 的位置
 *    即模式串的前 next[j]-1 个字符已经和主串匹配好了
 *
 *  手算提示: next[1] 固定为 0, next[2] 固定为 1,
 *            从第 3 位开始, 找前缀和后缀的最长公共子串长度+1
 */
void get_next(char T[], int Tlen, int next[]) {
    int i = 1, j = 0;
    next[1] = 0;

    while (i < Tlen) {
        if (j == 0 || T[i] == T[j]) {
            ++i; ++j;
            next[i] = j;
        } else {
            j = next[j]; /* 回退 */
        }
    }
}

/*
 *  get_nextval: next 数组的优化版
 *
 *  当 T[i] == T[next[i]] 时, 即使回退到 next[i],
 *  同样会失配 (因为字符相同), 所以直接继承 nextval[next[i]]
 */
void get_nextval(char T[], int Tlen, int nextval[]) {
    int i = 1, j = 0;
    nextval[1] = 0;

    while (i < Tlen) {
        if (j == 0 || T[i] == T[j]) {
            ++i; ++j;
            if (T[i] != T[j])
                nextval[i] = j;        /* 不等, 正常赋值 */
            else
                nextval[i] = nextval[j]; /* 相等, 继承 */
        } else {
            j = nextval[j];
        }
    }
}

/*
 *  KMP 主匹配: 在 S 中查找 T 第一次出现的位置
 *  下标从 1 开始
 */
int Index_KMP(char S[], int Slen, char T[], int Tlen, int next[]) {
    int i = 1, j = 1;

    while (i <= Slen && j <= Tlen) {
        if (j == 0 || S[i] == T[j]) {
            ++i; ++j;
        } else {
            j = next[j]; /* 模式串回退, 主串不回溯 */
        }
    }

    if (j > Tlen)
        return i - Tlen; /* 返回匹配位置 (1-based) */
    else
        return 0;        /* 未找到 */
}


/* ========== 折半查找 ========== */
int BinarySearch(int A[], int n, int key) {
    int low = 0, high = n - 1, mid;
    while (low <= high) {
        mid = (low + high) / 2;
        if (A[mid] == key) return mid;
        else if (A[mid] > key) high = mid - 1;
        else low = mid + 1;
    }
    return -1;
}


/* ========== main 演示 ========== */
int main() {
    printf("===== 408 KMP 串匹配与查找 可运行演示 =====\n\n");

    /* --- KMP 演示 ---
     * 注意: 教材风格, S[0] 和 T[0] 不用, 有效字符从下标 1 开始
     * 所以我们在字符串前面加一个占位符 '#'
     */
    /* 主串:   "aababaabaabcac"  (14 个字符) */
    /* 模式串: "abaabcac"        (8 个字符)  */
    char S[] = "#aababaabaabcac"; /* S[1]='a', S[2]='a', ... */
    char T[] = "#abaabcac";       /* T[1]='a', T[2]='b', ... */
    int Slen = 14, Tlen = 8;

    int next[20], nextval[20];
    get_next(T, Tlen, next);
    get_nextval(T, Tlen, nextval);

    int i;
    printf("模式串 T = \"abaabcac\"\n");
    printf("位序:    ");
    for (i = 1; i <= Tlen; i++) printf("%d ", i);
    printf("\n字符:    ");
    for (i = 1; i <= Tlen; i++) printf("%c ", T[i]);
    printf("\nnext:    ");
    for (i = 1; i <= Tlen; i++) printf("%d ", next[i]);
    printf("\nnextval: ");
    for (i = 1; i <= Tlen; i++) printf("%d ", nextval[i]);
    printf("\n\n");

    int pos = Index_KMP(S, Slen, T, Tlen, next);
    printf("主串 S = \"aababaabaabcac\"\n");
    if (pos > 0)
        printf("T 在 S 中第一次出现的位置 = %d\n\n", pos);
    else
        printf("未找到\n\n");

    /* --- 折半查找 --- */
    printf("--- 折半查找 ---\n");
    int arr[] = {7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43};
    int n = 11;
    printf("有序数组: ");
    for (i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("\n");

    int key = 33;
    int result = BinarySearch(arr, n, key);
    printf("查找 %d -> 下标 %d\n", key, result);

    key = 20;
    result = BinarySearch(arr, n, key);
    printf("查找 %d -> %s\n", key, result == -1 ? "未找到" : "找到了");

    return 0;
}

07 图算法

c
/*
 * ============================================================
 *   408 考研数据结构 —— 图: BFS / DFS / Dijkstra (可编译运行版)
 *   文件: 07_图算法.c
 *   编译: gcc 07_图算法.c -o graph && graph
 * ============================================================
 *
 *   本文件包含:
 *     1. 邻接矩阵存储图
 *     2. ★ DFS 深度优先遍历 (递归)
 *     3. ★ BFS 广度优先遍历 (队列)
 *     4. ★ Dijkstra 最短路径 (邻接矩阵)
 *     5. ★ 拓扑排序 (邻接表, 选择题/大题常考)
 *
 *   测试图 (无向图, 5 个顶点):
 *       0 --- 1 --- 3
 *       |     |
 *       2 --- 4
 * ============================================================
 */

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

#define MaxV 20
#define INF 999999

/* ========== 邻接矩阵存储 ========== */
typedef struct {
    int vex[MaxV];              /* 顶点表 */
    int Edge[MaxV][MaxV];       /* 邻接矩阵 */
    int vexnum, arcnum;         /* 顶点数, 边数 */
} MGraph;

int visited[MaxV]; /* 访问标记数组 */

/* 初始化图 */
void InitGraph(MGraph *G, int n) {
    int i, j;
    G->vexnum = n;
    G->arcnum = 0;
    for (i = 0; i < n; i++) {
        G->vex[i] = i;
        for (j = 0; j < n; j++)
            G->Edge[i][j] = (i == j) ? 0 : INF;
    }
}

/* 添加无向边 (权值默认为 1) */
void AddEdge(MGraph *G, int u, int v, int w) {
    G->Edge[u][v] = w;
    G->Edge[v][u] = w;
    G->arcnum++;
}


/* ========== DFS 深度优先遍历 ========== */
/*
 * 递归原理:
 *   1. 访问当前顶点, 标记
 *   2. 遍历邻接矩阵找未访问的邻居, 递归进去
 */
void DFS(MGraph *G, int v) {
    printf("%d ", v);
    visited[v] = 1;

    int w;
    for (w = 0; w < G->vexnum; w++) {
        if (G->Edge[v][w] != 0 && G->Edge[v][w] != INF && !visited[w])
            DFS(G, w);
    }
}

void DFSTraverse(MGraph *G) {
    int i;
    for (i = 0; i < G->vexnum; i++) visited[i] = 0;
    for (i = 0; i < G->vexnum; i++)
        if (!visited[i]) DFS(G, i);
}


/* ========== BFS 广度优先遍历 ========== */
/*
 * 队列实现:
 *   1. 源点入队, 标记
 *   2. 出队, 遍历邻居, 未访问的入队并标记
 */
void BFS(MGraph *G, int v) {
    int queue[MaxV], front = 0, rear = 0;

    printf("%d ", v);
    visited[v] = 1;
    queue[rear++] = v;

    while (front != rear) {
        int u = queue[front++];
        int w;
        for (w = 0; w < G->vexnum; w++) {
            if (G->Edge[u][w] != 0 && G->Edge[u][w] != INF && !visited[w]) {
                printf("%d ", w);
                visited[w] = 1;
                queue[rear++] = w;
            }
        }
    }
}

void BFSTraverse(MGraph *G) {
    int i;
    for (i = 0; i < G->vexnum; i++) visited[i] = 0;
    for (i = 0; i < G->vexnum; i++)
        if (!visited[i]) BFS(G, i);
}


/* ========== Dijkstra 最短路径 ========== */
/*
 * 从源点 src 到其余各点的最短路径
 * dist[i]: 源点到 i 的当前最短距离
 * path[i]: i 的前驱顶点 (用于输出路径)
 * final[i]: 顶点 i 是否已经确定最短路
 */
void Dijkstra(MGraph *G, int src) {
    int dist[MaxV], path[MaxV], final[MaxV];
    int i, j;

    /* 初始化 */
    for (i = 0; i < G->vexnum; i++) {
        dist[i] = G->Edge[src][i];
        final[i] = 0;
        path[i] = (dist[i] < INF) ? src : -1;
    }
    dist[src] = 0;
    final[src] = 1;

    /* 循环 n-1 次, 每次确定一个最短路顶点 */
    for (i = 0; i < G->vexnum - 1; i++) {
        int min = INF, u = -1;

        /* 找未确定的最短距离顶点 */
        for (j = 0; j < G->vexnum; j++) {
            if (!final[j] && dist[j] < min) {
                min = dist[j];
                u = j;
            }
        }
        if (u == -1) break; /* 不连通 */
        final[u] = 1;

        /* 用 u 更新其邻居 (松弛操作) */
        for (j = 0; j < G->vexnum; j++) {
            if (!final[j] && G->Edge[u][j] < INF &&
                dist[u] + G->Edge[u][j] < dist[j]) {
                dist[j] = dist[u] + G->Edge[u][j];
                path[j] = u;
            }
        }
    }

    /* 输出结果 */
    printf("Dijkstra (源点 %d):\n", src);
    for (i = 0; i < G->vexnum; i++) {
        if (dist[i] >= INF)
            printf("  到 %d: 不可达\n", i);
        else
            printf("  到 %d: 最短距离 = %d, 前驱 = %d\n", i, dist[i], path[i]);
    }
}


/* ========== 拓扑排序 (邻接矩阵简化版) ========== */
/*
 * 算法:
 *   1. 先统计每个顶点的入度
 *   2. 入度为 0 的入栈(或队)
 *   3. 弹出一个, 输出, 将其所有邻居的入度 -1
 *      如果新的入度为 0 则入栈
 *   4. 若输出顶点数 < 总数, 则有环
 */
int TopologicalSort(MGraph *G) {
    int indegree[MaxV] = {0};
    int stack[MaxV], top = -1;
    int i, j, count = 0;

    /* 统计入度 (邻接矩阵: Edge[i][j]!=0 且 !=INF 说明有边 i->j) */
    for (j = 0; j < G->vexnum; j++)
        for (i = 0; i < G->vexnum; i++)
            if (G->Edge[i][j] != 0 && G->Edge[i][j] != INF)
                indegree[j]++;

    /* 入度为 0 的入栈 */
    for (i = 0; i < G->vexnum; i++)
        if (indegree[i] == 0) stack[++top] = i;

    printf("拓扑序列: ");
    while (top != -1) {
        int v = stack[top--]; /* 出栈 */
        printf("%d ", v);
        count++;

        for (j = 0; j < G->vexnum; j++) {
            if (G->Edge[v][j] != 0 && G->Edge[v][j] != INF) {
                indegree[j]--;
                if (indegree[j] == 0)
                    stack[++top] = j;
            }
        }
    }
    printf("\n");

    if (count < G->vexnum) {
        printf("存在环, 拓扑排序失败!\n");
        return 0;
    }
    return 1;
}


/* ========== main 演示 ========== */
int main() {
    printf("===== 408 图算法 可运行演示 =====\n\n");

    /* 构建无向图:
     *   0 --- 1 --- 3
     *   |     |
     *   2 --- 4
     */
    MGraph G;
    InitGraph(&G, 5);
    AddEdge(&G, 0, 1, 1);
    AddEdge(&G, 0, 2, 1);
    AddEdge(&G, 1, 3, 1);
    AddEdge(&G, 1, 4, 1);
    AddEdge(&G, 2, 4, 1);

    printf("--- DFS ---\n");
    DFSTraverse(&G);
    printf("\n\n");

    printf("--- BFS ---\n");
    BFSTraverse(&G);
    printf("\n\n");

    /* 构建带权有向图测试 Dijkstra:
     *   0 --(1)--> 1 --(3)--> 3
     *   |          |
     *  (6)        (2)
     *   v          v
     *   2 --(1)--> 4
     */
    printf("--- Dijkstra ---\n");
    MGraph G2;
    InitGraph(&G2, 5);
    G2.Edge[0][1] = 1; G2.Edge[0][2] = 6;
    G2.Edge[1][3] = 3; G2.Edge[1][4] = 2;
    G2.Edge[2][4] = 1;
    Dijkstra(&G2, 0);
    printf("\n");

    /* 拓扑排序 (有向无环图):
     *   0 --> 1 --> 3
     *   |     |
     *   v     v
     *   2 --> 4
     */
    printf("--- 拓扑排序 ---\n");
    TopologicalSort(&G2);

    return 0;
}

Released under the MIT License.