查找算法学习总结2:代码分析篇

一、顺序查找

整体流程

  1. 从数组第一个元素开始
  2. 逐个与目标值比较
  3. 找到相等元素时返回索引
  4. 遍历完未找到,返回-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;  // 未找到
}

核心要点

"线性遍历,简单直接,无需排序,效率最低"

二、二分查找(折半查找)

整体流程

  1. 设置low=0, high=n-1
  2. 当low≤high时循环:
    • 计算mid=(low+high)/2
    • 比较data[mid]与目标值
    • 目标值大则low=mid+1,小则high=mid-1,相等则返回mid
  3. 循环结束未找到,返回-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;  // 未找到
}

核心要点

"必须有序,每次砍半,高效稳定,静态最优"

三、折半查找判定树

整体流程

  1. 选择数组中间元素作为根节点
  2. 递归地对左半部分构建左子树
  3. 递归地对右半部分构建右子树
  4. 最终形成完全二叉树结构

关键代码

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 查找操作

整体流程
  1. 从根节点开始
  2. 若当前节点为空,返回未找到
  3. 若当前节点值等于目标值,返回找到
  4. 若目标值小于当前节点,向左子树查找
  5. 若目标值大于当前节点,向右子树查找
关键代码
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 插入操作

整体流程
  1. 查找要插入的位置
  2. 如果值已存在,返回失败
  3. 创建新节点
  4. 根据大小关系,将新节点作为叶子节点插入
关键代码
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 删除操作

整体流程
  1. 找到要删除的节点
  2. 根据子节点情况分三种处理:
    • 无子节点:直接删除
    • 一个子节点:用子节点替代
    • 两个子节点:用后继节点(右子树最小值)替代
关键代码
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树)

整体流程

  1. 按BST方式插入/删除节点
  2. 从操作点向上回溯,检查平衡因子
  3. 发现不平衡节点,根据类型执行相应旋转
  4. 更新节点高度,继续回溯

关键代码(旋转操作)

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+树叶子存全部,链表加速范围查询"

七、哈希查找

整体流程

  1. 通过哈希函数计算键的哈希值
  2. 根据哈希值定位存储位置
  3. 若发生冲突,采用冲突解决策略
  4. 存储或查找元素

关键代码(链地址法)

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;  // 未找到
}

核心要点

"哈希函数定位置,冲突处理是关键,链地址法额外空间,开放地址法内部解决"

八、记忆宝典

代码模式总结

  1. 顺序查找 : for循环遍历
  2. 二分查找 : while(low<=high) + mid=(low+high)/2
  3. BST查找: 递归 + "小左大右"
  4. BST插入: 查找 + 叶子处插入
  5. BST删除: 三种情况 + 前驱/后继替代
  6. AVL平衡: 插入/删除 + 平衡因子检查 + 四种旋转
  7. 哈希查找: 哈希函数 + 冲突处理

一句话记忆每种算法核心

  • 顺序查找: "一个一个比,简单直接"
  • 二分查找: "有序才有效,每次砍一半"
  • 判定树: "二分可视化,完全二叉最优"
  • 二叉排序树: "左小右大,动态结构"
  • 平衡二叉树: "旋转保平衡,高度差不超1"
  • B/B+树: "多路平衡,减少IO,数据库最爱"
  • 哈希查找: "计算直接得,冲突要处理"

代码速记口诀

复制代码
顺序查找用for,遍历比较不落空;
二分查找要有序,low high mid三剑客;
BST递归有套路,小左大右记清楚;
插入先找再添加,删除三情要区分;
AVL插入后平衡,四种旋转保稳定;
哈希函数定位置,链地址法或探测。
相关推荐
元亓亓亓2 小时前
LeetCode热题100--118. 杨辉三角--简单
算法·leetcode·职场和发展
Brookty2 小时前
Java文件操作系列(一):从基础概念到File类核心方法
java·学习·java-ee·文件io
非凡ghost2 小时前
eDiary电子日记本(记录生活点滴)
windows·学习·生活·软件需求
StudyWinter2 小时前
【c++】thread总结
开发语言·c++·算法
饕餮怪程序猿2 小时前
贪心算法经典应用:活动选择问题(C++实现)
c++·算法·贪心算法
Han.miracle2 小时前
数据库圣经-分析 MySQL 事务隔离级别与并发问题
数据结构·mysql·事务
暗然而日章2 小时前
C++基础:Stanford CS106L学习笔记 15 RAII&智能指针&构建C++工程
c++·笔记·学习
光羽隹衡2 小时前
决策树项目——电信客户流失预测
算法·决策树·机器学习
TL滕2 小时前
从0开始学算法——第二十一天(高级链表操作)
笔记·学习·算法