【C++】从零到一掌握红黑树:数据结构中的平衡之道

个人主页: 起名字真南的CSDN博客

个人专栏:

  • 【数据结构初阶】 📘 基础数据结构
  • 【C语言】 💻 C语言编程技巧
  • 【C++】 🚀 进阶C++
  • 【OJ题解】 📝 题解精讲

目录

  • 前言
  • 1 红黑树的概念
      • **红黑树的五大性质**
  • 2 红黑树的实现
    • 2.1 红黑树的结构
    • 2.2 红黑树的插入
      • 2.2.1 红黑树插入的大概过程
      • 2.2.2 关于红黑树插入新节点的变色问题
      • 2.2.3 单旋加变色
      • 2.2.4 双旋加变色
      • 2.2.5 具体代码实现
      • 2.2.6 红黑树插入后变色旋转总结
      • 红黑树插入时需要处理的问题
      • 插入调整的基本步骤
      • 插入调整的条件、操作和原因
        • 情况 1:叔叔节点是红色
        • 情况 2:叔叔节点是黑色(或不存在)
          • 子情况 2.1:当前节点是父节点的左子节点(左左情况)
          • 子情况 2.2:当前节点是父节点的右子节点(左右情况)
          • 子情况 2.3:当前节点是父节点的右子节点(右右情况)
          • 子情况 2.4:当前节点是父节点的左子节点(右左情况)
      • 插入调整总结
        • 图解:插入调整过程
    • 2.3 红黑树的查找
    • 2.4 判断是否平衡

前言

红黑树被广泛应用于许多领域,例如 C++ STL 中的 map 和 set,Java 的 TreeMap 和 TreeSet,以及 Linux 内核、数据库索引等。相比其他平衡树,红黑树调整代价更低,尤其适合插入和删除操作频繁的场景。

1 红黑树的概念

红黑树(Red-Black Tree)是一种自平衡二叉搜索树,通过对节点的颜色、结构及调整规则的约束,实现了树的动态平衡。它的主要目的是在插入、删除等操作后,保持树的高度尽可能小,从而保证在最坏情况下的时间复杂度为 ( O(\log N) )。

红黑树的五大性质

  1. 节点颜色:每个节点要么是红色,要么是黑色。
  2. 根节点黑色:根节点必须是黑色。
  3. 红色节点限制:红色节点的子节点必须是黑色(红色节点不能连续相邻,红黑交替)。
  4. 黑高相等:从任意节点到其每个叶子节点的路径中,包含相同数量的黑色节点。
  5. 叶子节点为黑色:所有的叶子节点(空节点)都被视为黑色。

在这里插入图片描述

2 红黑树的实现

我们想要实现红黑树就要像清楚红黑树的构成时候枚举类型Colour,以及节点,和树一起构成的

2.1 红黑树的结构

  • 需要包含关键字段、包括键值对(Key,Value)、左右节点指针,父亲节点指针,以及枚举类型Colour用来记录节点的颜色
  • 示例代码
#include<iostream>
using namespace std;
enum Colour
{RED,Black
};template<class K,class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _colour;RBTreeNode(const pair<K,V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr){}};
  • 树 的定义

    • 包含节点。还有根节点
  • 示例代码

class RBTree
{typedef RBTreeNode<K, V> Node;
public://实现树的各种操作
private:Node* _root = nullptr;
};

在这串代码中并没有构造函数,是因为树是由节点和指针构成的,在构造时会调用节点的构造函数,根节点 也给了初始值。


2.2 红黑树的插入

2.2.1 红黑树插入的大概过程

  1. 在插入之前我们首先要了解以下红黑树的规则,也就是要遵循上面提到的五个性质
  2. 因为我们使用的枚举类型,只有两个变量即红色或黑色所以满足第一条
  3. 第二条根节点的颜色必须是黑色,所以我们在插入的过程中首先要进行判断插入的树是否为空树如果为空树则新增节点为黑色
  4. 不能出现两个连续的节点都是红色节点,如果一个节点是红色节点那么它的孩子必须是黑色,
  5. 黑高相等即每一条路径上面的黑色节点数量必须是一致的,所以我们新插入的节点必须是红色节点如果新插入的节点的父亲节点也是红色这里就涉及到我们接下来要讲的变色问题,

