下面我将为您提供一个全面的指南,包括:

(图片来源网络,侵删)
- 核心思想:这本书的核心是什么?
- 核心数据结构及其 C 语言实现:附上关键代码片段。
- 核心算法分析:如何分析算法的效率。
- 完整示例代码:一个综合性的 C 语言项目,将多个数据结构结合起来。
- 学习建议:如何更好地学习和实践。
核心思想
这本书的核心思想可以概括为两点:
- 数据结构:组织和存储数据的方式,以便能够高效地访问和修改,数组、链表、栈、队列、树、图等。
- 算法分析:评估算法效率的方法,主要关注其时间复杂度和空间复杂度,这能让我们在解决同一个问题时,选择最优的算法,而不是简单地实现一个“能用”的版本。
目标:教会你如何根据问题的特性,选择最合适的数据结构和算法,并能够用 C 语言高效地实现它们。
核心数据结构及其 C 语言实现
以下是书中介绍的主要数据结构的 C 语言实现,为了清晰,我会省略一些错误处理和细节,只展示核心逻辑。
a. 单链表
链表由一系列节点组成,每个节点包含数据和指向下一个节点的指针。

(图片来源网络,侵删)
// list.h
#ifndef LIST_H
#define LIST_H
typedef struct Node {
int data;
struct Node* next;
} Node;
// 创建新节点
Node* createNode(int data);
// 在链表头部插入
void insertAtHead(Node** head, int data);
// 在链表尾部插入
void insertAtTail(Node** head, int data);
// 删除值为 data 的第一个节点
void deleteNode(Node** head, int data);
// 打印链表
void printList(Node* head);
// 释放链表内存
void freeList(Node* head);
#endif // LIST_H
// list.c
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
void insertAtHead(Node** head, int data) {
Node* newNode = createNode(data);
newNode->next = *head;
*head = newNode;
}
void insertAtTail(Node** head, int data) {
Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
Node* current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = newNode;
}
void deleteNode(Node** head, int data) {
if (*head == NULL) return;
Node* temp = *head;
if (temp->data == data) {
*head = temp->next;
free(temp);
return;
}
Node* prev = NULL;
while (temp != NULL && temp->data != data) {
prev = temp;
temp = temp->next;
}
if (temp == NULL) return; // Data not found
prev->next = temp->next;
free(temp);
}
void printList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
void freeList(Node* head) {
Node* current = head;
while (current != NULL) {
Node* next = current->next;
free(current);
current = next;
}
}
b. 栈
栈是“后进先出”(LIFO)的数据结构,可以用数组或链表实现,这里用数组实现。
// stack.h
#ifndef STACK_H
#define STACK_H
#define MAX_SIZE 100
typedef struct {
int items[MAX_SIZE];
int top;
} Stack;
// 初始化栈
void initStack(Stack* s);
// 检查栈是否为空
int isEmpty(Stack* s);
// 检查栈是否已满
int isFull(Stack* s);
// 入栈
void push(Stack* s, int value);
// 出栈
int pop(Stack* s);
// 查看栈顶元素
int peek(Stack* s);
#endif // STACK_H
// stack.c
#include <stdio.h>
#include <stdbool.h>
#include "stack.h"
void initStack(Stack* s) {
s->top = -1;
}
bool isEmpty(Stack* s) {
return s->top == -1;
}
bool isFull(Stack* s) {
return s->top == MAX_SIZE - 1;
}
void push(Stack* s, int value) {
if (isFull(s)) {
printf("Stack is full\n");
return;
}
s->items[++s->top] = value;
}
int pop(Stack* s) {
if (isEmpty(s)) {
printf("Stack is empty\n");
return -1; // 返回一个错误值
}
return s->items[s->top--];
}
int peek(Stack* s) {
if (isEmpty(s)) {
printf("Stack is empty\n");
return -1;
}
return s->items[s->top];
}
c. 队列
队列是“先进先出”(FIFO)的数据结构,用循环数组实现可以避免假溢出。
// queue.h
#ifndef QUEUE_H
#define QUEUE_H
#define MAX_SIZE 100
typedef struct {
int items[MAX_SIZE];
int front;
int rear;
int size; // 为了方便,也可以不使用 size,而是用 front 和 rear 的关系判断
} Queue;
void initQueue(Queue* q);
int isEmpty(Queue* q);
int isFull(Queue* q);
void enqueue(Queue* q, int value);
int dequeue(Queue* q);
int peekQueue(Queue* q);
#endif // QUEUE_H
// queue.c
#include <stdio.h>
#include <stdbool.h>
#include "queue.h"
void initQueue(Queue* q) {
q->front = 0;
q->rear = -1;
q->size = 0;
}
bool isEmpty(Queue* q) {
return q->size == 0;
}
bool isFull(Queue* q) {
return q->size == MAX_SIZE;
}
void enqueue(Queue* q, int value) {
if (isFull(q)) {
printf("Queue is full\n");
return;
}
q->rear = (q->rear + 1) % MAX_SIZE;
q->items[q->rear] = value;
q->size++;
}
int dequeue(Queue* q) {
if (isEmpty(q)) {
printf("Queue is empty\n");
return -1;
}
int value = q->items[q->front];
q->front = (q->front + 1) % MAX_SIZE;
q->size--;
return value;
}
int peekQueue(Queue* q) {
if (isEmpty(q)) {
printf("Queue is empty\n");
return -1;
}
return q->items[q->front];
}
d. 二叉搜索树
一种重要的树形结构,左子树所有节点值小于根节点,右子树所有节点值大于根节点。
// bst.h
#ifndef BST_H
#define BST_H
typedef struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
// 插入节点
TreeNode* insert(TreeNode* node, int data);
// 查找最小值节点
TreeNode* findMin(TreeNode* node);
// 删除节点
TreeNode* deleteNode(TreeNode* root, int data);
// 中序遍历 (有序输出)
void inorderTraversal(TreeNode* root);
// 释放树内存
void freeTree(TreeNode* root);
#endif // BST_H
// bst.c
#include <stdio.h>
#include <stdlib.h>
#include "bst.h"
TreeNode* insert(TreeNode* node, int data) {
if (node == NULL) {
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->data = data;
newNode->left = newNode->right = NULL;
return newNode;
}
if (data < node->data) {
node->left = insert(node->left, data);
} else if (data > node->data) {
node->right = insert(node->right, data);
}
// 如果值相等,什么都不做(不允许重复)
return node;
}
TreeNode* findMin(TreeNode* node) {
TreeNode* current = node;
while (current && current->left != NULL) {
current = current->left;
}
return current;
}
TreeNode* deleteNode(TreeNode* root, int data) {
if (root == NULL) return root;
if (data < root->data) {
root->left = deleteNode(root->left, data);
} else if (data > root->data) {
root->right = deleteNode(root->right, data);
} else {
// 找到了要删除的节点
// 情况1: 没有子节点或只有一个子节点
if (root->left == NULL) {
TreeNode* temp = root->right;
free(root);
return temp;
} else if (root->right == NULL) {
TreeNode* temp = root->left;
free(root);
return temp;
}
// 情况2: 有两个子节点
// 获取右子树的最小节点
TreeNode* temp = findMin(root->right);
// 将该节点的值复制到当前节点
root->data = temp->data;
// 删除右子树中的那个最小节点
root->right = deleteNode(root->right, temp->data);
}
return root;
}
void inorderTraversal(TreeNode* root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}
void freeTree(TreeNode* root) {
if (root == NULL) return;
freeTree(root->left);
freeTree(root->right);
free(root);
}
核心算法分析
算法分析主要使用大O表示法来描述算法的增长率。

