一、顺序查找
整体流程
- 从数组第一个元素开始
- 逐个与目标值比较
- 找到相等元素时返回索引
- 遍历完未找到,返回-1
关键代码
c
int search(int* data, int len, int value) {
for (int i = 0; i < len; i++) {
if (data[i] == value) {
return i; // 找到返回下标
}
}
return -1; // 未找到
}
核心要点
"线性遍历,简单直接,无需排序,效率最低"
二、二分查找(折半查找)
整体流程
- 设置low=0, high=n-1
- 当low≤high时循环:
- 计算mid=(low+high)/2
- 比较data[mid]与目标值
- 目标值大则low=mid+1,小则high=mid-1,相等则返回mid
- 循环结束未找到,返回-1
关键代码
c
int binary_search(int* data, int len, int value) {
int low = 0;
int high = len - 1;
int mid;
while(low <= high) {
mid = (low + high) / 2; // 取中间位置
if (value > data[mid]) {
low = mid + 1; // 在右半区查找
} else if(value < data[mid]) {
high = mid - 1; // 在左半区查找
} else {
return mid; // 找到目标
}
}
return -1; // 未找到
}
核心要点
"必须有序,每次砍半,高效稳定,静态最优"
三、折半查找判定树
整体流程
- 选择数组中间元素作为根节点
- 递归地对左半部分构建左子树
- 递归地对右半部分构建右子树
- 最终形成完全二叉树结构
关键代码
c
TreeNode* buildDecisionTree(int* arr, int start, int end) {
if (start > end) return NULL;
// 选择中间元素作为根
int mid = (start + end) / 2;
TreeNode* root = (TreeNode*)malloc(sizeof(TreeNode));
root->data = arr[mid];
// 递归构建左右子树
root->lchild = buildDecisionTree(arr, start, mid-1);
root->rchild = buildDecisionTree(arr, mid+1, end);
return root;
}
// 计算平均查找长度
float calculateASL(TreeNode* root, int depth) {
if (root == NULL) return 0;
// 当前节点贡献 + 左子树贡献 + 右子树贡献
return (depth+1) +
calculateASL(root->lchild, depth+1) +
calculateASL(root->rchild, depth+1);
}
核心要点
"二分查找的树形表示,中间为根,完全二叉时最优,高度决定最坏情况"
四、二叉排序树(BST)
4.1 查找操作
整体流程
- 从根节点开始
- 若当前节点为空,返回未找到
- 若当前节点值等于目标值,返回找到
- 若目标值小于当前节点,向左子树查找
- 若目标值大于当前节点,向右子树查找
关键代码
c
int search_bst(BiTree T, int value, BiTree parent, BiTree *pos) {
if (T == NULL) { // 未找到
*pos = parent; // 记录应插入位置的父节点
return 0;
}
if (T->data == value) { // 找到
*pos = T;
return 1;
}
if (value < T->data) { // 小值向左
return search_bst(T->lchild, value, T, pos);
} else { // 大值向右
return search_bst(T->rchild, value, T, pos);
}
}
4.2 插入操作
整体流程
- 查找要插入的位置
- 如果值已存在,返回失败
- 创建新节点
- 根据大小关系,将新节点作为叶子节点插入
关键代码
c
int insert_bst(BiTree *T, int value) {
BiTree pos = NULL;
BiTree newNode;
// 先查找位置
if (search_bst(*T, value, NULL, &pos)) {
return 0; // 已存在,插入失败
}
// 创建新节点
newNode = (BiTree)malloc(sizeof(TreeNode));
newNode->data = value;
newNode->lchild = NULL;
newNode->rchild = NULL;
// 插入到正确位置
if (pos == NULL) {
*T = newNode; // 空树,新节点为根
} else if (value < pos->data) {
pos->lchild = newNode; // 作为左孩子
} else {
pos->rchild = newNode; // 作为右孩子
}
return 1;
}
4.3 删除操作
整体流程
- 找到要删除的节点
- 根据子节点情况分三种处理:
- 无子节点:直接删除
- 一个子节点:用子节点替代
- 两个子节点:用后继节点(右子树最小值)替代
关键代码
c
int delete_node(BiTree *d) {
BiTree temp, successor;
// 情况1:只有左子树或无子树
if ((*d)->rchild == NULL) {
temp = *d;
*d = (*d)->lchild;
free(temp);
}
// 情况2:只有右子树
else if ((*d)->lchild == NULL) {
temp = *d;
*d = (*d)->rchild;
free(temp);
}
// 情况3:有两个子树
else {
// 找到中序前驱(左子树最大值)
temp = *d;
successor = (*d)->lchild;
while (successor->rchild != NULL) {
temp = successor;
successor = successor->rchild;
}
// 用前驱值替换当前节点
(*d)->data = successor->data;
// 删除前驱节点
if (temp != *d) {
temp->rchild = successor->lchild;
} else {
temp->lchild = successor->lchild;
}
free(successor);
}
return 1;
}
// 删除指定值
int delete_bst(BiTree *T, int value) {
if (*T == NULL) return 0;
if ((*T)->data == value) {
return delete_node(T); // 找到要删除的节点
} else if (value < (*T)->data) {
return delete_bst(&(*T)->lchild, value); // 在左子树删除
} else {
return delete_bst(&(*T)->rchild, value); // 在右子树删除
}
}
核心要点
"左小右大,递归查找,插入叶端,删除三情况"
五、平衡二叉树(AVL树)
整体流程
- 按BST方式插入/删除节点
- 从操作点向上回溯,检查平衡因子
- 发现不平衡节点,根据类型执行相应旋转
- 更新节点高度,继续回溯
关键代码(旋转操作)
c
// 计算节点高度
int height(BiTree T) {
if (T == NULL) return 0;
return max(height(T->lchild), height(T->rchild)) + 1;
}
// 获取平衡因子
int getBalance(BiTree T) {
if (T == NULL) return 0;
return height(T->lchild) - height(T->rchild);
}
// LL旋转(右旋)
void LL_Rotation(BiTree *T) {
BiTree temp = (*T)->lchild;
(*T)->lchild = temp->rchild;
temp->rchild = *T;
*T = temp;
}
// RR旋转(左旋)
void RR_Rotation(BiTree *T) {
BiTree temp = (*T)->rchild;
(*T)->rchild = temp->lchild;
temp->lchild = *T;
*T = temp;
}
// LR旋转(先左后右)
void LR_Rotation(BiTree *T) {
RR_Rotation(&(*T)->lchild);
LL_Rotation(T);
}
// RL旋转(先右后左)
void RL_Rotation(BiTree *T) {
LL_Rotation(&(*T)->rchild);
RR_Rotation(T);
}
// 插入并平衡
BiTree insert_avl(BiTree T, int value) {
// 1. 按BST方式插入
if (T == NULL) {
T = (BiTree)malloc(sizeof(TreeNode));
T->data = value;
T->lchild = T->rchild = NULL;
return T;
}
if (value < T->data) {
T->lchild = insert_avl(T->lchild, value);
} else if (value > T->data) {
T->rchild = insert_avl(T->rchild, value);
} else {
return T; // 已存在
}
// 2. 检查平衡
int balance = getBalance(T);
// 3. 不平衡时旋转
// LL情况
if (balance > 1 && value < T->lchild->data) {
LL_Rotation(&T);
}
// RR情况
else if (balance < -1 && value > T->rchild->data) {
RR_Rotation(&T);
}
// LR情况
else if (balance > 1 && value > T->lchild->data) {
LR_Rotation(&T);
}
// RL情况
else if (balance < -1 && value < T->rchild->data) {
RL_Rotation(&T);
}
return T;
}
核心要点
"平衡因子±2要旋转,LL右旋RR左,LR先左后右,RL先右后左"
六、B树/B+树
B树核心特性
- 每个节点最多m个子节点
- 除根外,每个内部节点至少⌈m/2⌉个子节点
- 所有叶子节点在同一层
- 节点结构:[n, key₁, ptr₁, key₂, ptr₂, ..., keyₙ, ptrₙ]
B+树特有特性
- 数据只存储在叶子节点
- 叶子节点通过指针连接成链表
- 内部节点只存储键值用于导航
B+树关键操作伪代码
插入(key, value):
1. 从根开始查找叶子节点
2. 将(key, value)插入到叶子节点
3. 如果节点溢出(>m-1个键):
a. 将节点分裂为两个
b. 将中间键提升到父节点
c. 递归处理父节点溢出
查询(key):
1. 从根开始
2. 在每个节点中查找key应处的区间
3. 沿对应指针向下,直到叶子节点
4. 在叶子节点中查找key
核心要点
"多路平衡,磁盘友好,B树内部存数据,B+树叶子存全部,链表加速范围查询"
七、哈希查找
整体流程
- 通过哈希函数计算键的哈希值
- 根据哈希值定位存储位置
- 若发生冲突,采用冲突解决策略
- 存储或查找元素
关键代码(链地址法)
c
#define TABLE_SIZE 10
typedef struct HashNode {
int key;
int value;
struct HashNode* next;
} HashNode;
typedef struct {
HashNode* buckets[TABLE_SIZE];
} HashTable;
// 哈希函数
int hash(int key) {
return key % TABLE_SIZE;
}
// 初始化
void init_hash_table(HashTable* table) {
for (int i = 0; i < TABLE_SIZE; i++) {
table->buckets[i] = NULL;
}
}
// 插入
void hash_insert(HashTable* table, int key, int value) {
int index = hash(key);
// 创建新节点
HashNode* newNode = (HashNode*)malloc(sizeof(HashNode));
newNode->key = key;
newNode->value = value;
// 头插法
newNode->next = table->buckets[index];
table->buckets[index] = newNode;
}
// 查找
HashNode* hash_search(HashTable* table, int key) {
int index = hash(key);
HashNode* current = table->buckets[index];
while (current != NULL) {
if (current->key == key) {
return current; // 找到
}
current = current->next;
}
return NULL; // 未找到
}
关键代码(开放地址法-线性探测)
c
#define TABLE_SIZE 10
#define EMPTY -1
#define DELETED -2
typedef struct {
int keys[TABLE_SIZE];
int values[TABLE_SIZE];
} HashTable;
// 初始化
void init_table(HashTable* table) {
for (int i = 0; i < TABLE_SIZE; i++) {
table->keys[i] = EMPTY;
}
}
// 哈希函数
int hash_func(int key) {
return key % TABLE_SIZE;
}
// 线性探测插入
int hash_insert(HashTable* table, int key, int value) {
int index = hash_func(key);
int original_index = index;
// 寻找空位
while (table->keys[index] != EMPTY &&
table->keys[index] != DELETED &&
table->keys[index] != key) {
index = (index + 1) % TABLE_SIZE;
// 表满
if (index == original_index) {
return 0;
}
}
// 插入数据
table->keys[index] = key;
table->values[index] = value;
return 1;
}
// 查找
int hash_search(HashTable* table, int key) {
int index = hash_func(key);
int original_index = index;
while (table->keys[index] != EMPTY) {
if (table->keys[index] == key) {
return table->values[index]; // 找到
}
index = (index + 1) % TABLE_SIZE;
// 遍历完整个表
if (index == original_index) {
break;
}
}
return -1; // 未找到
}
核心要点
"哈希函数定位置,冲突处理是关键,链地址法额外空间,开放地址法内部解决"
八、记忆宝典
代码模式总结
- 顺序查找 :
for循环遍历 - 二分查找 :
while(low<=high)+mid=(low+high)/2 - BST查找: 递归 + "小左大右"
- BST插入: 查找 + 叶子处插入
- BST删除: 三种情况 + 前驱/后继替代
- AVL平衡: 插入/删除 + 平衡因子检查 + 四种旋转
- 哈希查找: 哈希函数 + 冲突处理
一句话记忆每种算法核心
- 顺序查找: "一个一个比,简单直接"
- 二分查找: "有序才有效,每次砍一半"
- 判定树: "二分可视化,完全二叉最优"
- 二叉排序树: "左小右大,动态结构"
- 平衡二叉树: "旋转保平衡,高度差不超1"
- B/B+树: "多路平衡,减少IO,数据库最爱"
- 哈希查找: "计算直接得,冲突要处理"
代码速记口诀
顺序查找用for,遍历比较不落空;
二分查找要有序,low high mid三剑客;
BST递归有套路,小左大右记清楚;
插入先找再添加,删除三情要区分;
AVL插入后平衡,四种旋转保稳定;
哈希函数定位置,链地址法或探测。