C++进阶——set和map

目录

前言

一、set

1.set的基本介绍

2.set的使用

2.1构造与迭代器

2.2插入

2.3删除

2.4查找

 2.5获取需要的区间

2.6一些使用示例

3.set和multiset的区别

4.set相关oj题目

二、map

1.map的基本介绍

2.map的使用

2.1构造与迭代器

2.2增删查以及获取区间

2.3数据修改

3.map和multimap的区别

4.map相关oj题目


前言

set和map呢,底层是红黑树(后面我的博客也会重点讲解),红黑树是一种平衡二叉搜索树,set和map本身是不支持插入重复元素的,不过也有实现的支持插入重复元素的版本multiset和multimap。

set和map的区别呢,主要在于其应用场景,set用于key场景,map用于key/value场景(我上一篇博客有提到过,不知道的可以移步我上篇博客了解C++进阶——二叉搜索树-CSDN博客)。

这篇博客会着重讲解set和map的使用,模拟实现我会放到后续的博客来讲解。


一、set

文档资料:set - C++ Reference (cplusplus.com)

1.set的基本介绍

1.set的模板声明中,T就是key的类型,后面的Compare是仿函数,用于底层搜索树的比较逻辑,一般情况下不用我们自己传,缺省值给的就够用,当默认的仿函数不符合我们需求时,我们需要自己实现一个仿函数然后传递,第三个模板参数时空间配置器,这个我们暂时先不用管。

2.set的底层是红黑树,是一种与完全二叉树形态很相似的二叉搜索树,高度是logN,所以增删查的效率是O(logN)

3.迭代器是双向迭代器(bidirectional iterator),走的中序遍历,也就是说迭代器遍历出的序列是有序的。


2.set的使用

2.1构造与迭代器

1.set支持默认构造、拷贝构造、初始化列表构造和迭代器区间构造等,跟之前的容器很类似,就不再一一演示。

2.迭代器是中序遍历,所以遍历出来的序列一定是有序的。

#include <iostream>
#include <set>
using namespace std;

int main()
{
	set<string> s{ "banana","apple","pear","orange" };
	set<string>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}


	return 0;
}

2.2插入

1.支持指定key插入,如果存在key则插入失败,不存在则插入成功

pair<iterator, bool> insert(const value_type& val);

 pair<iterator, bool>是啥?

我们可以借由这个返回值来判断插入成功还是失败,以及获取插入后该结点对应的迭代器(插入失败则会返回原来存在的与key值相等的结点的迭代器) 

#include <iostream>
#include <set>
using namespace std;

int main()
{
	set<string> s{ "banana","apple","pear","orange" };
	set<string>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}

	cout << endl;

	auto iit = s.find("apple");
	auto ret = s.insert("apple");
	auto iit1 = ret.first;
	if (iit1 == s.end()) cout << "插入失败1" << endl;
	if (iit1 == iit) cout << "插入失败2" << endl;
	// 所以插入失败了返回的迭代器是原相同key值对应的迭代器,一定不是end()


	return 0;
}

2.支持初始化列表插入,没有返回值,列表中搜索树中不存在的就插入,存在的就跳过。

void insert(initializer_list<value_type> il);

3.支持迭代器区间插入,已经存在的值将不会再插入,无返回值

template <class InputIterator>
void insert(InputIterator first, InputIterator last); 

2.3删除

1.删除指定迭代器对应结点

iterator erase(const_iterator position);

返回的迭代器是删除结点位置的下一个结点(中序),如果没有下一个就是返回end()。

2.删除指定值为key的结点

size_type erase(const value_type& val);

删除成功则返回1,不存在删除失败则返回0,为什么不是返回bool值?也是因为需要跟multiset的接口对其,multiset返回的是删除key值为val的结点的个数。 

3.删除一段迭代器区间(也就是中序遍历的一段序列)

iterator erase(const_iterator first, const_iterator last);

