Qz学算法-数据结构篇(链表、栈)

简介: 数据结构的链表和栈

链表(Linked List)

链表是有序的列表,但是它在内存中是存储如下

网络异常,图片无法展示
|

介绍

  • 链表是以节点的方式来存储,是链式存储
  • 每个节点包含data域,next域:指向下一个节点.
  • 如图:发现链表的各个节点不一定是连续存储
  • 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定

1.单链表

单链表(带头结点)逻辑结构示意图如下

网络异常,图片无法展示
|

1.1单链表的创建和遍历

网络异常,图片无法展示
|

  • 添加
  • 先创建一个head头节点,作用就是表示单链表的头
  • 后面我们每添加一个节点,就直接加入到链表的最后
  • 遍历
  • 通过一个辅助变量遍历,帮助遍历整个链表

代码实现

要创建两个对象一个是节点对象,一个是链表对象

做add添加时,先找到链表的最后,如果这个链表没有最后,那么我们加入的这个node节点就是这次的头指针指向下一个节点

publicclassSingleLinkedListDemo {
publicstaticvoidmain(String[] args) {
//进行测试//先创建节点HeroNodehero1=newHeroNode(1, "宋江", "及时雨");
HeroNodehero2=newHeroNode(2, "卢俊义", "玉麒麟");
HeroNodehero3=newHeroNode(3, "吴用", "智多星");
HeroNodehero4=newHeroNode(4, "林冲", "豹子头");
//创建一个链表SingleLinkedListsingleLinkedList=newSingleLinkedList();
//加入singleLinkedList.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
singleLinkedList.list();
    }
}
//定义SingleLinkedList 管理我们的HeroclassSingleLinkedList{
//先初始化一个头节点,头节点不要动,不存放具体的数据privateHeroNodehead=newHeroNode(0,"","");
//返回头节点publicHeroNodegetHead(){
returnhead;
    }
//添加节点到单向链表//思路:当不考虑编号顺序时//1.找到当前链表的最后节点//2.将最后这个节点的next 指向新的节点publicvoidadd(HeroNodeheroNode){
//因为head节点不能动,因此我们需要一个辅助变量tempHeroNodetemp=head;
//遍历链表.找到最后while(true){
//找到链表的最后if(temp.next==null){
break;
            }
//如果没有找到最后,将temp后移temp=temp.next;
        }
//当退出while循环时,temp就指向了链表的最后//将最后这个节点的next指向新的节点temp.next=heroNode;
    }
//显示链表[遍历]publicvoidlist(){
//判断链表是否为空if (head.next==null){
System.out.println("链表为空");
return;
        }
//因为头节点不能动,因此我们需要一个辅助变量来遍历HeroNodetemp=head.next;
while(true){
//判断是否到链表最后if (temp==null){
break;
            }
//输出节点信息System.out.println(temp);
//将temp后移,一定注意temp=temp.next;
        }
    }
}
//定义一个HeroNode,每个HeroNode对象就是一个节点classHeroNode{
publicintno;
publicStringname;
publicStringnickName;
publicHeroNodenext;//指向下一个节点publicHeroNode(intno, Stringname, StringnickName) {
this.no=no;
this.name=name;
this.nickName=nickName;
    }
//为了显示方法,我们重新toString@OverridepublicStringtoString() {
return"HeroNode{"+"no="+no+", name='"+name+'\''+", nickName='"+nickName+'\''+'}';
    }
}

1.2按顺序插入

代码实现

//第二种方式在添加英雄时,根据排名将英雄插入到指定位置publicvoidaddByOrder(HeroNodeheroNode){
//因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置//因为单链表,因此我们找的temp是位于添加位置的前一个节点,否则插入不了HeroNodetemp=head;
booleanflag=false; //flag标志添加的编号是否存在,默认为falsewhile(true){
if (temp.next==null) break;  //说明temp已经在链表最后if (temp.next.no>heroNode.no){  //位置找到,就在temp的后面插入break;
        }elseif (temp.next.no==heroNode.no){//说明希望添加的heroNode的编号已然存在flag=true;  //说明编号存在break;
        }
