二叉搜索树+set和map

news2024/11/16 12:37:30

前言

现在我们开始进行对树的学习,这一节我们主要讲二叉搜索树和set和map的使用,这两个的使用我们只讲一些,然后就是一些练习题,综合使用stl

1. key类型的二叉搜索树的实现

//实现二叉搜索树
template<class K>
struct BSNode
{
	BSNode<K>* _left;
	BSNode<K>* _right;
	K _key;
	typedef BSNode<K> Node;
	BSNode(const K&key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

template<class K>
class BSTree
{
public:
	typedef BSNode<K> Node;

	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;
			}
		}
		//但要注意_root==nullptr的时候
		if (key > parent->_key)
		{
			parent->_right = new Node(key);
		}
		else
		{
			parent->_left = new Node(key);
		}
		return true;
	}

	//写个中序遍历
	//_root是this指针里面的东西,所以不好搞,不好递归,所以采用调用函数的方法
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//搜索某个数据
	Node* Search(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
			{
				return cur;
			}
		}
		return nullptr;
	}

	//删除某个数据
	bool Erase(const K& key)
	{
		if (_root == nullptr)
		{
			return false;
		}
		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
			{
				//找到了要删除的数据
				//删除的话就分为三种//
				//第一种就是没有孩子直接删除
				//第二种就是有一个孩子的话就直接连在父亲的后面
				//第三种就是有两个孩子
				//其中第一种和第二种可以合并。因为可以把没有孩子当做空指针的孩子
				if (cur->_left == nullptr)
				{
					if (parent == nullptr)//说明删的是根
					{
						_root = cur->_right;
						delete cur;
						return true;
					}
					//先看cur在parent的左还是右
					if (cur->_key < parent->_key)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					if (parent == nullptr)//说明删的是根
					{
						_root = cur->_left;
						delete cur;
						return true;
					}
					if (cur->_key < parent->_key)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
					delete cur;
					return true;
				}
				else//现在左右孩子都不为空
				{
					//如果是头结点,也不用单独考虑,我们这样设计的话
					//我们直接找到右孩子的最小值放在cur,然后删除这个最小值节点就可以了,最小值就是一直往左走就可以了
					Node* min = cur->_right;
					Node* min_parent = cur;
					while (min->_left)
					{
						min_parent = min;
						min = min->_left;
					}
					cur->_key = min->_key;
					//删除min节点
					//因为min的左孩子一定为空,所以很好删除
					if (min_parent == cur)//说明没走
					{
						min_parent->_right = min->_right;
					}
					else
					{
						min_parent->_left = min->_right;
					}
					delete min;
					return true;
				}
			}
		}
		return false;
	}

private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
	Node* _root=nullptr;
};

这种类型的二叉搜索树只有一个模版参数,只能用于那种在不在的问题
比如门禁系统(刷卡看找不找得到),检查英文小说中是否有错误单词
(把所有单词存入二叉树中,来一个单词就去树中找,看找不找得到)
主要作用就是看这个东西在不在二叉树中

2. key/value类型的二叉搜索树的实现

和上面那个很类似

template<class K,class V>
class BSTree
{
public:
	typedef BSNode<K,V> Node;

