set、map的使用

1.关联式容器

vector、list、deque等容器被称之为序列式容器,因为它们的底层为线性序列的数据结构,里面存储的是元素的本身

关联式容器也是用来存储数据的,与序列式容器不同,里面存储的是<key,value>结构的键值对,在数据检索时比序列式容器的效率更高

2.键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息

比如将key设置为中文,value设置为英文,就可以通过中文去查找对应的单词

3.树形结构的关联式容器

根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树形结构与哈希结构。树形结构的关联式容器主要有四种:map、set、

multimap、multiset(multi表示多样的,不去重)。 这四种容器的共同特点是使用平衡搜索树(红黑树)作为底层结构,容器中的元素是一个有序序列

4.set的使用介绍

  1. set是按照一定次序存储元素的容器
  2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素 不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
  4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
  5. set在底层是用二叉搜索树(红黑树)实现的
  6. 常用于排序和去重

4.1set的模板参数

image-20210517144423604

4.2使用介绍

4.2.1排序和去重

image-20210517145742231

4.2.2查找

image-20210517145852877

5.map的使用介绍

1.map是关联式容器,按照特定的次序(按照key值比较)存储由键值key和值value组合而成的元素

2.在map中,键值key通常用于排序和唯一的标识元素,值value中存储与键值key关联的内容,键值key和值value的类型可能不同,并且在map的内部,key和value可以通过成员类型value_type绑定在一起,取名为pair(结构体)

3.在内部,map中的元素总是按照键值key进行比较排序

4.map中通过键值访问单个元素的速度通常比unordered_map慢,但map允许根据顺序对元素进行直接迭代(搜索树的中序遍历)

5.map支持下标访问,即通过在[]中放入key,就可以找到key对应的value

6.map通常被实现为二叉搜索树(平衡二叉搜索树(红黑树))

7.常用于排序+去重、判断一个元素在不在、kv查找模型、统计次数

5.1模板参数

image-20210517151224767

5.2元素插入

C++11中的插入是emplace、emplace_hint(引入了右值引用)

image-20210517154302499

image-20210524104147867

5.3元素的交换

在C++98之中,调用如下图所示,类中的swap只会交换两个树的根节点。如果调用的是库函数里面的swap则需要发生3次深拷贝消耗是巨大的

在C++11之中,由于右值引用的设计,两者的效率是一样的,但是在不明确运行环境的情况之下,还是建议使用中的swap

image-20210524085959142

5.4排序、去重、统计次数

image-20210517154235623

image-20210517154312005

5.5查找

image-20210517154241815

5.6删除

image-20210524091438277

5.7[]原理

image-20210524104922277

image-20210524105452481

5.8map实际应用

1.给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

题目链接

//利用map统计数组之中不同元素的个数
//然后构建一个小根堆 -> 此时写一个仿函数,利用value值构建小根堆,最后得到的就是频率前k高的元素

class Solution {
public:
    //建小堆仿函数->greater
    struct greater 
    {
        bool operator()(map<int,int>::iterator &x,map<int,int>::iterator &y)//根据value值进行建堆
        {
            return x->second>y->second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        //建立map,统计次数,利用堆获取前K个最大的val
        map<int,int>count_map;//统计数组
        for(auto& e:nums)
        {
            count_map[e]++;//统计每个元素出现的次数
        }
       vector<int>ret;
       typedef map<int,int>::iterator count_it;

       auto it=count_map.begin();
       priority_queue<count_it,vector<count_it>,greater> heap;

       while(it!=count_map.end())//根据value建K个元素的小堆
       {
           if(heap.size()<k)//小于K个的时候
                heap.push(it);
            else
            {
                auto val=heap.top();
                if(it->second>val->second)//大于入堆
                {
                    heap.pop();
                    heap.push(it);
                }
            }
            it++;
       }

       while(!heap.empty())
       {
           ret.push_back(heap.top()->first);//K值进入返回数组
           heap.pop();
       }

       return ret;
    }
};

2.请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。题目链接

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(!head)
            return nullptr;

        map<Node*,Node*> list;
        Node *newhead=head;
        //构造map
        while(newhead)//构造新旧链表的map
        {
            Node *cur=new Node(newhead->val); 
            list.insert(make_pair(newhead,cur));//将节点插入map之中
            newhead=newhead->next;
        }

        //从map之中将拷贝的链表取出来
        newhead=head;
        Node *ret_node=new Node(0);
        Node *ret=ret_node;

        while(newhead)
        {
            ret_node->next=list[newhead];//将新链表链接起来
            ret_node=ret_node->next;

        //填入对应的random
            if(newhead->random==NULL)
                ret_node->random=NULL;
            else
                ret_node->random=list[newhead->random];
        //更新位置
            newhead=newhead->next;
        }

        return ret->next;
    }
};

3.统计最欢迎的水果

解法1:

利用map将所有的水果出现的次数统计起来,然后将map的迭代器放入vector之中,最后构造仿函数 ->second进行比较,利用sort进行排序即可;

class Fruit
{
  private:
    vector<string> _str;
    int _k;

  public:
    
    Fruit(vector<string> &str,int k)
    :_str(str)
     ,_k(k)
    {}

    void TopK()
    {
      map<string,int> count_map;
      
      typedef map<string,int>::iterator count_it;//重定义迭代器


      for(auto&e:_str)//统计水果的次数
      {
        count_map[e]++;
      }

      vector<count_it> arr;
      count_it it=count_map.begin();
      while(it!=count_map.end())//将map中迭代器放入数组之中
      {
        arr.push_back(it);
        it++;
      }

      typedef struct Com//仿函数
      { 
        bool operator()(count_it &x,count_it y)
        {
          return x->second > y->second;
        }
      }compare;

      sort(arr.begin(),arr.end(),compare());//排升序
      

      while(_k--)//输出内容
      {
        cout<<arr[_k]->first<<" "<<arr[_k]->second<<endl;
      }
  }
};

