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
#include
using namespace std;
int main() {
//初始化
vectorvec = { 1,2 };
//构建一个5×5,且所有元素均为0的二维动态数组
vector> arrVec(5, vector(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 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
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」是一种遵循先进后出的逻辑的线性数据结构。栈的底层由链表实现。
#include
using namespace std;
int main() {
/* 初始化栈 */
stack 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(); //非空返回false
cout << "empty = " << empty << endl;
return 0;
}
#include
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 toVector() {
ListNode* node = stackTop;
vector 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 ->1
listStack.push(1);
listStack.push(3);
listStack.push(2);
listStack.push(5);
listStack.push(8);
vector arr = listStack.toVector();
for (int v : arr) cout << v << " ";
cout << endl;
listStack.pop();
cout << "after pop:" << endl;
vector 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」是一种遵循先进先出规则的线性数据结构。队列的底层由链表实现。
#include
using namespace std;
int main() {
/* 初始化队列 */
queue 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(); //非空返回false
cout << "size = " << size << " empty = " << empty << endl;
return 0;
}
链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。
#include
using namespace std;
/* 基于链表实现的队列 */
class LinkedListQueue {
private:
//链表数据结构
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}; //构造方法
};
ListNode* front, * rear; // 头节点 front ,尾节点 rear
int 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) {
// 尾节点后添加 num
ListNode* 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 toVector() {
ListNode* node = front;
vector 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 arr = queue.toVector();
for (int v : arr) cout << v << " ";
cout << endl;
queue.pop();
cout << "after pop:" << endl;
vector 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;
}
在头部和尾部都允许执行元素的添加或删除操作。
pushFirst() 将元素添加至队首
pushLast() 将元素添加至队尾
popFirst() 删除队首元素
popLast() 删除队尾元素
peekFirst() 访问队首元素
peekLast() 访问队尾元素
#include
using namespace std;
int main() {
dequedeque;
//1->2->3->4
deque.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
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 ,尾节点 rear
int 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 都指向 node
if (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 toVector() {
DoublyListNode* node = front;
vector 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->5
doubleQueue.pushFirst(2); //队列头部添加元素
doubleQueue.pushFirst(1);
doubleQueue.pushFirst(0);
doubleQueue.pushLast(3); //队列尾部添加元素
doubleQueue.pushLast(4);
doubleQueue.pushLast(5);
vectordqueue = 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(); //非空返回false
cout << "size = " << size << " empty = " << empty << endl;
return 0;
}
哈希表中进行增删查改的时间复杂度都是O(1)
#include
using namespace std;
int main() {
/* 初始化哈希表 */
unordered_map map;
/* 添加操作 */
// 在哈希表中添加键值对 (key, value)
map[1] = "小哈";
map[2] = "小啰";
map[3] = "小算";
map[4] = "小法";
map[5] = "小鸭";
/* 查询操作 */
// 向哈希表输入键 key ,得到值 value
string name = map[1];
/* 删除操作 */
// 在哈希表中删除键值对 (key, value),参数是key
map.erase(2);
//查找元素,参数是key,如果key不存在,find会返回end
if (map.find(3) != map.end()) cout << "find isExistKey true " << endl;
else cout << "find isExistKey false " << endl;
//查找元素,参数是key,存在返回1,不存在返回0
int isExistKey = map.count(5);
cout << "count isExistKey = " << isExistKey << " (存在返回1,不存在返回0)" << endl;
/* 遍历哈希表 */
// 遍历键值对 key->value
for (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
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 buckets; //定义数组桶
int capacity = 100; // 数组桶大小
public:
ArrayHashMap() {
cout << "调用了 ArrayHashMap 的构造方法" << endl;
// 初始化数组,包含 100 个桶
buckets = vector(capacity);
}
~ArrayHashMap() {
cout << "调用了 ArrayHashMap 的析构方法" << endl;
// 释放内存
for (const auto& bucket : buckets) {
delete bucket;
}
buckets.clear();
}
/* 哈希函数 */
int hashFunc(int key) {
//对key值取模,如 4 % 100 = 4
int 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对应的pair
return 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);
// 先释放内存,在置为 nullptr
delete buckets[index];
buckets[index] = nullptr;
}
/* 获取所有键值对 */
vector pairSet() {
vector pairSet;
for (Pair* pair : buckets) {
if (pair != nullptr) {
pairSet.push_back(pair);
}
}
return pairSet;
}
/* 获取所有键 */
vector keySet() {
vector keySet;
for (Pair* pair : buckets) {
if (pair != nullptr) {
keySet.push_back(pair->key);
}
}
return keySet;
}
/* 获取所有值 */
vector valueSet() {
vector 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 vps = map.pairSet();
for (Pair* vp : vps) {
cout << "key = " << vp->key << " value = " << vp->val << endl;
}
return 0;
}
通常情况下哈希函数的输入空间远大于输出空间,改良哈希表数据结构,使得哈希表可以在存在哈希冲突时正常工作。即当哈希冲突比较严重时,才执行扩容操作。哈希表的结构改良方法主要包括“链式地址”和“开放寻址”。
在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 separate chaining」将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。
key
,经过哈希函数得到桶索引,即可访问链表头节点,然后遍历链表并对比 key
以查找目标键值对。#include
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/3
int extendRatio; // 扩容倍数。以下实现包含哈希表扩容方法。当负载因子超过2/3时,我们将哈希表扩容至2倍。
//[[{key,val},{key,val},{key,val}...],[{key,val},{key,val},{key,val}...],[{key,val},{key,val},{key,val}...]]
vector> 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> 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 则返回对应 val
for (Pair* pair : buckets[index]) {
if (pair->key == key) {
return pair->val;
}
}
cout << "Not find the key = " << key << "data!" << endl;
// 若未找到 key 则返回 nullptr
return nullptr;
}
/* 删除操作 */
void remove(int key) {
int index = hashFunc(key);
vector& 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);
vectorbucket = buckets[index];
return bucket.size();
}
/* 打印哈希表 */
void print() {
for (vector& 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
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 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索引被标记了TOMBSTONE
if (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标记的,则返回对应 val
if (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);
// 若找到键值对,删除它,然后标记TOMBSTONE
if (buckets[index] != nullptr && buckets[index] != TOMBSTONE) {
delete buckets[index];
buckets[index] = TOMBSTONE;
size--;
}
}
int sizeMap() {
return capacity;
}
/* 扩容哈希表 */
void extend() {
// 暂存原哈希表
vector bucketsTmp = buckets;
// 初始化扩容后的新哈希表
capacity *= extendRatio;
buckets = vector(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;
}
每种哈希算法的最后一步都是对大质数 1000000007 取模,以确保哈希值在合适的范围内。
当我们使用大质数作为模数时,可以最大化地保证哈希值的均匀分布。因为质数不会与其他数字存在公约数,可以减少因取模操作而产生的周期性模式,从而避免哈希冲突。
总而言之,通常选取质数作为模数,并且这个质数最好足够大,以尽可能消除周期性模式,提升哈希算法的稳健性。
#include
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」。
在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树。
完美二叉树又称满二叉树,「完美二叉树 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 时,我们无法直接删除它,而需要使用一个节点替换该节点。由于要保持二叉搜索树“左 < 根 < 右”的性质,因此这个节点可以是右子树的最小节点或左子树的最大节点。
假设我们选择右子树的最小节点(即中序遍历的下一个节点),则删除操作流程如下。
tmp
。tmp
的值覆盖待删除节点的值,并在树中递归删除节点 tmp
。二叉搜索树中进行中序遍历时,总是会优先遍历下一个最小节点,从而得出一个重要性质:二叉搜索树的中序遍历序列是升序的
#include
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 queue;
queue.push(root);
//保存遍历序列
vectorvec;
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);
//删除节点4
deleSearchTreeNode(n8, 4);
BFS(n8);
return 0;
}
搜索二叉树的查找元素、插入元素、删除元素的时间复制复杂度均为O(logn)
确保在持续添加和删除节点后,AVL 树不会退化,从而使得各种操作的时间复杂度保持在 O(logn) 级别。换句话说,在需要频繁进行增删查改操作的场景中,AVL 树能始终保持高效的数据操作性能,具有很好的应用价值。
AVL 树既是二叉搜索树也是平衡二叉树,同时满足这两类二叉树的所有性质,因此也被称为「平衡二叉搜索树 balanced binary search tree」。
“节点高度”是指从该节点到最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 0 ,而空节点的高度为 -1 。
节点的「平衡因子 balance factor」定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 0 。节点平衡因子 = 左子树高度 - 右子树高度
旋转操作既能保持“二叉搜索树”的性质,也能使树重新变为“平衡二叉树”,将平衡因子绝对值 >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
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);
else
return 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或1
if (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 ,直接删除 node
else {
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 ,叶节点高度为 0
return node == nullptr ? -1 : node->height;
}
/* 获取平衡因子 */
int balanceFactor(TreeNode* node) {
// 空节点平衡因子为 0
if (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;
// 找到目标节点,跳出循环
else
break;
}
// 返回目标节点
return cur;
}
//BFS遍历
void BFS(TreeNode* root) {
//初始化队列
queue queue;
queue.push(root);
//保存遍历序列
vectorvec;
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」,它体现了一种“先走到尽头,再回溯继续”的遍历方式。
「层序遍历 level-order traversal」从顶部到底部逐层遍历二叉树,并在每一层按照从左到右的顺序访问节点。层序遍历本质上属于「广度优先搜索 breadth-first Search,又称BFS」,它体现了一种“一圈一圈向外扩展”的逐层遍历方式。
深度优先搜索通常基于递归实现,时间复杂O(n)
#include
using namespace std;
/* 二叉树节点结构体 */
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
//保存遍历序列
vectorvec;
//增加节点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 queue;
queue.push(root);
//保存遍历序列
vectorvec;
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;
}