返回的迭代器是删除最后一个结点位置的下一个结点(中序),如果没有下一个就是返回end()。 

2.4查找

1.find,返回找到位置的迭代器,找不到就返回end()

iterator find(const value_type& val);

2.count,返回key值为val的结点的个数

size_type count(const value_type& val) const; 

为什么返回个数而不是bool值?同样是为了跟multiset的接口对齐

 2.5获取需要的区间

需要两个接口来打配合:

1.lower_bound    返回大于等于val位置的迭代器

iterator lower_bound(const value_type& val) const;

2.upper_bound        返回大于val位置的迭代器

iterator upper_bound(const value_type& val) const; 

怎么打配合呢? 

两者可以配合着来找我们需要找的迭代器区间,看名字也很容易记住,lower就是区间下限,upper就是区间下限嘛,然后返回的两个迭代器组成的区间(左闭右开)中就包含了所有搜索树中具有我们传递的[val1,val2]中的所有数据。(我们传递数据图方便和思维习惯肯定是左闭右闭了~)

#include <iostream>
#include <set>
#include <vector>
using namespace std;


void test2()
{
	set<int> s{ 5, 4, 7, 5, 2, 1, 9, 4, 3, 5, 2, 6, 8 };
	// 1 2 3 4 5 6 7 8 9
	for (auto e : s)
		cout << e << " ";

	cout << endl;

	// 两个接口的配合,可以找到我们需要获取的迭代器区间
	auto lower_it = s.lower_bound(4);  // 大于等于4     4
	auto upper_it = s.upper_bound(7);  // 大于7          8

	//                    [4, 8)   迭代器都是左闭右开
	vector<int> v(lower_it, upper_it);
	// 4 5 6 7
	for (auto e : v)
		cout << e << " ";

	// 如果4 和 7 不在里面呢?会找到[5,8)      5,6,7也符合我们的需求(在[4, 7]区间内的所有数据组成的区间)
	// 总而言之,两个配合可以找到所有在[4, 7]区间内的所有数据构成的迭代器区间
}

int main()
{
	test2();

	return 0;
}

2.6一些使用示例

下面是上面一些接口的使用示例,大家可以看看:

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