解法2:

利用大小堆来进行排序

class Fruit
{
  private:
    vector<string> _str;
    int _k;

  public:
    
    Fruit(vector<string> &str,int k)
    :_str(str)
     ,_k(k)
    {}
    
    void TopK()
    {
      map<string,int> count_map;
      
      typedef map<string,int>::iterator count_it;


      for(auto&e:_str)//统计水果的次数
      {
        count_map[e]++;
      }

      typedef struct Com
      {
        bool operator()(count_it &x,count_it &y)
        {
          return x->second > y->second;
        }

      }compare;

    priority_queue<count_it,vector<count_it>,compare> heap;//建立小堆
    count_it it=count_map.begin();
    while(it!=count_map.end())
    {
      if(heap.size() < _k)
        heap.push(it);
      else
      {
        if(it->second > heap.top()->second)//大于则入堆
        {
          heap.pop();
          heap.push(it);
        }
      }
      it++;
    }

    while(!heap.empty())//打印
    {
      cout<<heap.top()->first<<" "<<heap.top()->second<<endl;
      heap.pop();
    }

  }
};


int main()
{
  vector<string>arr={"苹果","苹果","西瓜","西瓜","香蕉","西瓜"};
  Fruit(arr,2).TopK();

  return 0;
}

6.multiset、multimap

multiset、multimap的用法和接口与set和map几乎没有区别、他们的共同点是排序、不同点是multiset和multimap不会进行去重

multimap中的元素默认将key按照小来比较

multimap没有[] -> 因为不知道是取哪一个

find的时候,找的是中序的第一个

插入的元素key相同时,插入的顺序就是输出的顺序

image-20210524111534705

6.1实际应用

最受欢迎的水果:

利用map统计数据之后,再用multimap进行排序(按second),不能使用map排序是因为去重问题,利用second时,相同个数的水果会被去重

class Fruit
{
private:
	vector<string> _str;
	int _k;

public:

	Fruit(vector<string> &str, int k)
		:_str(str)
		, _k(k)
	{}

	void TopK()
	{
		map<string, int> count_map;

		typedef map<string, int>::iterator count_it;


		for (auto&e : _str)//统计水果的次数
		{
			count_map[e]++;
		}


		multimap<int, string, greater<int>> map_sort;//排降序

		for (auto e : count_map)//将first和second颠倒位置(用int进行排序)
		{
			map_sort.insert(make_pair(e.second,e.first));
		}

		auto it = map_sort.begin();
		while (_k--)
		{
			cout << it->second << " " << it->first << endl;
			it++;
		}
	}
};

int main()
{
	vector<string>arr = { "苹果", "苹果", "西瓜", "西瓜", "香蕉", "西瓜" };
	Fruit(arr, 2).TopK();

	system("pause");
	return 0;
}

前K个高频单词:

给一非空的单词列表,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。

题目链接

方法1:

先用map统计出现的次数,然后建立一个小堆,就转变成了topK问题。需要注意的是优先级队列的仿函数的写法,当second的值相等的时候,就用first的值去比较(单词的ASCII)。将ASCII较大的单词放在堆顶(小根堆 -> 最后结果需要进行逆转)

typedef map<string,int>::iterator count_it;

typedef struct com
{
    bool operator()(count_it &x,count_it &y)
    {
        if(x->second!=y->second)
            return x->second > y->second;
        else//出现频率相同,按照字母顺序排->即ASCII
            return x->first < y->first;
    }
}Greater;
class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> count_map;

        //统计单词出现次数,并且将单词按照ASCII排了升序
        for(auto&e:words)
        {
            count_map[e]++;
        }

        priority_queue<count_it,vector<count_it>,Greater> heap;

        count_it it=count_map.begin();
        while(it!=count_map.end())
        {
            if(heap.size()<k)
                heap.push(it);
            else
            {
            //由于前面map排序了 -> 因此频率相等的单词,较小的ASCII已经入堆了 ->就不再判断
                if(it->second>heap.top()->second)
                {
                    heap.pop();
                    heap.push(it);
                }
            }
            it++;
        }
        vector<string> ret;
        while(!heap.empty())
        {
            ret.push_back(heap.top()->first);
            heap.pop();
        }
        reverse(ret.begin(),ret.end());
        return ret;
    }
};

方法2:

先用map统计单词出现的频率,并且将单词按照ASCII进行了排序

然后用multimap对单词出现的频率,排降序

因为map之中已经按照单词的ASCII进行了排序,因此当频率相同的单词出现时,谁先进入multimap之中,遍历时谁就先出来,因此ASCII小的先进入,ASCII小的先出来,就保证了单词的顺序性

class Solution {
public:
    vector<string> topKFrequent(vector<string>& words, int k) {
        map<string,int> count_map;
        //统计单词出现的次数
        for(auto&e:words)
        {
            count_map[e]++;
        }

        multimap<int,string,greater<int>>mt_map;//按照逆序排
        for(auto&e:count_map)
        {
            mt_map.insert(make_pair(e.second,e.first));
        }

        vector<string> ret;
        auto it=mt_map.begin();
        while(k--)
        {
            ret.push_back(it->second);
            it++;
        }
        return ret;
    }
};
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值