temp=temp.next;//后移.遍历当前链表    }
//判断当前flag的值if (flag){  //不能添加,说明编号存在System.out.printf("准备插入的英雄编号%d,已经存在,不能添加\n",heroNode.no);
    }else{
//插入到链表中,temp后面heroNode.next=temp.next;
temp.next=heroNode;
    }
}
heroNode.next = temp.next;
        temp.next = heroNode;

这里我们把hero.next=temp.next操作是因为,我们要插入一个节点,那么就需要让索引后移一维,即插入位置的后一个节点指向临时变量节点指向的后一个节点,临时变量指向的节点等于要插入的节点

我们按照1423的顺序插入

singleLinkedList.addByOrder(hero1);
singleLinkedList.addByOrder(hero4);
singleLinkedList.addByOrder(hero2);
singleLinkedList.addByOrder(hero3);

image.png

1.3修改节点信息

代码实现

//修改节点信息  根据no编号来修改,即no编号不能改,//说明//1. 根据根据newHeroNode的no来修改即可publicvoidupdate(HeroNodenewHeroNode){
//判断是否为空if (head.next==null){
System.out.println("链表为空");
return;
    }
//找到需要修改的节点,根据no编号//定义一个铺助变量HeroNodetemp=head.next;
booleanflag=false; //表示是否找到节点while(true){
if (temp==null){
break;  //已经遍历完链表        }
if (temp.no==newHeroNode.no){
//找到flag=true;
break;
        }
temp=temp.next;
    }
//根据flag 判断是否找到要修改的节点if (flag){
temp.name=newHeroNode.name;
temp.nickName=newHeroNode.nickName;
    }else{//没有找到System.out.printf("没有找到编号%d的节点",newHeroNode.no);
    }
}

网络异常,图片无法展示
|

1.4删除节点

思路

  • 我们先找到需要删除的这个节点的前一个节点temp
  • temp.next =temp.next.next
  • 被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收

代码实现

//删除节点
public void del(int no){
    HeroNode temp = head;
    boolean flag = false; //标志是否找到待删除节点
    while(true){
        if (temp.next == null) break;//已经到链表的最后
        if (temp.next.no == no){
            //找到了待删除的节点的前一节点temp
            flag = true;
            break;
        }
        temp = temp.next;   //temp后移,遍历
    }
    //判断flag
    if (flag){//找到
        //可以删除
        temp.next = temp.next.next;
    }else {
        System.out.printf("要删除的%d节点不存在",no);
    }
}
复制代码

2.双向链表

2.1单链表的缺点

  • 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
  • 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp的下一个节点来删除的

2.2双向链表的操作思路

  • 遍历方式和单链表一样,只是可以向前查找.也可以向后查找
  • 添加(默认添加到双线链表的最后)
  • 先找到双线链表的最后这个节点
  • temp.next = newHeroNode
  • newHeroNode.pre=temp
  • 修改思路和原来的单向链表一样
  • 删除
  • 因为是双向链表,因此,我么可以实现自我删除某个节点
  • 直接找到要删除的这个节点,比如temp
  • temp.pre.next = temp.next
  • temp.next.pre = temp.pre

2.3代码实现

publicclassDoubleLinkedListDemo {
publicstaticvoidmain(String[] args) {
System.out.println("双向链表的测试");
HeroNode2hero1=newHeroNode2(1, "宋江", "及时雨");
HeroNode2hero2=newHeroNode2(2, "卢俊义", "玉麒麟");
HeroNode2hero3=newHeroNode2(3, "吴用", "智多星");
HeroNode2hero4=newHeroNode2(4, "林冲", "豹子头");
//创建一个双向链表DoubleLinkedListdoubleLinkedList=newDoubleLinkedList();
doubleLinkedList.add(hero1);
doubleLinkedList.add(hero2);
doubleLinkedList.add(hero3);
doubleLinkedList.add(hero4);
doubleLinkedList.list();
//修改HeroNode2newHeroNode=newHeroNode2(4,"公孙胜","入云龙");
doubleLinkedList.update(newHeroNode);
System.out.println("修改后的链表情况");
doubleLinkedList.list();
//删除doubleLinkedList.del(3);
System.out.println("删除后的链表情况");
doubleLinkedList.list();
    }
}
//创建一个双线链表的类classDoubleLinkedList{
//先初始化一个头节点,头节点不要动,不存放具体的数据privateHeroNode2head=newHeroNode2(0,"","");
//返回头节点publicHeroNode2getHead(){
returnhead;
    }
