1.一致性哈希
(1)一致性哈希的程序实现
cpp
class PhysicalHost;
//虚拟节点
class VirtualHost
{
public:
VirtualHost(string ip,PhysicalHost *physicalHost)
:ip_(ip)
,physicalHost_(physicalHost)
{
md5_ =md5::MD5::hash32(ip);
}
//<运算符的重载
bool operator<(const VirtualHost &host) const
{
return md5_ < host.md5_;
}
//==运算符的重载
bool operator==(const VirtualHost &host) const
{
return ip_ == host.ip_;
}
//获取md5_,用于进行客服端md5比较确定其的分配到的虚拟节点
unsigned int Getmd5() const
{
return md5_;
}
//获取该虚拟节点隶属的真实物理节点
PhysicalHost* Getphysicalhost() const
{
return physicalHost_;
}
private:
string ip_; //虚拟节点的ip地址
unsigned int md5_; //虚拟节点通过哈希计算的MD5哈希值
PhysicalHost *physicalHost_; //虚拟节点隶属的物理节点
};
//物理节点
class PhysicalHost
{
public:
PhysicalHost(string ip,int size)
:ip_(ip)
{
for (int i = 0; i < size; i++)
{
virtualList_.emplace_back(ip_ + "#" + to_string(i),this);
}
}
string GetIp() const
{
return ip_;
}
const list<VirtualHost>& GetVirtualList() const
{
return virtualList_;
}
private:
string ip_;
list<VirtualHost> virtualList_;
};
//一致性哈希
class ConsistentHash
{
public:
//在一致性哈希环中添加所有的虚拟节点
void AddVirtualHost(PhysicalHost &host)
{
//获取虚拟节点表
auto list = host.GetVirtualList();
//将虚拟节点表中的所有虚拟节点添加到哈希环
for(auto virhost : list)
{
hashCircle_.insert(virhost);
}
}
//在一致性哈希环中删除所有物理节点对应的虚拟节点
void DelVirtualHost(PhysicalHost &host)
{
//获取虚拟节点表
auto list = host.GetVirtualList();
//将虚拟节点表中的所有虚拟节点删除
for(auto virhost : list)
{
auto it = hashCircle_.find(virhost);
if(it != hashCircle_.end())
{
hashCircle_.erase(virhost);
}
}
}
//获取负载的真实节点ip
string GetPhysicalHostIp(string &client)
{
unsigned int md5 =md5::MD5::hash32(client);
for(auto virhost : hashCircle_)
{
if(virhost.Getmd5() > md5)
{
return virhost.Getphysicalhost()->GetIp();
}
}
//遍历完哈希环没有对应的md5值,故放在哈希环的第一个虚拟节点上
return hashCircle_.begin()->Getphysicalhost()->GetIp();
}
private:
set<VirtualHost> hashCircle_;
};
//测试显示函数
void ShowConsistentHash(ConsistentHash &hash)
{
string cl[] = {
"192.168.1.123",
"192.168.1.12",
"192.168.1.13",
"192.168.1.64",
"192.168.1.32",
"192.168.1.21",
"192.168.1.93",
"192.168.4.12",
"192.168.1.23",
"192.168.1.92",
"192.168.1.22"
};
map<string,list<string>> umap;
for(string ip : cl)
{
string host = hash.GetPhysicalHostIp(ip);
umap[host].emplace_back(ip);
}
//访问map
for(auto pair : umap)
{
cout << "物理主机:" << pair.first << endl;
cout << "客服端数量:" << pair.second.size() << endl;
for(auto ip : pair.second)
{
cout << ip << endl;
}
cout << "---------------------------------------------"<<endl;
}
}
int main(int argc, char const *argv[])
{
//使用三个物理节点ip构造好虚拟节点列表
PhysicalHost host1("10.117.124.10",150);
PhysicalHost host2("10.117.124.20",150);
PhysicalHost host3("10.117.124.30",150);
//通过构建好的物理节点虚拟节点列表来添加哈希环的虚拟节点
ConsistentHash chash;
chash.AddVirtualHost(host1);
chash.AddVirtualHost(host2);
chash.AddVirtualHost(host3);
//测试一致性哈希表对客户端ip的主机分配
ShowConsistentHash(chash);
//测试当其中一个物理主机被删掉后的分配
chash.DelVirtualHost(host1);
ShowConsistentHash(chash);
return 0;
}
关于md5的hpp文件内容如下:
cpp
#ifndef MD5_HPP
#define MD5_HPP
#include <string>
#include <cstdint>
#include <cstring>
#include <sstream>
#include <iomanip>
#include <vector>
namespace md5 {
class MD5 {
private:
// MD5算法内部状态
uint32_t state[4]; // A, B, C, D
uint32_t count[2]; // 64位消息长度计数器
uint8_t buffer[64]; // 输入缓冲区
// 字节序转换(小端)
static uint32_t le32(uint32_t x) {
return ((x & 0x000000FF) << 24) |
((x & 0x0000FF00) << 8) |
((x & 0x00FF0000) >> 8) |
((x & 0xFF000000) >> 24);
}
// MD5基本操作
static uint32_t F(uint32_t x, uint32_t y, uint32_t z) {
return (x & y) | (~x & z);
}
static uint32_t G(uint32_t x, uint32_t y, uint32_t z) {
return (x & z) | (y & ~z);
}
static uint32_t H(uint32_t x, uint32_t y, uint32_t z) {
return x ^ y ^ z;
}
static uint32_t I(uint32_t x, uint32_t y, uint32_t z) {
return y ^ (x | ~z);
}
// 循环左移
static uint32_t rotateLeft(uint32_t x, uint32_t n) {
return (x << n) | (x >> (32 - n));
}
// 内部辅助函数
static void encode(uint8_t* output, const uint32_t* input, size_t length) {
for (size_t i = 0, j = 0; j < length; i++, j += 4) {
output[j] = static_cast<uint8_t>(input[i] & 0xff);
output[j + 1] = static_cast<uint8_t>((input[i] >> 8) & 0xff);
output[j + 2] = static_cast<uint8_t>((input[i] >> 16) & 0xff);
output[j + 3] = static_cast<uint8_t>((input[i] >> 24) & 0xff);
}
}
static void decode(uint32_t* output, const uint8_t* input, size_t length) {
for (size_t i = 0, j = 0; j < length; i++, j += 4) {
output[i] = static_cast<uint32_t>(input[j]) |
(static_cast<uint32_t>(input[j + 1]) << 8) |
(static_cast<uint32_t>(input[j + 2]) << 16) |
(static_cast<uint32_t>(input[j + 3]) << 24);
}
}
static void transform(uint32_t state[4], const uint8_t block[64]) {
// MD5算法核心变换函数
uint32_t a = state[0];
uint32_t b = state[1];
uint32_t c = state[2];
uint32_t d = state[3];
uint32_t x[16];
// 将64字节块解码为16个32位字
decode(x, block, 64);
// MD5常量表
static const uint32_t T[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
static const uint32_t S[64] = {
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21
};
// 第1轮
for (uint32_t i = 0; i < 16; ++i) {
uint32_t f = F(b, c, d);
uint32_t g = i;
uint32_t temp = d;
d = c;
c = b;
b = b + rotateLeft(a + f + T[i] + x[g], S[i]);
a = temp;
}
// 第2轮
for (uint32_t i = 16; i < 32; ++i) {
uint32_t f = G(b, c, d);
uint32_t g = (5 * i + 1) % 16;
uint32_t temp = d;
d = c;
c = b;
b = b + rotateLeft(a + f + T[i] + x[g], S[i]);
a = temp;
}
// 第3轮
for (uint32_t i = 32; i < 48; ++i) {
uint32_t f = H(b, c, d);
uint32_t g = (3 * i + 5) % 16;
uint32_t temp = d;
d = c;
c = b;
b = b + rotateLeft(a + f + T[i] + x[g], S[i]);
a = temp;
}
// 第4轮
for (uint32_t i = 48; i < 64; ++i) {
uint32_t f = I(b, c, d);
uint32_t g = (7 * i) % 16;
uint32_t temp = d;
d = c;
c = b;
b = b + rotateLeft(a + f + T[i] + x[g], S[i]);
a = temp;
}
// 更新状态
state[0] += a;
state[1] += b;
state[2] += c;
state[3] += d;
// 清空敏感数据
memset(x, 0, sizeof(x));
}
// 完成计算
void final(uint8_t digest[16]) {
static uint8_t padding[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
// 保存消息长度(原始长度,不包括填充)
uint8_t bits[8];
encode(bits, count, 8);
// 填充到448位(56字节)模512
uint32_t index = (count[0] >> 3) & 0x3f;
uint32_t padLen = (index < 56) ? (56 - index) : (120 - index);
update(padding, padLen);
// 添加原始长度(64位)
update(bits, 8);
// 生成最终摘要
encode(digest, state, 16);
// 清空敏感数据
memset(buffer, 0, sizeof(buffer));
memset(count, 0, sizeof(count));
}
// 转换为十六进制字符串
static std::string toHexString(const uint8_t* data, size_t length) {
std::ostringstream oss;
oss << std::hex << std::setfill('0');
for (size_t i = 0; i < length; ++i) {
oss << std::setw(2) << static_cast<int>(data[i]);
}
return oss.str();
}
public:
MD5() {
reset();
}
void reset() {
// MD5初始化常量
state[0] = 0x67452301;
state[1] = 0xefcdab89;
state[2] = 0x98badcfe;
state[3] = 0x10325476;
count[0] = count[1] = 0;
memset(buffer, 0, sizeof(buffer));
}
void update(const uint8_t* data, size_t length) {
// 计算已有字节数
uint32_t index = (count[0] >> 3) & 0x3f;
// 更新长度计数器
uint32_t oldCount0 = count[0];
count[0] += static_cast<uint32_t>(length << 3);
if (count[0] < oldCount0) {
count[1]++;
}
count[1] += static_cast<uint32_t>(length >> 29);
uint32_t partLen = 64 - index;
size_t i = 0;
if (length >= partLen) {
memcpy(&buffer[index], data, partLen);
transform(state, buffer);
for (i = partLen; i + 63 < length; i += 64) {
transform(state, &data[i]);
}
index = 0;
}
memcpy(&buffer[index], &data[i], length - i);
}
void update(const std::string& data) {
update(reinterpret_cast<const uint8_t*>(data.data()), data.length());
}
// 静态接口 - 直接计算字符串的MD5
static std::string hexDigest(const std::string& message) {
MD5 md5;
md5.update(message);
return md5.finalizeHex();
}
static std::vector<uint8_t> rawDigest(const std::string& message) {
MD5 md5;
md5.update(message);
return md5.finalizeRaw();
}
static uint64_t hash64(const std::string& message) {
MD5 md5;
md5.update(message);
return md5.finalize64();
}
static uint32_t hash32(const std::string& message) {
MD5 md5;
md5.update(message);
return md5.finalize32();
}
// 流式处理接口
std::string finalizeHex() {
uint8_t digest[16];
final(digest);
reset();
return toHexString(digest, 16);
}
std::vector<uint8_t> finalizeRaw() {
uint8_t digest[16];
final(digest);
reset();
return std::vector<uint8_t>(digest, digest + 16);
}
uint64_t finalize64() {
uint8_t digest[16];
final(digest);
reset();
// 使用前8个字节生成64位哈希
uint64_t hash = 0;
for (int i = 0; i < 8; ++i) {
hash = (hash << 8) | digest[i];
}
return hash;
}
uint32_t finalize32() {
uint8_t digest[16];
final(digest);
reset();
// 使用前4个字节生成32位哈希
uint32_t hash = 0;
for (int i = 0; i < 4; ++i) {
hash = (hash << 8) | digest[i];
}
return hash;
}
};
} // namespace md5
// 提供全局函数接口
namespace md5_global {
inline std::string md5(const std::string& message) {
return md5::MD5::hexDigest(message);
}
inline uint32_t md5_32(const std::string& message) {
return md5::MD5::hash32(message);
}
inline uint64_t md5_64(const std::string& message) {
return md5::MD5::hash64(message);
}
}
#endif // MD5_HPP
(2)相关知识
一致性哈希是负载均衡器用来解决负载请求映射到服务器的一个问题,对于这个映射问题还有轮询,权重比,最小连接,普通哈希等方法,但是以上的方法当其中一个服务器崩溃或者添加一个新的服务器时,原来使用以上提及到的一些方法来进行分映射时,本来应该被映射到A服务器的负载被映射到了B服务器就会导致一些请求响应不到等问题。但是一致性哈希的使用可以在一个服务器崩溃或者添加一个新服务器时不会影响到原来负载的映射关系。
在上面程序中的实现原理就是通过一个逻辑结构的哈希环来进行一致性哈希,每一个负载的ip使用md5转化成32个十六进制的字符我们取其四个字节长度数据用于进行哈希函数求值,通过除留余数法得到索引值,然后通过一致性哈希的哈希环原理将器映射到对应的虚拟地址节点上,然后通过虚拟节点找到真实物理节点地址,进行分配。
2.BST树
(1)BST树类的程序实现
cpp
#include <iostream>
#include <functional>
#include <stack>
#include <queue>
using namespace std;
template <typename T, typename Comp = less<T>>
class BSTree
{
public:
BSTree()
: root_(nullptr)
{
}
~BSTree() {}
// 插入非递归实现
void Insert(T val)
{
if (root_ == nullptr)
{
root_ = new Node(val);
}
Node *parent = nullptr;
Node *cur = root_;
while (cur != nullptr)
{
if (cur->data_ == val)
{
return;
}
else if (comp_(cur->data_, val))
{
parent = cur;
cur = cur->right_;
}
else
{
parent = cur;
cur = cur->left_;
}
}
cur = new Node(val);
if (comp_(parent->data_, val))
{
parent->right_ = cur;
}
else
{
parent->left_ = cur;
}
}
// 插入的递归实现
void InsertRecurtion(T val)
{
root_ = InsertRecurtion(root_, val);
}
// 删除的非递归实现
void erase(T val)
{
if (root_ == nullptr)
{
return;
}
Node *parent = nullptr;
Node *cur = root_;
// 找到要删除的节点
while (cur != nullptr)
{
if (cur->data_ == val)
{
break;
}
else if (comp_(cur->data_, val))
{
parent = cur;
cur = cur->right_;
}
else
{
parent = cur;
cur = cur->left_;
}
}
// 要删除的节点是第三种情况删除的是有两个孩子节点的节点->删除前驱节点或者后继节点
if (cur->left_ != nullptr && cur->right_ != nullptr)
{
parent = cur;
Node *pre = cur->left_;
while (pre->right_ != nullptr)
{
parent = pre;
pre = pre->right_;
}
cur->data_ = pre->data_; // 变成了第一种或第二种情况
cur = pre;
}
// 要删除的节点是第2种情况删除的是有一个孩子节点的节点->将该孩子节点连接到对应的要删除节点的父节点上
Node *child = cur->left_;
if (child == nullptr)
{
child = cur->right_;
}
// 要删除的节点是第一种和第二种情况的统一处理->将该节点的"孩子节点"连接到要删除的父节点上
// 如果要删除的节点是根节点
if (parent == nullptr)
{
root_ = child;
}
// 要删除的节点不是根节点
else
{
if (cur == parent->right_) // 注意要判断的是要删除的节点是父节点的左孩子还是右孩子
{
parent->right_ = child;
}
else
{
parent->left_ = child;
}
}
delete cur;
}
// 删除的递归实现
void eraseRecurtion(T val)
{
root_ = eraseRecurtion(root_, val);
}
// 查询的非递归实现
bool find(T val)
{
Node *cur = root_;
while (cur != nullptr)
{
if (cur->data_ == val)
{
return true;
}
else if (comp_(cur->data_, val))
{
cur = cur->right_;
}
else
{
cur = cur->left_;
}
}
return false;
}
// 查询的递归实现
bool findRecurtion(T val)
{
return findRecurtion(root_, val) != nullptr;
}
// 前序遍历的递归实现
void preorder()
{
cout << "[递归]前序遍历:";
preorder(root_);
cout << endl;
}
// 前序遍历的非递归实现
void n_preorder()
{
cout << "[非递归]前序遍历:";
if (root_ == nullptr)
{
return;
}
stack<Node *> s;
s.push(root_);
Node *top = nullptr;
while (!s.empty())
{
top = s.top();
cout << top->data_ << " ";
s.pop();
if (top->right_ != nullptr)
{
s.push(top->right_);
}
if (top->left_ != nullptr)
{
s.push(top->left_);
}
}
cout << endl;
}
// 中序遍历的递归实现
void inorder()
{
cout << "[递归]中序遍历:";
inorder(root_);
cout << endl;
}
// 中序遍历的非递归实现
void n_inorder()
{
cout << "[非递归]中序遍历:";
if (root_ == nullptr)
{
return;
}
stack<Node *> s;
Node *cur = root_;
while (!s.empty() || cur != nullptr)
{
if (cur != nullptr)
{
s.push(cur);
cur = cur->left_;
}
else
{
Node *top = s.top();
s.pop();
cout << top->data_ << " ";
cur = top->right_;
}
}
cout << endl;
}
// 后序遍历递归实现
void postorder()
{
cout << "[递归]后序遍历:";
postorder(root_);
cout << endl;
}
// 后序遍历非递归实现
void n_postorder()
{
// 后序遍历非递归实现思路:将LRV转化成VRL
cout << "[非递归]后序遍历:";
stack<Node *> s1;
stack<Node *> s2;
s1.push(root_);
while (!s1.empty())
{
Node *top = s1.top();
s1.pop();
s2.push(top);
if (top->left_ != nullptr)
{
s1.push(top->left_);
}
if (top->right_ != nullptr)
{
s1.push(top->right_);
}
}
while (!s2.empty())
{
cout << s2.top()->data_ << " ";
s2.pop();
}
cout << endl;
}
// 层序遍历的非递归实现
void levelorder()
{
cout << "[递归]层序遍历:";
int h = high();
for (int i = 0; i < h; i++)
{
levelorder(root_, i);
}
cout << endl;
}
// 层序遍历的非递归实现
void n_levelorder()
{
cout << "[非递归]层序遍历:";
if (root_ == nullptr)
{
return;
}
queue<Node *> que;
que.push(root_);
while (!que.empty())
{
Node *front = que.front();
que.pop();
cout << front->data_ << " ";
if (front->left_ != nullptr)
{
que.push(front->left_);
}
if (front->right_ != nullptr)
{
que.push(front->right_);
}
}
cout << endl;
}
// 递归求二叉树高度
int high()
{
return high(root_);
}
// 递归求二叉树节点个数
int number()
{
return number(root_);
}
public:
struct Node
{
Node(T data = 0)
: data_(data), left_(nullptr), right_(nullptr)
{
}
T data_;
Node *left_;
Node *right_;
};
Node *root_;
Comp comp_;
// 前序遍历接口 VLR
void preorder(Node *p)
{
if (p != nullptr)
{
cout << p->data_ << " ";
preorder(p->left_);
preorder(p->right_);
}
}
// 中序遍历接口 LVR
void inorder(Node *p)
{
if (p != nullptr)
{
inorder(p->left_);
cout << p->data_ << " ";
inorder(p->right_);
}
}
// 后序遍历接口 LRV
void postorder(Node *p)
{
if (p != nullptr)
{
postorder(p->left_);
postorder(p->right_);
cout << p->data_ << " ";
}
}
// 求二叉树层数递归接口
int high(Node *p)
{
if (p == nullptr)
{
return 0;
}
int left = high(p->left_);
int right = high(p->right_);
return (left > right ? left : right) + 1;
}
// 求二叉树节点个数递归接口
int number(Node *p)
{
if (p == nullptr)
{
return 0;
}
int left = number(p->left_);
int right = number(p->right_);
return left + right + 1;
}
// 层序遍历的递归接口
void levelorder(Node *p, int i)
{
if (p == nullptr)
{
return;
}
if (i == 0)
{
cout << p->data_ << " ";
}
levelorder(p->left_, i - 1);
levelorder(p->right_, i - 1);
}
// 递归删除接口实现
Node *eraseRecurtion(Node *node, T val)
{
if (node == nullptr)
{
return nullptr;
}
if (node->data_ == val) // 找到了要删除的节点
{
// 第三种情况
if (node->left_ != nullptr && node->right_ != nullptr)
{
// 找出该节点的前驱节点
Node *pre = node->left_;
while (pre->right_ != nullptr)
{
pre = pre->right_;
}
// 将前驱节点的值赋给当前要删除的节点
node->data_ = pre->data_;
// 删除前驱节点
node->left_ = eraseRecurtion(node->left_, node->data_); // 变成了第一二种情况
}
else // 注意这里的情况三与情况一二就不像递归一样是将情况三往情况一二转化然后进行一二操作
// ,递归这里是需要进行分类判断处理,因为情况三是进行了递归然后进行一二情况处理了的
{ // 若这里不进行分类则情况三经过处理归回来的处理好的数又会再进行情况一二的处理然后导致出现问题
// 第一二种情况
if (node->left_ != nullptr)
{
Node *left = node->left_;
delete node;
return left;
}
else if (node->right_ != nullptr)
{
Node *right = node->right_;
delete node;
return right;
}
else
{
return nullptr;
}
}
}
else if (comp_(node->data_, val))
{
node->right_ = eraseRecurtion(node->right_, val);
}
else
{
node->left_ = eraseRecurtion(node->left_, val);
}
return node;
}
// 递归插入接口实现
Node *InsertRecurtion(Node *node, T val)
{
if (node == nullptr)
{
return new Node(val);
}
if (node->data_ == val)
{
return node;
}
else if (comp_(node->data_, val))
{
node->right_ = InsertRecurtion(node->right_, val);
}
else
{
node->left_ = InsertRecurtion(node->left_, val);
}
return node;
}
// 递归查询接口实现
Node *findRecurtion(Node *node, T val) // 对于递归的查询来说返回值应该是查询到的节点的地址
{
if (node == nullptr)
{
return nullptr;
}
if (node->data_ == val)
{
return node;
}
else if (comp_(node->data_, val))
{
return findRecurtion(node->right_, val);
}
else
{
return findRecurtion(node->left_, val);
}
}
};
其中的BST类的成员函数插入,删除,查询,前序遍历,中序遍历,后序遍历,层序遍历均使用了递归和非递归实现,其中还有取树的高度,树的节点个数等方法。
(2)相关知识
求BST区间[i,j]内的节点问题
cpp
// 求BST的在区间[i,j]的节点
void findValues(vector<T> &vec, int i, int j)
{
return findValues(root_, vec, i, j);
}
// 区间递归接口
void findValues(Node *node, vector<T> &vec, int i, int j)
{
if (node != nullptr)
{
if (node->data_ >= i)
{
findValues(node->left_, vec, i, j); // L
}
// V
if (node->data_ >= i && node->data_ <= j)
{
vec.push_back(node->data_);
}
if (node->data_ <= j)
{
findValues(node->right_, vec, i, j); // R
}
}
}
判断一个树是不是BST树
cpp
// 判断一个二叉树是不是一个BST树
bool isBSTree()
{
Node *pre = nullptr;
return isBSTree(root_, pre);
}
// 判断是否为bst树的递归接口
bool isBSTree(Node *node, Node *pre)
{
if (node == nullptr)
{
return true;
}
if (!isBSTree(node->left_, pre)) // L
{
return false;
}
// V
if (pre != nullptr)
{
return node->data_ > pre->data_;
}
pre = node;
return isBSTree(node->right_, pre); // R
}
判断一个树是不是另一个数的子树
cpp
// 判断是否是子树问题
bool isChildTree(BSTree<T, Comp> &child)
{
if (child.root_ == nullptr)
{
return true;
}
// 在该树中找到子树的根节点
Node *cur = root_;
while (cur != nullptr)
{
if (cur->data_ == child.root_->data_)
{
break;
}
else if (comp_(cur->data_, child.root_->data_))
{
cur = cur->right_;
}
else
{
cur = cur->left_;
}
}
if (cur == nullptr)
{
return false;
}
// 两树从根节点开始比较
return isChildTree(cur, child.root_);
}
// 判断是否子树的递归接口
bool isChildTree(Node *father, Node *child)
{
// 递归结束条件
if (father == nullptr && child == nullptr)
{
return true;
}
if (father == nullptr)
{
return false;
}
if (child == nullptr)
{
return true;
}
// 判断
if (father->data_ != child->data_) // 这里是递归进行的前置判断条件,可以减少递归的次数
{ // 当两数的根节点的数据不相同了就证明已经不是子树了可以直接返回,注意不要判断相等
return false;
}
return isChildTree(father->left_, child->left_) && isChildTree(father->right_, child->right_);
}
判断公共祖先lca问题
cpp
// 判断lca,两个节点最近的公共祖先问题
int getLCA(int i, int j)
{
Node *node = getLCA(root_, i, j);
if (node == nullptr)
{
throw "no lca!";
}
return node->data_;
}
// LCA问题递归接口
Node *getLCA(Node *node, int val1, int val2)
{
if (node == nullptr)
{
return nullptr;
}
if (comp_(val1, node->data_) && comp_(val2, node->data_))
{
return getLCA(node->left_, val1, val2);
}
else if (comp_(node->data_, val1) && comp_(node->data_, val2))
{
return getLCA(node->right_, val1, val2);
}
else
{
return node;
}
}
镜像翻转实现
cpp
// BSTree镜像翻转问题
void mirror()
{
mirror(root_);
}
// 镜像反转问题递归接口->就是将当前节点的左右子树进行交换即可
void mirror(Node *node)
{
if (node == nullptr)
{
return;
}
// V
Node *tmp = node->left_;
node->left_ = node->right_;
node->right_ = tmp;
mirror(node->left_); // L
mirror(node->right_); // R
}
镜像对称问题
cpp
// 二叉树镜像对称问题
bool mirror_symmetry()
{
return mirror_symmetry(root_->left_,root_->right_);
}
// 镜像对称问题递归接口
bool mirror_symmetry(Node *node1,Node *node2)
{
if(node1 == nullptr && node2 == nullptr)
{
return true;
}
if(node1 == nullptr || node2 == nullptr)
{
return false;
}
if(node1->data_ != node2->data_)
{
return false;
}
return mirror_symmetry(node1->right_,node2->left_)
&& mirror_symmetry(node1->left_,node2->right_);
}
其中我也提供一些测试程序可以使用:
cpp
// 判断是否是子树的测试函数
void test()
{
BSTree<int> bst;
int arr[] = {58, 24, 67, 0, 34, 62, 69, 5, 41, 64, 78};
for (int val : arr)
{
bst.InsertRecurtion(val);
}
BSTree<int> bst1;
int brr[] = {67, 62, 69, 60};
for (int val : brr)
{
bst1.InsertRecurtion(val);
}
cout << bst.isChildTree(bst1) << endl;
}
// 判断LCA问题测试函数
void test1()
{
BSTree<int> bst;
int arr[] = {58, 24, 67, 0, 34, 62, 69, 5, 41, 64, 78};
for (int val : arr)
{
bst.InsertRecurtion(val);
}
cout << bst.getLCA(34, 41) << endl;
}
// 镜像反转问题测试函数
void test2()
{
BSTree<int> bst;
int arr[] = {58, 24, 67, 0, 34, 62, 69, 5, 41, 64, 78};
for (int val : arr)
{
bst.InsertRecurtion(val);
}
bst.inorder();
bst.mirror();
bst.inorder();
}
// 镜像对称问题测试函数
void test3()
{
using Node = BSTree<int>::Node;
BSTree<int> bst;
bst.root_ = new Node(40);
Node *node1 = new Node(20);
Node *node2 = new Node(20);
Node *node3 = new Node(10);
Node *node4 = new Node(10);
Node *node5 = new Node(15);
Node *node6 = new Node(15);
bst.root_->left_ = node1;
bst.root_->right_ = node2;
node1->left_ = node3;
node1->right_ = node5;
node2->right_ = node4;
//node2->left_ = node6;
cout << bst.mirror_symmetry() << endl;
}
(3)关注点
1.在递归删除接口实现中,其中第三种情况即要删除的节点有左右孩子节点其中我们的处理方法就是找出其中的前驱节点将前驱节点的值赋值给要删除的节点然后将前驱节点删除,即可转化成第一二种情况即要删除的节点只有一个孩子节点或者没有孩子节点。但是这里和非递归处理不同的是在同一个函数中非递归的时在同一个函数种处理所以是将其转化成第一二种情况后继续执行第一二中的处理方式,但是在递归中我们实现的是转化后将该节点的左孩子节点当作树的根节点然后进行删除,所以此时就要与第一二种情况进行分类处理。
2.对于递归查询函数来说,其返回值应该是查询到的节点的地址,而不是像递归的删除返回的是一当前节点当作根节点的树进行删除,返回的当前的节点。
3.在递归中我们可以在递归前进行一个数据的判断,对于这个判断我们要判断的是递归不执行的条件。