	bool insert(const K& key, const V& value)
	{
		if (_root == nullptr)
		{
			_root = new Node(key,value);
			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;
			}
		}
		//但要注意_root==nullptr的时候
		if (key > parent->_key)
		{
			parent->_right = new Node(key,value);
		}
		else
		{
			parent->_left = new Node(key,value);
		}
		return true;
	}

	//写个中序遍历
	//_root是this指针里面的东西,所以不好搞,不好递归,所以采用调用函数的方法
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	//搜索某个数据
	Node* Search(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
			{
				return cur;
			}
		}
		return nullptr;
	}

	//删除某个数据
	bool Erase(const K& key)
	{
		if (_root == nullptr)
		{
			return false;
		}
		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
			{
				//找到了要删除的数据
				//删除的话就分为三种//
				//第一种就是没有孩子直接删除
				//第二种就是有一个孩子的话就直接连在父亲的后面
				//第三种就是有两个孩子
				//其中第一种和第二种可以合并。因为可以把没有孩子当做空指针的孩子
				if (cur->_left == nullptr)
				{
					if (parent == nullptr)//说明删的是根
					{
						_root = cur->_right;
						delete cur;
						return true;
					}
					//先看cur在parent的左还是右
					if (cur->_key < parent->_key)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					if (parent == nullptr)//说明删的是根
					{
						_root = cur->_left;
						delete cur;
						return true;
					}
					if (cur->_key < parent->_key)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
					delete cur;
					return true;
				}
				else//现在左右孩子都不为空
				{
					//如果是头结点,也不用单独考虑,我们这样设计的话
					//我们直接找到右孩子的最小值放在cur,然后删除这个最小值节点就可以了,最小值就是一直往左走就可以了
					Node* min = cur->_right;
					Node* min_parent = cur;
					while (min->_left)
					{
						min_parent = min;
						min = min->_left;
					}
					cur->_key = min->_key;
					//删除min节点
					//因为min的左孩子一定为空,所以很好删除
					if (min_parent == cur)//说明没走
					{
						min_parent->_right = min->_right;
					}
					else
					{
						min_parent->_left = min->_right;
					}
					delete min;
					return true;
				}
			}
		}
		return false;
	}

private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " "<<root->_value<<endl;
		_InOrder(root->_right);
	}
	Node* _root = nullptr;
};

这个的主要作用就是通过一个值找另一个值,比如字典(通过中文找英文)车库收费系统(一个记录车牌号,一个记录进入时间,最后通过车牌号找到进入时间加上结束时间算车费),统计单词出现次数,一个记录单词,一个记录次数

	BSTree<string, string> t;
	t.insert("右", "right");
	t.insert("左", "left");
	t.insert("上", "up");
	t.insert("下", "down");
	string s;
	while (cin >> s)
	{
		auto x = t.Search(s);
		if (x != nullptr)
		{
			cout << x->_key << " " << x->_value << endl;
		}
		else
		{
			cout << "查找失败" << endl;
		}
	}

在这里插入图片描述
这个是字典查找

string arr[] = { "cccc","asqdq","aaaa","bbbbbb","cccc","aaaa" };
BSTree<string ,int> t;
for (auto x : arr)
{
	auto p = t.Search(x);
	if (p == nullptr)
	{
		t.insert(x, 1);
	}
	else
	{
		p->_value++;
	}
}
t.InOrder();

在这里插入图片描述
这个是统计次数

3. 默认构造函数的实现

3.1 拷贝构造函数

	BSTree() = default;

	BSTree(const BSTree& t)
	{
		_root = copy(t._root);
	}
	Node* copy(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}
		//先拷贝根,在拷贝左右子树
		Node* tmp = new Node(root->_key, root->_value);
		tmp->_left = copy(root->_left);
		tmp->_right = copy(root->_right);
		return tmp;
	}

3.2 析构函数

	void destroy(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		destroy(root->_left);
		destroy(root->_right);
		delete root;
	}

3. set的使用

set是key类型的红黑树

3.1 insert

在这里插入图片描述
第一个insert的返回值是一个pair类型的类模板,这里先不讲

	set<int> s ;
	s.insert(1);
	s.insert(3);
	s.insert(5);
	s.insert(7);
	s.insert(9);
	s.insert(3);
	s.insert(4);
	s.insert(2);
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

可以看出insert插进去的,相同的值就不会插进去了
然后迭代器走的就是中序遍历

3.2 erase

在这里插入图片描述
然后erase就是删除一个值,一个迭代器,和迭代器区间
下面演示两个实例,第一个
删除最小值

	s.erase(s.begin());

判断某个值在不在
因为erase的第二个函数的返回值如果为0个就说明删除失败

