JavaScript 自定义集合(Set)数据结构实现详解

作者:纸飞机

时间:2026-10-23

浏览:602

本文详细讲解如何在 JavaScript 中基于数组手动实现集合(Set)数据结构。内容涵盖集合的基本定义、构造函数初始化,以及 add、remove、show 等基础操作方法。重点解析并集、交集、子集判断和补集等高级集合运算的算法逻辑与代码实现,帮助开发者深入理解集合的特性及其在 JS 中的应用。

文章目录

本文详细介绍 JavaScript 中算法与数据结构的核心概念——集合(Set)。以下内容为技术参考示例,旨在帮助读者理解相关实现原理。

集合(Set)

与数学定义一致,集合是由一组无序但具有特定关联性的成员组成的数据结构。每个成员在集合中仅出现一次。与字典或链表不同,集合是一种包含不同元素的数据结构,其核心特征有两个:成员无序且成员唯一,即集合内不存在重复元素。

在许多编程语言中,集合并非内置的数据类型。然而,当需要保存一组唯一元素时,集合数据结构便显得尤为重要。下文将探讨如何在 JavaScript 中实现这一结构。

集合的定义

在实现集合之前,需明确其基本定义:

  • 不包含任何成员的集合被称为空集;包含所有可能成员的集合被称为全集。
  • 若两个集合包含完全相同的成员,则称这两个集合相等。
  • 若一个集合的所有成员均包含于另一个集合中,则前者称为后者的子集。

集合的操作

集合通常支持以下三种基本操作:

  • 并集:合并两个集合的所有成员,生成一个新集合。
  • 交集:由两个集合中共有的成员组成一个新集合。
  • 补集:由属于一个集合但不属于另一个集合的成员组成一个新集合。

集合的实现

本文基于数组实现集合(Set),利用数组存储数据。结合前述概念,定义集合构造函数如下(为避免与 ES6 内置的 Set 类型混淆,此处命名为 MySet):


//构造函数

function MySet () {
  this.dataStore = [];      // 数据存储
  this.add = add;         // 添加成员
  this.remove = remove;      // 删除成员
  this.size = size;        // 集合元素个数
  this.union = union;       // 集合求并集
  this.intersect = intersect;   // 集合求交集
  this.subset = subset;      // 判断一个集合是否是另一集合的子集
  this.difference = difference;  // 集合求补集
  this.contains = contains;    // 判断某成员是否属于该集合
  this.show = show;        // 显示当前集合
}

首先实现向集合添加成员的方法,即 add 方法。

add:向集合中添加一个成员


//添加元素

function add (data) {
  //判断元素是否存在集合当中
  if( this.dataStore.indexOf( data ) < 0 ){
    this.dataStore.push(data);
    return true;
  }else{
    console.warn( 'Can not add ' + data + ', must already be in set');
    return false;
  }
}

由于集合元素具有唯一性,在将数据存储到数组前,需确保该数据不存在。通过 indexOf 方法检查新元素是否存在:若存在,返回其在数组中的位置;若不存在,返回 -1。因此,add 方法返回布尔值:添加成功返回 true,否则返回 false,以此明确指示元素是否成功插入。

remove:删除集合中某个成员


//删除元素

function remove (data) {
  //判断元素是否存在集合当中
  var pos = this.dataStore.indexOf(data);
  if( pos > -1 ){
    this.dataStore.splice(pos,1);
    return true;
  }else{
    console.warn( data + ' is not in set');
    return false;
  }
}

实现删除方法时,逻辑与 add 类似。首先检查待删除元素是否存在于数组中。若存在,调用数组的 splice() 方法删除该元素并返回 true;若不存在,直接返回 false,表示集合中无该元素。

完成添加和删除功能后,在测试前需定义 show 方法以显示集合成员。该方法实现简单,直接返回存储数据的数组即可:

show:显示集合中的成员


// 显示集合成员

function show(){
  console.log(this.dataStore);
  return this.dataStore;
}

接下来进行功能测试:


var fruits = new MySet();

// 添加成员
fruits.add('Apple');
fruits.add('Banana');
fruits.add('Pear');
fruits.show();       // ["Apple", "Banana", "Pear"]

// 添加重复成员
fruits.add('Apple');    // Can not add Apple, must already be in set

// 删除成员
fruits.remove('Banana');  
fruits.show();       // ["Apple", "Pear"]

// 删除不存在的成员
fruits.remove('Banana');  // Banana is not in set

基础功能运行正常,现在可以实现集合的高级操作,首先查看 union(并集)的实现。

union:求集合并集

求并集即合并两个集合并去除重复元素。实现思路是:将第一个集合的成员放入临时集合,然后判断第二个集合的成员是否已存在于临时集合中。若存在(即为重复元素),则跳过;否则将该成员加入临时集合,最后返回该集合。