(图片来源网络,侵删)
- O(1) - 常数时间:操作时间不随数据量 n 的变化而变化,数组的随机访问、栈的
push/pop(不考虑扩容)。 - O(log n) - 对数时间:每次操作都将问题规模减半,在平衡的二叉搜索树中查找、插入、删除。
- O(n) - 线性时间:操作时间与数据量 n 成正比,遍历链表、在无序数组中查找。
- O(n log n) - 线性对数时间:高效的排序算法,如归并排序、快速排序的平均情况。
- O(n²) - 平方时间:嵌套循环,简单的排序算法(冒泡排序、选择排序)、在无序数组中查找所有重复元素。
如何分析?
- 基本操作:确定算法中的关键操作(如比较、赋值)。
- 计算执行次数:计算基本操作执行次数与输入规模
n的函数关系T(n)。 - 简化:忽略常数项和低阶项,只保留最高阶项,用大O表示。
示例:分析链表 insertAtTail 的时间复杂度。
- 操作:需要遍历整个链表。
- 执行次数:如果链表有
n个节点,最坏情况下需要n次移动指针。 - 时间复杂度为 O(n)。
完整示例代码:使用栈检查括号匹配
这是一个经典的算法问题,可以很好地展示栈的应用。
问题:给定一个字符串,检查其中的括号 , [], 是否匹配。
思路:
- 遇到左括号 ,
[, ,就将其压入栈。 - 遇到右括号 ,
], ,就检查栈顶元素。- 如果栈为空,或者栈顶元素不是对应的左括号,则不匹配。
- 如果匹配,就将栈顶元素弹出。
- 遍历完整个字符串后,如果栈为空,则所有括号都匹配;否则不匹配。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "stack.h" // 使用前面定义的栈
// 判断两个括号是否匹配
int isMatchingPair(char char1, char char2) {
if (char1 == '(' && char2 == ')') return 1;
if (char1 == '[' && char2 == ']') return 1;
if (char1 == '{' && char2 == '}') return 1;
return 0;
}
// 检查表达式中的括号是否平衡
int areBracketsBalanced(char* expr) {
Stack s;
initStack(&s);
for (int i = 0; i < strlen(expr); i++) {
// 如果是左括号,压入栈
if (expr[i] == '(' || expr[i] == '[' || expr[i] == '{') {
push(&s, expr[i]);
}
// 如果是右括号
else if (expr[i] == ')' || expr[i] == ']' || expr[i] == '}') {
// 栈为空,或者不匹配
if (isEmpty(&s) || !isMatchingPair(pop(&s), expr[i])) {
return 0; // 不匹配
}
}
}
// 最终栈为空才匹配
return isEmpty(&s);
}
int main() {
char expr1[] = "{[()]}";
char expr2[] = "([)]";
char expr3[] = "(a + b) * [c - {d + e}]";
if (areBracketsBalanced(expr1)) {
printf("表达式 \"%s\" 的括号是平衡的,\n", expr1);
} else {
printf("表达式 \"%s\" 的括号不平衡,\n", expr1);
}
if (areBracketsBalanced(expr2)) {
printf("表达式 \"%s\" 的括号是平衡的,\n", expr2);
} else {
printf("表达式 \"%s\" 的括号不平衡,\n", expr2);
}
if (areBracketsBalanced(expr3)) {
printf("表达式 \"%s\" 的括号是平衡的,\n", expr3);
} else {
printf("表达式 \"%s\" 的括号不平衡,\n", expr3);
}
return 0;
}
学习建议
- 先理解,再编码:在敲代码之前,务必用纸笔画出数据结构的结构(比如链表的连接关系),并手动模拟算法的执行过程(BST 的插入)。
- 分模块实现:像上面的示例一样,将每个数据结构或算法写成独立的
.c和.h文件,这有助于代码的组织和重用。 - 动手实践:光看代码是不够的,尝试自己实现书中的每一个数据结构,并编写测试用例来验证你的代码是否正确。
- 分析复杂度:对于你实现的每一个操作,都要能说出它的时间复杂度和空间复杂度,并解释为什么。
- 使用调试器:使用 GDB 等调试工具来单步执行你的代码,观察变量的变化,这对于理解指针和递归尤其重要。
- 阅读源码:学习 C 标准库中的一些实现,
stdlib.h中的链表(lsearch,lfind),string.h中的字符串处理函数,它们都是高效算法的典范。
希望这份详细的指南能帮助你更好地学习和掌握《数据结构与算法分析:C 语言描述》中的内容!