//遍历双向链表的方法//显示链表[遍历]publicvoidlist(){
//判断链表是否为空if (head.next==null){
System.out.println("链表为空");
return;
        }
//因为头节点不能动,因此我们需要一个辅助变量来遍历HeroNode2temp=head.next;
while(true){
//判断是否到链表最后if (temp==null){
break;
            }
//输出节点信息System.out.println(temp);
//将temp后移,一定注意temp=temp.next;
        }
    }
//添加一个节点到双向链表的最后publicvoidadd(HeroNode2heroNode){
//因为head节点不能动,因此我们需要一个辅助变量tempHeroNode2temp=head;
//遍历链表.找到最后while(true){
//找到链表的最后if(temp.next==null){
break;
            }
//如果没有找到最后,将temp后移temp=temp.next;
        }
//当退出while循环时,temp就指向了链表的最后//形成一个双向链表temp.next=heroNode;
heroNode.pre=temp;
    }
//修改一个节点的内容,可以看到双向链表的节点内容修改和单向链表一样//知识节点类型改成了HeroNode2publicvoidupdate(HeroNode2newHeroNode){
//判断是否为空if (head.next==null){
System.out.println("链表为空");
return;
        }
//找到需要修改的节点,根据no编号//定义一个铺助变量HeroNode2temp=head.next;
booleanflag=false; //表示是否找到节点while(true){
if (temp==null){
break;  //已经遍历完链表            }
if (temp.no==newHeroNode.no){
//找到flag=true;
break;
            }
temp=temp.next;
        }
//根据flag 判断是否找到要修改的节点if (flag){
temp.name=newHeroNode.name;
temp.nickName=newHeroNode.nickName;
        }else{//没有找到System.out.printf("没有找到编号%d的节点",newHeroNode.no);
        }
    }
//从双向链表中删除一个节点//说明//1 对于双向链表,我们可以直接找到要删除的这个节点//2 找到后,自我删除即可publicvoiddel(intno){
//判断当前链表是否为空if (head.next==null){
System.out.println("链表为空,无法删除");
return;
        }
HeroNode2temp=head.next;//辅助变量(指针)booleanflag=false; //标志是否找到待删除节点while(true){
if (temp==null) break;//已经到链表的最后if (temp.no==no){
//找到了待删除的节点的前一节点tempflag=true;
break;
            }
temp=temp.next;   //temp后移,遍历        }
//判断flagif (flag){//找到//可以删除//temp.next = temp.next.next; [单向链表]temp.pre.next=temp.next;
//这里代码有风险//如果是最后一个节点,就不需要执行下面这句话,否则出现空指针异常if (temp.next==null){
temp.next.pre=temp.pre;
            }
        }else {
System.out.printf("要删除的%d节点不存在",no);
        }
    }
}
//定义一个HeroNode2,每个HeroNode对象就是一个节点classHeroNode2{
publicintno;
publicStringname;
publicStringnickName;
publicHeroNode2next;//指向下一个节点,默认为nullpublicHeroNode2pre;//指向前一个节点,默认为nullpublicHeroNode2(intno, Stringname, StringnickName) {
this.no=no;
this.name=name;
this.nickName=nickName;
    }
//为了显示方法,我们重新toString@OverridepublicStringtoString() {
return"HeroNode{"+"no="+no+", name='"+name+'\''+", nickName='"+nickName+'\''+'}';
    }
复制代码

3.环形链表和约瑟夫问题

3.1引入

Josephus(约瑟夫、约瑟夫环)问题

Josephus问题为:设编号为1,2,…n的n个人围坐一圈,约定编号为(1<=k<=n)的人从1开始报数,数到m的那个 人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

提示:

用一个不带头结点的循环链表来处理Josephus问题:先构成一个有个结点的单循环链表,然后由结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中册则除算法结束。

网络异常,图片无法展示
|

网络异常,图片无法展示
|

3.2思路

