有序Map的实现--TreeMap

背景

我们知道Java中HashMap是一个无序的数据结构,因为其键值对<key,value>中key的底层实现是一个set,所以HashMap在遍历的时候,元素的遍历顺序和插入顺序是不一致的。在一些情况下,我们需要一种数据结构兼具Hash性以及有序性,这个时候就会需要使用到新的数据结构了–TreeMap

在leetcode刷题写每日一题的时候,在学习题解的过程中,了解到了TreeMap(其实之前的时候就有遇到过,不过没有细致研究),所以这次做好笔记,希望对以后的学习有帮助(下面的题解是参考灵茶山艾府的解答)

https://leetcode.cn/problems/count-integers-in-intervals/description/

leetcode

1.Java中的TreeMap

1.1TreeMap和HashMap的区别

  • 内部实现
    HashMap使用哈希表(Hash Table)实现,通过哈希算法将键映射到存储桶(bucket)上。
    TreeMap使用红黑树(Red-Black Tree)实现,它是一种自平衡的二叉搜索树。

  • 排序:
    HashMap不会对键进行排序,因此键的顺序是不确定的。
    TreeMap会对键进行排序,它根据键的自然顺序或者通过Comparator进行排序。

  • 性能:
    HashMap的插入、删除和查找操作的平均时间复杂度为O(1),具有常数级别的性能。
    TreeMap的插入、删除和查找操作的时间复杂度为O(log n),其中n是元素的数量。由于红黑树的自平衡特性,TreeMap的性能相对较稳定。

  • 迭代顺序:
    HashMap的迭代顺序是不确定的,它与键的插入顺序无关。
    TreeMap的迭代顺序是按照键的顺序进行的,可以通过自然顺序或者Comparator进行控制。

  • null键和null值:
    HashMap允许null键和null值,可以存储一个null键和多个null值。
    TreeMap不允许null键,但允许null值。

1.2TreeMap具体细节

TreeMap中有许多具体的内容,内容太多了,可以仔细学习下面这篇博文。

https://blog.csdn.net/qq_45344586/article/details/129727558

import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;

public class TreeMap_Learning {
    public static void main(String[] args) {
        TreeMap<String, Integer> map = new TreeMap<>();

        // 向 TreeMap 中添加键值对
        map.put("Alice", 90);
        map.put("Bob", 80);
        map.put("Charlie", 70);
        map.put("David", 60);

        //================TreeMap的遍历输出(按照自然顺序输出)====================
        for(Map.Entry<String, Integer> entry : map.entrySet()){
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

        //==============实现TreeMap按照key值逆序:创建一个新的TreeMap,并传入逆序的Comparator==========
        TreeMap<String, Integer> reverseTreeMap = new TreeMap<>(Comparator.reverseOrder());
        // 将原始TreeMap中的键值对逐个复制到新的TreeMap中
        reverseTreeMap.putAll(map);
        // 遍历新的TreeMap,按照逆序输出键值对
        for (String key : reverseTreeMap.keySet()) {
            System.out.println(key + ": " + reverseTreeMap.get(key));
        }

        // 根据键获取对应的值
        System.out.println(map.get("Alice")); // 90
        System.out.println(map.get("Eve")); // null

        // 根据键删除对应的键值对
        map.remove("Bob");
        System.out.println(map); // {Alice=90, Charlie=70, David=60}

        // 获取 TreeMap 中的键值对的数量
        System.out.println(map.size()); // 3

        // ===================判断 TreeMap 中是否包含某个键=================
        System.out.println(map.containsKey("Charlie")); // true
        System.out.println(map.containsKey("Eve")); // false

        // ===========判断 TreeMap 中是否包含某个值===================
        System.out.println(map.containsValue(70)); // true
        System.out.println(map.containsValue(50)); // false

        // 清空 TreeMap 中的所有键值对
        //map.clear();
        //System.out.println(map);

        //==================相较于HashMap多出的一些操作=================
        // 初始化的TreeMap为{Alice=90, Bob=80, Charlie=70, David=60}
        // 返回 TreeMap 中最小和最大的键
        System.out.println(map.firstKey()); // Alice
        System.out.println(map.lastKey()); // David

        // 返回 TreeMap 中小于和大于给定键的最近的键
        System.out.println(map.lowerKey("Bob")); // Alice
        System.out.println(map.higherKey("Bob")); // Charlie

        // 返回 TreeMap 中小于等于和大于等于给定键的最近的键
        System.out.println(map.floorKey("Bob")); // Bob
        System.out.println(map.ceilingKey("Bob")); // Bob

        // 返回一个子映射,包含给定范围内的所有键值对
        System.out.println(map.subMap("Alice", true, "Charlie", true)); // {Alice=90, Bob=80, Charlie=70}

        // 返回一个子映射,包含小于或等于给定键或大于或等于给定键的所有键值对
        System.out.println(map.headMap("Charlie", true)); // {Alice=90, Bob=80, Charlie=70}
        System.out.println(map.tailMap("Charlie", true)); // {Charlie=70, David=60}

        // 返回 TreeMap 中最小和最大的键值对
        System.out.println(map.firstEntry()); // Alice=90
        System.out.println(map.lastEntry()); // David=60

        // 返回 TreeMap 中小于和大于给定键的最近的键值对
        System.out.println(map.lowerEntry("Bob")); // Alice=90
        System.out.println(map.higherEntry("Bob")); // Charlie=70

        // 返回 TreeMap 中小于等于和大于等于给定键的最近的键值对
        System.out.println(map.floorEntry("Bob")); // Bob=80
        System.out.println(map.ceilingEntry("Bob")); // Bob=80

        // 返回并删除 TreeMap 中最小和最大的键值对
        System.out.println(map.pollFirstEntry()); // Alice=90
        System.out.println(map.pollLastEntry()); // David=60
    }
}

1.3题解

在这里插入图片描述

class CountIntervals {