// 构造、迭代器、insert
void show1()
{
	// 去重+升序排序
	set<int> s;
	// 去重+降序排序(降序要自己传仿函数)(默认是升序Less<T>)
	//set<int, greater<int>> s;
	s.insert(5);
	s.insert(2);
	s.insert(7);
	s.insert(5);

	auto it = s.begin();
	while (it != s.end())
	{
		// error C3892: “it”: 不能给常量赋值
		// key是肯定不能修改的,不管是不是const迭代器,修改了就可能会破坏掉搜索树的结构
		// *it = 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;
	// 插⼊⼀段initializer_list列表值,已经存在的值插⼊失败
	s.insert({ 2,8,3,9 });
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	set<string> strset = { "banana","apple","pear","orange" };
	// 遍历string,比较的方法跟字符串一样,ascll比较
	for (auto& e : strset)
	{
		cout << e << " ";
	}
	cout << endl;
}

// find、erase
void show2()
{
	set<int> s = { 4,2,7,2,8,5,9 };
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	// 删除最小值
	s.erase(s.begin());

	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;


	// 直接删除x
	int x;
	cin >> x;
	int num = s.erase(x);
	if (num == 0)
	{
		cout << x << "不存在!" << endl;
	}
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;


	// 直接查找再利用迭代器删除x
	cin >> x;
	auto pos = s.find(x);
	if (pos != s.end())
	{
		s.erase(pos);
	}
	else
	{
		cout << x << "不存在!" << endl;
	}
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;


	// 算法库的查找 O(N)
	auto pos1 = find(s.begin(), s.end(), x);
	// set自身实现的查找 O(logN)
	auto pos2 = s.find(x);
	// 包是自身的查找好的呀,算法库里面的查找是双向迭代器的容器通用的,都没利用set的特性

	// 利⽤count间接实现快速查找
	cin >> x;
	if (s.count(x))
	{
		cout << x << "在!" << endl;
	}
	else

	{
		cout << x << "不存在!" << endl;
	}
}

// lower_bound和upper_bound
void show3()
{
	set<int> s{ 5, 4, 7, 5, 2, 1, 9, 4, 3, 5, 2, 6, 8 };
	// 1 2 3 4 5 6 7 8 9
	for (auto e : s)
		cout << e << " ";

	cout << endl;

	// 两个接口的配合,可以找到我们需要获取的迭代器区间
	auto lower_it = s.lower_bound(4);  // 大于等于4     4
	auto upper_it = s.upper_bound(7);  // 大于7          8

	//                    [4, 8)   迭代器都是左闭右开
	vector<int> v(lower_it, upper_it);
	// 4 5 6 7
	for (auto e : v)
		cout << e << " ";

	// 如果4 和 7 不在里面呢?会找到[5,8)      5,6,7也符合我们的需求(在[4, 7]区间内的所有数据组成的区间)
	// 总而言之,两个配合可以找到所有在[4, 7]区间内的所有数据构成的迭代器区间
}

int main()
{
	show2();

	return 0;
}

3.set和multiset的区别

首先注意:set和multiset都是在同一个头文件<set>里面的

最主要的区别还是set不支持重复元素,multiset支持重复元素插入(废话)。

不过它们使用的时候的区别也是从这句废话中产生的:

multiset的增删查由于支持相同数据的插入所以会相对set有所变动。

1.构造:都会排序,但是multiset不去重

2.insert:multiset不去重插入,给什么插入什么

3.erase:multiset会删除所有的值为x的结点

4.find:查找中序遍历的第一个值为x的结点

5.count:返回值为x的结点的个数,结果不再是0或1

4.set相关oj题目

相关思路及细节我都会写在代码的注释中~

1.349. 两个数组的交集 - 力扣(LeetCode)

// 这里两个数组的交集是不用考虑重复元素的问题的,也就是说,我们可以利用set去重之后再进行找交集的操作
// set天然的结构使迭代器的遍历(中序)是有序的,且数据是去重过的,那么我们小小地使用一下双指针就可以解决~~

class Solution
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
    {
        set<int> s1(nums1.begin(), nums1.end());
        set<int> s2(nums2.begin(), nums2.end());
        vector<int> ret;

        // 利用双指针(迭代器)来遍历s1和s2
        auto it1 = s1.begin();
        auto it2 = s2.begin();

        while(it1 != s1.end() && it2 != s2.end())
        {
            // 小的++,相等时同时++,这样确保不漏掉交集元素
            if(*it1 < *it2) ++it1;
            else if(*it1 > *it2) ++it2;
            else
            {
                ret.push_back(*it1);
                ++it1;
                ++it2;
            }
        }

        return ret;
    }
};

2.142. 环形链表 II - 力扣(LeetCode)

// 原先的时候,我们要判断链表是否带环,是使用的快慢指针的方法
// 快指针一次走两步,慢指针一次走一步,那么如果有环,快慢指针就注定会相遇
// 如今,我们学了set,只需要遍历链表,把遍历到的每一个数据都插入到set,插入之前count或者find一下
// 如果有这个数据了之前,那么就一定是环形链表

class Solution
{
public:
    ListNode *detectCycle(ListNode *head)
    {
        // 注意这里的模板类型和后面的插入类型相匹配,*是必须的
        set<ListNode*> s;
        ListNode* cur = head;
        while(cur)
        {
            if(s.count(cur) == 1)
                return cur;
            else
                s.insert(cur);
            cur = cur->next;
        }
        return nullptr;
    }
};

二、map

文档资料:map - C++ Reference (cplusplus.com)

1.map的基本介绍

map与set极为类似,不同的是多了一个模板参数类型T,这个T本质上就是key/value类型的value。

