基本概念
数据、数据元素、数据项、数据结构
逻辑结构:集合、线性、树形、图状
物理结构(存储结构):顺序存储、链式存储、索引存储、散列存储
抽象数据类型(ADT):定义、实现与操作
算法分析
时间复杂度(大O表示法)
常见阶:O(1)、O(log n)、O(n)、O(n log n)、O(n²)、O(2ⁿ)
斐波那契数列递归实现的时间复杂度为 O (2ⁿ),而迭代实现为 O (n)16。
空间复杂度
最坏/平均/最好情况分析
特点:连续内存、随机访问
操作:插入、删除、查找(时间复杂度分析)
应用:动态数组(Vector/ArrayList)
单向链表:节点结构、遍历、插入/删除(头/中/尾)
双向链表:结构优势、插入/删除
循环链表:约瑟夫问题应用
链表 vs 数组:内存灵活性 vs 访问效率
后进先出(LIFO)特性
操作:push
, pop
, peek
实现:数组栈 vs 链表栈
应用:函数调用栈、括号匹配、表达式求值(中缀转后缀)
先进先出(FIFO)特性
操作:enqueue
, dequeue
循环队列:解决假溢出问题
双端队列 (Deque):两端操作
优先队列(通常用堆实现,见树章节)
术语:根、节点、边、度、深度、高度、叶子节点
二叉树:定义、性质(第i层最多2ⁱ⁻¹节点)
满二叉树 & 完全二叉树
深度优先:
先序(根-左-右)
中序(左-根-右)→ BST有序输出
后序(左-右-根)
广度优先:层序遍历(队列实现)
定义:左子树 < 根 < 右子树
操作:查找(O(h))、插入、删除(三种情况)
缺点:退化成链表(需平衡树优化)
AVL树:
平衡因子(|左高-右高| ≤ 1)
旋转操作:LL/RR/LR/RL
红黑树(近似平衡):
五大规则(根黑、叶黑、红节点子必黑等)
应用:Java TreeMap, C++ map
大顶堆 & 小顶堆
操作:插入(上浮)、删除堆顶(下沉)
建堆:O(n) 时间复杂度证明
应用:优先队列、堆排序
B树:磁盘存储优化(多关键字、阶数)
B+树:非叶节点仅索引、叶子链表链接 → 数据库索引
Trie树(字典树):字符串前缀匹配
术语:顶点、边、有向图/无向图、权重、度、连通分量
表示法:
邻接矩阵:O(1) 查边,O(n²) 空间
邻接表:O(deg(v)) 查边,节省空间
广度优先搜索 (BFS):队列实现 → 最短路径(无权图)
深度优先搜索 (DFS):递归/栈实现 → 拓扑排序、连通性检测
最短路径:
Dijkstra(无负权单源最短)
Floyd(多源最短)
最小生成树:
Prim(贪心,优先队列)
Kruskal(并查集)
拓扑排序:有向无环图(DAG)任务调度
核心思想
关键字 → 哈希函数 → 存储位置
冲突解决:
开放定址法:线性探测、二次探测
链地址法(常用):冲突位置拉链表
设计要点
哈希函数设计(均匀性、效率)
装载因子(Load Factor)与扩容机制
应用:Python dict、Java HashMap
并查集 (Disjoint Set)
操作:find
(路径压缩)、union
(按秩合并)
应用:连通分量检测、Kruskal算法
跳跃表 (Skip List)
多层链表结构,O(log n) 查找
Redis有序集合实现
线段树 & 树状数组
区间查询/更新问题(如区间求和)
算法 | 时间复杂度(平均) | 时间复杂度(最坏) | 空间复杂度 | 适用场景 |
---|---|---|---|---|
顺序查找 | O(n) | O(n) | O(1) | 无序小数据集 |
二分查找 | O(log n) | O(log n) | O(1) | 有序数组 |
插值查找 | O(log log n) | O(n) | O(1) | 均匀分布的有序数组 |
二叉搜索树查找 | O(log n) | O(n) | O(n) | 动态数据集 |
哈希查找 | O(1) | O(n) | O(n) | 快速查找,键值对存储 |
稳定性定义: 查找算法的 “稳定性” 指 “当存在重复元素时,是否保证查找结果的顺序一致性”(如顺序查找必找第一个匹配项,哈希链地址法必找同桶内最先插入的匹配项,故稳定;二分查找可能定位到中间匹配项,故不稳定)。
哈希查找细节: 若采用开放定址法(如线性探测),空间复杂度为 \(O(1)\)(复用原数组),但平均时间复杂度会因 “聚集效应” 上升,且稳定性不稳定(可能跳过前面的重复元素),故表格以更典型的链地址法为例。
BST 与 AVL 对比: BST 最坏情况退化为链表(时间 \(O(n)\)),AVL 通过旋转保持平衡,确保时间始终 \(O(\log n)\),但插入 / 删除操作更复杂。
算法 | 平均时间复杂度 | 空间复杂度 | 稳定性 | 备注 |
---|---|---|---|---|
冒泡排序 | O(n²) | O(1) | 稳定 | 相邻交换 |
快速排序 | O(n log n) | O(log n) | 不稳定 | 分治 + 基准划分 |
归并排序 | O(n log n) | O(n) | 稳定 | 分治 + 合并有序数组 |
堆排序 | O(n log n) | O(1) | 不稳定 | 建堆 + 交换堆顶 |
插入排序 | O(n²) | O(1) | 稳定 | 适合小规模数据 |
#include
#include
struct ListNode {
int val;
struct ListNode *next;
};
// 链表翻转函数
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode *prev = NULL;
struct ListNode *curr = head;
while (curr != NULL) {
struct ListNode *nextTemp = curr->next; // 暂存下一节点
curr->next = prev; // 反转指针
prev = curr; // prev前移
curr = nextTemp; // curr后移
}
return prev; // 新链表头
}
// 测试代码
int main() {
// 创建链表 1->2->3->4->5
struct ListNode nodes[5];
for (int i = 0; i < 5; i++) {
nodes[i].val = i + 1;
nodes[i].next = (i < 4) ? &nodes[i+1] : NULL;
}
struct ListNode* newHead = reverseList(&nodes[0]);
// 打印翻转结果:5->4->3->2->1
struct ListNode* p = newHead;
while (p != NULL) {
printf("%d->", p->val);
p = p->next;
}
printf("NULL\n");
return 0;
}
关键点:
使用三指针法:prev, curr, nextTemp
时间复杂度:O(n),空间复杂度:O(1)
注意指针操作顺序,避免链表断裂
#include
#include
#define MAX_SIZE 100
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
// 队列结构
struct Queue {
struct TreeNode* data[MAX_SIZE];
int front;
int rear;
};
// 初始化队列
void initQueue(struct Queue *q) {
q->front = 0;
q->rear = 0;
}
// 入队
void enqueue(struct Queue *q, struct TreeNode* node) {
if ((q->rear + 1) % MAX_SIZE == q->front) return; // 队满
q->data[q->rear] = node;
q->rear = (q->rear + 1) % MAX_SIZE;
}
// 出队
struct TreeNode* dequeue(struct Queue *q) {
if (q->front == q->rear) return NULL; // 队空
struct TreeNode* node = q->data[q->front];
q->front = (q->front + 1) % MAX_SIZE;
return node;
}
// 层序遍历
void levelOrder(struct TreeNode* root) {
if (root == NULL) return;
struct Queue q;
initQueue(&q);
enqueue(&q, root);
while (q.front != q.rear) {
int levelSize = (q.rear - q.front + MAX_SIZE) % MAX_SIZE;
for (int i = 0; i < levelSize; i++) {
struct TreeNode* node = dequeue(&q);
printf("%d ", node->val);
if (node->left != NULL) enqueue(&q, node->left);
if (node->right != NULL) enqueue(&q, node->right);
}
printf("\n"); // 换行表示新的一层
}
}
// 测试代码
int main() {
/* 构建二叉树:
1
/ \
2 3
/ \ \
4 5 6
*/
struct TreeNode n1 = {1, NULL, NULL};
struct TreeNode n2 = {2, NULL, NULL};
struct TreeNode n3 = {3, NULL, NULL};
struct TreeNode n4 = {4, NULL, NULL};
struct TreeNode n5 = {5, NULL, NULL};
struct TreeNode n6 = {6, NULL, NULL};
n1.left = &n2; n1.right = &n3;
n2.left = &n4; n2.right = &n5;
n3.right = &n6;
printf("层序遍历结果:\n");
levelOrder(&n1);
return 0;
}
输出:
层序遍历结果: 1 2 3 4 5 6
#include
// 交换两个元素
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 分区函数
int partition(int arr[], int low, int high) {
int pivot = arr[high]; // 选择最后一个元素为基准
int i = (low - 1); // 小于基准的边界索引
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
// 快速排序主函数
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
// 测试代码
int main() {
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
quickSort(arr, 0, n - 1);
printf("\n排序后: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
return 0;
}
关键点:
分区函数是核心,时间复杂度平均O(n log n)
最坏情况(已排序数组)时间复杂度O(n²)
优化方法:随机选择基准点 int pivot = arr[low + rand() % (high - low + 1)];
#include
#include
#define V 5
// 邻接矩阵表示图
int graph[V][V] = {
{0, 1, 1, 0, 0},
{1, 0, 1, 1, 0},
{1, 1, 0, 0, 1},
{0, 1, 0, 0, 1},
{0, 0, 1, 1, 0}
};
// DFS递归函数
void DFS(int vertex, int visited[]) {
visited[vertex] = 1;
printf("%d ", vertex);
for (int i = 0; i < V; i++) {
if (graph[vertex][i] && !visited[i]) {
DFS(i, visited);
}
}
}
// DFS主函数
void DFSTraversal() {
int visited[V];
for (int i = 0; i < V; i++) visited[i] = 0;
printf("DFS遍历结果: ");
for (int i = 0; i < V; i++) {
if (!visited[i]) {
DFS(i, visited);
}
}
printf("\n");
}
int main() {
DFSTraversal();
return 0;
}
#include
#include
// 哈希表节点
struct HashNode {
int key;
int value;
struct HashNode* next;
};
// 哈希表
struct HashTable {
int size;
struct HashNode** table;
};
// 创建哈希表
struct HashTable* createHashTable(int size) {
struct HashTable* ht = malloc(sizeof(struct HashTable));
ht->size = size;
ht->table = malloc(sizeof(struct HashNode*) * size);
for (int i = 0; i < size; i++) ht->table[i] = NULL;
return ht;
}
// 哈希函数
int hash(int key, int size) {
return abs(key) % size;
}
// 插入哈希表
void insert(struct HashTable* ht, int key, int value) {
int index = hash(key, ht->size);
struct HashNode* newNode = malloc(sizeof(struct HashNode));
newNode->key = key;
newNode->value = value;
newNode->next = ht->table[index];
ht->table[index] = newNode;
}
// 查找哈希表
int find(struct HashTable* ht, int key) {
int index = hash(key, ht->size);
struct HashNode* node = ht->table[index];
while (node) {
if (node->key == key) return node->value;
node = node->next;
}
return -1; // 未找到
}
// 两数之和解法
int* twoSum(int* nums, int numsSize, int target) {
struct HashTable* ht = createHashTable(numsSize);
int* result = malloc(2 * sizeof(int));
for (int i = 0; i < numsSize; i++) {
int complement = target - nums[i];
int complementIndex = find(ht, complement);
if (complementIndex != -1) {
result[0] = complementIndex;
result[1] = i;
return result;
}
insert(ht, nums[i], i);
}
return NULL; // 无解
}
// 测试
int main() {
int nums[] = {2, 7, 11, 15};
int target = 9;
int* result = twoSum(nums, 4, target);
if (result) {
printf("索引: [%d, %d]\n", result[0], result[1]);
printf("值: %d + %d = %d\n", nums[result[0]], nums[result[1]], target);
} else {
printf("无解\n");
}
return 0;
}
#include
#include
#include
// 最小栈节点
struct MinStackNode {
int val;
int min; // 记录当前最小值
struct MinStackNode* next;
};
// 最小栈结构
typedef struct {
struct MinStackNode* top;
} MinStack;
// 创建栈
MinStack* minStackCreate() {
MinStack* stack = malloc(sizeof(MinStack));
stack->top = NULL;
return stack;
}
// 入栈
void minStackPush(MinStack* obj, int val) {
struct MinStackNode* node = malloc(sizeof(struct MinStackNode));
node->val = val;
// 计算当前最小值
if (obj->top == NULL) {
node->min = val;
} else {
node->min = (val < obj->top->min) ? val : obj->top->min;
}
node->next = obj->top;
obj->top = node;
}
// 出栈
void minStackPop(MinStack* obj) {
if (obj->top == NULL) return;
struct MinStackNode* temp = obj->top;
obj->top = obj->top->next;
free(temp);
}
// 获取栈顶元素
int minStackTop(MinStack* obj) {
if (obj->top == NULL) return INT_MIN;
return obj->top->val;
}
// 获取最小值
int minStackGetMin(MinStack* obj) {
if (obj->top == NULL) return INT_MIN;
return obj->top->min;
}
// 测试
int main() {
MinStack* stack = minStackCreate();
minStackPush(stack, -2);
minStackPush(stack, 0);
minStackPush(stack, -3);
printf("最小值: %d\n", minStackGetMin(stack)); // -3
minStackPop(stack);
printf("栈顶: %d\n", minStackTop(stack)); // 0
printf("最小值: %d\n", minStackGetMin(stack)); // -2
return 0;
}
内存管理:
手动分配/释放内存(malloc/free)
防止内存泄漏(确保每个malloc都有free)
检查分配是否成功 if (ptr == NULL) { /* 处理错误 */ }
指针操作:
理解指针的指针(如链表操作)
结构体指针访问成员:node->next
替代 node.next
常用技巧:
// 动态数组
int* arr = malloc(size * sizeof(int));
// 二维数组
int** matrix = malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++)
matrix[i] = malloc(cols * sizeof(int));
// 位运算优化
#define SET_BIT(arr, n) (arr[n/32] |= (1 << (n%32)))
#define GET_BIT(arr, n) (arr[n/32] & (1 << (n%32)))
调试技巧:
使用Valgrind检测内存泄漏
打印指针地址:printf("%p", ptr)
断言检查:#include
建议练习:
实现循环队列(数组版)
手写AVL树旋转函数
实现LRU缓存(哈希表+双向链表)
完成Dijkstra最短路径算法(邻接矩阵版)