数据结构查找算法大全
前言
查找(搜索)是笔试、机试、面试核心考点,分为静态查找(数据集不变)、动态查找(支持增删)两大类。 本文包含:顺序查找、二分查找、分块查找、二叉搜索树、平衡二叉树 AVL、红黑树(简述)、哈希查找、跳表,每种算法包含:
- 算法原理
- 时间 / 空间复杂度
- 优缺点与适用场景
- 完整可运行 C++ 代码
- 细节易错点说明
一、静态查找(无插入删除,仅查询)
1. 顺序查找(线性查找)
原理
从头到尾逐个遍历数组,逐个比对关键字,找到返回下标,遍历完无匹配返回 - 1。 支持无序数组、有序数组。
复杂度
- 最好 O (1),最坏 O (n),平均 O (n)
- 空间 O (1)
优缺点
优点:实现简单、不要求有序; 缺点:大数据量效率极低。
C++ 实现
cpp
#include <iostream> #include <vector> using namespace std; // 顺序查找,返回下标,无则-1 int seqSearch(vector<int>& arr, int key) { for (int i = 0; i < arr.size(); i++) { if (arr[i] == key) { return i; } } return -1; } int main() { vector<int> arr = {5, 2, 9, 1, 5, 6}; int target = 9; int idx = seqSearch(arr, target); if (idx != -1) cout << "找到元素,下标:" << idx << endl; else cout << "未找到" << endl; return 0; }优化哨兵版(减少循环判断)
cpp
int seqSearchGuard(vector<int>& arr, int key) { arr.push_back(key); // 末尾放哨兵 int i = 0; while (arr[i] != key) i++; arr.pop_back(); return i == arr.size() ? -1 : i; }2. 二分查找(折半查找,仅有序数组)
原理
数组升序,每次取中间元素对比:
- 中间值 == key:命中
- 中间值 < key:去右半区间查找
- 中间值 > key:去左半区间查找
复杂度
平均 / 最坏 O (logn),空间迭代 O (1)、递归 O (logn)
适用场景
静态有序数组,无频繁插入删除
迭代版(推荐,无栈溢出)
cpp
#include <iostream> #include <vector> using namespace std; int binarySearch(vector<int>& arr, int key) { int l = 0, r = arr.size() - 1; while (l <= r) { // 防止溢出,等价 (l+r)/2 int mid = l + (r - l) / 2; if (arr[mid] == key) return mid; else if (arr[mid] < key) l = mid + 1; else r = mid - 1; } return -1; } int main() { vector<int> arr = {1,2,3,4,5,6,7,8,9}; cout << binarySearch(arr, 6) << endl; return 0; }递归版二分
cpp
int binaryRec(vector<int>& arr, int l, int r, int key) { if (l > r) return -1; int mid = l + (r - l) / 2; if (arr[mid] == key) return mid; else if (arr[mid] < key) return binaryRec(arr, mid+1, r, key); else return binaryRec(arr, l, mid-1, key); }高频衍生题型代码
- 查找左边界(第一个等于 key)
cpp
int leftBound(vector<int>& arr, int key) { int l = 0, r = arr.size()-1; int ans = -1; while(l <= r) { int mid = l + (r-l)/2; if(arr[mid] == key) { ans = mid; r = mid - 1; } else if(arr[mid] < key) l = mid + 1; else r = mid - 1; } return ans; }- 查找右边界(最后一个等于 key)
cp
int rightBound(vector<int>& arr, int key) { int l = 0, r = arr.size()-1; int ans = -1; while(l <= r) { int mid = l + (r-l)/2; if(arr[mid] == key) { ans = mid; l = mid + 1; } else if(arr[mid] < key) l = mid + 1; else r = mid - 1; } return ans; }3. 分块查找(索引顺序查找)
原理
- 数组分若干块,块内无序、块间有序;
- 建立索引表:存储每块最大值、块起始下标;
- 二分索引表确定目标块,块内顺序查找。
复杂度
索引二分 O (logm) + 块内顺序 O (s),m 块、块大小 s
C++ 完整实现
cpp
#include <iostream> #include <vector> using namespace std; // 索引结构体 struct Index { int maxVal; int start; }; // 构建索引表 vector<Index> buildIndex(vector<int>& arr, int blockSize) { vector<Index> idx; int n = arr.size(); for (int i = 0; i < n; i += blockSize) { Index tmp; tmp.start = i; int maxx = arr[i]; int end = min(i + blockSize, n); for (int j = i; j < end; j++) maxx = max(maxx, arr[j]); tmp.maxVal = maxx; idx.push_back(tmp); } return idx; } int blockSearch(vector<int>& arr, vector<Index>& idx, int blockSize, int key) { // 二分索引找块 int l = 0, r = idx.size()-1; int targetBlock = -1; while(l <= r) { int mid = l + (r-l)/2; if(idx[mid].maxVal >= key) { targetBlock = mid; r = mid - 1; } else { l = mid + 1; } } if(targetBlock == -1) return -1; // 块内顺序查找 int start = idx[targetBlock].start; int end = min(start + blockSize, (int)arr.size()); for(int i = start; i < end; i++) { if(arr[i] == key) return i; } return -1; } int main() { vector<int> arr = {22,12,13,8,20,33,42,44,38,24,48,60,58,74,49}; int block = 5; auto idx = buildIndex(arr, block); cout << blockSearch(arr, idx, block, 44) << endl; return 0; }二、动态查找(支持增、删、查,数据集可变)
4. 二叉搜索树 BST
性质
左子树所有节点 < 根 < 右子树所有节点;中序遍历升序。
复杂度
平衡时 O (logn),极端有序数据退化成链表 O (n)
C++ 完整实现(增删查)
cpp
#include <iostream> using namespace std; struct BSTNode { int val; BSTNode* left; BSTNode* right; BSTNode(int x) : val(x), left(nullptr), right(nullptr) {} }; // 查找 BSTNode* bstSearch(BSTNode* root, int key) { if (!root || root->val == key) return root; if (key < root->val) return bstSearch(root->left, key); else return bstSearch(root->right, key); } // 插入 BSTNode* bstInsert(BSTNode* root, int key) { if (!root) return new BSTNode(key); if (key < root->val) root->left = bstInsert(root->left, key); else if (key > root->val) root->right = bstInsert(root->right, key); return root; } // 找最小值节点(删除用) BSTNode* getMin(BSTNode* root) { while (root->left) root = root->left; return root; } // 删除节点 BSTNode* bstDelete(BSTNode* root, int key) { if (!root) return nullptr; if (key < root->val) root->left = bstDelete(root->left, key); else if (key > root->val) root->right = bstDelete(root->right, key); else { // 找到待删节点 // 情况1:叶子 / 单孩子 if (!root->left) { BSTNode* tmp = root->right; delete root; return tmp; } if (!root->right) { BSTNode* tmp = root->left; delete root; return tmp; } // 情况2:左右都有,取右子树最小替换 BSTNode* minRight = getMin(root->right); root->val = minRight->val; root->right = bstDelete(root->right, minRight->val); } return root; } // 中序遍历 void inOrder(BSTNode* root) { if (!root) return; inOrder(root->left); cout << root->val << " "; inOrder(root->right); } int main() { BSTNode* root = nullptr; root = bstInsert(root,5); root = bstInsert(root,3); root = bstInsert(root,7); root = bstInsert(root,1); root = bstInsert(root,4); inOrder(root); cout << endl; root = bstDelete(root,3); inOrder(root); return 0; }5. AVL 平衡二叉树(自平衡 BST)
核心规则
任意节点左右子树高度差(平衡因子)绝对值≤1;插入 / 删除失衡通过旋转恢复平衡。 旋转类型:LL、RR、LR、RL。
C++ 完整实现(增、查、旋转)
cpp运行
#include <iostream> #include <algorithm> using namespace std; struct AVLNode { int val; int height; AVLNode* left; AVLNode* right; AVLNode(int x) : val(x), height(1), left(nullptr), right(nullptr) {} }; int getHeight(AVLNode* root) { return root ? root->height : 0; } int getBalance(AVLNode* root) { return getHeight(root->left) - getHeight(root->right); } // 右旋 LL AVLNode* rightRotate(AVLNode* y) { AVLNode* x = y->left; AVLNode* T = x->right; x->right = y; y->left = T; y->height = 1 + max(getHeight(y->left), getHeight(y->right)); x->height = 1 + max(getHeight(x->left), getHeight(x->right)); return x; } // 左旋 RR AVLNode* leftRotate(AVLNode* x) { AVLNode* y = x->right; AVLNode* T = y->left; y->left = x; x->right = T; x->height = 1 + max(getHeight(x->left), getHeight(x->right)); y->height = 1 + max(getHeight(y->left), getHeight(y->right)); return y; } // 插入 AVLNode* avlInsert(AVLNode* root, int key) { if (!root) return new AVLNode(key); if (key < root->val) root->left = avlInsert(root->left, key); else if (key > root->val) root->right = avlInsert(root->right, key); else return root; root->height = 1 + max(getHeight(root->left), getHeight(root->right)); int bal = getBalance(root); // LL if (bal > 1 && key < root->left->val) return rightRotate(root); // RR if (bal < -1 && key > root->right->val) return leftRotate(root); // LR if (bal > 1 && key > root->left->val) { root->left = leftRotate(root->left); return rightRotate(root); } // RL if (bal < -1 && key < root->right->val) { root->right = rightRotate(root->right); return leftRotate(root); } return root; } AVLNode* avlSearch(AVLNode* root, int key) { if (!root || root->val == key) return root; if (key < root->val) return avlSearch(root->left, key); return avlSearch(root->right, key); } void inOrder(AVLNode* root) { if (!root) return; inOrder(root->left); cout << root->val << " "; inOrder(root->right); } int main() { AVLNode* root = nullptr; int arr[] = {10,20,30,40,50,25}; for (int x : arr) root = avlInsert(root, x); inOrder(root); return 0; }6. 哈希查找(散列查找,平均 O (1))
原理
哈希函数映射关键字到数组下标;冲突解决:链地址法、开放寻址。
方案 1:链地址法(C++ 实现,推荐)
cpp
#include <iostream> #include <vector> #include <list> using namespace std; const int MOD = 10; // 哈希表:数组+链表 vector<list<int>> hashTable(MOD); // 哈希函数 int hashFunc(int key) { return key % MOD; } // 插入 void hashInsert(int key) { int idx = hashFunc(key); hashTable[idx].push_back(key); } // 查找 bool hashSearch(int key) { int idx = hashFunc(key); for (int num : hashTable[idx]) { if (num == key) return true; } return false; } // 删除 void hashRemove(int key) { int idx = hashFunc(key); hashTable[idx].remove(key); } int main() { hashInsert(12); hashInsert(22); hashInsert(35); cout << boolalpha << hashSearch(22) << endl; hashRemove(22); cout << hashSearch(22) << endl; return 0; }方案 2:开放寻址法(线性探测)
cpp
#include <iostream> #include <vector> using namespace std; const int SIZE = 15; const int EMPTY = -1; vector<int> hashTable(SIZE, EMPTY); int hashFunc(int k) { return k % SIZE; } void insert(int key) { int idx = hashFunc(key); while (hashTable[idx] != EMPTY) { idx = (idx + 1) % SIZE; } hashTable[idx] = key; } bool search(int key) { int idx = hashFunc(key); int start = idx; while (hashTable[idx] != EMPTY) { if (hashTable[idx] == key) return true; idx = (idx + 1) % SIZE; if (idx == start) break; } return false; }7. 跳表 SkipList(Redis ZSet 底层)
原理
多层有序链表,上层作为索引加速查找,平均 O (logn);无需平衡旋转,实现比 AVL / 红黑树简单。 简化 C++ 极简实现:
cpp
#include <iostream> #include <vector> #include <cstdlib> #include <ctime> using namespace std; #define MAX_LEVEL 5 struct SkipNode { int val; vector<SkipNode*> next; SkipNode(int v, int level) : val(v), next(level, nullptr) {} }; class SkipList { private: SkipNode* head; int curLevel; // 随机层数 int randomLevel() { int lvl = 1; while (rand() % 2 == 0 && lvl < MAX_LEVEL) lvl++; return lvl; } public: SkipList() { srand((unsigned)time(NULL)); head = new SkipNode(-1, MAX_LEVEL); curLevel = 1; } // 查找 bool search(int key) { SkipNode* p = head; for (int i = curLevel - 1; i >= 0; i--) { while (p->next[i] && p->next[i]->val < key) p = p->next[i]; } p = p->next[0]; return p && p->val == key; } // 插入 void insert(int key) { vector<SkipNode*> update(MAX_LEVEL, nullptr); SkipNode* p = head; for (int i = curLevel - 1; i >= 0; i--) { while (p->next[i] && p->next[i]->val < key) p = p->next[i]; update[i] = p; } int newLvl = randomLevel(); SkipNode* newNode = new SkipNode(key, newLvl); for (int i = 0; i < newLvl; i++) { newNode->next[i] = update[i]->next[i]; update[i]->next[i] = newNode; } if (newLvl > curLevel) curLevel = newLvl; } }; int main() { SkipList sl; sl.insert(3); sl.insert(1); sl.insert(5); cout << boolalpha << sl.search(3) << endl; return 0; }三、所有查找算法对比汇总
表格
| 算法 | 平均查找 | 最坏查找 | 插入删除 | 是否有序 | 适用场景 |
|---|---|---|---|---|---|
| 顺序查找 | O(n) | O(n) | O(n) | 无序 / 有序 | 少量数据、链表 |
| 二分查找 | O(logn) | O(logn) | O(n) | 有序静态数组 | 固定不变有序数组 |
| 分块查找 | O(logm+s) | O(logm+s) | O(n) | 块间有序 | 大数据静态表 |
| BST | O(logn) | O(n) | O(logn) | 动态有序 | 小规模动态数据 |
| AVL 平衡树 | O(logn) | O(logn) | O(logn) | 动态有序 | 频繁查改,要求稳定复杂度 |
| 哈希查找 | O(1) | O(n) | O(1) | 无序 | 缓存、键值映射 |
| 跳表 | O(logn) | O(logn) | O(logn) | 动态有序 | Redis 有序集合 |
四、核心考点细节总结
- 二分查找必须有序,只适用于数组,链表不能二分;
- BST 退化根源:有序数据连续插入,平衡树解决该问题;
- 哈希查找优势是平均常数级,但存在哈希冲突最坏退化;
- AVL 平衡因子严格 ±1,旋转多;红黑树放宽平衡条件,工业更常用;
- 跳表用多层链表替代平衡树,无复杂旋转,工程实现简单;
- 静态查找适合一次性构建、极少修改场景;动态查找支持频繁增删。