Key用作比较,T是跟Key相关的数据。

 但是这里有个设计比较搓的地方,key_type是K(key)没问题,mapped_type按理来说设计成V(value)会更好理解,不过它设计成了T那也就按这么来吧。

再下面的value_type是一个pair类型的类类型,这个类里面存了两个类型(K和T)的参数分别一个,这个pair正是map底层红黑树存储的结点数据

2.map的使用

由于map和set的使用真的很相似很相似,我只列举比较重要且核心的

2.1构造与迭代器

只需要了解一点与set不同的:

1.由于map的底层红黑树的结点数据是pair<K, T>类型的,所以我们需要在构造的时候传pair<K, T>类型的参数而不是让K和T分开传

2.迭代器用->访问key或者value

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

void map_test1()
{
	map<string, string> m{ {"apple", "苹果"}, {"pear", "梨"}};
	// 匿名对象
	m.insert(pair<string, string>("orange", "橙子"));
	// 调用函数返回pair
	m.insert(make_pair("banana", "香蕉"));
	// 隐式类型转换
	//map<string, string> m{ {"apple", "苹果"}, {"pear", "梨"} };

	auto it = m.begin();
	while (it != m.end())
	{
		//cout << (*it).first << " : " << (*it).second << endl;
		// operator->()是特殊处理过的,这里是省略一个箭头的写法
		// operator->()返回的是pair的指针
		//cout << it.operator->()->first;
		cout << it->first << " : " << it->second << endl;
		++it;
	}
	// map这边operator->()很常用

}


int main()
{
	map_test1();

	return 0;
}

2.2增删查以及获取区间

皆与set相类似,就不再赘述

2.3数据修改

与set不同,set不能修改key主要是因为修改key会造成结构损坏,而map虽不能修改key,但是可以修改与key连带在一起的value,这样不会破坏底层红黑树的结构。

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

void map_test2()
{
	// 利用[]插⼊+修改功能,巧妙实现统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
	"苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		// []先查找水果果在不在map中
		// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 0},同时返回次数的引用,++⼀下就变成1次了
		// 2、在,则返回⽔果对应的次数++
		countMap[str]++;
	}
	for (const auto& e : countMap)
	{
		cout << e.first << ":" << e.second << endl;
	}
	cout << endl;
}

void map_show1()
{
	map<string, string> m{ {"apple", "苹果"}, {"pear", "梨"}, {"banana", "香蕉"}, {"orange", "橙子"} };

	// iterator find (const key_type& k);
	auto ret = m.find("apple");
	// ret是迭代器
	ret->second = "_苹果";

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

	// pair<iterator,bool> insert (const value_type& val);
	auto iit = m.insert({"pineapple", "菠萝"});
	// insert 的返回值:
	// 插入成功就是返回当前结点的迭代器+true
	// 插入失败就是返回与插入key值相同的原有结点的迭代器+false
	// 也就是说无论插入成功与否都能返回值为key的结点
	// 所以我们也可以利用insert来充当查找的功能
	iit.first->second = "_菠萝";

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

	// mapped_type& operator[] (const key_type& k);
	// 传key
	// [] 返回的是value的引用,所以就可以利用这个来统计出现次数

	// []的内部实现原理:
	//mapped_type& operator[] (const key_type& k)
	//{
	//	// 传key进来,利用value的缺省值构造出一个pair
	//	// 插入这个pair,如果有就插入,没有就算了,不过还是都返回了key值的结点迭代器
	//	// 实现了查找功能
	//	pair<iterator, bool> ret = insert({ k, mapped_type() });
	//	iterator it = ret.first;
	//	return it->second;
	//}

	map_test2();


}


int main()
{
	map_show1();

	return 0;
}

3.map和multimap的区别

两者主要区别在于是否允许key值冗余,所以增删查会有所区别,与set和multiset的区别几乎完全一样,还有就是multimap不支持[],因为multimap的key是可以冗余的,所以就算实现[]也是插入,不过我们已经有insert了。