int x;
cin >> x;
int ret=s.erase(x);
if (ret == 0)
{
	cout << "不存在" << endl;
}

3.3 find

在这里插入图片描述
find找到了就会返回该个迭代器

auto a = find(s.begin(), s.end(), 3);
auto b = s.find(3);

第一个是库里面的find,主要是通过迭代器加加来找东西的,所以时间复杂度为O(n)
第二个是通过红黑树的特性来查找的,所以为O(logN)
在这里插入图片描述
find如果没找到的话,就会返回end的迭代器

3.4 count

在这里插入图片描述
count是查找某个数据,然后返回该数据的个数,但在这里不是0就是1
是0说明没有这个数据,是1说明有
然后就是这个的时间复杂度是O(n),因为要找个数嘛,所以要中序遍历,因为相同的值在一起,这样才更好计数

3.5 lower_bound和upper_bound

直接看代码

int main()
{
    std::set<int> myset;
    std::set<int>::iterator itlow, itup;

    for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90

    itlow = myset.lower_bound(30);                //       ^
    itup = myset.upper_bound(60);                 //                   ^

    myset.erase(itlow, itup);                     // 10 20 70 80 90

    std::cout << "myset contains:";
    for (std::set<int>::iterator it = myset.begin(); it != myset.end(); ++it)
        std::cout << ' ' << *it;
    std::cout << '\n';

    return 0;
}
itlow = myset.lower_bound(30);                
itup = myset.upper_bound(60);      

仔细看这两段代码
lower_bound返回的是大于等于那个值的迭代器,所以返回的是30的迭代器,如果传的值是25,那么返回的就是大于等于25的迭代器,还是30
upper_bound返回的是大于那个值的迭代器,就是返回大于60的迭代器,那么就是70,然后erase的区间又是左闭右开,所以就相当于时30,60的闭区间被销毁了

4. multiset

multiset的头文件也是set,也是set的一种,只不过区别就是,它面对insert相同的值不是不插入了,而是要插入,插入在相同的值的左右孩子都可以

int main()
{
	multiset<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(4);
	s.insert(6);
	s.insert(7);
	s.insert(4);
	s.insert(8);
	s.insert(43);
	s.insert(3);
	s.insert(5);
	s.insert(3);
	s.insert(1);
	s.insert(3);
	for (auto x : s)
	{
		cout << x << ' ';
	}
	return 0;
}

在这里插入图片描述
这个就可以看出来,相同的值也可以插入树

4.1 find

这里的find找一个值,就是返回中序遍历的第一个值的迭代器
下面是实现一个函数,打印所有的你要查找的x值

	int x;
	cin >> x;
	auto it = s.find(x);
	while (it != s.end() && *it == x)
	{
		cout << *it << " ";
		it++;
	}

在这里插入图片描述
然后根据这个我们就可以求出count的实现了

4.2 erase

然后这里的erase删除一个x是删除所有值为x的节点

	s.erase(3);
		for (auto x : s)
	{
		cout << x << ' ';
	}

在这里插入图片描述

4.3 equal_range

上一个set没讲这个函数,我们在这里讲
在这里插入图片描述
这个函数是你传入一个x值,然后这个函数返回所有含有这个值的迭代器区间,区间存在一个pair的类模板中
在这里插入图片描述
这个类模板有两个可以访问的成员变量,一个first类型对应为T1,一个second类型对应为T2,在这里就是,first为迭代器开始,second为迭代器末尾

	pair<multiset<int>::iterator, multiset<int>::iterator>ran=s.equal_range(3);
	auto it = ran.first;
	while (it != ran.second)
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

在这里插入图片描述

5. map

map就是key/value类型的了

5.1 insert

在这里插入图片描述
在这里插入图片描述
这里我们可以看出,insert的值是一个value_type的值,而value_type就是pair
在这里插入图片描述
而pair也是一个模版

	map<string, string> m;
	pair<string,string> p("left", "左");
	m.insert(p);
	m.insert(pair<string, string>("right", "右"));

