C++数据结构与算法
学习算法参考:https://www.hello-algo.com/
Visual Studio快捷键:https://learn.microsoft.com/zh-cn/visualstudio/ide/default-keyboard-shortcuts-in-visual-studio?view=vs-2019
启动时不调试 Ctrl+F5
设置文档格式 Ctrl+K、Ctrl+D
注释选定内容 Ctrl+K、Ctrl+C
取消注释选定内容 Ctrl+K、Ctrl+U
行 - 删除 Ctrl+DShift+L
复制行 Ctrl+D
变量和常量命名规范
- 类名:首字母大写和驼峰原则
- 方法名(函数)、类成员变量、局部变量、package包命名:首字母小写和驼峰原则
- 常量:大写字母和下划线
vector
#include<bits/stdc++.h>
using namespace std;int main() {//初始化vector<int>vec = { 1,2 };//构建一个5×5,且所有元素均为0的二维动态数组vector<vector<int>> arrVec(5, vector<int>(5, 0));/* 访问元素 */int num = vec[1]; // 访问索引 1 处的元素/* 更新元素 */vec[1] = 0; // 将索引 1 处的元素更新为 0/*清空列表*/vec.clear();/* 尾部添加元素 */vec.push_back(1);vec.push_back(3);//加到尾部vec.emplace_back(4);//插入元素,插入头部vec.insert(vec.begin(), 11);/* 中间插入元素 */vec.insert(vec.begin() + 3, 6); // 在索引 3 处插入数字 6/* 删除元素 */vec.erase(vec.begin() + 3); // 删除索引 3 处的元素/* 拼接两个列表 */vector<int> vec2 = { 6, 8, 7, 10, 9 };// 将vec 后面添加 vec2 vec.insert(vec.end(), vec2.begin(), vec2.end());/* 排序列表 */sort(vec.begin(), vec.end()); // 排序后,默认元素从小到大排列//从大到小排序sort(vec.begin(), vec.end(), [](int a, int b) {return a > b;});//遍历for (int v : vec) cout << v << " ";return 0;}
链表
单链表
#include<bits/stdc++.h>
using namespace std;/***
*
*单链表
*
***///链表数据结构
struct ListNode {int val;ListNode* next;ListNode(int x) : val(x), next(nullptr) {}; //构造方法
};//遍历链表
void printListNode(ListNode* node) {ListNode* root = node;while (root != nullptr) {cout << root->val << " ";root = root->next;}cout << endl;
}//增加节点
void addListNode(ListNode* node, ListNode* newNode) {cout << "add num = " << newNode->val << endl;newNode->next = node->next;node->next = newNode;
}//删除节点
void deleListNode(ListNode* node) {//只有一个元素的时候if (node->next == nullptr) return;ListNode* delnode = node->next;cout << "dele num = " << delnode->val << endl;node->next = delnode->next;delete delnode; //删除这个节点的内存,在 C 和 C++ 等语言中,我们需要手动释放节点内存。
}//修改节点
void updateListNode(ListNode* node, int updateNum) {//只有一个元素的时候if (node->next == nullptr) return;ListNode* updateNode = node->next;cout << "original num = " << updateNode->val << " to update num = " << updateNum << endl;updateNode->val = updateNum;}//访问某一节点
void lookListNode(ListNode* node, int index) {ListNode* root = node;int idx = index;while (root != nullptr && idx > 0) {root = root->next;--idx;}int num = root->val;printListNode(node);cout << "index = " << index << ",val = " << num << endl;
}//查找某个值所在的节点和索引值
void findListNode(ListNode* node, int target) {ListNode* root = node;int idx = 0;while (root != nullptr) {if (root->val == target) {cout << "idx = " << idx << ",target = " << target << ",after the node is: ";printListNode(root);break;}root = root->next;++idx;}
}int main() {/* 初始化链表 2 -> 5 -> 3 -> 8 */ListNode* n0 = new ListNode(2);ListNode* n1 = new ListNode(5);ListNode* n2 = new ListNode(3);ListNode* n3 = new ListNode(8);n0->next = n1;n1->next = n2;n2->next = n3;cout << "[+] original node:" << endl;printListNode(n0);cout << "[+] add node:" << endl;ListNode* newNode = new ListNode(7);addListNode(n0, newNode);printListNode(n0);cout << "[+] dele node:" << endl;deleListNode(n0);printListNode(n0);cout << "[+] update node with num:" << endl;updateListNode(n0, 9);printListNode(n0);cout << "[+] look node with index:" << endl;lookListNode(n0, 2);cout << "[+] find node with target:" << endl;printListNode(n0);findListNode(n0, 9);return 0;
}
Stack栈
「栈 stack」是一种遵循先进后出的逻辑的线性数据结构。栈的底层由链表实现。
#include<bits/stdc++.h>
using namespace std;int main() {/* 初始化栈 */stack<int> stack;/* 元素入栈 */stack.push(1);stack.push(3);stack.push(2);stack.push(6);/* 访问栈顶元素 */int top = stack.top();cout << "top num = " << top << endl;/* 元素出栈 */stack.pop(); // 无返回值,把栈顶的值删除/* 获取栈的长度 */int size = stack.size();cout << "size = " << size << endl;/* 判断是否为空 */bool empty = stack.empty(); //非空返回falsecout << "empty = " << empty << endl;return 0;
}
基于链表实现的栈
#include<bits/stdc++.h>
using namespace std;/* 基于链表实现的栈 */
class LinkedListStack {private://链表数据结构struct ListNode {int val;ListNode* next;ListNode(int x) : val(x), next(nullptr) {}; //构造方法};ListNode* stackTop; // 将头节点作为栈顶int stkSize; // 栈的长度public:// LinkedListStack类的构造方法LinkedListStack() {cout << "启动了 LinkedListStack的构造方法" << endl;stackTop = nullptr;stkSize = 0;}/**析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。**/~LinkedListStack() {// 遍历链表删除节点,释放内存freeMemoryLinkedList(stackTop);}void freeMemoryLinkedList(ListNode* stackTop) {cout << "调用了 LinkedListStack 析构函数" << endl;delete stackTop;};/* 获取栈的长度 */int size() {return stkSize;}/* 判断栈是否为空 */bool isEmpty() {return size() == 0;}/* 入栈 */void push(int num) {ListNode* node = new ListNode(num);//新的值查到头部(栈顶)node->next = stackTop;//stackTop变成头节点stackTop = node;stkSize++;}/* 出栈 */void pop() {int num = top();ListNode* tmp = stackTop;stackTop = stackTop->next;// 释放内存delete tmp;stkSize--;}/* 访问栈顶元素 */int top() {if (isEmpty())throw out_of_range("Stack is null");return stackTop->val;}/* 将 List 转化为 Array 并返回 */vector<int> toVector() {ListNode* node = stackTop;vector<int> res(size());for (int i = res.size() - 1; i >= 0; i--) {res[i] = node->val;node = node->next;}return res;}};int main() {LinkedListStack listStack;// 栈顶到栈底顺序: 8 ->5 ->2 ->3 ->1listStack.push(1);listStack.push(3);listStack.push(2);listStack.push(5);listStack.push(8);vector<int> arr = listStack.toVector();for (int v : arr) cout << v << " ";cout << endl;listStack.pop();cout << "after pop:" << endl;vector<int> arr2 = listStack.toVector();for (int v2 : arr2) cout << v2 << " ";cout << endl;int topNum = listStack.top();cout << "topNum = " << topNum << " Stack size = " << listStack.size() << endl;return 0;
}
Queue队列
「队列 queue」是一种遵循先进先出规则的线性数据结构。队列的底层由链表实现。
#include<bits/stdc++.h>
using namespace std;int main() {/* 初始化队列 */queue<int> queue;/* 元素入队 */queue.push(1);queue.push(3);queue.push(2);queue.push(5);queue.push(4);/* 访问队首元素 */int front = queue.front();/* 访问队尾元素 */int back = queue.back();cout << "front = " << front << " back = " << back << endl;/* 元素出队,会删除元素 */queue.pop();/* 获取队列的长度 */int size = queue.size();/* 判断队列是否为空 */bool empty = queue.empty(); //非空返回falsecout << "size = " << size << " empty = " << empty << endl;return 0;
}
基于链表实现的队列
链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。
#include<bits/stdc++.h>
using namespace std;/* 基于链表实现的队列 */
class LinkedListQueue {
private://链表数据结构struct ListNode {int val;ListNode* next;ListNode(int x) : val(x), next(nullptr) {}; //构造方法};ListNode* front, * rear; // 头节点 front ,尾节点 rearint queSize;public:LinkedListQueue() {cout << "启动了 LinkedListQueue的构造方法" << endl;front = nullptr;rear = nullptr;queSize = 0;}~LinkedListQueue() {// 遍历链表删除节点,释放内存freeMemoryLinkedList(front);}void freeMemoryLinkedList(ListNode* front) {cout << "调用了 LinkedListQueue 析构函数" << endl;delete front;}/* 获取队列的长度 */int size() {return queSize;}/* 判断队列是否为空 */bool isEmpty() {return queSize == 0;}/* 尾部节点入队 */void push(int num) {// 尾节点后添加 numListNode* node = new ListNode(num);// 如果队列为空,则令头、尾节点都指向该节点if (front == nullptr) {front = node;rear = node;}// 如果队列不为空,则将该节点添加到尾节点后else {rear->next = node;rear = node;}queSize++;}/* 头部节点出队 */void pop() {// 删除头节点ListNode* tmp = front;front = front->next;// 释放内存delete tmp;queSize--;}/* 访问队首元素 */int peek() {if (size() == 0)throw out_of_range("Queue is null");return front->val;}/* 将链表转化为 Vector 并返回 */vector<int> toVector() {ListNode* node = front;vector<int> res(size());for (int i = 0; i < res.size(); i++) {res[i] = node->val;node = node->next;}return res;}
};int main() {LinkedListQueue queue;queue.push(1);queue.push(3);queue.push(2);queue.push(4);queue.push(8);queue.push(6);vector<int> arr = queue.toVector();for (int v : arr) cout << v << " ";cout << endl;queue.pop();cout << "after pop:" << endl;vector<int> arr2 = queue.toVector();for (int v2 : arr2) cout << v2 << " ";cout << endl;int topNum = queue.peek();cout << "peekNum = " << topNum << " Stack size = " << queue.size() << endl;return 0;
}
double-ended queue双向队列
在头部和尾部都允许执行元素的添加或删除操作。
pushFirst() 将元素添加至队首
pushLast() 将元素添加至队尾
popFirst() 删除队首元素
popLast() 删除队尾元素
peekFirst() 访问队首元素
peekLast() 访问队尾元素
#include<bits/stdc++.h>
using namespace std;int main() {deque<int>deque;//1->2->3->4deque.push_back(3); // 添加至队尾deque.push_back(4);deque.push_front(2);// 添加至队首deque.push_front(1);/* 访问元素,不删除元素 */int front = deque.front(); // 队首元素int back = deque.back(); // 队尾元素cout << "front = " << front << " back = " << back << endl;/* 元素出队 ,删除元素*/deque.pop_front(); // 队首元素出队deque.pop_back(); // 队尾元素出队/* 获取双向队列的长度 */int size = deque.size();/* 判断双向队列是否为空 */bool empty = deque.empty();cout << "size = " << size << " empty = " << empty << endl;return 0;
}
基于双向链表实现双向队列
双向队列底层是由双向链表实现
#include<bits/stdc++.h>
using namespace std;/* 基于双向链表实现的双向队列 */
class LinkedListDeque {
private:/* 双向链表节点 */struct DoublyListNode {int val; // 节点值DoublyListNode* next; // 后继节点指针DoublyListNode* prev; // 前驱节点指针DoublyListNode(int val) : val(val), prev(nullptr), next(nullptr) {}};DoublyListNode* front, * rear; // 头节点 front ,尾节点 rearint queSize = 0; // 双向队列的长度public:/* 构造方法 */LinkedListDeque() : front(nullptr), rear(nullptr) {cout << "调用了 LinkedListDeque 的构造方法" << endl;}/* 析构方法 */~LinkedListDeque() {cout << "调用了 LinkedListDeque 的析构方法" << endl;// 遍历链表删除全部节点,释放内存,DoublyListNode* pre, * cur = front;while (cur != nullptr) {pre = cur;cur = cur->next;delete pre;}}/* 获取双向队列的长度 */int size() {return queSize;}/* 判断双向队列是否为空 */bool isEmpty() {return size() == 0;}/* 入队操作 */void push(int num, bool isFront) {DoublyListNode* node = new DoublyListNode(num);// 若链表为空,则令 front, rear 都指向 nodeif (isEmpty())front = rear = node;// 队首入队操作else if (isFront) {// 将 node 添加至链表头部 (新增的node <-> 当前的front)front->prev = node;node->next = front;front = node; // 更新头节点// 队尾入队操作}else {// 将 node 添加至链表尾部 (当前的rear <-> 新增的node )rear->next = node;node->prev = rear;rear = node; // 更新尾节点}queSize++; // 更新队列长度}/* 队首入队 */void pushFirst(int num) {push(num, true);}/* 队尾入队 */void pushLast(int num) {push(num, false);}/* 出队操作 */int pop(bool isFront) {if (isEmpty())throw out_of_range("Double queue is null!");int val;// 队首出队操作if (isFront) {val = front->val; // 暂存头节点值// 删除头节点DoublyListNode* fNext = front->next; //(第一个的头节点front <-> 下一个节点fNext)if (fNext != nullptr) {fNext->prev = nullptr;front->next = nullptr;delete front; //在 C 和 C++ 中需要手动释放内存。}front = fNext; // 更新头节点// 队尾出队操作}else {val = rear->val; // 暂存尾节点值// 删除尾节点DoublyListNode* rPrev = rear->prev; //(上一个节点rPrev <-> 最后的尾节点rear)if (rPrev != nullptr) {rPrev->next = nullptr;rear->prev = nullptr;delete rear; //在 C 和 C++ 中需要手动释放内存。}rear = rPrev; // 更新尾节点}queSize--; // 更新队列长度return val;}/* 队首出队 */int popFirst() {return pop(true);}/* 队尾出队 */int popLast() {return pop(false);}/* 访问队首元素 */int peekFirst() {if (isEmpty())throw out_of_range("Double queue is null!");return front->val;}/* 访问队尾元素 */int peekLast() {if (isEmpty())throw out_of_range("Double queue is null!");return rear->val;}/* 返回数组用于打印 */vector<int> toVector() {DoublyListNode* node = front;vector<int> res(size());for (int i = 0; i < res.size(); i++) {res[i] = node->val;node = node->next;}return res;}
};
int main() {LinkedListDeque doubleQueue;// 0->1->2->3->4->5doubleQueue.pushFirst(2); //队列头部添加元素doubleQueue.pushFirst(1);doubleQueue.pushFirst(0);doubleQueue.pushLast(3); //队列尾部添加元素doubleQueue.pushLast(4);doubleQueue.pushLast(5);vector<int>dqueue = doubleQueue.toVector();cout << "dqueue num:" << endl;for (int dq : dqueue) cout << dq << " ";cout << endl;doubleQueue.popFirst();//队列头部删除弹出元素doubleQueue.popLast();//队列尾部删除弹出元素cout << "after popFirst and popLast:" << endl;int peekFirstNum = doubleQueue.peekFirst(); //访问头部元素,不删除int peekLastNum = doubleQueue.peekLast(); //访问尾部元素,不删除cout << "peekFirstNum = " << peekFirstNum << " peekLastNum = " << peekLastNum << endl;int size = doubleQueue.size();bool empty = doubleQueue.isEmpty(); //非空返回falsecout << "size = " << size << " empty = " << empty << endl;return 0;
}
unordered_map哈希表
哈希表中进行增删查改的时间复杂度都是O(1)
#include<bits/stdc++.h>
using namespace std;int main() {/* 初始化哈希表 */unordered_map<int, string> map;/* 添加操作 */// 在哈希表中添加键值对 (key, value)map[1] = "小哈";map[2] = "小啰";map[3] = "小算";map[4] = "小法";map[5] = "小鸭";/* 查询操作 */// 向哈希表输入键 key ,得到值 valuestring name = map[1];/* 删除操作 */// 在哈希表中删除键值对 (key, value),参数是keymap.erase(2);//查找元素,参数是key,如果key不存在,find会返回endif (map.find(3) != map.end()) cout << "find isExistKey true " << endl;else cout << "find isExistKey false " << endl;//查找元素,参数是key,存在返回1,不存在返回0int isExistKey = map.count(5);cout << "count isExistKey = " << isExistKey << " (存在返回1,不存在返回0)" << endl;/* 遍历哈希表 */
// 遍历键值对 key->valuefor (auto kv : map) {cout << kv.first << " -> " << kv.second << endl;}//c++ 17特性/*for (auto [k, v] : map) {cout << "key = " << k << " value = " << v << endl;}*///删除全部元素map.clear();//统计map的大小int size = map.size();cout << "after map clear,the map size = " << size << endl;return 0;
}
哈希表的实现
输入一个 key
,哈希函数的计算过程分为以下两步。
-
- 通过某种哈希算法
hash()
计算得到哈希值。
- 通过某种哈希算法
-
- 将哈希值对桶数量(数组长度)
capacity
取模,从而获取该key
对应的数组索引index
。
- 将哈希值对桶数量(数组长度)
index = hash(key) % capacity
就可以利用 index
在哈希表中访问对应的桶,从而获取 value
。
#include<bits/stdc++.h>
using namespace std;/* 键值对 */
struct Pair {
public:int key;string val;Pair(int key, string val) {this->key = key;this->val = val;}
};/* 基于数组简易实现的哈希表 */
class ArrayHashMap {
private:// [{key,val},{key,val},{key,val} ...]vector<Pair*> buckets; //定义数组桶int capacity = 100; // 数组桶大小public:ArrayHashMap() {cout << "调用了 ArrayHashMap 的构造方法" << endl;// 初始化数组,包含 100 个桶buckets = vector<Pair*>(capacity);}~ArrayHashMap() {cout << "调用了 ArrayHashMap 的析构方法" << endl;// 释放内存for (const auto& bucket : buckets) {delete bucket;}buckets.clear();}/* 哈希函数 */int hashFunc(int key) {//对key值取模,如 4 % 100 = 4int index = key % capacity;return index;}/* 查询操作 */string get(int key) {int index = hashFunc(key);Pair* pair = buckets[index];if (pair == nullptr)return nullptr;//key通过hash后的index,拿到index对应的pairreturn pair->val;}/* 添加操作 */void put(int key, string val) {//初始化pair对象Pair* pair = new Pair(key, val);int index = hashFunc(key);//key通过hash后的index添加到桶里buckets[index] = pair;}/* 删除操作 */void remove(int key) {int index = hashFunc(key);// 先释放内存,在置为 nullptrdelete buckets[index];buckets[index] = nullptr;}/* 获取所有键值对 */vector<Pair*> pairSet() {vector<Pair*> pairSet;for (Pair* pair : buckets) {if (pair != nullptr) {pairSet.push_back(pair);}}return pairSet;}/* 获取所有键 */vector<int> keySet() {vector<int> keySet;for (Pair* pair : buckets) {if (pair != nullptr) {keySet.push_back(pair->key);}}return keySet;}/* 获取所有值 */vector<string> valueSet() {vector<string> valueSet;for (Pair* pair : buckets) {if (pair != nullptr) {valueSet.push_back(pair->val);}}return valueSet;}/* 打印哈希表 */void print() {for (Pair* kv : pairSet()) {cout << kv->key << " -> " << kv->val << endl;}}
};int main() {ArrayHashMap map;map.put(0, "0");map.put(1, "1");map.put(2, "2");map.put(3, "3");map.put(4, "4");int getKey = 2;string val = map.get(getKey);cout << "getKey = " << getKey << " value = " << val << endl;int removeKey = 4;map.remove(removeKey);// 或者 map.print()vector<Pair*> vps = map.pairSet();for (Pair* vp : vps) {cout << "key = " << vp->key << " value = " << vp->val << endl;}return 0;
}
哈希冲突
通常情况下哈希函数的输入空间远大于输出空间,改良哈希表数据结构,使得哈希表可以在存在哈希冲突时正常工作。即当哈希冲突比较严重时,才执行扩容操作。哈希表的结构改良方法主要包括“链式地址”和“开放寻址”。
链式地址
在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 separate chaining」将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。
- 查询元素:输入
key
,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比key
以查找目标键值对。 - 添加元素:先通过哈希函数访问链表头节点,然后将节点(即键值对)添加到链表中。
- 删除元素:根据哈希函数的结果访问链表头部,接着遍历链表以查找目标节点,并将其删除。
#include<bits/stdc++.h>
using namespace std;/* 键值对 */
struct Pair {int key;string val;Pair(int key, string val) {this->key = key;this->val = val;}
};/* 链式地址哈希表 */
class HashMapChaining {
private:int size; // 所有键值对的总和个数int capacity; // 最外层的哈希表容量double loadThres; // 触发扩容的负载因子阈值,这里设置成2/3int extendRatio; // 扩容倍数。以下实现包含哈希表扩容方法。当负载因子超过2/3时,我们将哈希表扩容至2倍。//[[{key,val},{key,val},{key,val}...],[{key,val},{key,val},{key,val}...],[{key,val},{key,val},{key,val}...]]vector<vector<Pair*>> buckets; // 桶数组public:/* 构造方法 ,且初始化变量的值*/HashMapChaining() : size(0), capacity(4), loadThres(2.0 / 3.0), extendRatio(2) {cout << "调用了 HashMapChaining 的构造方法" << endl;//重新定义桶数组buckets的大小buckets.resize(capacity);}/* 析构方法 */~HashMapChaining() {cout << "调用了 HashMapChaining 的析构方法" << endl;for (auto& bucket : buckets) {for (Pair* pair : bucket) {// 释放内存delete pair;}}}/* 哈希函数 */int hashFunc(int key) {// key % 数组桶大小的容量//index是buckets第一层数组的数据int index = key % capacity;return index;}/* 负载因子 */double loadFactor() {//所有键值对的大小容量跟最外层的桶容量的占比return (double)size / (double)capacity;}/* 扩容哈希表(最外层) */void extend() {// 暂存原哈希表vector<vector<Pair*>> bucketsTmp = buckets;// 初始化扩容后的新哈希表,扩容至原来的2倍capacity *= extendRatio;//清空原来的桶数组的数据buckets.clear();//重新定义桶数组的大小buckets.resize(capacity);size = 0;// 将键值对从原哈希表搬运至新哈希表for (auto& bucket : bucketsTmp) {for (Pair* pair : bucket) {//重新存数据put(pair->key, pair->val);// 删除释放临时的内存delete pair;}}}/* 添加操作 */void put(int key, string val) {// 当存储数据时,发现负载因子超过2/3阈值时,执行扩容//也就是说所有键值对的大小容量跟最外层的桶容量的占比大于2/3时则扩容if (loadFactor() > loadThres) {extend();}int index = hashFunc(key);// 遍历桶,若遇到指定 key存在时 ,则更新对应 val 并返回for (Pair* pair : buckets[index]) {if (pair->key == key) {pair->val = val;cout << "find the key data,key = " << key << ",update it!" << endl;return;}}// 若无该 key ,则将键值对添加至尾部buckets[index].push_back(new Pair(key, val));size++;}/* 查询操作 */string get(int key) {int index = hashFunc(key);// 遍历桶,若找到 key 则返回对应 valfor (Pair* pair : buckets[index]) {if (pair->key == key) {return pair->val;}}cout << "Not find the key = " << key << "data!" << endl;// 若未找到 key 则返回 nullptrreturn nullptr;}/* 删除操作 */void remove(int key) {int index = hashFunc(key);vector<Pair*>& bucket = buckets[index];// 遍历桶,从中删除键值对for (int i = 0; i < bucket.size(); i++) {if (bucket[i]->key == key) {Pair* tmp = bucket[i];bucket.erase(bucket.begin() + i); // 从中删除键值对delete tmp; // 释放内存size--;return;}}}//返回整个外层桶数组的大小,也就是说是capacity的大小int mapSize() {return capacity;}//返回相同key的桶数组大小int sizeWithKey(int key) {int index = hashFunc(key);vector<Pair*>bucket = buckets[index];return bucket.size();}/* 打印哈希表 */void print() {for (vector<Pair*>& bucket : buckets) {cout << "[";for (Pair* pair : bucket) {cout << pair->key << " -> " << pair->val << ", ";}cout << "]\n";}}
};int main() {HashMapChaining mapChain;mapChain.put(0, "0");mapChain.put(1, "1");int Key1 = 1;string val = mapChain.get(Key1);cout << "Key1 = " << Key1 << ",val = " << val << endl;mapChain.print();int capacity = mapChain.mapSize();cout << "capacity = " << capacity << endl;mapChain.put(5, "5");mapChain.put(3, "3");mapChain.put(4, "4");mapChain.print();int capacity2 = mapChain.mapSize();cout << "after put data ,the capacity2 = " << capacity2 << endl;return 0;
}
开放寻址
「开放寻址 open addressing」不引入额外的数据结构,而是通过“多次探测”来处理哈希冲突,探测方式主要包括线性探测、平方探测、多次哈希等。
不能在开放寻址哈希表中直接删除元素。这是因为删除元素会在数组内产生一个空桶 None ,而当查询元素时,线性探测到该空桶就会返回,因此在该空桶之下的元素都无法再被访问到,程序可能误判这些元素不存在。
为了解决该问题,我们可以采用「懒删除 lazy deletion」机制:它不直接从哈希表中移除元素,而是利用一个常量 TOMBSTONE
来标记这个桶。在该机制下,None 和 TOMBSTONE
都代表空桶,都可以放置键值对。但不同的是,线性探测到 TOMBSTONE
时应该继续遍历,因为其之下可能还存在键值对。
然而,懒删除可能会加速哈希表的性能退化。这是因为每次删除操作都会产生一个删除标记,随着 TOMBSTONE
的增加,搜索时间也会增加,因为线性探测可能需要跳过多个 TOMBSTONE
才能找到目标元素。
为此,考虑在线性探测中记录遇到的首个 TOMBSTONE
的索引,并将搜索到的目标元素与该 TOMBSTONE
交换位置。这样做的好处是当每次查询或添加元素时,元素会被移动至距离理想位置(探测起始点)更近的桶,从而优化查询效率。
#include<bits/stdc++.h>
using namespace std;/* 键值对 */
struct Pair {int key;string val;Pair(int key, string val) {this->key = key;this->val = val;}
};/* 开放寻址哈希表 */
class HashMapOpenAddressing {
private:int size; // 键值对数量int capacity = 4; // 哈希表容量const double loadThres = 2.0 / 3.0; // 触发扩容的负载因子阈值const int extendRatio = 2; // 扩容倍数vector<Pair*> buckets; // 桶数组Pair* TOMBSTONE = new Pair(-1, "-1"); // 删除标记public:/* 构造方法 */HashMapOpenAddressing() : size(0), buckets(capacity, nullptr) {cout << "调用了 HashMapOpenAddressing 的构造方法" << endl;}/* 析构方法 */~HashMapOpenAddressing() {cout << "调用了 HashMapOpenAddressing 的析构方法" << endl;for (Pair* pair : buckets) {if (pair != nullptr && pair != TOMBSTONE) {delete pair;}}delete TOMBSTONE;}/* 哈希函数 */int hashFunc(int key) {return key % capacity;}/* 负载因子 */double loadFactor() {return (double)size / capacity;}/* 搜索 key 对应的桶索引 */int findBucket(int key) {int index = hashFunc(key);//首次标记int firstTombstone = -1;// 线性探测,当遇到空桶时跳出while (buckets[index] != nullptr) {// 若遇到 key ,返回对应桶索引if (buckets[index]->key == key) {// 若之前标记过的,不是第一次删除标记的,则将index对应的键值对移动到firstTombstone索引这里if (firstTombstone != -1) {//也就是说现在找到的pair键值对移动到上次删除的键值对桶里。buckets[firstTombstone] = buckets[index];//现在的pair键值对就标记为删除了。buckets[index] = TOMBSTONE;return firstTombstone; // 因为移动了键值对,就返回移动的那个桶索引}return index; // 还没删除过桶索引,默认返回桶索引就行了。}//没找到key,且是首个删除标记和当前index索引被标记了TOMBSTONEif (firstTombstone == -1 && buckets[index] == TOMBSTONE) {//首次的删除标记被更新了firstTombstone = index;}// 计算桶索引,越过尾部返回头部index = (index + 1) % capacity;}// 若 key 不存在,且还没找到删除标记的桶索引,则返回index索引,否则返回删除标记的索引return firstTombstone == -1 ? index : firstTombstone;}/* 查询操作 */string get(int key) {// 搜索 key 对应的桶索引int index = findBucket(key);// 若找到键值对,且不时TOMBSTONE标记的,则返回对应 valif (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {return buckets[index]->val;}// 若键值对不存在,则返回空字符串return "";}/* 添加操作 */void put(int key, string val) {// 当负载因子超过阈值时,执行扩容if (loadFactor() > loadThres) {extend();}// 搜索 key 对应的桶索引int index = findBucket(key);// 若找到键值对,且不是TOMBSTONE标记的,则覆盖 val 并返回if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {buckets[index]->val = val;return;}// 若键值对不存在,则添加该键值对buckets[index] = new Pair(key, val);size++;}/* 删除操作 */void remove(int key) {// 搜索 key 对应的桶索引int index = findBucket(key);// 若找到键值对,删除它,然后标记TOMBSTONEif (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {delete buckets[index];buckets[index] = TOMBSTONE;size--;}}int sizeMap() {return capacity;}/* 扩容哈希表 */void extend() {// 暂存原哈希表vector<Pair*> bucketsTmp = buckets;// 初始化扩容后的新哈希表capacity *= extendRatio;buckets = vector<Pair*>(capacity, nullptr);size = 0;// 将键值对从原哈希表搬运至新哈希表for (Pair* pair : bucketsTmp) {if (pair != nullptr && pair != TOMBSTONE) {put(pair->key, pair->val);delete pair;}}}/* 打印哈希表 */void print() {for (Pair* pair : buckets) {if (pair == nullptr) {cout << "nullptr" << endl;}else if (pair == TOMBSTONE) {cout << "TOMBSTONE" << endl;}else {cout << pair->key << " -> " << pair->val << endl;}}}
};int main() {HashMapOpenAddressing openAddrMap;openAddrMap.put(0, "0");openAddrMap.put(1, "1");openAddrMap.put(2, "2");openAddrMap.put(3, "3");openAddrMap.put(4, "4");string val = openAddrMap.get(2);cout << "val = " << val << endl;openAddrMap.remove(3);openAddrMap.print();int capacitySize = openAddrMap.sizeMap();cout << "capacitySize = " << capacitySize << endl;return 0;
}
哈希算法设计
- 加法哈希:对输入的每个字符的 ASCII 码进行相加,将得到的总和作为哈希值。
- 乘法哈希:利用了乘法的不相关性,每轮乘以一个常数,将各个字符的 ASCII 码累积到哈希值中。
- 异或哈希:将输入数据的每个元素通过异或操作累积到一个哈希值中。
- 旋转哈希:将每个字符的 ASCII 码累积到一个哈希值中,每次累积之前都会对哈希值进行旋转操作。
每种哈希算法的最后一步都是对大质数 1000000007 取模,以确保哈希值在合适的范围内。
当我们使用大质数作为模数时,可以最大化地保证哈希值的均匀分布。因为质数不会与其他数字存在公约数,可以减少因取模操作而产生的周期性模式,从而避免哈希冲突。
总而言之,通常选取质数作为模数,并且这个质数最好足够大,以尽可能消除周期性模式,提升哈希算法的稳健性。
#include<bits/stdc++.h>
using namespace std;/* 加法哈希 */
int addHash(string key) {long long hash = 0;const int MODULUS = 1000000007;for (unsigned char c : key) {hash = (hash + (int)c) % MODULUS;}return (int)hash;
}/* 乘法哈希 */
int mulHash(string key) {long long hash = 0;const int MODULUS = 1000000007;for (unsigned char c : key) {hash = (31 * hash + (int)c) % MODULUS;}return (int)hash;
}/* 异或哈希 */
int xorHash(string key) {int hash = 0;const int MODULUS = 1000000007;for (unsigned char c : key) {hash ^= (int)c;}return hash & MODULUS;
}/* 旋转哈希 */
int rotHash(string key) {long long hash = 0;const int MODULUS = 1000000007;for (unsigned char c : key) {hash = ((hash << 4) ^ (hash >> 28) ^ (int)c) % MODULUS;}return (int)hash;
}int main() {string str = "360357923174893";int addNum = addHash(str);cout << "addNum :" << addNum << endl;int mulNum = mulHash(str);cout << "mulNum :" << mulNum << endl;int xorNum= xorHash(str);cout << "xorNum :" << xorNum << endl;int rotNum = rotHash(str);cout << "rotNum :" << rotNum << endl;return 0;
}
二叉树
「二叉树 binary tree」是一种非线性数据结构,二叉树的基本单元是节点,每个节点包含:值、左子节点引用、右子节点引用。
每个节点都有两个引用(指针),分别指向「左子节点 left-child node」(节点是偶数)和「右子节点 right-child node」(节点是奇数),该节点被称为这两个子节点的「父节点 parent node」。当给定一个二叉树的节点时,我们将该节点的左子节点及其以下节点形成的树称为该节点的「左子树 left subtree」,同理可得「右子树 right subtree」。
在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树。
- 「根节点 root node」:位于二叉树顶层的节点,没有父节点。
- 「叶节点 leaf node」:没有子节点的节点,其两个指针均指向 None 。
- 「边 edge」:连接两个节点的线段,即节点引用(指针)。
- 节点所在的「层 level」:从顶至底递增,根节点所在层为 1 。
- 节点的「度 degree」:节点的子节点的数量。在二叉树中,度的取值范围是 0、1、2 。
- 二叉树的「高度 height」:从根节点到最远叶节点所经过的边的数量。
- 节点的「深度 depth」:从根节点到该节点所经过的边的数量。
- 节点的「高度 height」:从最远叶节点到该节点所经过的边的数量。
完美二叉树
完美二叉树又称满二叉树,「完美二叉树 perfect binary tree」所有层的节点都被完全填满。在完美二叉树中,叶节点的度为 0 ,其余所有节点的度都为 2 ;若树高度为 ℎ ,则节点总数为 2^(ℎ+1)−1 ,呈现标准的指数级关系,反映了自然界中常见的细胞分裂现象。
若节点的索引为i ,则该节点的左子节点索引为 2i+1 ,右子节点索引为 2i+2 。
满二叉树中第n层的节点个数为
2 ( n − 1 ) 2^{(n-1)} 2(n−1)
满二叉树中全部节点的个数为:
2 n − 1 2^n-1 2n−1
完全二叉树
「完全二叉树 complete binary tree」只有最底层的节点未被填满,且最底层节点尽量靠左填充。
完满二叉树
「完满二叉树 full binary tree」除了叶节点之外,其余所有节点都有两个子节点。
平衡二叉树
「平衡二叉树 balanced binary tree」中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。
左子树高度 - 右子树高度 = d ,|d| <= 1
搜索二叉树
「二叉搜索树 binary search tree」满足以下条件。
-
- 对于根节点,左子树中所有节点的值 < 根节点的值 < 右子树中所有节点的值。
-
- 任意节点的左、右子树也是二叉搜索树,即同样满足条件
1.
。
- 任意节点的左、右子树也是二叉搜索树,即同样满足条件
查找节点
给定目标节点值 num
,可以根据二叉搜索树的性质来查找。声明一个节点 cur
,从二叉树的根节点 root
出发,循环比较节点值 cur.val
和 num
之间的大小关系。
- 若
cur.val < num
,说明目标节点在cur
的右子树中,因此执行cur = cur.right
。 - 若
cur.val > num
,说明目标节点在cur
的左子树中,因此执行cur = cur.left
。 - 若
cur.val = num
,说明找到目标节点,跳出循环并返回该节点。
插入节点
- 查找插入位置:与查找操作相似,从根节点出发,根据当前节点值和
num
的大小关系循环向下搜索,直到越过叶节点(遍历至 None )时跳出循环。 - 在该位置插入节点:初始化节点
num
,将该节点置于 None 的位置。
二叉搜索树不允许存在重复节点,否则将违反其定义。因此,若待插入节点在树中已存在,则不执行插入,直接返回。
为了实现插入节点,我们需要借助节点 pre
保存上一轮循环的节点。这样在遍历至 None 时,我们可以获取到其父节点,从而完成节点插入操作。
删除节点
先在二叉树中查找到目标节点,再将其从二叉树中删除。
与插入节点类似,我们需要保证在删除操作完成后,二叉搜索树的“左子树 < 根节点 < 右子树”的性质仍然满足。
当待删除节点的度为 0 时,表示该节点是叶节点,可以直接删除。
当待删除节点的度为 1 时,将待删除节点替换为其子节点即可。
当待删除节点的度为 2 时,我们无法直接删除它,而需要使用一个节点替换该节点。由于要保持二叉搜索树“左 < 根 < 右”的性质,因此这个节点可以是右子树的最小节点或左子树的最大节点。
假设我们选择右子树的最小节点(即中序遍历的下一个节点),则删除操作流程如下。
- cur找到待删除节点在“中序遍历序列”中的下一个节点,记为
tmp
。 - 将
tmp
的值覆盖待删除节点的值,并在树中递归删除节点tmp
。
二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小节点,从而得出一个重要性质:二叉搜索树的中序遍历序列是升序的
#include<bits/stdc++.h>
using namespace std;// 搜索二叉树/* 二叉树节点结构体 */
struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};//BFS遍历
void BFS(TreeNode* root) {//初始化队列queue<TreeNode*> queue;queue.push(root);//保存遍历序列vector<int>vec;while (!queue.empty()) {//队列先进去的节点出队TreeNode* node = queue.front();queue.pop();//存储当前出队节点的值vec.push_back(node->val);//当前节点存在左子树,则当前节点的左节点加入队列if (node->left != nullptr) queue.push(node->left);//当前节点存在右子树,则当前节点的右节点加入队列if (node->right != nullptr) queue.push(node->right);}cout << "BFS: ";for (int v : vec) cout << v << " ";cout << endl;
}//搜索二叉树,参数num为要寻找的节点值
TreeNode* searchTreeNode(TreeNode* root, int num) {TreeNode* cur = root;//左节点的值 < 根节点的值 < 右节点的值while (cur != nullptr) {if (cur->val < num) cur = cur->right; //往右节点找else if (cur->val > num) cur = cur->left; //往左节点找else break; //找到了,跳出循环}return cur;
}//搜索二叉树 插入节点 参数num为要插入的节点值
void addSearchTreeNode(TreeNode* root, int num) {//若树为空,则复制上该节点值if (root == nullptr) {root = new TreeNode(num);return;}TreeNode* cur = root; // 当前节点TreeNode* pre = nullptr; // 上一个节点while (cur != nullptr) {//存在该值,直接返回if (cur->val == num) return;//当前的节点不断的赋值给上一个节点,更新上次节点pre = cur;//左节点的值 < 根节点的值 < 右节点的值if (cur->val < num) cur = cur->right;//往右节点找else cur = cur->left;//往左节点找}//找到了pre最后的位置,插入该值TreeNode* newNode = new TreeNode(num);if (pre->val < num) pre->right = newNode; //插到pre节点的右边else pre->left = newNode;//插到pre节点的左边
}//搜索二叉树 删除节点 参数num为要删除的节点值
void deleSearchTreeNode(TreeNode* root, int num) {if (root == nullptr) return;TreeNode* cur = root;TreeNode* pre = nullptr;while (cur != nullptr) {//找到相同的值,跳出循环if (cur->val == num) break;pre = cur;//更新上次节点if (cur->val < num) cur = cur->right;else cur = cur->left;}// 若无待删除节点,则直接返回if (cur == nullptr) return;//cur节点的度为0或1时if (cur->left == nullptr || cur->right == nullptr) {//child 为cur 的左节点或者cur的右节点TreeNode* child = cur->left != nullptr ? cur->left : cur->right;//删除cur节点if (cur != root) {if (pre->left == cur) pre->left = child;else pre->right = child;}else root = child; //若删除节点为根节点,则重新指定根节点delete cur;}else {//cur节点的度为2时//通过中序遍历拿到cur的下一个节点,右子树最小节点,接就是说遍历cur的左子树TreeNode* tmpNode = cur->right;while (tmpNode->left != nullptr) tmpNode = tmpNode->left;int deleVal = tmpNode->val;//递归删除tmpNode节点deleSearchTreeNode(cur, tmpNode->val);//用tmpNode的值覆盖cur的值cur->val = deleVal;}
}int main() {/* 初始化二叉树 */TreeNode* n8 = new TreeNode(8);TreeNode* n4 = new TreeNode(4);TreeNode* n12 = new TreeNode(12);TreeNode* n3 = new TreeNode(3);TreeNode* n6 = new TreeNode(6);TreeNode* n10 = new TreeNode(10);TreeNode* n14 = new TreeNode(14);TreeNode* n5 = new TreeNode(5);TreeNode* n7 = new TreeNode(7);TreeNode* n9 = new TreeNode(9);TreeNode* n11 = new TreeNode(11);TreeNode* n13 = new TreeNode(13);TreeNode* n15 = new TreeNode(15);// 构建引用指向(即指针)n8->left = n4;n8->right = n12;n4->left = n3;n4->right = n6;n12->left = n10;n12->right = n14;n6->left = n5;n6->right = n7;n10->left = n9;n10->right = n11;n14->left = n13;n14->right = n15;BFS(n8);//删除节点4deleSearchTreeNode(n8, 4);BFS(n8);return 0;
}
搜索二叉树的查找元素、插入元素、删除元素的时间复制复杂度均为O(logn)
平衡二叉搜素树AVL
确保在持续添加和删除节点后,AVL 树不会退化,从而使得各种操作的时间复杂度保持在 O(logn) 级别。换句话说,在需要频繁进行增删查改操作的场景中,AVL 树能始终保持高效的数据操作性能,具有很好的应用价值。
AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉树的所有性质,因此也被称为「平衡二叉搜索树 balanced binary search tree」。
“节点高度”是指从该节点到最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 0 ,而空节点的高度为 -1 。
节点的「平衡因子 balance factor」定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 0 。节点平衡因子 = 左子树高度 - 右子树高度
AVL树旋转
旋转操作既能保持“二叉搜索树”的性质,也能使树重新变为“平衡二叉树”,将平衡因子绝对值 >1 的节点称为“失衡节点”,旋转操作分为四种:右旋、左旋、先右旋后左旋、先左旋后右旋。
右旋
找到失衡节点,记为node,其左子节点记为child,node节点执行“右旋”操作(在以child节点上为原点顺时针旋转90°),若child右节点存在,记为grandChild,则然后child代替原来node的位置,然后grandChild补在node的左节点上。【node顺转补左】
左旋
找到失衡节点,记为node,其右子节点记为child,node节点执行“左旋”操作(在以child节点上为原点逆时针旋转90°),若child左节点存在,记为grandChild,则然后child代替原来node的位置,然后grandChild补在node的右节点上。【node逆转补右】
可见,右旋和左旋操作在逻辑上是镜像对称的,它们分别解决的两种失衡情况也是对称的
左旋转后右旋
仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 child
执行“左旋”,再对 node
执行“右旋”。
右旋后左旋
对于上述失衡二叉树的镜像情况,需要先对 child
执行“右旋”,然后对 node
执行“左旋”。
四种旋转情况的选择条件:
失衡节点的平衡因子 | 子节点的平衡因子 | 应采用的旋转方法 |
---|---|---|
>1 (即左偏树) | >= 0 | 右旋 |
>1 (即左偏树) | < 0 | 先左旋后右旋 |
<−1 (即右偏树) | <= 0 | 左旋 |
<−1 (即右偏树) | > 0 | 先右旋后左旋 |
#include<bits/stdc++.h>
using namespace std;// 平衡二叉搜索树 AVL/* AVL 树节点结构体 */
struct TreeNode {int val{}; // 节点值int height = 0; // 节点高度,跟最远叶节点的距离(边)TreeNode* left{}; // 左子节点TreeNode* right{}; // 右子节点TreeNode() = default;explicit TreeNode(int x) : val(x) {}
};/* AVL 树 */
class AVLTree {private:/* 更新节点高度 */void updateHeight(TreeNode* node) {// 节点高度等于最高子树高度 + 1,这个1是包括本身node->height = max(height(node->left), height(node->right)) + 1;cout << "updateHight = " << node->height << endl;}/* 右旋操作 */TreeNode* rightRotate(TreeNode* node) {TreeNode* child = node->left;TreeNode* grandChild = child->right;// 以 child 为原点,将 node 向右旋转child->right = node;node->left = grandChild;// 更新节点高度updateHeight(node);updateHeight(child);// 返回旋转后子树的根节点return child;}/* 左旋操作 */TreeNode* leftRotate(TreeNode* node) {TreeNode* child = node->right;TreeNode* grandChild = child->left;// 以 child 为原点,将 node 向左旋转child->left = node;node->right = grandChild;// 更新节点高度updateHeight(node);updateHeight(child);// 返回旋转后子树的根节点return child;}/* 执行旋转操作,使该子树重新恢复平衡 */TreeNode* rotate(TreeNode* node) {// 获取节点 node 的平衡因子int _balanceFactor = balanceFactor(node);// 左偏树if (_balanceFactor > 1) {//判断node左节点的平衡因子if (balanceFactor(node->left) >= 0) {// 右旋return rightRotate(node);}else {// 先左旋后右旋node->left = leftRotate(node->left);return rightRotate(node);}}// 右偏树if (_balanceFactor < -1) {if (balanceFactor(node->right) <= 0) {// 左旋return leftRotate(node);}else {// 先右旋后左旋node->right = rightRotate(node->right);return leftRotate(node);}}// 平衡树,无须旋转,直接返回return node;}/* 递归插入节点(辅助方法) */TreeNode* insertHelper(TreeNode* node, int val) {if (node == nullptr)return new TreeNode(val);/* 1. 查找插入位置,并插入节点 */if (val < node->val)node->left = insertHelper(node->left, val);else if (val > node->val)node->right = insertHelper(node->right, val);elsereturn node; // 重复节点不插入,直接返回updateHeight(node); // 更新节点高度/* 2. 执行旋转操作,使该子树重新恢复平衡 */node = rotate(node);// 返回子树的根节点return node;}/* 递归删除节点(辅助方法) */TreeNode* removeHelper(TreeNode* node, int val) {if (node == nullptr)return nullptr;/* 1. 查找节点,并删除之 */if (val < node->val)node->left = removeHelper(node->left, val);else if (val > node->val)node->right = removeHelper(node->right, val);else {//该节点的度为0或1if (node->left == nullptr || node->right == nullptr) {TreeNode* child = node->left != nullptr ? node->left : node->right;// 子节点数量 = 0 ,直接删除 node 并返回if (child == nullptr) {delete node;return nullptr;}// 子节点数量 = 1 ,直接删除 nodeelse {delete node;node = child;}}else {// 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点TreeNode* temp = node->right;//node右子树的最小值(左节点上)while (temp->left != nullptr) {temp = temp->left;}int tempVal = temp->val;node->right = removeHelper(node->right, temp->val);node->val = tempVal;}}updateHeight(node); // 更新节点高度/* 2. 执行旋转操作,使该子树重新恢复平衡 */node = rotate(node);// 返回子树的根节点return node;}public:TreeNode* root; // 根节点/*构造方法*/AVLTree() : root(nullptr) {}/*析构方法*/~AVLTree() {freeMemoryTree(root);}/* Free the memory allocated to a tree */void freeMemoryTree(TreeNode* root) {if (root == nullptr) return;freeMemoryTree(root->left);freeMemoryTree(root->right);// 释放内存delete root;}/* 获取节点高度 */int height(TreeNode* node) {// 空节点高度为 -1 ,叶节点高度为 0return node == nullptr ? -1 : node->height;}/* 获取平衡因子 */int balanceFactor(TreeNode* node) {// 空节点平衡因子为 0if (node == nullptr) return 0;// 节点平衡因子 = 左子树高度 - 右子树高度return height(node->left) - height(node->right);}/* 插入节点 */void insert(int val) {root = insertHelper(root, val);}/* 删除节点 */void remove(int val) {root = removeHelper(root, val);}/* 查找节点 */TreeNode* search(int val) {TreeNode* cur = root;// 循环查找,越过叶节点后跳出while (cur != nullptr) {// 目标节点在 cur 的右子树中if (cur->val < val)cur = cur->right;// 目标节点在 cur 的左子树中else if (cur->val > val)cur = cur->left;// 找到目标节点,跳出循环elsebreak;}// 返回目标节点return cur;}//BFS遍历void BFS(TreeNode* root) {//初始化队列queue<TreeNode*> queue;queue.push(root);//保存遍历序列vector<int>vec;while (!queue.empty()) {//队列先进去的节点出队TreeNode* node = queue.front();queue.pop();//存储当前出队节点的值vec.push_back(node->val);//当前节点存在左子树,则当前节点的左节点加入队列if (node->left != nullptr) queue.push(node->left);//当前节点存在右子树,则当前节点的右节点加入队列if (node->right != nullptr) queue.push(node->right);}cout << "BFS: ";for (int v : vec) cout << v << " ";cout << endl;}};int main() {AVLTree avlTree;avlTree.insert(1);avlTree.insert(0);avlTree.insert(4);avlTree.insert(3);avlTree.insert(5);avlTree.insert(6);avlTree.BFS(avlTree.root);return 0;
}
二叉树的遍历
二叉树常见的遍历方式包括层序遍历(BFS)、深度优先搜索(DFS)、前序遍历(根左右)、中序遍历(左根右)和后序遍历(左右根)等。前序、中序和后序遍历都属于「深度优先遍历 depth-first traversal」,它体现了一种“先走到尽头,再回溯继续”的遍历方式。
广度优先遍历BFS
「层序遍历 level-order traversal」从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。层序遍历本质上属于「广度优先搜索 breadth-first Search,又称BFS」,它体现了一种“一圈一圈向外扩展”的逐层遍历方式。
深度优先搜索DFS
深度优先搜索通常基于递归实现,时间复杂O(n)
#include<bits/stdc++.h>
using namespace std;/* 二叉树节点结构体 */
struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};//保存遍历序列
vector<int>vec;//增加节点6
void addTreeNode(TreeNode* root, TreeNode* p) {TreeNode* n2 = root->left;root->left = p;p->left = n2;cout << "add node 6" << endl;
}//删除节点6
void deleTreeNode(TreeNode* root) {TreeNode* p = root->left;TreeNode* n2 = p->left;root->left = n2;cout << "dele node 6" << endl;
}//前序遍历 根左右
void preOrder(TreeNode* root) {if (root == nullptr) return;vec.push_back(root->val); //保存当前节点的值preOrder(root->left);// 左节点preOrder(root->right);//右节点
}//中序遍历 左根右
void inOrder(TreeNode* root) {if (root == nullptr) return;inOrder(root->left);//左节点vec.push_back(root->val);inOrder(root->right); //右节点
}//后序遍历
void postOrder(TreeNode* root) {if (root == nullptr) return;postOrder(root->left);postOrder(root->right);vec.push_back(root->val);
}//BFS遍历
void BFS(TreeNode* root) {//初始化队列queue<TreeNode*> queue;queue.push(root);//保存遍历序列vector<int>vec;while (!queue.empty()) {//队列先进去的节点出队TreeNode* node = queue.front();queue.pop();//存储当前出队节点的值vec.push_back(node->val);//当前节点存在左子树,则当前节点的左节点加入队列if (node->left != nullptr) queue.push(node->left);//当前节点存在右子树,则当前节点的右节点加入队列if (node->right != nullptr) queue.push(node->right);}cout << "BFS: ";for (int v : vec) cout << v << " ";cout << endl;
}int main() {/* 初始化二叉树 */TreeNode* n1 = new TreeNode(1);TreeNode* n2 = new TreeNode(2);TreeNode* n3 = new TreeNode(3);TreeNode* n4 = new TreeNode(4);TreeNode* n5 = new TreeNode(5);// 构建引用指向(即指针)n1->left = n2;n1->right = n3;n2->left = n4;n2->right = n5;BFS(n1);preOrder(n1);cout << "preOrder: ";for (int v : vec) cout << v << " ";cout << endl;vec.clear();inOrder(n1);cout << "inOrder: ";for (int v : vec) cout << v << " ";cout << endl;vec.clear();postOrder(n1);cout << "postOrder: ";for (int v : vec) cout << v << " ";cout << endl;TreeNode* addTree = new TreeNode(6);addTreeNode(n1, addTree);BFS(n1);deleTreeNode(n1);BFS(n1);return 0;
}