一.搜索二叉树(排序)
1.实现
左边比根小,右边比根大(写的时候一定要画图)
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
K _key;
BSTreeNode(const K& key)
:_left(nullptr),_right(nullptr),_key(key)
{}
};
template<class K>
class BSTree //BinarySearchTree
{
typedef BSTreeNode<K> Node;
public:
BSTree(const BSTree<K>& t)
{
_root = copy(t._root);
}
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key);
if (key > parent->_key)
{
//cur = parent->_right; 反了
parent->_right = cur; //让有指向的连接上没指向的
}
else if (key < parent->_key)
{
parent->_left = cur;
}
return true;
}
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_key)
{
cur = cur->_right;
}
else if(key < cur->_key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void InOrder()
{
_InOrder(_root); //在外面套上一层不需要传参的函数,就可以解决类外无法调用的问题
}
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
//先找
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
//删除cur
//1.左为空(托孤)
if (cur->_left == nullptr)
{
if (parent == nullptr)
{
_root = cur->_right; //另外一半为空,更新根 _root
delete cur;
break;
}
if (parent->_left == cur)
{
parent->_left = cur->_right;
}
else //parent->_right == cur
{
parent->_right = cur->_right;
}
delete cur;
}
//2.右为空(托孤)
else if (cur->_right == nullptr)
{
if (parent == nullptr)
{
_root = cur->_left; //另外一半为空,更新根 _root
delete cur;
break;
}
if (parent->_right == cur)
{
parent->_right = cur->_left;
}
else //parent->_right != cur 左边等或者两边都不等
{
parent->_left = cur->_left;
}
delete cur;
}
//3.左右都不为空(找替代节点:右数最小节点或左数最大节点)
else
{
Node* Right_min = cur->_right;
Node* P_Right_min = cur; //代替节点的父亲,因为代替节点也可能有孩子需要托孤
while (Right_min->_left) //右树的最小结点
{
P_Right_min = Right_min;
Right_min = Right_min->_left;
}
cur->_key = Right_min->_key; //这里的cur不能被P_Right_min代替
if (P_Right_min->_left == Right_min) //Right_min这边的那一堆都比P_Right_min小
{
P_Right_min->_left = Right_min->_right;
}
else //父亲的左边并不是Right_min,而是别的 (类似于当cur为头结点时)
{
P_Right_min->_right = Right_min->_right;
}
delete Right_min;
}
return true;
}
}
return false;
}
//递归版本:
bool _FindR(Node* root, const K& key) //递归
{
if (root == nullptr)
{
return false;
}
if (key > root->_key)
{
return _FindR(root->_right, key);
}
else if (root->_key < key)
{
return _FindR(root->_left, key);
}
else
{
return true;
}
}
bool FindR(const K& key) //搜索递归
{
return _FindR(_root,key);
}
bool _InsertR(Node*& root,const K& key) //这个引用非常关键
{
if (root == nullptr)
{
root = new Node(key); //这个root就是_root->left 的别名,所以可以不需要额外的链接步骤
return true;
}
if (key > root->_key) //要插入的数更大
{
return _InsertR(root->_right, key);
}
else if (key < root->_key)
{
return _InsertR(root->_left, key);
}
else
{
return false;
}
}
bool InsertR(const K& key) //插入递归
{
return _InsertR(_root, key);
}
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (key > root->_key)
{
return _EraseR(root->_right, key);
}
else if (key < root->_key)
{
return _EraseR(root->_left, key);
}
else
{
//删除
Node* del = root; //del指向要删除的结点
//1.左为空
if (root->_left == nullptr)
{
root = root->_right; //root是“10的右指针”的别名,root存的值是14的地址
}
//2.右为空
else if (root->_right == nullptr)
{
root = root->_left; //这里的 = 理解成赋值(改变指向),不是指向,指针赋值,把root->_left赋给root
}
//3.左右都不为空
else
{
Node* maxleft = root->_left;
while (maxleft->_right)
{
maxleft = maxleft->_right;
}
swap(maxleft->_key , root->_key); //注意这里是交换不是单纯的赋值
return _EraseR(root->_left, key); //再转化成在左子树中删除maxleft结点,最后一定是右为空的,注意key是换过来的那个值
}
delete del;
return true;
}
}
Node* copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* newRoot = new Node(root->_key);
newRoot->_left = copy(root->_left); //该结点的左连接到左结点上
newRoot->_right = copy(root->_right);
return newRoot;
}
void Destroy(Node* root) //后序的递归
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
~BSTree()
{
Destroy(_root);
_root = nullptr;
}
private:
Node* _root = nullptr;
};
2.应用
3.二叉树的非递归写法:拆成左路结点和其对应的右子树
当从栈中取出左路节点时,就意味着这个结点的左子树访问完了
例题:
①前序:根 左子树 右子树
class Solution
{
public:
//非递归写法
vector<int> preorderTraversal(TreeNode* root)
{
stack<TreeNode*> st;
TreeNode* cur=root;
vector<int> v;
while(cur!=nullptr || !st.empty())
{
//开始访问一棵树
//1.左路结点
//2.左路节点对应的右子树
while(cur)
{
v.push_back(cur->val);
st.push(cur); //入栈是为了更好访问它的右子树
cur=cur->left;
}
//开始访问右子树
TreeNode* top=st.top();
st.pop();
//子问题访问右子树
cur=top->right;
}
return v;
}
};
②中序:左子树 根 右子树
class Solution
{
public:
vector<int> inorderTraversal(TreeNode* root)
{
stack<TreeNode*> st;
TreeNode* cur=root;
vector<int> v;
while(cur || !st.empty())
{
//1.先访问左路节点
while(cur)
{
st.push(cur);
cur=cur->left;
}
//从栈里取到左路节点,说明他的左子树访问完了
TreeNode* top=st.top();
st.pop();
v.push_back(top->val);
//访问左路节点的右子树
cur=top->right;
}
return v;
}
};
中序与前序不同在 v.push_back(cur->val); 出现的位置不一样。
③后序:左子树 右子树 根 (当下面没子时才输出根)
思路一: 弄出一个 “根 右子树 左子树 ”的递归,相当于前序的变式(分为右路结点和它们对应的左路节点),之后再逆置vector。
思路二:判断子节点是否被访问过
class Solution
{
public:
vector<int> postorderTraversal(TreeNode* root)
{
stack<TreeNode*> st;
TreeNode* cur=root;
vector<int> v;
TreeNode* prev=nullptr;
while(cur || !st.empty())
{
//1.先访问左路节点
while(cur)
{
st.push(cur);
cur=cur->left;
}
//从栈里取到左路节点,说明他的左子树访问完了
TreeNode* top=st.top();
//2.右为空或右已经访问过了,才可以访问根节点
if(top->right==nullptr || top->right==prev) //右子树等于上一个访问过的结点
{
v.push_back(top->val);
st.pop();
prev=top; //当一个结点被访问过了,就变成了上一个节点
}
else //访问左路节点的右子树
{
cur=top->right;
}
}
return v;
}
};
二.map和set
1.简介
1.map set 底层都是搜索二叉树,实例化时会自动排升序 (搜索树不允许修改key值,不然会破坏树的结构)
swt -- key模型
map -- key/val模型
2.
vector\list\deque.... 序列式容器
map\set........ 关联式容器 (数据之间有很强的关联关系)
3.
set 排序+去重
multiset 不去重,只排序 有多个keys时,find的时候找找的是中序的第一个key
例子:
int main()
{
map<string, string> dict;
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("string", "字符串"));
dict.insert(make_pair("count", "计数"));
for (auto& kv : dict)
{
cout << kv.first << ":" << kv.second << endl;
}
return 0;
}
适用于两个毫不相关的对象(可以不同类)之间建立关系
方括号的用法:
例题:
class Solution
{
public:
Node* copyRandomList(Node* head)
{
map<Node*,Node*> copyNodeMap;
Node* cur=head;
Node *copyhead,*copytail;
copyhead=copytail=nullptr;
while(cur)
{
Node* copy=new Node(cur->val);
copyNodeMap[cur]=copy; //cur是原节点,通过原结点,来储存copy结点
if(copytail==nullptr)
{
copyhead=copytail=copy;
}
else
{
copytail->next=copy;
copytail=copytail->next;
}
cur=cur->next;
}
cur=head;
Node* copy=copyhead;
while(cur)
{
if(cur->random==nullptr)
{
copy->random=nullptr;
}
else
{
copy->random=copyNodeMap[cur->random];
}
cur=cur->next;
copy=copy->next; //cur,copy都要跟着动
}
return copyhead;
}
};
2.底层结构——AVL树(高度平衡搜索二叉树)
AVL树不一定有平衡因子,使用平衡因子只是他的一种实现方式
3.旋转:
①单旋(直线形)
右边高往左单旋,左边高往右单旋 。
为什么不是直接把a和c交换:更改指针指向更简单,而且交换会破坏树的结构
左单旋如图:
②双旋(折线形状)
先变成单纯的左边高,再右旋
4.AVL树部分代码
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent; //引入parent ,构成三叉链
pair<K, V> _kv;
int _bf; //balance factor (平衡因子)
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0)
{}
};
template<class K,class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
if (kv.first > parent->_kv.first)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent; //反向链接
//每次插完,都要更新平衡因子
//若更新过后,平衡因子没出问题(|bf|<=1),说明插入对树的平衡结构不影响,不需要处理
//若更新过后,平衡因子出现问题(|bf|>1),平衡结构受到影响,需要处理(旋转) bf=(右链到底的所有结点-左链到底的所有结点)
//更新:
//cur == parent->right parent->bf++;
//cur == parent->left parent->bf--;
//1. 若(parent->bf == 1 || parent->bf == -1) ,说明parent所在的子树变了,继续向上更新
//2. 若(parent->bf == 2 || parent->bf == -2) ,说明parent所在的子树不平衡,需要旋转
//3. 若(parent->bf == 0) ,平衡了,不需要更新了
//更新平衡因子
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 1 || parent->_bf == -1)
{
//继续向上更新
parent = parent->_parent;
cur = cur->_parent;
}
else if (parent->_bf == 0)
{
break;
}
else if(parent->_bf == 2 || parent->_bf == -2)
{
//需要旋转处理 ---> 让这颗子树平衡,同时降低高度差bf
if (parent->_bf == 2 && cur->_bf == 1) //右边高
{
RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1) //左边高
{
RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1) //左边树高,而且左边子树中右边高
{
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1) //右边树高,而且右边子树中左边高
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false); //防止树本来的平衡因子出现2以上
}
}
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalance()
{
return _IsBalance(_root);
}
private:
void RotateL(Node* parent) //左单旋
{
Node* subR = parent->_right; //subR将作为新的根结点
Node* subRL = subR->_left;
Node* pparent = parent->_parent;
parent->_right = subRL;
if (subRL)
{
subRL->_parent = parent; //subRL有可能是空指针
}
subR->_left = parent;
parent->_parent = subR;
if (pparent == nullptr) //说明parent就是整棵树的根,上面没东西了
{
_root = subR;
_root->_parent = nullptr;
}
else //说明parent不是整棵树的根,上面还有结点
{
if (pparent->_left == parent) //如果之前的根是左结点,那么就连在左结点上
{
pparent->_left = subR;
}
else //如果之前的根是左结点,那么就连在右结点上
{
pparent->_right = subR;
}
subR->_parent = pparent; //别忘了把parent指针指回去
}
parent->_bf = subR->_bf = 0; //更新平衡因子
}
void RotateR(Node* parent) //右单旋
{
Node* subL = parent->_left; //subL将作为新的根结点
Node* subLR = subL->_right;
Node* pparent = parent->_parent;
parent->_left = subLR;
if (subLR)
{
subLR->_parent = parent; //subLR有可能是空指针
}
subL->_right = parent;
parent->_parent = subL;
if (pparent == nullptr) //说明parent就是整棵树的根,上面没东西了
{
_root = subL;
_root->_parent = nullptr;
}
else //说明parent不是整棵树的根,上面还有结点
{
if (pparent->_left == parent) //如果之前的根是左结点,那么就连在左结点上
{
pparent->_left = subL;
}
else //如果之前的根是左结点,那么就连在右结点上
{
pparent->_right = subL;
}
subL->_parent = pparent; //别忘了把parent指针指回去
}
parent->_bf = subL->_bf = 0; //更新平衡因子
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf; //subLR的平衡因子不同,最后结果的平衡因子就不同
RotateL(parent->_left);
RotateR(parent);
//调整平衡因子
if (bf == 1)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 1;
subLR->_bf = 0;
subL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subLR->_bf = 0;
subL->_bf = 0;
}
else
{
assert(false);
}
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf; //subRL的平衡因子不同,最后结果的平衡因子就不同
RotateR(parent->_right);
RotateL(parent);
if (bf == -1)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = -1;
subRL->_bf = 0;
subR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 0;
}
else
{
assert(false);
}
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
return (leftH > rightH) ? leftH + 1 : rightH + 1;
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
if (rightH - leftH != root->_bf)
{
cout << root->_kv.first << "该结点平衡因子异常" << endl;
return false;
}
return (abs(leftH - rightH) <= 1) && _IsBalance(root->_left) && _IsBalance(root->_right);
//还要递归每一棵子树,判断是不是平衡的
}
private:
Node* _root = nullptr;
};
5.红黑树
①介绍
保证最长路径不会大于最短路径的两倍
②性质
1. 黑红红 :父亲和叔叔变黑,祖父变红 (不能出现连续的红色结点),再往上递归,最后把根变黑。
2.黑红红,但是叔叔是空或为黑 :父亲和叔叔变黑,祖父变红 (不能出现连续的红色结点),再往上递归,最后把根变黑。 (涉及到旋转)
(1)叔叔为空
(2) 叔叔为黑
旋转+变色,父亲一定为黑,祖父为红
三.哈希表
1.unorder系列(尤哈希表底层实现)
综合场景下,unorder系列比set,map性能要好。
2.哈希(散列)
①闭散列
enum State
{
EMPTY,
EXIST,
DELETE
};
template<class K,class V>
struct HashData
{
pair<K, V> _kv;
State _state = EMPTY;
};
template<class K,class V>
class HashTable
{
public:
bool Insert(const pair<K, V>& kv)
{
//负载因子(已经有的占表的比)超过0.7就扩容
//if((double)_n / (double) _tables.size() >= 0.7)
if (_tables.size() == 0 || _n * 10 / _tables.size() >= 7)
{
size_t newsize = (_tables.size() == 0 ? 10 : _tables.size() * 2); //新容量
HashTable<K, V> newht;
newht._tables.resize(newsize); //创建一个大小为newsize的新表
//遍历旧表,重新映射到新表
for (auto& e : _tables)
{
if (e._state == EXIST)
{
//重新插入新表,因为扩容后数据在新表的位置与旧表不同
newht.Insert(e._kv);
}
}
_tables.swap(newht._tables); //与新表交换
}
size_t hashi = kv.first % _tables.size();
//线性探测
size_t i = 1;
size_t index = hashi;
while (_tables[index]._state == EXIST)
{
index = hashi + i;
index %= _tables.size(); //超过结尾了再绕回来
i++;
}
//找到之后再修改
_tables[index]._kv = kv;
_tables[index]._state = EXIST;
_n++;
return true;
}
HashData<K, V>* Find(const K& key)
{
size_t hashi = key % _tables.size();
//线性探测
size_t i = 1;
size_t index = hashi;
while (_tables[index]._state != EMPTY)
{
if (_tables[index]._state == EXIST && _tables[index]._kv.first == key)
{
return &_tables[index]; //返回该数的地址
}
index = hashi + i;
index = index % _tables.size(); //超过结尾了再绕回来
i++;
}
if (index = hashi)
{
break; //如果找了一圈了,那么说明全是存在或删除
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<K, V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
--_n;
return true;
}
else
{
return false;
}
}
private:
vector<HashData<K,V>> _tables;
size_t _n = 0; //存储的数据个数
};