所以说有上面两种插入方法
还有一种插入方法就是make_pair

	m.insert(make_pair("up", "上"));

这个make_pair就是通过两个参数来推出pair的类型,进而建立pair类型

	m.insert({"down","下"});

还有一种方法就是,直接有{},因为万物均可用{}初始化,先用{}构造make_pair,再来构造pair

	map<string, string> m = { {"left", "左"},{"right", "右"},{"up", "上"},{"down","下"} };

5.2 迭代器

	auto it = m.begin();
	while (it != m.end())
	{
		cout << (*it).first << ":" << (*it).second << endl;
		++it;
	}

在这里插入图片描述
因为这里的数据类型是pair,key和value就存在pair中,所以对迭代器解引用得到的就是pair,但这样比较麻烦了,可以直接用->

	auto it = m.begin();
	while (it != m.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}

这个->我们前面已经讲过了,这里就不讲了
接下来我们来写范围for

for (const auto& x : m)
{
	cout << x.first << ":" << x.second << endl;
}
cout << endl;

在这里插入图片描述

for (const auto& [x,y] : m)
{
	cout << x << ":" << y<< endl;
}
cout << endl;

在这里插入图片描述
这里还有一种范围for的遍历方法,这里是要C++17才可以支持的
这个就相当于把first给了x,second给了y

5.3 find

在这里插入图片描述
在这里插入图片描述
这里我们发现find找的是key然后返回迭代器,没找到就返回末尾的迭代器

	map<string, string> m = { {"left", "左"},{"right", "右"},{"up", "上"},{"down","下"} };
	string str;
	while (cin >> str)
	{
		auto it = m.find(str);
		if (it == m.end())
		{
			cout << "没找到" << endl;
		}
		else
		{
			cout << "存在" << it->first << ":" << it->second << endl;
		}
	}

在这里插入图片描述

5.4 operator[]

string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int> countTree;
for (const auto& str : arr)
{
	// 先查找水果在不在搜索树中
	// 1、不在,说明水果第一次出现,则插入<水果, 1>
	// 2、在,则查找到的节点中水果对应的次数++
	//BSTreeNode<string, int>* ret = countTree.Find(str);
	auto ret = countTree.find(str);
	if (ret == countTree.end())
	{
		countTree.insert({ str, 1 });
	}
	else
	{
		ret->second++;
	}
}

在这里插入图片描述
如上图我们可以利用这个程序来计算数组各个元素个数

string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int> countTree;
for (const auto& str : arr)
{
	countTree[str]++;
}

在这里插入图片描述
其实还可以这样计数,那么我们就可以推测了operator[key]得到的是value,然后如果不存在的话,就会新建立一个key

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看出operator[key]的返回值就是mapped_type类型也就是value
在这里插入图片描述
然后[]里面有这个实现逻辑,就是会用k和mapped_type的默认值传递给insert
在这里插入图片描述
而insert这个函数,如果插入成功的话,就会返回你插入成功位置的迭代器,为ture,插入失败的话,也就是已经有这个key值了,就会插入失败,然后就会返回以前那个相同的key值的迭代器,为false
然后insert的返回值的first就是迭代器,迭代器的second就是value,于是就这样实现了
所以operator[]传入key返回value的引用,没有这种key就自己建立,有这种key就可以改变value值
所以operator有插入作用,修改作用,插入+修改作用

string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
map<string, int> countTree;
for (const auto& str : arr)
{
	countTree[str]++;
}
countTree["hello"];
countTree["苹果"] = 3;
countTree["left"] = 10;

countTree[“hello”];对应的value就是用的默认值

6. multimap

multimap<string, int> mul;
mul.insert({ "aaa",1 });
mul.insert({ "aaa",2 });
mul.insert({ "aaa",1 });
mul.insert({ "bbb",2 });
mul.insert({ "bbb",2 });
map<string, int> m;
m.insert({ "aaa",1 });
m.insert({ "aaa",2 });