2.2.2 关于红黑树插入新节点的变色问题

首先我们要先确定好变色的前置条件就是新增节点是红色节点,并且它的父亲节点也是红色节点违反了红黑树的特性所以我们需要变色。
在接下来的图中用c来表示当前节点,p表示当前节点的父节点,u表示父亲节点的兄弟节点,g表示父亲节点的父亲节点
在这里插入图片描述
变色原因,因为c和p是两个连续的红色节点违反了红黑树的结构所以需要将p和u变成黑色,但是如果变成黑色以后会增加这两条路径上的黑色节点数量所以为了和以前保持一致需要将他们的父亲节点g节点变成红色,如果g节点的父亲节点也是红色则需要继续向上调整,由于情况有很多种所以我们将下面的结构抽象化处理。
在这里我们需要注意u和p一样在变化之前都是红色节点,如果u为黑色节点则c不是新增节点
在这里插入图片描述

2.2.3 单旋加变色

前置条件:出现了两个连续的红色节点并且u节点为黑色或者不存在,这个情况c可能不是新增节点,而是因为上述情况经过向上调整c为他们的g节点由开始的黑色变成了红色,此时如果新增的节点在左侧并且g节点也就是现在的c节点变成了红色此时u为黑色或者不存在,c在u的左侧这个时候需要进行右单旋,相反则需要进行左单旋,旋转以后需要将父亲节点变成黑色,祖父节点变成红色
在这里插入图片描述
在这里插入图片描述

2.2.4 双旋加变色

前置条件:在向上调整的时候出现了连续的红色节点并且叔叔节点为黑色或者不存在(因为如果不存在我们按照红黑树的特性也可以认为他有一个黑色的空结点)并且如果p在g的左侧,c在p的右侧这个时候我们需要先进行以p节点为旋转节点进行左旋在以g节点作为旋转节点进行右旋。
在这里插入图片描述

在这里插入图片描述
变色都是将g节点变成红色并且p节点和u节点变成黑色。

2.2.5 具体代码实现

bool Insert(const pair<K, V>& kv)
{if (_root == nullptr){_root = new Node(kv);_root->_colour = Black;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);//新插入的结点都是红色cur->_colour = RED;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;//父亲是红色,出现了连续的红色节点while (parent && parent->_colour == RED){Node* grandfarther = parent->_parent;//找叔叔节点if (grandfarther->_left == parent){//叔叔在右边Node* uncle = grandfarther->_right;if (uncle && uncle->_colour == RED){//变色是为了结点的数量不变,并且不能出现连续的红色结点parent->_colour = Black;uncle->_colour = Black;grandfarther->_colour = RED;//继续往上处理cur = grandfarther;parent = cur->_parent;//如果cur是根节点,则父亲为空,while循环条件造成了空引用//在循环条件中加上不能为空}else{if (cur == parent->_left){//叔叔节点为黑色或者不存在//右单旋//对grandfather位置进行旋转//      g                p//    p    u     ->   c    g  //  c                         uRotateR(grandfarther);parent->_colour = Black;grandfarther->_colour = RED;}else{//      g               c//    p    u    ->   p     g//      c                    u//需要双旋RotateL(parent);RotateR(grandfarther);cur->_colour = Black;grandfarther->_colour = RED;}break;}}else{//叔叔在左边Node* uncle = grandfarther->_left;if (uncle && uncle->_colour == RED){//变色是为了结点的数量不变,并且不能出现连续的红色结点parent->_colour = Black;uncle->_colour = Black;grandfarther->_colour = RED;//继续往上处理cur = grandfarther;parent = cur->_parent;//如果cur是根节点,则父亲为空,while循环条件造成了空引用//在循环条件中加上不能为空}else{if (cur == parent->_right){//左单旋//对grandfather位置进行旋转//      g                u//    u    p     ->   c    g  //  c                         pRotateL(grandfarther);parent->_colour = Black;grandfarther->_colour = RED;}else{//      g               c//    u    p    ->   u     g//      c                    p//需要双旋RotateR(parent);RotateL(grandfarther);cur->_colour = Black;grandfarther->_colour = RED;}break;}}}//必须保证跟节点是黑色_root->_colour = Black;return true;
}void RotateR(Node* parent)
{//         p                    subL//    subL           ->      c         p //  c     subLR            a       subLR//aNode* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* pParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (parent == pParent->_left){pParent->_left = subL;}else{pParent->_right = subL;}subL->_parent = pParent;}}void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* pParent = parent->_parent;subR->_left = parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (parent == pParent->_left){pParent->_left = subR;}else{pParent->_right = subR;}subR->_parent = pParent;}
}