构建一个单向的环形链表思路

  • 先创建第一个节点,让first指向该节点,并形成环形
  • 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可

遍历环形链表

  • 先让一个辅助指针(变量)curBoy,指向first节点
  • 然后通过一个while循环遍历该环形链表即可curBoy.next=first结束

3.3代码实现(构建环形链表和遍历)

publicclassJosephus {
publicstaticvoidmain(String[] args) {
//测试一把看看构建环形链表,和遍历是否QkCircleSingleLinkedListcircleSingleLinkedList=newCircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.showBoy();
    }
}
//创建一个环形单向链表classCircleSingleLinkedList{
//创建一个first节点,当前没有编号的privateBoyfirst=null;
//添加小孩的节点,构建成一个环形链表publicvoidaddBoy(intnums){
//nums 做一个数据校验if (nums<1){
System.out.println("nums的值不正确");
return;
        }
BoycurBoy=null;//辅助指针,帮助构建环形链表//使用for循环来创建环形链表for (inti=1; i<=nums; i++) {
//根据编号,创建小孩节点Boyboy=newBoy(i);
//如果是第一个小孩if (i==1){
first=boy;
first.setNext(first); //构成环curBoy=first;//让curBoy指向第一个小孩            }else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy=boy;
            }
        }
    }
//遍历当前环形链表publicvoidshowBoy(){
//判断链表是否为空if (first==null){
System.out.println("链表为空,没有任何小孩");
return;
        }
//因为first不能动,因此我们仍然使用一个辅助指针完成遍历BoycurBoy=first;
while (true){
System.out.printf("小孩的编号%d\n",curBoy.getNo());
if (curBoy.getNext()==first){//说明已经遍历完毕break;
            }
curBoy=curBoy.getNext();  //curBoy后移        }
    }
}
//创建一个Boy类,表示一个节点classBoy{
privateintno;//编号privateBoynext;//指向下一个节点,默认nullpublicBoy(intno) {
this.no=no;
    }
publicintgetNo() {
returnno;
    }
publicvoidsetNo(intno) {
this.no=no;
    }
publicBoygetNext() {
returnnext;
    }
publicvoidsetNext(Boynext) {
this.next=next;
    }
}
复制代码

3.4代码实现(出圈操作)

1.需求创建一个辅助指针(变量)helper,事先应该指向环形链表的最后这个节点 补充:小孩报数前,先让first和helper移动k-1次


2.当小孩报数时,让first和helper指针同时的移动m-1次


3.这时就可以将first指向的小孩节点出圈 first = first.next helper.next=first 原来fist指向的节点就没有任何引用,就会被回收

//根据用户的输入,计算小孩出圈的顺序/*** @param startNo  表示从第几个小孩开始数数* @param countNum 表示数几下* @param nums     表示最初有多少小孩在圈中*/publicvoidcountBoy(intstartNo, intcountNum, intnums) {
//先对数据进行校验if (first==null||startNo<1||startNo>nums) {
System.out.println("参数输入有误,请重新输入");
return;
        }
//创建辅助指针,帮助小孩 出圈Boyhelper=first;
//需求创建一个辅助指针(变量)helper,事先应该指向环形链表的最后这个节点while (true) {
if (helper.getNext() ==first) {//说明helper指向最后小孩节点break;
            }
helper=helper.getNext();
        }
//小孩报数前,先让first和helper移动k-1次for (inti=0; i<startNo-1; i++) {
first=first.getNext();
helper=helper.getNext();
        }
//当小孩报数时,让first和helper指针同时的移动m-1次,然后出圈//这里是一个循环操作,知道圈中只有一个节点while(true){
if (helper==first){//说明圈中只有一个节点break;
            }
//让first和helper指针同时移动countNum-1for (inti=0; i<countNum-1; i++) {
first=first.getNext();
helper=helper.getNext();
            }
//这时first指向的节点,就是小孩要出圈的节点System.out.printf("小孩%d出圈\n",first.getNo());
//这时将first指向的小孩节点出圈first=first.getNext();
helper.setNext(first);
        }
System.out.printf("最后留在圈中的小孩编号%d\n",first.getNo());
    }
}
复制代码

需求引入

计算式:[7*2*2-5+1-5+3-3]

