数据结构进阶

一.搜索二叉树(排序)

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.二叉树的非递归写法:拆成左路结点和其对应的右子树

当从栈中取出左路节点时,就意味着这个结点的左子树访问完了

例题: 

144. 二叉树的前序遍历 - 力扣(LeetCode)

①前序:根   左子树   右子树 

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;
    }
};

94. 二叉树的中序遍历 - 力扣(LeetCode)

②中序:左子树    根   右子树  

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);  出现的位置不一样。

145. 二叉树的后序遍历 - 力扣(LeetCode)

③后序:左子树    右子树    根    (当下面没子时才输出根)

思路一: 弄出一个  “根   右子树   左子树 ”的递归,相当于前序的变式(分为右路结点和它们对应的左路节点),之后再逆置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;
}


适用于两个毫不相关的对象(可以不同类)之间建立关系

方括号的用法:

例题:

138. 随机链表的复制 - 力扣(LeetCode)

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;   //存储的数据个数
};

②开散列(哈希桶,拉链法)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

对玛导至昏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值