具体的旋转细节请参考:

【C++】从「树」到「平衡」:全面解密 AVL 树的奥秘与实现

其中详细的介绍了单旋和双旋大家可以作为参考

2.2.6 红黑树插入后变色旋转总结

在红黑树的插入过程中,为了保持红黑树的性质,需要结合旋转和变色操作进行调整。以下是插入过程中各种旋转和变色的前置条件及其原因的详细梳理。


红黑树插入时需要处理的问题

插入新节点可能破坏以下红黑树性质:

  1. 性质 2:根节点必须是黑色。
  2. 性质 4:不能有两个连续的红色节点。
  3. 性质 5:每条从根到叶子节点的路径必须包含相同数量的黑色节点。

因此,调整过程主要是为了修复连续红色节点问题黑高失衡问题


插入调整的基本步骤

  1. 新插入的节点默认为红色(不会破坏黑高平衡)。
  2. 根据父节点和叔叔节点的颜色,可能需要变色和/或旋转调整。

插入调整的条件、操作和原因

情况 1:叔叔节点是红色
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点也是红色。
  • 操作

    • 父节点和叔叔节点变为黑色。
    • 祖父节点变为红色。
    • 将当前节点移动到祖父节点,继续检查祖父节点。
  • 原因

    • 变色的目的是修复性质 3(连续红色节点)。
    • 因为祖父节点变红,可能导致祖父节点和它的父节点连续红色,所以需要继续向上调整。

情况 2:叔叔节点是黑色(或不存在)
子情况 2.1:当前节点是父节点的左子节点(左左情况)
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点是黑色或不存在。
    • 当前节点是父节点的左子节点
  • 操作

    • 对祖父节点进行右旋
    • 父节点变为黑色,祖父节点变为红色。
  • 原因

    • 右旋是为了修复树的平衡,避免左重失衡。
    • 变色是为了满足性质 3 和性质 4。

子情况 2.2:当前节点是父节点的右子节点(左右情况)
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点是黑色或不存在。
    • 当前节点是父节点的右子节点
  • 操作

    • 对父节点进行左旋,将问题转化为左左情况。
    • 转换后再对祖父节点进行右旋
    • 父节点变为黑色,祖父节点变为红色。
  • 原因

    • 左旋是为了将右偏问题化简为左偏问题。
    • 后续的右旋和变色则恢复了树的平衡性和红黑树性质。

子情况 2.3:当前节点是父节点的右子节点(右右情况)
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点是黑色或不存在。
    • 当前节点是父节点的右子节点
  • 操作

    • 对祖父节点进行左旋
    • 父节点变为黑色,祖父节点变为红色。
  • 原因

    • 左旋是为了修复树的平衡,避免右重失衡。
    • 变色是为了满足性质 3 和性质 4。

