数据结构之Set | 让我们一块来学习数据结构
数组(列表)、栈、队列和链表这些顺序数据结构对你来说应该不陌生了。现在我们要学习集合,这是一种不允许值重复的顺序数据结构。我们将要学到如何创建集合这种数据结构,如何添加和移除值,如何搜索值是否存在。你也会学到如何进行并集、交集、差集等数学运算。
本章内容包括:
- 从头创建一个 Set 类
- 用 Set 来进行数学运算
构建数据集合
集合是由一组无序且唯一(即不能重复)的项组成的。该数据结构使用了与有限集合相同的数学概念,但应用在计算机科学的数据结构中。
在深入学习集合的计算机科学实现之前,我们先看看它的数学概念。在数学中,集合是一组不同对象的集。
比如说,一个由大于或等于 0 的整数组成的自然数集合:N = {0, 1, 2, 3, 4, 5, 6, …}。集合中的对象列表用花括号({})包围。
还有一个概念叫空集。空集就是不包含任何元素的集合。比如 24 和 29 之间的素数集合,由于 24 和 29 之间没有素数(除了 1 和自身,没有其他正因数的、大于 1 的自然数),这个集合就是空集。空集用{ }表示。
你也可以把集合想象成一个既没有重复元素,也没有顺序概念的数组。在数学中,集合也有并集、交集、差集等基本运算。本文也会介绍这些运算。
创建集合类
创建基础类
用下面的 Set 类以及它的构造函数声明作为开始。
class Set {
constructor() {
this.items = {};
}
}
有一个非常重要的细节是,我们使用对象而不是数组来表示集合(items)。不过,也可以用数组实现。此处用对象来实现,和我们在第 4 章与第 5 章中学习到的对象实现方式很相似。同样地,JavaScript 的对象不允许一个键指向两个不同的属性,也保证了集合里的元素都是唯一的。
接下来,需要声明一些集合可用的方法(我们会尝试模拟与 ECMAScript 2015 实现相同的Set 类)。
- add(element):向集合添加一个新元素。
- delete(element):从集合移除一个元素。
- has(element):如果元素在集合中,返回 true,否则返回 false。
- clear():移除集合中的所有元素。
- size():返回集合所包含元素的数量。它与数组的 length 属性类似。
- values():返回一个包含集合中所有值(元素)的数组。
has(item)方法
首先要实现的是 has(element)
方法,因为它会被 add
、delete
等其他方法调用。它用来检验某个元素是否存在于集合中,下面看看它的实现。
has(item) {
return Object.prototype.hasOwnProperty.call(this.items, item);
}
除了使用
Object.prototype.hasOwnProperty
方法实现之外,还可以使用item in this.items
和this.items.hasOwnProperty(item)
来实现has
方法。
add(item)方法
接下来要实现 add(item)
方法。
add(item) {
if (this.has(item)) {
return false;
}
this.items[item] = item;
return true;
}
对于给定的 item,可以检查它是否存在于集合中。如果不存在,就把 item 添加到集合中,并返回 true,表示添加了该元素。如果集合中已经有了这个元素,就返回 false,表示没有添加它。
delete(item) 和 clear() 方法
下面要实现 delete(item)
方法。
delete(item) {
if (this.has(item)) {
delete this.items[item];
return true
}
return false
}
在 delete 方法中,我们会验证给定的 item 是否存在于集合中。如果存在,就从集合中移除 item,返回 true,表示元素被移除;否则返回 false。
由于我们是使用对象来存储集合的items对象,那么就可以简单的使用对象的delete运算符从items中删除元素。
如果想移除集合中的所有值,可以用 clear 方法。
clear() {
this.items = {}
}
size() 方法
实现size方法有几种方式:
- 使用一个 length 变量,每当使用 add 或 delete 方法时就控制它
Object.keys(this.items).length
- 使用
for in
(要记得使用hasOwnProperty
判断一下)- ...
现在我们使用第2中方式来实现,代码如下
size() {
return Object.keys(this.items).length;
}
values() 方法
要实现 values()
方法,我们同样可以使用 Object
类内置的 values
方法。
values() {
return Object.values(values);
}
Object.values()
方法返回了一个包含给定对象所有属性值的数组。它是在ECMAScript 2017
中被添加进来的,目前只在现代浏览器中可用。
使用Set类
现在数据结构已经完成了,看看如何使用它吧。试着执行一些命令,测试我们的 Set 类。
const set = new Set();
set.add(1);
console.log(set.values()); // 输出[1]
console.log(set.has(1)); // 输出 true
console.log(set.size()); // 输出 1
set.add(2);
console.log(set.values()); // 输出[1, 2]
console.log(set.has(2)); // 输出 true
console.log(set.size()); // 输出 2
set.delete(1);
console.log(set.values()); // 输出[2]
set.delete(2);
console.log(set.values()); // 输出[]
集合运算
集合是数学中基础的概念,在计算机领域也非常重要。它在计算机科学中的主要应用之一是数据库,而数据库是大多数应用程序的根基。集合被用于查询的设计和处理。当我们创建一条从关系型数据库(Oracle、Microsoft SQL Server、MySQL 等)中获取一个数据集合的查询语句时,使用的就是集合运算,并且数据库也会返回一个数据集合。当我们创建一条 SQL
查询命令时,可以指定是从表中获取全部数据还是获取其中的子集;也可以获取两张表共有的数据、只存在于一张表中的数据(不存在于另一张表中),或是存在于两张表内的数据(通过其他运算)。这些 SQL
领域的运算叫作联接,而 SQL
联接的基础就是集合运算。
我们可以对集合进行如下运算。
- 并集:对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。
- 交集:对于给定的两个集合,返回一个包含两个集合中共有元素的新集合。
- 差集:对于给定的两个集合,返回一个包含所有存在于第一个集合且不存在于第二个集合的元素的新集合。
- 子集:验证一个给定集合是否是另一集合的子集。
重要的是要注意,本文实现的
union
、intersection
和difference
方法不会修改当前的Set
类实例或是作为参数传入的otherSet
。没有副作用的方法和函数被称为纯函数。纯函数不会修改当前的实例或参数,只会生成一个新的结果。这在函数式编程中是非常重要的概念。
并集
并集的数学概念。集合 A 和集合 B 的并集表示为 \(A ∪ B\),定义如下。
\]
意思是 x(元素)存在于 A 中,或 x 存在于 B 中。下图展示了并集运算。
代码实现
union(otherSet) {
let unionSet = new Set();
let values = this.values();
values.forEach((value) => {
unionSet.add(value);
});
values = otherSet.values();
values.forEach((value) => {
unionSet.add(value);
});
return unionSet;
}
交集
交集的数学概念。集合 A 和集合 B 的交集表示为 \(A ∩ B\),定义如下。
\]
意思是 x(元素)存在于 A 中,且 x 存在于 B 中。下图展示了交集运算。
代码实现
intersection(otherSet) {
let intersectionSet = new Set();
const values = this.values();
const otherValues = otherSet.values();
let smallerValues = values;
let biggerValues = otherValues;
if (otherValues.length < values.length) {
smallerValues = otherValues;
biggerValues = values;
}
smallerValues.forEach((value) => {
if (biggerValues.includes(value)) {
intersectionSet.add(value);
}
});
return intersectionSet;
}
> 为了减少循环次数,在代码中判断了哪个集合的长度最小,然后循环长度较小的集合,以达到减少循环次数的目的。
差集
差集的数学概念。集合 A 和集合 B 的差集表示为 \(A - B\),定义如下。
\]
意思是 x(元素)存在于 A 中,且 x 不存在于 B 中。下图展示了集合 A 和集合 B 的差集运算。
代码实现
difference(otherSet) {
let differenceSet = new Set();
this.values().forEach((value) => {
if (!otherSet.has(value)) {
differenceSet.add(value);
}
});
return differenceSet;
}
子集
要介绍的最后一个集合运算是子集。其数学概念的一个例子是集合 A 是集合 B 的子集(或集合 B 包含集合 A),表示为 \(A ∈ B\),定义如下。
\]
意思是集合 A 中的每一个 x(元素),也需要存在于集合 B 中。下图展示了集合 A 是集合 B 的子集。
代码实现
isSubsetOf(otherSet) {
if (this.size() > otherSet.size()) {
return false;
}
let isSubset = true;
const values = this.values();
const size = this.size();
for (let i = 0; i < size; i++) {
if (!otherSet.has(values[i])) {
isSubset = false;
break;
}
}
return isSubset;
}
参考资料
- 学习JavaScript数据结构与算法第三版
其它数据结构的文章
- 数据结构之LinkedList | 让我们一块来学习数据结构
- 数据结构之List | 让我们一块来学习数据结构
- 数据结构之Stack | 让我们一块来学习数据结构
- 数据结构之Queue | 让我们一块来学习数据结构
数据结构之Set | 让我们一块来学习数据结构的更多相关文章
- 数据结构之Queue | 让我们一块来学习数据结构
前面的两篇文章分别介绍了List和Stack,下面让我们一起来学习Queue 数据结构之List | 让我们一块来学习数据结构 数据结构之Stack | 让我们一块来学习数据结构 队列的概况 队列是一 ...
- 数据结构之LinkedList | 让我们一块来学习数据结构
highlight: monokai theme: vue-pro 上一篇文章中使用列表(List)对数据排序,当时底层储存数据的数据结构是数组.本文将讨论另外一种列表:链表.我们会解释为什么有时链表 ...
- 数据结构之Stack | 让我们一块来学习数据结构
栈的介绍 栈就是和列表类似的一种数据结构,它可用来解决计算机世界里的很多问题.栈是一种高 效的数据结构,因为数据只能在栈顶添加或删除,所以这样的操作很快,而且容易实现. 栈的使用遍布程序语言实现的方方 ...
- 数据结构之List | 让我们一块来学习数据结构
列表[List]的定义 列表是一组有序的数据.每个列表中的数据项称为元素.在 JavaScript 中,列表中的元素 可以是任意数据类型.列表中可以保存多少元素并没有事先限定,实际使用时元素的数量 受 ...
- 《Java程序设计与数据结构教程(第二版)》学习指导
<Java程序设计与数据结构教程(第二版)>学习指导 欢迎关注"rocedu"微信公众号(手机上长按二维码) 做中教,做中学,实践中共同进步! 原文地址:http:// ...
- SqList *L 和 SqList * &L的区别/学习数据结构突然发现不太懂 小祥我查找总结了一下
小祥在学习李春葆的数据结构教程时发现一个小问题,建立顺序表和输出线性表,这两个函数的形参是不一样的. 代码在这里↓↓↓ //定义顺序表L的结构体 typedef struct { Elemtype d ...
- 在Object-C中学习数据结构与算法之排序算法
笔者在学习数据结构与算法时,尝试着将排序算法以动画的形式呈现出来更加方便理解记忆,本文配合Demo 在Object-C中学习数据结构与算法之排序算法阅读更佳. 目录 选择排序 冒泡排序 插入排序 快速 ...
- MySQL 学习 --- 数据结构和索引
本文参考了多篇文章集成的笔记,希望各位学习之前可以阅读以下参考资料先 概述 文章分几个部分 :第一部分介绍了B-Tree 和 B+Tree 这种数据结构作为索引:第二部分介绍索引的最左前缀原则和覆盖索 ...
- 省选算法学习-数据结构-splay
于是乎,在丧心病狂的noip2017结束之后,我们很快就要迎来更加丧心病狂的省选了-_-|| 所以从写完上一篇博客开始到现在我一直深陷数据结构和网络流的漩涡不能自拔 今天终于想起来写博客(只是懒吧.. ...
随机推荐
- vue全家桶和react全家桶
vue全家桶:vue + vuex (状态管理) + vue-router (路由) + vue-resource +axios +elementui react全家桶 : react + re ...
- MySQL基础知识:MySQL Connection和Session
在connection的生命里,会一直有一个user thread(以及user thread对应的THD)陪伴它. Connection和Session概念 来自Stackoverflow的一个回答 ...
- C# 8 中的异步迭代器 IAsyncEnumerable<T> 解析
异步编程已经流行很多年了,.NET 引入的 async 和 await 关键词让异步编程更具有可读性,但有一个遗憾,在 C# 8 之前都不能使用异步的方式处理数据流,直到 C# 8 引入的 IAsyn ...
- A Color Game
题目大意: 给定一个只包含七种字母的字符串,如果满足一段连续相同的字符长度大于等于K那么即可消除,问最后能不能变为空字符. 题解:很明显是用区间dp来解决,我们设dp[l][r][k]代表的是在[l ...
- A New Stone Game POJ - 1740
题目链接:https://vjudge.net/problem/POJ-1740#author=0 题意:有n堆石子,每次你可以选一堆拿走任意数量的石子,而且你还可以选择从这一堆剩下石子中取任意数量石 ...
- Jmeter(四十) - 从入门到精通进阶篇 - Jmeter配置文件的刨根问底 - 中篇(详解教程)
1.简介 为什么宏哥要对Jmeter的配置文件进行一下讲解了,因为有的童鞋或者小伙伴在测试中遇到一些需要修改配置文件的问题不是很清楚也不是很懂,就算修改了也是模模糊糊的.更有甚者觉得那是禁地神圣不可轻 ...
- (五)Struts2处理结果管理
当Action处理完用户请求时,处理结果应该通过视图资源实现,但将哪个视图呈现给浏览者呢.由<result.../>来决定 Action处理完用户请求后,返回一个普通字符串.整个普通字符串 ...
- joda-time的简单使用及mysql时间函数的使用(今天,本周,本月)
近期在做一些首页的统计数据复习了下mysql的时间函数,以及后续修改成 传入时间查询时使用的joda-time 软件简介 JodaTime 提供了一组Java类包用于处理包括ISO8601标准在内的d ...
- (二) LDAP 安装
LDAP 安装 参考:https://blog.51cto.com/bigboss/2341986
- Day01_06_Java注释
Java注释 注释 - 单行注释:// xxxxx - 多行注释:/* xxxxx */ - javadoc注释: /** * * * */ - javadoc注释可以被bin目录下的javadoc. ...