为此,需要判断成员是否存在于集合中,因此引入辅助方法 contains()。其实现简单,直接使用 indexOf 判断即可。


//判断元素是否属于该集合

function contains (data) {
  if( this.dataStore.indexOf(data) > -1 ){
    return true;
  }else{
    return false;
  }
}

现在可以定义 union 方法了。


//求集合的并集

function union ( set ) {
  var tempSet = new MySet();
  for( var i = 0 ; i < this.dataStore.length ; i++ ){
    tempSet.add(this.dataStore[i]);
  }
  for( var i = 0 ; i< set.dataStore.length ; i++ ){
    if( !tempSet.contains(set.dataStore[i])){
      tempSet.dataStore.push(set.dataStore[i]);
    }
  }
  return tempSet;
}

至此,集合的并集功能已完成。


var fruits1 = new MySet();
fruits1.add('Apple');
fruits1.add('Banana');
fruits1.add('Pear');

var fruits2 = new MySet();
fruits2.add('Grape');
fruits2.add('Banana');
fruits2.add('Pear');
fruits2.add('Orange');

var union = fruits1.union( fruits2 );
union.show();              // ["Apple", "Banana", "Pear", "Grape", "Orange"]

测试通过,接下来查看求集合交集的实现。

intersect:求集合的交集

基于并集的实现思路,交集的定义相对简单:遍历第一个集合的成员,若发现该成员也属于第二个集合,则将其加入新集合,最后返回新集合。


//求集合的交集

function intersect (set) {
  var tempSet = new MySet();
  for(var i = 0 ; i < this.dataStore.length ; i++ ){
    if( set.contains(this.dataStore[i])){
      tempSet.add(this.dataStore[i]);
    }
  }
  return tempSet;
}

利用上述两个集合继续演示交集运算:


var intersect = fruits1.intersect( fruits2 );
intersect.show();                // ["Banana", "Pear"]

接下来定义 subset 操作。

subset:判断集合是否是另一集合的子集

该方法首先检查当前集合的长度是否小于待比较集合。若当前集合更大,则肯定不是子集。仅当待比较集合较大时,才判断当前集合的成员是否全部属于待比较集合。若有一个成员不属于,直接返回 false;仅当所有元素都属于待比较集合时,才返回 true。为方便查看,代码中加入了 console 打印。


//子集判断

function subset (set) {
  if( this.size() > set.size() ){
    console.log('not a subset');
    return false;
  }else{
    for ( var i = 0 ; i < this.dataStore.length ; i++ ){
      if( !set.contains(this.dataStore[i])){
        console.log('not a subset');
        return false;
      }
    }
  }
  console.log(' a subset');
  return true;
}

上述逻辑用到了 size 方法,其定义如下:


//返回集合长度

function size () {
  return this.dataStore.length;
}

保留之前的 fruits1 和 fruits2,新建 fruits3 来演示 subset 方法。


var fruits3 = new MySet();
fruits3.add('Apple');
fruits3.add('Banana');
fruits3.add('Pear');
fruits3.add('Grape');
fruits3.add('Orange');

//子集判断

fruits1.subset( fruits2 );   // not a subset
fruits2.subset( fruits2 );   // a subset
fruits1.subset( fruits3 );   // a subset

目前进展顺利,仅剩最后一个 difference 方法。该方法返回一个新集合,由属于第一个集合但不属于第二个集合的成员组成。

difference:补集

基于交集的思路,补集的实现逻辑自然清晰。


//补集

function difference (set) {
  var tempSet = new MySet();
  for( var i = 0 ; i < this.dataStore.length ; i ++ ){
    if( !set.contains(this.dataStore[i])){
      tempSet.dataStore.push( this.dataStore[i] );
    }
  }
  return tempSet;
}

进行测试:


fruits1.difference(fruits2).show();   // ['Apple']
fruits1.difference(fruits3).show();   // []
fruits2.difference(fruits1).show();   // ["Grape", "Orange"]

至此,一个完整的 Set 集合实现完毕。

本文介绍的集合与 ES6 提供的 Set 数据结构略有差异。ES6 的 Set 提供了大量现成方法,使用便捷。不熟悉 ES6 特性的读者可参考关于 ES6 中 Symbol、Set 和 Map 的相关文章,相信会有所收获。

感兴趣的朋友可以使用在线 HTML/CSS/JavaScript 代码运行工具:http://tools.jb51.net/code/HtmlJsRun 测试上述代码的运行效果。

更多关于 JavaScript 相关内容可查看本站专题:《JavaScript数学运算用法总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript数组操作技巧总结》、《JavaScript排序算法总结》、《JavaScript遍历算法与技巧总结》、《JavaScript查找算法技巧总结》及《JavaScript错误与调试技巧总结》。

希望本文所述对大家 JavaScript 程序设计有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章