子情况 2.4:当前节点是父节点的左子节点(右左情况)
  • 前置条件

    • 当前节点的父节点是红色。
    • 当前节点的叔叔节点是黑色或不存在。
    • 当前节点是父节点的左子节点
  • 操作

    • 对父节点进行右旋,将问题转化为右右情况。
    • 转换后再对祖父节点进行左旋
    • 父节点变为黑色,祖父节点变为红色。
  • 原因

    • 右旋是为了将左偏问题化简为右偏问题。
    • 后续的左旋和变色则恢复了树的平衡性和红黑树性质。

插入调整总结

条件操作原因
叔叔节点是红色变色修复连续红色问题,可能需要向上递归调整
叔叔节点是黑色,左左情况右旋 + 变色修复左重失衡和连续红色问题
叔叔节点是黑色,左右情况左旋 + 右旋 + 变色转化为左左情况后再修复
叔叔节点是黑色,右右情况左旋 + 变色修复右重失衡和连续红色问题
叔叔节点是黑色,右左情况右旋 + 左旋 + 变色转化为右右情况后再修复

图解:插入调整过程

左左情况

插入前:50(B)/30(R)/20(R)右旋 + 变色后:30(B)/    \20(R)  50(R)

右右情况

插入前:20(B)\30(R)\50(R)左旋 + 变色后:30(B)/    \20(R)  50(R)

左右情况

插入前:50(B)/20(R)\30(R)左旋后:50(B)/30(R)/20(R)右旋 + 变色后:30(B)/    \20(R)  50(R)

右左情况

插入前:20(B)\50(R)/30(R)右旋后:20(B)\30(R)\50(R)左旋 + 变色后:30(B)/    \20(R)  50(R)

原因总结

  1. 变色:解决颜色冲突(连续红色节点)。
  2. 旋转:解决树的结构失衡(左重或右重)。
  3. 结合变色与旋转:保证红黑树的五条性质不被破坏。

2.3 红黑树的查找

按照二叉树的旋转逻辑即可:

Node* find(const K& k)
{Node* cur = _root;while (cur){if (cur->_kv.first < k){cur = cur->_right;}else if (cur->_kv.first > k){cur = cur->_left;}else{return cur;}}return nullptr;
}

2.4 判断是否平衡

中心思想就是判断每个路径上的黑色节点数量是否相等,为了方便我们定义一个参考值用来记录其中一条路径上的黑色节点,然后再遍历其他路径和该路径的参考值进行比较,如果与参考值不相等则不平衡