    TreeMap<Integer, Integer> m = new TreeMap<>();

    int cnt; // 所有区间长度和

    public CountIntervals() {}

    public void add(int left, int right) {

        // 遍历所有被 [left,right] 覆盖到的区间(部分覆盖也算)

        for (var e = m.ceilingEntry(left); e != null && e.getValue() <= right; e = m.ceilingEntry(left)) {

            int l = e.getValue(), r = e.getKey();

            left = Math.min(left, l);   // 合并后的新区间,其左端点为所有被覆盖的区间的左端点的最小值

            right = Math.max(right, r); // 合并后的新区间,其右端点为所有被覆盖的区间的右端点的最大值

            cnt -= r - l + 1;

            m.remove(r);

        }

        cnt += right - left + 1;

        m.put(right, left); // 所有被覆盖到的区间与 [left,right] 合并成一个新区间

    }



    public int count() { return cnt; }

}

2.Python中的SortedDict

2.1 SortedDict和dict

Python中map实现是dict(字典),不过python 3.7之前的dict是无序的,但是之后dict就是有序的(按照插入的顺序)。在本题的解法中使用到了SortedDict(),这个实现方式通常是基于红黑树或者跳表等数据结构,以实现有序性。SortedDict的插入、删除和查找操作的时间复杂度通常为O(log n),其中n是元素的数量。由于需要维护有序性,相对于普通的字典,SortedDict的操作可能会稍慢一些。dict的插入、删除和查找操作的平均时间复杂度为O(1),具有常数级别的性能。SortedDict需要使用sortedcontainers库,因此需要额外安装该库。dict是Python标准库中的内置类型,无需额外安装。

2.2 SortedDict的常见操作

SortedDict的内容可以在下面的博文中学习:

https://blog.csdn.net/hgq522/article/details/122375627

# 导入模块库
from sortedcontainers import SortedDict

# 初始化
sorted_dict = SortedDict({1: 'a', 4: 'd', 2: 'b'})

# 打印整个sorted-list
print('sorted dict is: ', sorted_dict)

# 增加一个元素
sorted_dict[3] = 'c'

# 再次打印整个sorted-list
print('sorted dict after adding an element: ', sorted_dict)

# 获取key list
print('get the key list', sorted_dict.keys())

# 获取最小key
print('get the min key', sorted_dict.keys()[0])

# 获取最大key
print('get the max key', sorted_dict.keys()[-1])

# 删除最大key
maxKey = sorted_dict.keys()[-1]
sorted_dict.pop(maxKey)

# 再次打印整个sorted-list
print('sorted dict after adding an element: ', sorted_dict)

# 删除所有
sorted_dict.clear()

print('sorted dict after removing all elements: ', sorted_dict)

SortedList 的文档
https://grantjenks.com/docs/sortedcontainers/sortedlist.html#sortedcontainers.SortedList.bisect_left

在这里插入图片描述

2.3 Python解法

from sortedcontainers import SortedDict

class CountIntervals:
    def __init__(self):
        self.d = SortedDict()
        self.cnt = 0  # 所有区间长度和