4.map相关oj题目

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

法1(利用插入结点建立映射关系):

// 解决这道题需要:结点的深拷贝、结点的映射关系
// 如果不用map,这个映射关系需要深拷贝一个个结点插入原链表中,完成映射
// 也就是说7,13,11,10,1变成了7,7,13,13,11,11,10,10,1,1
// 然后再给新结点通过结点之间的映射关系来进行链接
// 最后还得再解除两个链表的链接,让两个链表再次独立

class Solution 
{
public:
    Node* copyRandomList(Node* head) 
    {
        if (!head) return nullptr; // 处理空链表

        Node* cur = head;

        // 结点深拷贝,插入原链表,建立映射关系
        while(cur)
        {
            Node* copycur = new Node(cur->val); // 创建新的节点
            copycur->next = cur->next; // 将新节点的 next 指向原链表的下一个节点
            cur->next = copycur; // 将原节点的 next 指向新节点
            cur = copycur->next; // 移动到下一原节点
        }

        // 搞定新链表各节点的random指针
        cur = head;
        while(cur)
        {   
            if(cur->random) // 检查是否有随机指针
                cur->next->random = cur->random->next; // 新节点的随机指向
                // random初始值就是nullptr所以不需要再else
            cur = cur->next->next; // 移动到下一个原节点
        }

        // 分离两个链表
        cur = head;
        Node* newHead = head->next; // 新链表的头节点
        Node* copyCur = newHead; // 复制链表的当前指针

        while(cur)
        {
            cur->next = cur->next->next; // 恢复原链表
            if (copyCur->next)
                copyCur->next = copyCur->next->next; // 移动复制链表
            cur = cur->next; // 继续移动原链表
            copyCur = copyCur->next; // 继续移动复制链表
        }

        return newHead; // 返回新链表的头节点
    }
};

法2(利用map建立映射关系): 