在这里插入图片描述

multimap与map的区别就是允许数据冗余
multimap对于key相同的,不管value相不相同都会插入
map对于key相同就不会插入了
因为map就是通过比较key来插入的

7. 练习题

7.1 环形链表

在这里插入图片描述
在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */

typedef ListNode Node;
class Solution {
public:
    bool hasCycle(ListNode* head) {
        //这里我们用map和set解决
        //用 map来统计节点次数
        map<Node*, int> m;
        Node* cur = head;
        while (cur)
        {
            m[cur]++;
            if (m[cur] == 2)//同一个节点有两次说明有环,因为节点地址都不一样嘛
            {
                return true;
            }
            cur = cur->next;
        }
        //cur为空也说明不为环
        return false;
    }
};

7.2 随机链表的复制

在这里插入图片描述
在这里插入图片描述

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        //这里我们还是用map来解决问题
        //先浅浅的拷贝一个链表,不拷贝random
        map<Node*, Node*> m;
        Node* cur = head;
        Node* copy_head = nullptr;
        Node* copy_tail = nullptr;
        while (cur)
        {
            if (copy_tail == nullptr)
            {
                copy_head = copy_tail = new Node(cur->val);
            }
            else
            {
                Node* new_node = new Node(cur->val);
                copy_tail->next = new_node;
                copy_tail = new_node;
            }
            m.insert({ cur ,copy_tail });
            cur = cur->next;
        }
        //开始链接random
        Node*cur1 = copy_head;
        Node*cur2 = head;
        while (cur1&&cur2)
        {
            if (cur2->random == nullptr)
            {
                cur1->random = nullptr;
            }
            else
            {
                cur1->random = m[cur2->random];//就这样就连接好random了
            }
            cur1 = cur1->next;
            cur2 = cur2->next;
        }
        return copy_head;
    }
};

7.3 前K个高频单词

在这里插入图片描述

//class my_compare
//{
//public:
//    bool operator()(pair<string, int> p1, pair<string, int> p2)
//    {
//        return p1.second > p2.second;//因为要降序
//    }
//};
//
//class Solution {
//public:
//    vector<string> topKFrequent(vector<string>& words, int k) {
//        //先把每个string统计好次数
//        map<string, int> m;
//        for (auto x : words)
//        {
//            ++m[x];
//        }
//        //这样就统计好次数了
//        // 先存入vector中再排序//因为sort只能排连续的//所以传入pair
//        vector<pair<string,int>> tmp;
//        for (auto x : m)
//        {
//            tmp.push_back(x);
//        }
//        //接下来按照次数来排序//我们用库里面的排序算法
//        //sort(tmp.begin(), tmp.end());//但这样不行,因为这样默认排的序是pair的first,如果first相同就比second,所以我们要自己写仿函数
//        sort(tmp.begin(), tmp.end(), my_compare());//传入对象进去就可以了,这里是匿名对象
//        //排序排好了,现在只需要取前k个就可以了
//        vector<string> ret;
//        for (int i = 0; i < k; i++)
//        {
//            ret.push_back(tmp[i].first);
//        }
//        return ret;
//    }
//};
//但是这样还不够,因为要求string次数相同的也要按照字节序来比较
//虽然我们的map插入的时候就是按照字节序比较的,相同次数的,字节序前的在前面,但是我们sort是快排,是不稳定的,
//所以可以考虑stable_sort,这个是稳定的
//class my_compare
//{
//public:
//    bool operator()(pair<string, int> p1, pair<string, int> p2)
//    {
//        return p1.second > p2.second;//因为要降序
//    }
//};
//
//class Solution {
//public:
//    vector<string> topKFrequent(vector<string>& words, int k) {
//        //先把每个string统计好次数
//        map<string, int> m;
//        for (auto x : words)
//        {
//            ++m[x];
//        }
//        //这样就统计好次数了
//        // 先存入vector中再排序//因为sort只能排连续的//所以传入pair
//        vector<pair<string, int>> tmp;
//        for (auto x : m)
//        {
//            tmp.push_back(x);
//        }
//        //接下来按照次数来排序//我们用库里面的排序算法
//        //sort(tmp.begin(), tmp.end());//但这样不行,因为这样默认排的序是pair的first,如果first相同就比second,所以我们要自己写仿函数
//        stable_sort(tmp.begin(), tmp.end(), my_compare());//传入对象进去就可以了,这里是匿名对象
//        //排序排好了,现在只需要取前k个就可以了
//        vector<string> ret;
//        for (int i = 0; i < k; i++)
//        {
//            ret.push_back(tmp[i].first);
//        }
//        return ret;
//    }
//};
//或者我们更改比较规则,让次数相同的还要比较字节序
class my_compare
{
public:
    bool operator()(pair<string, int> p1, pair<string, int> p2)
    {
        return (p1.second > p2.second)||(p1.second == p2.second&&p1.first<p2.first);//因为要降序//而且字节序小的在前面
//这样写就是次数大的在前面,字节序小的在前面
    }
};

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        //先把每个string统计好次数
        map<string, int> m;
        for (auto x : words)
        {
            ++m[x];
        }
        //这样就统计好次数了
        // 先存入vector中再排序//因为sort只能排连续的//所以传入pair
        vector<pair<string, int>> tmp;
        for (auto x : m)
        {
            tmp.push_back(x);
        }
        //接下来按照次数来排序//我们用库里面的排序算法
        //sort(tmp.begin(), tmp.end());//但这样不行,因为这样默认排的序是pair的first,如果first相同就比second,所以我们要自己写仿函数
        sort(tmp.begin(), tmp.end(), my_compare());//传入对象进去就可以了,这里是匿名对象
        //排序排好了,现在只需要取前k个就可以了
        vector<string> ret;
        for (int i = 0; i < k; i++)
        {
            ret.push_back(tmp[i].first);
        }
        return ret;
    }
};