请问:计算机底层是如何运算得到结果的?注意不是简单的把算式列出运算,因为我们看这个算式7*2*2-5,但是计算机怎么理解这个算式的对计算机而言,它接收到的就是一个字符串),我们讨论的是这个问题。=>栈

1.介绍

  • 栈的英文为(stack)
  • 栈是一个先入后出(FILO-First In LastOut))的有序列表。
  • 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另端为固定的一端,称为栈底(Bottom)。
  • 根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
  • 出栈和入栈的概念(如图所示)

网络异常,图片无法展示
|

2.应用场景

  • 子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
  • 处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
  • 表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。
  • 二叉树的遍历。
  • 图形的深度优先(depth一first)搜索法:

3.思路分析

网络异常,图片无法展示
|

  • 使用数组来模拟栈
  • 定义一个top来表示栈顶,初始化为1
  • 入栈的操作,当有数据加入到栈时,top++;stack[top]=data;
  • 出栈的操作,int value=stack[top];top--,return value;

4.代码实现

publicclassArrayStackDemo {
publicstaticvoidmain(String[] args) {
//测试一下ArrayStack//先创建一个Arraystack对象->表示栈ArrayStackstack=newArrayStack(4);
Stringkey="";
booleanloop=true;//控制是否退出菜单Scannerscanner=newScanner(System.in);
while(loop){
System.out.println("show:表示显示栈");
System.out.println("exit:退出程序");
System.out.println("push:添加数据到栈(入栈)");
System.out.println("show:从栈取出数据(出栈)");
System.out.println("请输入你的选择");
key=scanner.next();
switch (key){
case"show":
stack.list();
break;
case"push":
System.out.println("请输入一个数字");
intval=scanner.nextInt();
stack.push(val);
break;
case"pop":
try{
intres=stack.pop();
System.out.printf("出栈的数据是%d\n",res);
                    }catch (Exceptione){
System.out.println(e.getMessage());
                    }
break;
case"exit":
scanner.close();
loop=false;
break;
default:
break;
            }
        }
System.out.println("程序退出了");
    }
}
//定义一个ArrayStack 表示栈classArrayStack{
privateintmaxSize;//栈的大小privateint[] stack;//数组.数组模拟栈.数据就放在该数组中privateinttop=-1;//top表示栈顶,初始化位-1publicArrayStack(intmaxSize) {
this.maxSize=maxSize;
stack=newint[this.maxSize];
    }
//栈满publicbooleanisFull(){
returntop==maxSize-1;
    }
//栈空publicbooleanisEmpty(){
returntop==-1;
    }
//入栈-pushpublicvoidpush(intvalue){
//先判断栈是都满if (isFull()){
System.out.println("栈满");
return;
        }
top++;
stack[top]=value;
    }
//出栈-pop,将栈顶的数据返回publicintpop(){
//先判断栈是否空if (isEmpty()){
//抛出异常thrownewRuntimeException("栈空,没有数据");
        }
intvalue=stack[top];
top--;
returnvalue;
    }
//遍历栈的情况 遍历时,需要从栈顶开始显示数据publicvoidlist(){
if (isEmpty()){
System.out.println("栈空,没有数据");
return;
        }
for (inti=top; i>=0 ; i--) {
System.out.printf("stack[%d]=%d\n",i,stack[i]);
        }
    }
}


目录
相关文章
|
16天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
91 9
|
7天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
16 1
|
1月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
69 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
10天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。
|
13天前
|
存储 JavaScript 前端开发
执行上下文和执行栈
执行上下文是JavaScript运行代码时的环境,每个执行上下文都有自己的变量对象、作用域链和this值。执行栈用于管理函数调用,每当调用一个函数,就会在栈中添加一个新的执行上下文。
|
15天前
|
存储
系统调用处理程序在内核栈中保存了哪些上下文信息?
【10月更文挑战第29天】系统调用处理程序在内核栈中保存的这些上下文信息对于保证系统调用的正确执行和用户程序的正常恢复至关重要。通过准确地保存和恢复这些信息,操作系统能够实现用户模式和内核模式之间的无缝切换,为用户程序提供稳定、可靠的系统服务。
43 4
|
19天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
19天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
19天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
32 4