/*
// 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)
    {
        if (!head) return nullptr; // 处理空链表

        map<Node*, Node*> nodeMap; // 创建一个映射,原节点 -> 新节点

        Node* cur = head;

        // 第一遍历:复制节点并建立映射关系
        while (cur) 
        {
            nodeMap[cur] = new Node(cur->val); // 创建新节点并存入映射
            cur = cur->next; // 移动到下一个原节点
        }

        // 第二遍历:设置新节点的 next 和 random 指针
        cur = head;
        while (cur) 
        {
            nodeMap[cur]->next = nodeMap[cur->next]; // 设置 next 指针
            nodeMap[cur]->random = nodeMap[cur->random]; // 设置 random 指针
            cur = cur->next; // 移动到下一个原节点
        }
        return nodeMap[head]; // 返回新链表的头节点
    }
};

2.692. 前K个高频单词 - 力扣(LeetCode)

class Solution
{
public:
    struct myCmp
    {
        bool operator()(const pair<string, int>& p1, const pair<string, int>& p2)
        {
            // 由于出现次数相同的单词需要用字典序排序,所以加上后面的这个判断条件(sort本身是不稳定排序)
            return p1.second > p2.second || (p1.second == p2.second && p1.first < p2.first);
            // 次数不同次数大的在前面,次数相同字典序小的在前面
            // 如果没有字典序后面的判断也可以,但是需要用stable_sort
        }
    };


    vector<string> topKFrequent(vector<string>& words, int k)
    {
        // 给的单词列表需要排序,又因为需要统计次数,那么自然需要map
        map<string, int> m;
        for(auto& e : words)
            m[e]++;
        
        vector<pair<string, int>> vs(m.begin(), m.end());
        // 其实插入后m和vs已经是字典序了
        // 不过sort排序不是一个稳定排序,并且sort默认的仿函数不能解决我们pair比较的需求
        sort(vs.begin(), vs.end(), myCmp());

        vector<string> ret;
        auto vsit = vs.begin();
        while(k--)
        {
            ret.push_back((*vsit).first);
            ++vsit;
        }

        return ret;
    }
};

完结撒花~~~~~~~~~~~

耶╰(✿´⌣`✿)╯♡

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java基础:while循环-猜数字游戏(附源码)

知识点&#xff1a;while循环 参考该文&#xff1a;JAVA中while循环的使用 实践&#xff1a;猜数字游戏 通过while 判断是否正确&#xff0c; 猜中结束&#xff0c; 若猜不中&#xff0c;猜3次结束 package com.game;import java.util.Scanner;public class Game {public…

DNS安全概述

一、DNS的解析过程 1.递归解析 递归解析是一种由DNS客户端&#xff08;通常是用户的应用程序&#xff0c;如一个浏览器&#xff09;向本地DNS解析器发出解析请求&#xff0c;然后本地DNS解析器负责查询最终结果并将结果返回给客户端&#xff0c;而中间的所有查询请求都由本地D…

学习docker第四弹------docker将本地镜像发布到私有库

目录 1 是什么2 使用2.1 下载镜像2.2 运行私有库Registry&#xff0c;相当于本地有个私有Docker hub2.3 案例演示创建一个新镜像&#xff0c;ubuntu安装ifconfig命令2.3.1 从Hub上下载ubuntu镜像到本地并成功安装2.3.2 原始的ubuntu镜像是不带着ifconfig命令的2.3.3 外网连通的…

在线绘图工具drawio,visio的平替

Draw.io&#xff1a;灵活高效的在线绘图工具推荐 在工作和项目管理中&#xff0c;流程图、架构图和思维导图等可视化图表是非常重要的沟通工具。Draw.io&#xff08;现更名为diagrams.net&#xff09;是一个强大且免费的在线绘图工具&#xff0c;适用于创建各种类型的图表。它功…

ComfyUI一键更换服装:IP-Adapter V2 + FaceDetailer(DeepFashion)

在这篇文章中&#xff0c;我们将探索如何使用新版的IP-Adapter和ComfyUI软件为人物进行换装。 整个过程非常简单&#xff0c;仅需要两张图片&#xff1a;一张服装图片和一张人物图片。 通过一系列节点的操作&#xff0c;ComfyUI就会把这个服装换到人物身上&#xff0c;并利用…

施磊C++ | 进阶学习笔记 | 5.设计模式

五、设计模式 这里贴出常用的23中设计模式。视频课程仅包含部分&#xff0c;剩余部分需要找其他课程或者资料进行自学。 1.设计模式三大类型概述 C设计模式是一套被广泛认可的用于解决常见软件设计问题的最佳实践&#xff0c;它们可以帮助开发者编写更加清晰、可维护和可扩展…

小白投资理财 - 解读市净率

小白投资理财 - 解读市净率 什么是市净率公式 市净率的解释市净率的意义市净率的局限性市净率的应用场景市净率举例计算市净率如何解读分析可能的情景对比其他行业公司 市净率与其他估值指标的结合总结 什么是市净率 市净率&#xff08;Price-to-Book Ratio&#xff0c;简称 P…

飞腾X100适配Ubuntu说明

【写在前面】 飞腾开发者平台是基于飞腾自身强大的技术基础和开放能力&#xff0c;聚合行业内优秀资源而打造的。该平台覆盖了操作系统、算法、数据库、安全、平台工具、虚拟化、存储、网络、固件等多个前沿技术领域&#xff0c;包含了应用使能套件、软件仓库、软件支持、软件适…

强化学习的数学原理-01基本概念

state: T h e s t a t u s o f a g e n t w i t h r e s p e c t t o t h e e n v i r o n m e n t The \quad status \quad of \quad agent \quad with \quad respect \quad to \quad the \quad environment Thestatusofagentwithrespecttotheenvironment (agent 相对于环境的…

WPF 中的 StackPanel 详解

Windows Presentation Foundation&#xff08;WPF&#xff09;是微软开发的一种用于创建桌面客户端应用程序的用户界面框架。WPF 提供了一套丰富的控件和布局能力&#xff0c;使得开发者可以轻松构建出功能强大、视觉优美的用户界面。在 WPF 的布局系统中&#xff0c;StackPane…

【原创】java+ssm+mysql水费管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

StarRocks大批量数据导入方案-使用 Routine Load 导入数据

本文详细介绍如何使用Routine Load 导入数据 一、准备工作 1.1 安装基础环境 主要是安装StarRocks和Kafka&#xff0c;本文直接跳过不做详细介绍~ 二、概念及原理 2.1 概念 导入作业&#xff08;Load job&#xff09; 导入作业会常驻运行&#xff0c;当导入作业的状态为 R…

【Linux】了解pthread线程库,清楚并没有线程创建接口,明白Linux并不存在真正意义的线程(附带模型图详解析)

前言 大家好吖&#xff0c;欢迎来到 YY 滴Liunx系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Lin…

数字化转型的成功路径:最佳实践与实施技巧深度解析

在全球化和技术革命的双重推动下&#xff0c;企业正面临前所未有的市场竞争和运营压力。为了适应这种变化&#xff0c;企业纷纷开始了数字化转型的进程&#xff0c;希望借助技术力量提升效率、推动创新并保持竞争优势。尽管数字化转型的趋势已不可逆&#xff0c;但如何真正落地…

探索 Python 中的 XML 转换利器:xml2dict

文章目录 **探索 Python 中的 XML 转换利器&#xff1a;xml2dict**一、背景介绍二、xml2dict 是什么&#xff1f;三、如何安装 xml2dict&#xff1f;四、基本用法五、实际应用场景六、常见问题及解决方案七、总结 探索 Python 中的 XML 转换利器&#xff1a;xml2dict 一、背景…

软考(网工)——局域网和城域网

文章目录 &#x1f550;局域网基础1️⃣局域网和城域网体系架构 IEEE&#xff08;负责链路层&#xff09;2️⃣局域网拓扑结构 &#x1f551;CSMA/CD1️⃣CSMA/CD2️⃣CSMA/CD三种监听算法3️⃣冲突检测原理 &#x1f552;二进制指数退避算法1️⃣ 二进制指数退避算法 &#x1…

在SpringBoot项目中利用Redis实现防止订单重复提交

文章目录 0. 前言1. 常见的重复提交订单的场景2. 防止订单重复提交的解决方案2.1 前端&#xff08;禁用按钮&#xff09;2.2 后端 3. 在SpringBoot项目中利用Redis实现防止订单重复提交3.1 引入依赖3.2 编写配置文件3.3 OrderService.java3.4 OrderController.java3.5 index.ht…

HTML快速入门--第一节--五个基本标签

一、网络编程的三大基石 1.1 url 统一资源定位符&#xff1a; 网址:整个互联网中可以唯一且准确的确定一个资源的位置 (url项目外) 网址:https://www.baidu.com/ https://www.baidu.com/ 协议://ip端口/项目名/页面名 协议:交通法规获取资源 ip端口 &#xff08;域名&…

Java | Leetcode Java题解之第479题最大回文数乘积

题目&#xff1a; 题解&#xff1a; class Solution {public int largestPalindrome(int n) {if (n 1) {return 9;}int upper (int) Math.pow(10, n) - 1;int ans 0;for (int left upper; ans 0; --left) { // 枚举回文数的左半部分long p left;for (int x left; x >…

Redis实现全局ID生成器

全局ID生成器 为什么要用全局ID生成器 1.当我们使用数据库自增来实现id的生成时,规律过于明显,会给用户暴露很多信息 2.当我们订单量过大时无法用数据库的一张表来存放订单,如果两张表的id都是自增的话,id就会出现重复 什么是全局ID生成器 全局ID生成器,是一种在分布式系统…