伪代码版本 · C
按考场默写的书写习惯组织:省略 I/O 与错误处理细节,聚焦算法主干,变量命名与教材保持一致,便于直接誊写到答题卡。
文件一览
| 序号 | 内容 | 文件 |
|---|---|---|
| 01 | 线性表 | 01_线性表.c |
| 02 | 栈和队列 | 02_栈和队列.c |
| 03 | 树与层序遍历 | 03_树与层序遍历.c |
| 04 | 排序算法 | 04_排序算法.c |
| 05 | 串与查找 | 05_串与查找.c |
| 06 | 图的核心算法 | 06_图的核心算法.c |
01 线性表
c
#include <stdio.h>
#include <stdlib.h>
/* =========================================
* 408 统考数据结构核心代码 - 线性表 (纯 C 语言风格)
* 考试默写要求:极高。必须随手能写出链表的防断链操作。
* ========================================= */
#define MaxSize 50
typedef int ElemType;
// 1. 顺序表结构体定义
typedef struct {
ElemType data[MaxSize];
int length;
} SqList;
// 2. 单链表结构体定义
typedef struct LNode {
ElemType data;
struct LNode *next;
} LNode, *LinkList;
/* -----------------------------------------
* 【1】顺序表核心操作
* ----------------------------------------- */
// 初始化顺序表
void InitList(SqList *L) {
L->length = 0;
}
// 顺序表插入
// 注意:数组下标从 0 开始,第 i 个位置的下标为 i-1
int ListInsert(SqList *L, int i, ElemType e) {
if (i < 1 || i > L->length + 1) return 0; // 判断 i 的范围是否有效
if (L->length >= MaxSize) return 0; // 存满了不能插
for (int j = L->length; j >= i; j--) {
L->data[j] = L->data[j - 1]; // 将第 i 个元素及之后的元素后移
}
L->data[i - 1] = e; // 在位置 i 放上 e
L->length++;
return 1;
}
// 顺序表删除
int ListDelete(SqList *L, int i, ElemType *e) {
if (i < 1 || i > L->length) return 0; // 判断 i 的范围是否有效
*e = L->data[i - 1]; // 【考点】取出被删除的元素
for (int j = i; j < L->length; j++) {
L->data[j - 1] = L->data[j]; // 将第 i 个位置之后的元素前移
}
L->length--;
return 1;
}
/* -----------------------------------------
* 【2】单链表核心操作
* ----------------------------------------- */
// 头插法建立单链表(常用于链表的【原地逆置】)
LinkList List_HeadInsert(LinkList L) {
LNode *s;
int x;
L = (LinkList)malloc(sizeof(LNode)); // 创建头结点
L->next = NULL;
scanf("%d", &x);
while (x != 9999) { // 9999 为结束标志
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
// 核心两步,千万不能反!
s->next = L->next;
L->next = s;
scanf("%d", &x);
}
return L;
}
// 尾插法建立单链表(注意:必须增加一个尾指针 r)
LinkList List_TailInsert(LinkList L) {
int x;
L = (LinkList)malloc(sizeof(LNode));
LNode *s, *r = L; // r 为表尾指针
scanf("%d", &x);
while (x != 9999) {
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
r->next = s; // r 连上新结点
r = s; // r 指向新的表尾
scanf("%d", &x);
}
r->next = NULL; // 【易错点】尾结点指针置空
return L;
}
// 删除指定结点 p 的后继结点
// 考研大题常见子操作,注意判空保护
int DeleteNextNode(LNode *p) {
if (p == NULL || p->next == NULL) return 0;
LNode *q = p->next; // 记录要删除的结点
p->next = q->next; // 断开联系
free(q); // 释放空间
return 1;
}
// 找到链表的中间结点(经常利用双指针/快慢指针法)
LNode* FindMidNode(LinkList L) {
LNode *p = L->next, *q = L->next;
while (q != NULL && q->next != NULL) {
p = p->next; // 慢指针走一步
q = q->next->next; // 快指针走两步
}
return p; // 当快指针到底时,慢指针正好在中间
}02 栈和队列
c
#include <stdio.h>
#include <stdlib.h>
/* =========================================
* 408 统考数据结构核心代码 - 栈与队列 (纯 C 语言风格)
* 考试默写要求:高。循环队列的牺牲单元法判断满空必考。
* ========================================= */
#define MaxSize 50
typedef int ElemType;
// 1. 顺序栈定义
typedef struct {
ElemType data[MaxSize];
int top; // 栈顶指针,初始为 -1
} SqStack;
// 2. 循环队列定义
typedef struct {
ElemType data[MaxSize];
int front, rear; // front 队头,rear 队尾
} SqQueue;
/* -----------------------------------------
* 【1】顺序栈核心操作
* ----------------------------------------- */
// 初始化
void InitStack(SqStack *S) {
S->top = -1; // 初始化为 -1
}
// 判空
int StackEmpty(SqStack *S) {
if (S->top == -1) return 1;
else return 0;
}
// 进栈法
int Push(SqStack *S, ElemType x) {
if (S->top == MaxSize - 1) return 0; // 栈满报错
S->top = S->top + 1; // 先加指针
S->data[S->top] = x; // 再入元素
return 1;
}
// 出栈法
int Pop(SqStack *S, ElemType *x) {
if (S->top == -1) return 0; // 栈空报错
*x = S->data[S->top]; // 先取元素
S->top = S->top - 1; // 再减指针
return 1;
}
/* -----------------------------------------
* 【2】循环队列核心操作 (牺牲一个单元法)
* ----------------------------------------- */
// 初始化队列
void InitQueue(SqQueue *Q) {
Q->front = Q->rear = 0;
}
// 判空 (首尾相遇)
int QueueEmpty(SqQueue *Q) {
if (Q->front == Q->rear) return 1;
else return 0;
}
// 入队
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;
}03 树与层序遍历
c
#include <stdio.h>
#include <stdlib.h>
/* =========================================
* 408 统考数据结构核心代码 - 树与遍历 (纯 C 语言风格)
* 考试默写要求:超极高!前中后序的非递归大题神级利器!
* ========================================= */
typedef int ElemType;
// 二叉树链式存储定义
typedef struct BiTNode {
ElemType data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
/* -----------------------------------------
* 【1】必背神迹:各类递归遍历
* ----------------------------------------- */
void visit(BiTNode *p) {
printf("%d ", p->data);
}
// 先序遍历 (NLR)
void PreOrder(BiTree T) {
if (T != NULL) {
visit(T); // 访问根结点
PreOrder(T->lchild); // 遍历左子树
PreOrder(T->rchild); // 遍历右子树
}
}
// 中序遍历 (LNR)
void InOrder(BiTree T) {
if (T != NULL) {
InOrder(T->lchild);
visit(T);
InOrder(T->rchild);
}
}
// 后序遍历 (LRN)
void PostOrder(BiTree T) {
if (T != NULL) {
PostOrder(T->lchild);
PostOrder(T->rchild);
visit(T);
}
}
/* -----------------------------------------
* 【2】找双亲、找路径必备核心:DFS 加辅助栈的非递归 (中序演示)
* 该方法属于 408 最常见的大题压轴解题利器!务必熟练。
* ----------------------------------------- */
// 简化栈定义供演示
typedef struct {
BiTNode* data[100];
int top;
} BiStack;
// ... (此处省略 InitStack, Push, Pop 等基础实现)
int IsEmpty(BiStack *S) { return S->top == -1; }
void Push(BiStack *S, BiTNode* p) { S->data[++S->top] = p; }
void Pop(BiStack *S, BiTNode** p) { *p = S->data[S->top--]; }
// 非递归中序遍历
void InOrderNonRecursive(BiTree T) {
BiStack S;
S.top = -1;
BiTree p = T;
while (p != NULL || !IsEmpty(&S)) {
if (p != NULL) {
// 一头扎到最左边
Push(&S, p);
p = p->lchild;
} else {
// 左边走到头了,出栈访问,然后切入右边
Pop(&S, &p);
visit(p);
p = p->rchild;
}
}
}
/* -----------------------------------------
* 【3】层序遍历 (BFS) - 也是图的 BFS 与最短路的核心算法变种
* ----------------------------------------- */
typedef struct {
BiTNode* data[100];
int front, rear;
} BiQueue;
// ... 假装实现了队列基本操作
void EnQueue(BiQueue* Q, BiTNode* p) { Q->data[Q->rear++] = p; }
BiTNode* DeQueue(BiQueue* Q) { return Q->data[Q->front++]; }
int QueueEmpty(BiQueue* Q) { return Q->front == Q->rear; }
void LevelOrder(BiTree T) {
BiQueue Q;
Q.front = Q.rear = 0;
BiTree p;
if(T != NULL) {
EnQueue(&Q, T); // 根节点入队
}
while(!QueueEmpty(&Q)) {
p = DeQueue(&Q);
visit(p);
if (p->lchild != NULL) {
EnQueue(&Q, p->lchild);
}
if (p->rchild != NULL) {
EnQueue(&Q, p->rchild);
}
}
}
/* -----------------------------------------
* 【4】二叉树常见常考高级大题操作
* ----------------------------------------- */
// 1. 求二叉树深度 (经典递归)
int TreeDepth(BiTree T) {
if (T == NULL) return 0;
int ldepth = TreeDepth(T->lchild);
int rdepth = TreeDepth(T->rchild);
// 树的高度 = 左右子树最大的加自己本身(1)
return (ldepth > rdepth ? ldepth : rdepth) + 1;
}
// 2. 判别是否是平衡二叉树 (AVL 树)
// 利用后序遍历一边求深度一边判别,大幅度降级时间复杂度
int isBalanced(BiTree T, int *depth) {
if (T == NULL) {
*depth = 0;
return 1; // 空树自然是平衡的
}
int ldepth, rdepth;
// 分别检查左右孩子是否平衡
if (isBalanced(T->lchild, &ldepth) && isBalanced(T->rchild, &rdepth)) {
int diff = ldepth - rdepth;
// 左右子树高低差不能超过 1
if (diff >= -1 && diff <= 1) {
*depth = (ldepth > rdepth ? ldepth : rdepth) + 1; // 更新给上一层
return 1;
}
}
return 0; // 不平衡
}
/* -----------------------------------------
* 【5】并查集 (Union-Find) 极高频考点
* 一般用双亲表示法的结构数组来实现
* ----------------------------------------- */
#define SIZE 100
int UFSets[SIZE]; // 并查集实质是个双亲指针数组
// 初始化并查集
void Initial(int S[]) {
for (int i = 0; i < SIZE; i++) {
S[i] = -1; // 初始时每个人都是自己的祖先,值为 -1
}
}
// 并查集核心操作 1:Find 找祖宗
// (常考优化版本:路径压缩。直接顺带把沿途结点的爹全改成最大的祖宗)
int Find(int S[], int x) {
int root = x;
while (S[root] >= 0) { // 如果大于等于 0,说明有爹
root = S[root];
}
// 路径压缩 (408重点:写出这步能极大的防丢分保满分)
int curr = x, ptr;
while (curr != root) {
ptr = S[curr]; // 保存原来爹
S[curr] = root; // 把直接爹改成祖宗
curr = ptr;
}
return root; // 返回祖宗下标
}
// 并查集核心操作 2:Union 合并两个集合
// (传入的是祖宗节点,要求:小树合并入大树)
void Union(int S[], int Root1, int Root2) {
if (Root1 == Root2) return;
// 我们约定 S[] 存的负数的绝对值为该集合拥有的结点总数
if (S[Root2] < S[Root1]) { // Root2 人数更多 (负的更多)
S[Root2] += S[Root1]; // 把人家吞并了人数加过来
S[Root1] = Root2; // Root1 的爹变成 Root2
} else {
S[Root1] += S[Root2];
S[Root2] = Root1;
}
}04 排序算法
c
#include <stdio.h>
#include <stdlib.h>
/* =========================================
* 408 统考数据结构核心代码 - 排序算法 (纯 C 语言风格)
* 考试默写要求:快速排序的 Partition 函数每年极小选择和算法涉及极多!
* ========================================= */
// 函数:交换两者
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
/* -----------------------------------------
* 【1】快速排序及其核心 Partition 划分
* ----------------------------------------- */
// 快速排序的划分思想(408常考:求第K大,奇偶分离等变形均由此出)
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 pivotpos = Partition(A, low, high); // 划分
QuickSort(A, low, pivotpos - 1); // 治理左边
QuickSort(A, pivotpos + 1, high); // 治理右边
}
}
/* -----------------------------------------
* 【2】起泡排序 (冒泡) / 稳定的基础大哥
* ----------------------------------------- */
void BubbleSort(int A[], int n) {
int flag; // 标志位用于判断某趟有没有发生交换
for (int i = 0; i < n - 1; i++) {
flag = 0; // 假设没交换
for (int j = n - 1; j > i; j--) { // 从后往前冒泡
if (A[j - 1] > A[j]) {
swap(&A[j - 1], &A[j]);
flag = 1;
}
}
if (flag == 0) return; // 本趟全都没交换,说明已经彻底有序了!打卡下班。
}
}
/* -----------------------------------------
* 【3】折半插入排序
* ----------------------------------------- */
void InsertSortBinary(int A[], int n) {
int i, j, low, high, mid;
// 数组下标从1到n有效的话(A[0]作为哨兵或者辅助位保存数据)
for (i = 2; i <= n; i++) {
A[0] = A[i]; // 将 A[i] 暂存
low = 1;
high = i - 1;
// 折半查找应该插入的位置
while (low <= high) {
mid = (low + high) / 2;
if (A[mid] > A[0]) {
high = mid - 1;
} else {
low = mid + 1;
}
}
// 统一后移,空出位置
for (j = i - 1; j >= high + 1; j--) {
A[j + 1] = A[j];
}
A[high + 1] = A[0]; // 填入
}
}
/* -----------------------------------------
* 【4】归并排序 (Merge Sort)
* 【极度重要】需要单独开辟辅助数组 B,这是耗费空间 O(n) 的来源
* ----------------------------------------- */
int B[100]; // 辅助数组
// 归并两个有序小段的核心机制
void Merge(int A[], int low, int mid, int high) {
int i, j, k;
for (k = low; k <= high; ++k) {
B[k] = A[k]; // 临时复制到背板去
}
// i走前段表,j走后段表,看两边较小的值依次填入 A 中归并
for (i = low, j = mid + 1, k = i; i <= mid && j <= high; ++k) {
if (B[i] <= B[j]) {
A[k] = B[i++];
} else {
A[k] = B[j++];
}
}
// 如果某一段多出来了,将剩下的所有统统灌入末尾结账
while (i <= mid) A[k++] = B[i++];
while (j <= high) A[k++] = B[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); // 天下合并
}
}
/* -----------------------------------------
* 【5】408 快排核心变式应用集锦
* 这往往出现在 11 到 15 分的大题中,让你 O(n) 完成特殊分离!
* ----------------------------------------- */
// 王道经典考题:把所有的奇数移向数组前端,全部偶数移向末尾,要求时间 O(N) 一趟!
// 【破局原理】直接调用 Partition 思想。
void MoveOddEven(int A[], int len) {
int low = 0, high = len - 1;
while(low < high) {
// 后方找到第一个奇数
while(low < high && A[high] % 2 == 0) high--;
// 前方找到第一个偶数
while(low < high && A[low] % 2 != 0) low++;
if (low < high) {
swap(&A[low], &A[high]); // 被抓到两人不符合规矩,交换换身!
}
}
}05 串与查找
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* =========================================
* 408 统考数据结构核心代码 - 串与查找 (C 版本)
* KMP 算法的 next 数组推导属于选择与大题的高频常青树!
* ========================================= */
/* -----------------------------------------
* 【1】字符串匹配:KMP 算法核心
* 特别注意:王道考研中,字符串通常从下标 1 开始存储,
* S.ch[0] 用来存放真实的长度。
* ----------------------------------------- */
typedef struct {
char ch[100]; // 静态分配
int length;
} SString;
// 1. 推导 next 数组的灵魂逻辑 (必背)
void get_next(SString T, int next[]) {
int i = 1, j = 0;
next[1] = 0;
// 如果还没推到头
while (i < T.length) {
if (j == 0 || T.ch[i] == T.ch[j]) {
++i; ++j;
next[i] = j; // 匹配上了就将匹配长度同步递增赋予给下一位
} else {
j = next[j]; // 没匹配上,j 回退到次级的容错庇护所
}
}
}
// 2. KMP 主干匹配计算
int Index_KMP(SString S, SString T, int next[]) {
int i = 1, j = 1; // 设从 1 开始
while (i <= S.length && j <= T.length) {
if(j == 0 || S.ch[i] == T.ch[j]) {
++i; ++j; // 匹配就接着往下走
} else {
j = next[j]; // 失配借用 next 发生神级跳跃回溯!
}
}
if (j > T.length) {
return i - T.length; // 找到了
} else {
return 0; // 没找到
}
}
/* -----------------------------------------
* 【2】折半查找 (Binary Search)
* 要求必须建立在有序并且拥有随机访问能力(数组)的基础上!
* ----------------------------------------- */
// 非递归折半查找 (最常用)
int Binary_Search(int A[], int len, int key) {
int low = 0;
int high = len - 1;
int 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; // 查找失败
}
// 递归版本的折半查找 (大题中有可能会用于分治变形)
int Binary_Search_Rec(int A[], int low, int high, int key) {
if (low > high) return -1;
int mid = (low + high) / 2;
if (A[mid] == key) return mid;
if (A[mid] > key) {
return Binary_Search_Rec(A, low, mid - 1, key);
} else {
return Binary_Search_Rec(A, mid + 1, high, key);
}
}06 图的核心算法
c
#include <stdio.h>
#include <stdlib.h>
/* =========================================
* 408 统考数据结构核心代码 - 图的核心算法 (C 版本)
* 考研热点:基于邻接表的极简 BFS/DFS 搜索!
* ========================================= */
#define MaxVertexNum 100
// 边/弧结点
typedef struct ArcNode {
int adjvex; // 指向那个顶点
struct ArcNode *nextarc; // 指向下一条弧
// int info; // 如果有网,这里可以带权值
} ArcNode;
// 顶点结构体
typedef struct VNode {
int data; // 顶点信息
ArcNode *firstarc; // 指向第一个依附该顶点的弧
} VNode, AdjList[MaxVertexNum];
//图的邻接表体系
typedef struct {
AdjList vertices; // 存放全部顶点的表头
int vexnum, arcnum; // 目前的顶点数和弧数
} ALGraph;
/* -----------------------------------------
* 【1】BFS 与 DFS 的基本框架 (常考大题防丢分模板)
* ----------------------------------------- */
int visited[MaxVertexNum];
// 访问函数
void visit(int v) {
printf("访问到了节点 %d\n", v);
}
// ------ 深度优先 DFS ------
void DFS(ALGraph *G, int v) {
visit(v); // 1. 访问它
visited[v] = 1; // 2. 打点标记
ArcNode *p = G->vertices[v].firstarc; // 拿邻居
while (p != NULL) {
int w = p->adjvex;
if (!visited[w]) { // 如果该邻居没去过
DFS(G, w); // 递归往里扎
}
p = p->nextarc; // 退出来后找下一个兄弟遍历
}
}
// 处理非连通图的全盘唤醒调度器
void DFSTraverse(ALGraph *G) {
for (int i = 0; i < G->vexnum; i++) visited[i] = 0; // 清场
for (int i = 0; i < G->vexnum; i++) {
if (!visited[i]) {
DFS(G, i);
}
}
}
// ------ 广度优先 BFS (借助外部队列) ------
// 伪代码展示队列行为,由于是 C 所以手撸一根简易队伍
int queue[100];
int front = 0, rear = 0;
void EnQueue(int x) { queue[rear++] = x; }
int DeQueue() { return queue[front++]; }
int QueueEmpty() { return front == rear; }
void BFS(ALGraph *G, int v) {
visit(v);
visited[v] = 1;
EnQueue(v); // 入队
while (!QueueEmpty()) {
int target = DeQueue();
ArcNode *p = G->vertices[target].firstarc;
while (p != NULL) {
int w = p->adjvex;
if (!visited[w]) { // 如果没走过
visit(w); // 访问
visited[w] = 1;// 标记
EnQueue(w); // 扔进队伍做后续扩展
}
p = p->nextarc;
}
}
}
void BFSTraverse(ALGraph *G) {
for (int i = 0; i < G->vexnum; i++) visited[i] = 0;
front = rear = 0; // 重置队伍
for (int i = 0; i < G->vexnum; i++) {
if (!visited[i]) BFS(G, i);
}
}
/* -----------------------------------------
* 【2】Dijkstra 最短路径 (伪代码框架)
* 考研大题常见变式:在邻接矩阵图上寻求源点到各点最低票价
* ----------------------------------------- */
#define INF 999999
int dist[MaxVertexNum];
int path[MaxVertexNum];
int final[MaxVertexNum];
// G.Edge 即为邻接矩阵的距阵二阶表
void Dijkstra(int curr_vex, int vexnum, int Edge[][MaxVertexNum]) {
// 1. 初始化
for (int i = 0; i < vexnum; i++) {
dist[i] = Edge[curr_vex][i];
final[i] = 0; // 初始全在 P-S 集合
if (dist[i] < INF) {
path[i] = curr_vex;
} else {
path[i] = -1;
}
}
final[curr_vex] = 1;
dist[curr_vex] = 0;
// 2. 将剩下的所有点归位
for (int i = 0; i < vexnum - 1; i++) {
int min = INF;
int min_id = -1;
// (1)找 S 外缘跟 S 结合最紧密的那个小弟
for (int j = 0; j < vexnum; j++) {
if (final[j] == 0 && dist[j] < min) {
min = dist[j];
min_id = j;
}
}
if(min_id == -1) break; // 已经找无可找了
final[min_id] = 1; // 开光加入神坛集合 S
// (2)借助由于此小弟加入导致的新路,将原来其他非神坛的人距离拉踩刷新
for (int j = 0; j < vexnum; j++) {
if (final[j] == 0 && dist[min_id] + Edge[min_id][j] < dist[j]) {
dist[j] = dist[min_id] + Edge[min_id][j];
path[j] = min_id; // 修改他通往神坛的接入点
}
}
}
}