7.4 两个数组的交集

在这里插入图片描述

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //我们先将其存入两个set
        //因为这样就可以去重了
        set<int> s1(nums1.begin(), nums1.end());
        set<int> s2(nums2.begin(), nums2.end());
        //set就已经排好序了
        vector<int> ret;
        auto it1 = s1.begin();
        auto it2 = s2.begin();
        while (it1 != s1.end() && it2 != s2.end())
        {
            //小的加加,相同就都加加
            if (*it1 < *it2)
            {
                it1++;
            }
            else if (*it2 < *it1)
            {
                it2++;
            }
            else
            {
                ret.push_back(*it1);
                it1++;
                it2++;
            }
        }
        return ret;
    }
};

7.5 单词识别

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;

class my_compare
{
public:
    bool operator()(pair<string, int> p1, pair<string, int> p2)
    {
        return (p1.second > p2.second)||(p1.second == p2.second&&p1.first<p2.first);//因为要降序//而且字节序小的在前面
//这样写就是次数大的在前面,字节序小的在前面
    }
};

class Solution {
public:
    void topKFrequent(vector<string>& words) {
        //先把每个string统计好次数
        map<string, int> m;
        for (auto x : words)
        {
            ++m[x];
        }
        //这样就统计好次数了
        // 先存入vector中再排序//因为sort只能排连续的//所以传入pair
        vector<pair<string, int>> tmp;
        for (auto x : m)
        {
            tmp.push_back(x);
        }
        //接下来按照次数来排序//我们用库里面的排序算法
        //sort(tmp.begin(), tmp.end());//但这样不行,因为这样默认排的序是pair的first,如果first相同就比second,所以我们要自己写仿函数
        sort(tmp.begin(), tmp.end(), my_compare());//传入对象进去就可以了,这里是匿名对象
        //开始打印
        for(auto x:tmp)
        {
            cout<<x.first<<":"<<x.second<<endl;
        }
    }
};