bool IsBalance()
{if (_root == nullptr){return true;}if (_root->_colour == RED){return false;}//参考值int refnum = 0;Node* cur = _root;while (cur){if (cur->_colour == Black){refnum++;}cur = cur->_left;}return check(_root, 0, refnum);
}bool check(Node* root, int blacknum, const int refnum)
{if (root == nullptr){//前序遍历走到空了,意味着已经走完一条路径//和参考值进行比较if (blacknum == refnum){return true;}else{cout << "黑色节点数量不匹配" << endl;return false;}}// 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了if (root->_colour == RED && root->_parent->_colour == RED){cout << root->_kv.first << "存在连续的红色结点" << endl;return false;}if (root->_colour == Black){blacknum++;}return check(root->_left, blacknum, refnum)&& check(root->_right, blacknum, refnum);
}

在这里插入图片描述

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

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

相关文章

支持ACME协议可免费申请SSL证书的多种渠道

目录 一、ACME 协议 二、ACMESSL可视化 三、ACME证书申请流程 三、ACME申请提交 四、使用 ACME 的好处 五、ACME总结 一、ACME 协议 ACME 协议是一种开放标准&#xff0c;旨在自动执行数字证书颁发和续订流程&#xff0c;它彻底改变了证书管理。ACME 的开发旨在简化整个…

Socket编程(TCP/UDP详解)

前言&#xff1a;之前因为做项目和找实习没得空&#xff0c;计算机网络模块并没有写成博客&#xff0c;最近得闲了&#xff0c;把计算机网络模块博客补上。 目录 一&#xff0c;UDP编程 1&#xff09;创建套接字 2&#xff09;绑定端口号 3&#xff09;发送与接收数据 4&…

【人工智能-科普】图神经网络(GNN):与传统神经网络的区别与优势

文章目录 图神经网络(GNN):与传统神经网络的区别与优势什么是图神经网络?图的基本概念GNN的工作原理GNN与传统神经网络的不同1. 数据结构的不同2. 信息传递方式的不同3. 模型的可扩展性4. 局部与全局信息的结合GNN的应用领域总结图神经网络(GNN):与传统神经网络的区别与…

【Git 工具】用 IntelliJ IDEA 玩转 Git 分支与版本管理

文章目录 一、使用 IDEA 配置和操作 Git1.1 查看 Idea 中的 Git 配置1.2 克隆 Github 项目到本地 二、版本管理2.1 提交并推送修改2.2 拉取远程仓库2.3 查看历史2.4 版本回退 三、分支管理3.1 新建分支3.2 切换分支3.2 合并分支3.4 Cherry-Pick 参考资料 一、使用 IDEA 配置和操…

shell编程(4)脚本与用户交互以及if条件判断

声明&#xff1a; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

架构-微服务-服务调用Dubbo

文章目录 前言一、Dubbo介绍1. 什么是Dubbo 二、实现1. 提供统一业务api2. 提供服务提供者3. 提供服务消费者 前言 服务调用方案--Dubbo‌ 基于 Java 的高性能 RPC分布式服务框架&#xff0c;致力于提供高性能和透明化的 RPC远程服务调用方案&#xff0c;以及SOA服务治理方案。…

【Python网络爬虫笔记】2-HTTP协议中网络爬虫需要的请求头和响应头内容

1 HTTP 协议整理 HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;即超文本传输协议&#xff0c;是用于从万维网&#xff08;WWW&#xff09;服务器传输超文本到本地浏览器的传送协议&#xff0c;直白点儿&#xff0c;就是浏览器和服务器之间的数据交互就是通过 HTT…

TYUT设计模式大题

对比简单工厂&#xff0c;工厂方法&#xff0c;抽象工厂模式 比较安全组合模式和透明组合模式 安全组合模式容器节点有管理子部件的方法&#xff0c;而叶子节点没有&#xff0c;防止在用户在叶子节点上调用不适当的方法&#xff0c;保证了的安全性&#xff0c;防止叶子节点暴露…

SpringBoot集成Kafka和avro和Schema注册表

Schema注册表 为了提升kafka的性能&#xff0c;减少网络传输和存储的数据大小&#xff0c;可以把数据的schema部分单独存储到外部的schema注册表中&#xff0c;整体架构如下图所示&#xff1a; 1&#xff09;把所有数据需要用到的 schema 保存在注册表里&#xff0c;然后在记…

Wireshark 4.4.2:安全更新、错误修复、更新协议支持

流行的网络协议分析器Wireshark已更新至4.4.2版本。它可用于网络故障排除、分析、开发和教育。 已修复以下漏洞&#xff1a; wnpa-sec-2024-14 FiveCo RAP 解剖器无限循环。wnpa-sec-2024-15 ECMP 解析器崩溃。 更新的协议支持&#xff1a; ARTNET、ASN.1 PER、BACapp、B…

《Django 5 By Example》阅读笔记:p339-p358

《Django 5 By Example》学习第12天&#xff0c;p339-p358总结&#xff0c;总计20页。 一、技术总结 1.项目(购物网站) django-admin startproject myshop 虽然这里只是示例&#xff0c;但我觉得这种命名为 myxxx 的习惯非常不好&#xff0c;因为在实际应用中&#xff0c;是…

【简单好抄保姆级教学】javascript调用本地exe程序(谷歌,edge,百度,主流浏览器都可以使用....)

javascript调用本地exe程序 详细操作步骤结果 详细操作步骤 在本地创建一个txt文件依次输入 1.指明所使用注册表编程器版本 Windows Registry Editor Version 5.00这是脚本的第一行&#xff0c;指明了所使用的注册表编辑器版本。这是必需的&#xff0c;以确保脚本能够被正确解…

Milvus 2.5:全文检索上线,标量过滤提速,易用性再突破!

01. 概览 我们很高兴为大家带来 Milvus 2.5 最新版本的介绍。 在 Milvus 2.5 里&#xff0c;最重要的一个更新是我们带来了“全新”的全文检索能力&#xff0c;之所以说“全新”主要是基于以下两点&#xff1a; 第一&#xff0c;对于全文检索基于的 BM25 算法&#xff0c;我们采…

【数据分析】布朗运动(维纳过程)

文章目录 一、概述二、数学布朗运动2.1 数学定义2.2 布朗运动的数学模型2.21 标准布朗运动2.22 布朗运动的路径2.23 布朗运动的方程 三、布朗运动在金融学中的应用四、数学构造&#xff08;以傅里叶级数为例&#xff09;4.1 傅里叶级数的基本思想4.2 构造布朗运动 一、概述 布…

Spring Cloud(Kilburn 2022.0.2版本)系列教程(五) 服务网关(SpringCloud Gateway)

Spring Cloud(Kilburn 2022.0.2版本)系列教程(五) 服务网关(SpringCloud Gateway) 一、服务网关 1.1 什么是网关 在微服务架构中&#xff0c;服务网关是一个至关重要的组件。它作为系统的入口&#xff0c;负责接收客户端的请求&#xff0c;并将这些请求路由到相应的后端服务…

即时通讯| IM+RTC在AI技术加持下的社交体验

即时通讯作为互联网的重要应用之一&#xff0c;见证了中国互联网30年发展的辉煌历程。 它从最初的文字交流&#xff0c;发展到如今的语音、视频通话&#xff0c;甚至是虚拟现实社交&#xff0c;已经渗透到生活的社交、娱乐、商务等方方面面&#xff0c;成为现代社会不可或缺的一…

【Java基础入门篇】一、变量、数据类型和运算符

Java基础入门篇 一、变量、数据类型和运算符 1.1 变量 计算机中的数据表示方式是&#xff1a;“二进制(0/1)”&#xff0c;但是同时也可以兼容其他进制&#xff0c;例如八进制、十进制、十六进制等。 Java变量的本质是&#xff1a;存储在固定空间的内容&#xff0c;变量名是…

【博主推荐】C#的winfrom应用中datagridview常见问题及解决方案汇总

文章目录 1.datagridview绘制出现鼠标悬浮数据变空白2.datagridview在每列前动态添加序号2.1 加载数据集完成后绘制序号2.2 RowPostPaint事件绘制 3.datagridview改变行样式4.datagridview后台修改指定列数据5.datagridview固定某个列宽6.datagridview某个列的显示隐藏7.datagr…

使用经典的Java,还是拥抱新兴的Rust?

在当代互联网时代的企业级开发中&#xff0c;技术栈的选择往往牵动着每个团队的神经。随着Rust语言的崛起&#xff0c;许多开发团队开始重新思考&#xff1a;是继续坚持使用经典的Java&#xff0c;还是拥抱新兴的Rust&#xff1f;这个问题背后&#xff0c;折射出的是对技术演进…

Java项目运行报错“java: -source 1.5 中不支持 diamond 运算符“解决办法windows/linux系统踩坑实录

文章目录 一、问题描述二、解决方案 一、问题描述 在接手同事的Java项目时&#xff0c;依赖和打包都能正常操作&#xff0c;但一点击运行项目&#xff0c;就会报错&#xff1a; java: -source 1.5 中不支持 diamond 运算符 (请使用 -source 7 或更高版本以启用 diamond 运算符…