    def add(self, left: int, right: int) -> None:
        # 遍历所有被 [left,right] 覆盖到的区间(部分覆盖也算)
        i = self.d.bisect_left(left)
        while i < len(self.d) and self.d.values()[i] <= right:
            r, l = self.d.items()[i]
            left = min(left, l)    # 合并后的新区间,其左端点为所有被覆盖的区间的左端点的最小值
            right = max(right, r)  # 合并后的新区间,其右端点为所有被覆盖的区间的右端点的最大值
            self.cnt -= r - l + 1
            self.d.popitem(i)
        self.cnt += right - left + 1
        self.d[right] = left  # 所有被覆盖到的区间与 [left,right] 合并成一个新区间

    def count(self) -> int:
        return self.cnt

3.C++中的Map

3.1 C++中的有序关联容器和无序关联容器

在这里插入图片描述

3.2 C++中map的操作

具体的内容可以查看一下博文

https://blog.csdn.net/sinat_31608641/article/details/107424059

1.查找元素

//头文件
#includen <map>
 
map<int, string> ID_Name;
 
int main()
 {
	 std::map<char, int> mymap;
	 mymap['a'] = 50;
	 mymap['b'] = 100;
	 mymap['c'] = 150;
	 mymap['d'] = 200;
 
	 std::map<char, int>::iterator  it = mymap.find('b'); //查找值
	 if (it != mymap.end()) {
		 mymap.erase(it); // 删除某个值
	 }
	 return 0;
}

2.插入元素

#include <iostream>
#include <map>
 
int main()
{
    std::map<char, int> mymap;
 
    // 插入单个值
    mymap.insert(std::pair<char, int>('a', 100));
    mymap.insert(std::pair<char, int>('z', 200));
 
    //返回插入位置以及是否插入成功
    std::pair<std::map<char, int>::iterator, bool> ret;
    ret = mymap.insert(std::pair<char, int>('z', 500));
    if (ret.second == false) {
        std::cout << "element 'z' already existed";
        std::cout << " with a value of " << ret.first->second << '\n';
    }
 
    //指定位置插入
    std::map<char, int>::iterator it = mymap.begin();
    mymap.insert(it, std::pair<char, int>('b', 300));  //效率更高
    mymap.insert(it, std::pair<char, int>('c', 400));  //效率非最高
 
    //范围多值插入
    std::map<char, int> anothermap;
    anothermap.insert(mymap.begin(), mymap.find('c'));
 
    // 列表形式插入
    anothermap.insert({ { 'd', 100 }, {'e', 200} });
 
    return 0;
}

3.取值操作
Map中元素取值主要有at和[ ]两种操作,at会作下标检查,而[]不会

map<int, string> ID_Name;
 
//ID_Name中没有关键字2016,使用[]取值会导致插入
//因此,下面语句不会报错,但打印结果为空
cout<<ID_Name[2016].c_str()<<endl;
 
//使用at会进行关键字检查,因此下面语句会报错
ID_Name.at(2016) = "Bob";

4.容量查询

// 查询map是否为空
bool empty();
 
// 查询map中键值对的数量
size_t size();
 
// 查询map所能包含的最大键值对数量,和系统和应用库有关。
// 此外,这并不意味着用户一定可以存这么多,很可能还没达到就已经开辟内存失败了
size_t max_size();
 
// 查询关键字为key的元素的个数,在map里结果非0即1
size_t count( const Key& key ) const; //

5.交换
// 就是两个map的内容互换
void swap( map& other );

6.删除

// 删除迭代器指向位置的键值对,并返回一个指向下一元素的迭代器
iterator erase( iterator pos )
 
// 删除一定范围内的元素,并返回一个指向下一元素的迭代器
iterator erase( const_iterator first, const_iterator last );
 
// 根据Key来进行删除, 返回删除的元素数量,在map里结果非0即1
size_t erase( const key_type& key );
 
// 清空map,清空后的size为0
void clear();

3.3 C++题解

class CountIntervals {
    map<int, int> m;
    int cnt = 0; // 所有区间长度和

public:
    CountIntervals() {}
    
    void add(int left, int right) {
        // 遍历所有被 [left,right] 覆盖到的区间(部分覆盖也算)
        for (auto it = m.lower_bound(left); it != m.end() && it->second <= right; m.erase(it++)) {
            int l = it->second, r = it->first;
            left = min(left, l);   // 合并后的新区间,其左端点为所有被覆盖的区间的左端点的最小值
            right = max(right, r); // 合并后的新区间,其右端点为所有被覆盖的区间的右端点的最大值
            cnt -= r - l + 1;
        }
        cnt += right - left + 1;
        m[right] = left; // 所有被覆盖到的区间与 [left,right] 合并成一个新区间
    }
    
    int count() { return cnt; }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值