int main() {
    string str;
    vector<string> tmp;
    while (cin >>str) { // 注意 while 处理多个 case
        auto it=str.begin();
        while(it!=str.end())
        {
            if((*it)>='A'&&(*it)<='Z')//大写转小写
            {
                (*it)+=32;
            }
            if((*it)<'a'||(*it)>'z')//删除非字母
            {
                it=str.erase(it);//小心迭代器失效
            }
            else {
                it++;
            }
        }
        tmp.push_back(str);
    }
    Solution().topKFrequent(tmp);
}

这道题要结合7.3那道题

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<map>
using namespace std;

总结

下一节开始讲AVL树,如果可以的话,还可以讲一下红黑树

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1993573.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

nginx.conf alias 静态资源 别名 nginx配置

Linux系统Bug 报权限不足错误 user root; 解决server_name太长时报错的问题 #解决server_name太长时报错的问题server_names_hash_bucket_size 64; 解决文件上传默认限制1M的问题 #解决文件上传默认限制1M的问题client_max_body_size 100m; 监听所有端口 server_name _; a…

ABAP小白开发操作手册+(九)ABAP调用http

开发类型&#xff1a; 新增ABAP通过调用http的方式来发送业务数据到其他系统 开发申请&#xff1a; &#xff08;这里业务的开发申请没写完整SAP对应外部系统字段的对应关系&#xff0c;没关系&#xff0c;我们可以看接口文档&#xff09; 外围系统提供的接口文档&#xff1…

java之网络编程篇

前言 网络编程就是计算机和计算机之间通过网络进行数据传输&#xff0c;下面介绍一些概念和如何实现UDP和TCP两种模式的传输。 一、常见的软件架构C/S和B/S C/S架构需要一个客户端软件程序服务器 B/S只需要打开网页服务器 C/S架构的优缺点和应用场景 优点&#xff1a;画面可以…

看图学sql之sql的执行顺序

学完前面的内容&#xff0c;我们已经掌握了基本的sql语法了&#xff0c;那我们学的 select, distinct, from, where,group by, having, order by, limit 他们具体的执行顺序是什么样的呢&#xff1f; 语法&#xff1a; SELECT distinct column1, column2 FROM table1 join …

关系型数据库管理系统--MySQL

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

【经验总结】ShardingSphere5.2.1 + Springboot 快速开始

Sharding Sphere 官方文档地址&#xff1a; https://shardingsphere.apache.org/document/current/cn/overview/maven仓库&#xff1a;https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc 官方的文档写的很详尽到位&#xff0c;这里会截取部分…

基于Retina+PFLD+CNN人脸关键点及表情识别

对图片/视频中的人脸进行检测&#xff0c;并绘制人脸框。然后对检测到的人脸进行关键点识别&#xff0c;并进行绘制。最后根据人脸关键点&#xff0c;裁剪出人脸&#xff0c;判断该人脸的表情。 基于此&#xff0c;分别使用retina进行人脸检测&#xff0c;PFLD进行人脸关键点识…

软硬链接详解

目录 一、软硬链接命令 二、特征 三、相关知识 一、软硬链接命令 ln -s target link_name-s&#xff1a;选项表示创建一个符号链接&#xff08;软链接&#xff09;&#xff0c;如果没有-s就是硬链接target&#xff1a;是要指向的目标文件或目录的路径。link_name&#xff1…

「HarmonyNextOS」页面路由跳转Router更换为Navigation

前言 前段时间&#xff0c;鸿蒙发布了HarmonyNextOS系统&#xff0c;API直接升级到了12&#xff0c;许多API都发生了改变&#xff0c;页面跳转页从当初推荐的Router变换成Navigation&#xff0c;并且从API Version 10之后&#xff0c;都推荐使用NavPathStack来实现页面路由&am…

Go语言项目实战班04 Go语言课程管理系统项目实战 20240807 课程笔记和上课代码

预览 课程特色 本教程录制于2024年8月8日&#xff0c;使用Go1.22版本&#xff0c;基于Goland2024进行开发&#xff0c;采用的技术栈比较新。 每节课控制在十分钟以内&#xff0c;课时精简&#xff0c;每节课都是一个独立的知识点&#xff0c;如果有遗忘&#xff0c;完全可以当…

【JavaEE】synchronized原理

目录 前言 synchronized特性 synchronized特点 synchronize的加锁过程 1.无锁-->偏向锁 2.偏向锁->轻量级锁 3.轻量级锁->重量级锁 锁的优化操作 1.锁消除 2.锁粗化 3.自适应自旋锁 相关面试题 1.什么是偏向锁&#xff1f; 2.synchronized的实现原理是什…

LVS原理及实例

目录 LVS原理 LVS概念 lvs集群的类型 lvs-nat 解释 传输过程 lvs-dr 解释 传输过程 特点 lvs-tun LVS&#xff08;Linux Virtual Server&#xff09;常见的调度算法 防火墙标记&#xff08;Firewall Marking&#xff09;结合轮询调度 实战案例 lvs的nat模式配置 …

代码随想录算法刷题训练营day49:LeetCode(42)接雨水、LeetCode(84)柱状图中最大的矩形

代码随想录算法刷题训练营day49&#xff1a;LeetCode(42)接雨水、LeetCode(84)柱状图中最大的矩形 LeetCode(42)接雨水 题目 代码 import java.util.Stack;class Solution {public int trap(int[] height) {//用单调栈进行操作int sum0;Stack<Integer> stacknew Stac…

计算机的错误计算(五十六)

摘要 展示大数的正切函数值的错误计算。 由计算机的错误计算&#xff08;五十五&#xff09;知&#xff0c;国际IEEE 754 标准给出的正切函数的定义域是整个实数域范围。那么&#xff0c;在该范围内&#xff0c;软件的计算效果如何呢&#xff1f; 例1. 计算 . 在 Python下计…

字体识别验证码的介绍!

字体识别验证码 ​是一种安全机制&#xff0c;‌通过要求用户识别特定字体来验证用户的身份或防止自动化攻击。‌这种验证码通常包含一些经过特殊设计的字符&#xff0c;‌需要用户根据这些字符的特定样式&#xff08;‌如字体、‌字形等&#xff09;‌来进行识别和输入。‌字…

html+css网页制作 博云丝网5个页面 无js ui还原度100%

htmlcss网页制作 博云丝网5个页面 无js ui还原度100% 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取…

el-tree限制选中个数

el-tree限制选中个数 <el-treestyle"max-width: 600px":data"Treedata":check-strictly"true"show-checkboxnode-key"id":props"defaultProps":default-expanded-keys"[1, 2]"ref"treeRef"check&quo…

Java数组类型

目录 一维数组 一维数组的声明 动态数组初始化 静态数组的初始化 一维数组的访问 数组长度 数组的遍历操作 数组中的默认值 数组中的两个常见异常 越界访问异常ArrayIndexOutOfBoundsException 空指针异常NullPointerException Java中的内存划分 一维数组的内存分…

pdf怎么加密码怎么设置密码?pdf加密码的几种设置方法

在数字化时代&#xff0c;信息的保密性与安全性日益成为我们不可忽视的重要环节。尤其对于包含敏感信息或个人隐私的PDF文档而言&#xff0c;保护其免受未授权访问的侵扰显得尤为重要。通过为PDF文档设置密码保护&#xff0c;我们能够筑起一道坚实的防线&#xff0c;确保只有拥…

危化品安全生产风险监测预警系统的构建与实施

引言 1、背景与重要性 在现代工业生产中&#xff0c;危险化学品&#xff08;简称“危化品”&#xff09;的使用和管理日益广泛。它们在化工、制药、能源等多个领域中扮演着不可或缺的角色。然而&#xff0c;危化品因其固有的易燃、易爆、腐蚀、有毒等特性&#xff0c;一旦管理…