目录
【链表】
简介:链表是有序列表,链式存储,结点包含data域,next域,各结点间不一定连续存储,链表从根节点有无存储数据可分为:带头链表(根节点有数据),不带头链表(根节点无数据)。链表的实现有很多结构:普通类,结构体,内部类...,数据遍历方式有循环,递归...
分类:单向链表,双向链表,环形链表
【单向链表】
普通类结构的单向列表(以循环遍历数据)
简介:单向-普通类-循环-有头
结构分析:
1,创建两个类:Node类实现数据存储,输出格式等。LinkedList类实现根节点创建赋值,实现add,addByOrder,remove,show 等对链表的操作方法。
2,add:声明一个辅助结点temp并赋值为root,通过循环遍历链表,每次循环通过temp=temp.next来向后一位,当temp.next==null时为链表末,跳出循环,利用temp.next=newNode添加结点
3,addByOrder:声明一个辅助结点temp并赋值为root,声明一个标志用于标记是否是找到正确位置后跳出循环的,通过循环遍历链表,每次循环通过temp=temp.next来向后一位,当temp.next==null时为链表末,跳出循环,利用temp.next=newNode添加结点。当temp.next.no>newNode.no时查找到了应该插入的位置(升序),改标志为true跳出循环,利用newNode.next=temp.next;temp.next=newNode来插入结点
4,modify:通过类似于上述循环遍历过程,当匹配到与要修改序号相同的结点后,通过temp.next.data=newNode.data来修改结点
5,remove:通过类似于上述循环遍历过程,当匹配到与要删除序号相同的结点后,通过temp.next=temp.next.next来删除结点,该结点将成为垃圾数据被GC回收。
6,show:当链表非空时,遍历打印即可
代码实现:
package cn.dataStructure.demo;
import java.util.LinkedHashSet;
class Node{//结点类
public int no;
public String name;
public Node next;//保存下一个结点
public Node(int no,String name){
this.no=no;
this.name=name;
}
@Override
public String toString() {
return "["+this.no+"]"+"["+this.name+"]";
}
}
class LinkedList{
private Node root=new Node(0,"");//创建根节点
public void add(Node newNode){//普通add:不按序号存储
Node temp=root;//辅助结点
while (true){//查找链表最后结点
if (temp.next==null){
break;
}
temp=temp.next;//向后一位
}
temp.next=newNode;//添加结点
}
public void addByOrder(Node newNode){//按顺序add
Node temp=root;
boolean flag=false;//是否为重复编号
while (true){
if (temp.next==null){//找到链表最后一位
break;
}
if (temp.next.no>newNode.no){//找到合适位置(升序)
break;
}
if (temp.next.no==newNode.no){//找到重复编号
flag=true;
break;
}
temp=temp.next;//向后一位
}
if (flag){
System.out.println("查找到重复数据,无法加入");
return;
}else {
//插入新结点
newNode.next=temp.next;
temp.next=newNode;
}
}
public void modify(Node newNode){//根据Node中的no来匹配要修改的结点
Node temp=root;
boolean flag=false;//判断是否找到了序号一样Node
while (true){
if (temp.next==null){//遍历完链表,没找到
break;
}
if (temp.next.no==newNode.no){//找到了序号一样的
flag=true;
break;
}
temp=temp.next;//后退一位
}
if (flag){
temp.next.name=newNode.name;
}else {
System.out.println("没有找到要修改结点");
}
}
public void remove(int no){//删除结点
Node temp=root;
boolean flag=false;//判断是否找到了序号一样Node
while (true){
if (temp.next==null){//遍历完链表,没找到
break;
}
if (temp.next.no==no){//找到了序号一样的
flag=true;
break;
}
temp=temp.next;//后退一位
}
if (flag){
temp.next=temp.next.next;//删除结点
}else {
System.out.println("没有找到要删除结点");
}
}
public void show(){
if (root.next==null){//判断是否为空链表
System.out.println("链表为空");
return;
}
Node temp=root;
while (true){//遍历打印
if (temp.next==null){
break;
}
System.out.println(temp.next.toString());
temp=temp.next;//向后一位
}
}
}
public class 单链表_LinkedList_普通类循环实现 {
public static void main(String[] args) {
LinkedList link=new LinkedList();
// link.add(new Node(1,"小米"));
// link.add(new Node(3,"小明"));
// link.add(new Node(2,"小红"));
// System.out.println("不按序号add:");
// link.show();
link.addByOrder(new Node(1,"小米"));
link.addByOrder(new Node(3,"小明"));
link.addByOrder(new Node(2,"小红"));
System.out.println("按序号add:");
link.show();
link.modify(new Node(3,"小华"));
link.remove(2);
System.out.println("修改后:");
link.show();
}
}
上述链表:单向-普通类-循环-有头,但普通类结构的问题在于Node类数据为了LinkedList类的使用便利牺牲了封装性(当然也可以用get方法获取私有成员的形式,但有点麻烦)。为了解决既要访问数据便利,又要维持封装性,那么就启用Java方便的内部类吧!
内部类结构的单向列表(以递归遍历数据)
简介:单向-内部类-递归-无头
结构分析:
【ILink接口】:统一了链表操作规范,并在隐藏操作细节的同时暴露了链表操作的方法。
【LinkImple】:用于实现ILink中的方法,在其中拥有一个私有内部类 Node结点类,通过该类保存结点信息,该内部类中还有供外部类LinkImple调用的方法如:addNode,toArrayNode,getNode,setNode,removeNode,containsNode…这些方法都沿用了递归的思想,通过Node对象与Node的Next对象,不断递归找到条件满足的状态,实现具体的功能。这些方法给外部类LinkImple的实现方法来使用。
【add方法实现】:外部类先剔除空数据后,开辟一个Node类对象,判定根节点root是否为空,空就对象赋予root,否则通过内部类的addNode方法利用递归的形式找到空结点并赋值。
【count方法实现】:在外部类中有count变量,每当add一个数据时,就加一,在外部类count方法中只需要返回count值即可。
【isEmpty方法实现】:判断count数据是否为0即可。
【toArray方法实现】:在外部类中有一个Object类型的数组叫returnData[],外部类toArray方法中调用isEmpty方法先判断不为空后,通过内部类中的toArray的方法递归的将数据一个个填入returnData中,外部类只需要返回returnData即可。
【get方法实现】:当输入的索引小于链表长度时,通过调用内部类中的getNode方法来递归的寻找索引位置的结点,并返回其数据。
【set方法实现】:与get方法类似
【contains方法实现】:当输入数据不为空时,通过内部类中的containsNode方法来递归的寻找与输入数据相同的结点。找到了返回true。
【remove方法实现】:分为删除根节点与删除子节点之分,外部类中的remove方法先与根节点数据比较,相同时通过root.next赋值给root,来删除根节点。不相同时将此结点做为前一个结点返回给内部类中的removeNode方法通过递归到相同数据的结点时,将此结点的next赋值于上一个结点的next,完成此节点删除。注意外部类要将count数据减一
【clean方法实现】:将root赋值null数据即可。
package cn.dataStructure.demo;
interface ILink<E>
{
public void add(E e);//链表数据增加
public int count();//获取链表元素个数
public boolean isEmpty();//空集合判断
public Object[] toArray();//返回链表数据
public E get(int index);//根据索引取得数据
public void set(int index,E data);//修改链表数据
public boolean contains(E data);//数据内容查询
public void remove(E data);//删除链表数据:1.删除根节点,2.删除子节点
public void clean();//清空链表数据
}
class LinkImple<E> implements ILink<E>
{
private int count;
private Node root;
private int foot;
private Object [] returnData;
private class Node
{
private E data;
private Node next;
public Node(E data){//节点存储数据操作
this.data=data;
}
public void addNode(Node newNode){//节点存储下一个节点操作
if(this.next==null){//当前对象的下一个节点内存为空时,把传入的节点对象赋给当前对象的下一个节点内存中
this.next=newNode;
}else{//当前对象的下一个节点内存非空时,通过递归调用来实现节点对象的保存
this.next.addNode(newNode);
}
}
public void toArrayNode(){
LinkImple.this.returnData[LinkImple.this.foot++]=this.data;
if(this.next!=null){
this.next.toArrayNode();
}
}
public E getNode(int index){
if(LinkImple.this.foot++ == index){
return this.data;
}else{
return this.next.getNode(index);//将此处得到的数据再用一个return返回到调用处
}
}
public void setNode(int index,E data){
if(LinkImple.this.foot++ == index){
this.data=data;
}else{
this.next.setNode(index,data);
}
}
public boolean containsNode(E data){
if(data.equals(this.data)){
return true;
}else if(this.next==null){
return false;
}else{
return this.next.containsNode(data);
}
}
public void removeNode(Node previous,E data){//删除子节点
if(data.equals(this.data)){
previous.next=this.next;
}else if(this.next!=null){
this.next.removeNode(this,data);
}
}
}
public void add(E e){
if(e==null){
return ;
}
Node newNode=new Node(e);
if(this.root==null){
root=newNode;
}else{
this.root.addNode(newNode);
}
count++;
}
public int count(){
return this.count;
}
public boolean isEmpty(){
//return this.root==null;
return this.count==0;
}
public Object[] toArray(){
if(this.isEmpty()){
return null;
}
this.foot=0;
this.returnData=new Object[this.count];
this.root.toArrayNode();
return returnData;
}
public E get(int index){
if(index>=count){
return null;
}
this.foot=0;
return this.root.getNode(index);
}
public void set(int index,E data){
if(index>=count){
return ;
}
this.foot=0;
this.root.setNode(index,data);
}
public boolean contains(E data){
if(data==null){
return false;
}
return this.root.containsNode(data);
}
public void remove(E data){//删除根节点
if(data==null){
return ;
}
if(data.equals(this.root.data)){
this.root=this.root.next;
}else{
this.root.removeNode(this.root,data);
}
count--;
}
public void clean(){
this.root=null;
count=0;
}
}
public class 单链表_LinkedList_内部类递归实现
{
public static void main(String agrs[]){
ILink <String>link=new LinkImple <String> ();
System.out.println("初始化数据:"+link.count()+"\t 是否为空集合:"+link.isEmpty());
link.add("hello");
link.add("world");
link.add("!");
System.out.println("结果数据:"+link.count()+"\t 是否为空集合:"+link.isEmpty());
Object[] result=link.toArray();
for(Object temp:result){
System.out.println(temp);
}
System.out.println("---------------------根据索引获取链表元素--------------------");
System.out.println(link.get(1));
System.out.println("---------------------根据索引修改链表元素--------------------");
link.set(1,"WORLD");
System.out.println(link.get(1));
System.out.println("---------------------根据数组盘判断是否存在对应链表元素--------------------");
System.out.println(link.contains("WORLD"));
System.out.println("---------------------根据自动内容删除对应链表元素--------------------");
link.remove("WORLD");
Object[] resultB=link.toArray();
for(Object temp:resultB){
System.out.println(temp);
}
System.out.println("---------------------清空链表元素--------------------");
link.clean();
System.out.println(link.isEmpty());
}
}
初始化数据:0 是否为空集合:true
结果数据:3 是否为空集合:false
hello
world
!
---------------------根据索引获取链表元素--------------------
world
---------------------根据索引修改链表元素--------------------
WORLD
---------------------根据数组盘判断是否存在对应链表元素--------------------
true
---------------------根据自动内容删除对应链表元素--------------------
hello
!
---------------------清空链表元素--------------------
true
我喜欢这种单向链表,但在同等条件下,递归性能弱于循环,所以在链表较长的情况下采用循环的形式遍历数据,会更好
几个常见单向链表功能实现(以第一个源码例)
1,求单链表有效结点个数
非空-->循环遍历计数
public static int getLength(Node root){
if (root.next==null){
return 0;
}
int length=0;
Node temp=root.next;
while (temp!=null){
length++;
temp=temp.next;
}
return length;
}
2,查找单链表的倒数第K个结点
非空-->遍历获取链表长度length-->遍历找到第length-k个结点
public static Node findLastIndexNode(Node root,int index){
if (root.next==null){
return null;
}
int length=0;
Node temp=root.next;
while (temp!=null){
length++;
temp=temp.next;
}
if (index>length||index<=0){
return null;
}
temp=root.next;
for (int i=0;i<length-index;i++){
temp=temp.next;
}
return temp;
}
3,单向链表翻转
空或只有一个有效结点-->非空多个结点-->新定义一个存放翻转链表的链表-->遍历原链表,每遍历一个结点就把结点摘下来按在新链表的最前面(将结点从原链表拆下并与新链表的原先最前结点相连,将拆下的结点与新链表的头结点相连)-->最后将新链表的有效结点串连入原头结点即可
public static void reverseList(Node root){
if (root.next==null||root.next.next==null){
return;
}
Node reverse=new Node(0,"");//新建翻转链表根节点
Node temp=root.next;
Node next=null;//由于temp在拆结点时next会发生变化,所以要先保存一下
while (temp!=null){
next=temp.next;
temp.next=reverse.next;//将结点从原链表拆下并与新链表的原先最前结点相连
reverse.next=temp;//将拆下的结点与新链表的头结点相连
temp=next;//将原结点向后一位
}
root.next=reverse.next;//将翻转好的有效新链表接入原头结点之下
}
4,逆序打印链表
非空-->遍历压栈-->遍历出栈(利用了栈的先入后出原则。不能用3中翻转后再打印,这样破坏了原链表。需要导入java.util.stack)
public static void reversePrint(Node root){
if (root.next==null){
return;
}
Node temp=root.next;
Stack <Node> stack=new Stack<>();//创栈
while (temp!=null){
stack.push(temp);//遍历压栈
temp=temp.next;
}
while (stack.size()>0){
System.out.println(stack.pop());//stack先入后出
}
}
5,合并两个有序链表,合并之后仍有序
【方法1】循环顺序合并
新建混合顺序链表link3-->当两个链表都非空-->谁结点小把谁添加到link3的链表中(千万注意:要把链表中对应的结点初始化。由于循环的link1,link2链表结点的next域里很可能语句有了数据,所以一定要把结点中数据值重新赋给一个新建的Node 对象中,再调用add方法。否则给link3添加的link1,link2中的链表会连接到链表其余的结点上,最终link3链表将无限长,add方法将进入死循环。我哭了,为了这个bug,我deBug了一晚上!!)该方法前提两个链表是顺序链表
public static LinkedList conbineByOreder(LinkedList link1,LinkedList link2){
LinkedList link3=new LinkedList();
Node temp1=link1.root.next;
Node temp2=link2.root.next;
while (temp1!=null && temp2!=null){
if (temp1.no < temp2.no){
Node node=new Node(temp1.no,temp1.data);//就是这,要把链表中对应的结点初始化,否则你会哭
link3.add(node);
temp1=temp1.next;
}else {
Node node=new Node(temp2.no,temp2.data);//就是这,要把链表中对应的结点初始化,否则你会哭
link3.add(node);
temp2=temp2.next;
}
}
if (temp1==null){
while (temp2!=null){
Node node=new Node(temp2.no,temp2.data);//就是这,要把链表中对应的结点初始化,否则你会哭
link3.add(node);
temp2=temp2.next;
}
}else {
if (temp2==null){
while (temp1!=null){
Node node=new Node(temp1.no,temp1.data);//就是这,要把链表中对应的结点初始化,否则你会哭
link3.add(node);
temp1=temp1.next;
}
}
}
return link3;
}
【方法2】递归顺序合并(需要无头的链表,或者把有头链表无头化)
获取链表最前的有效结点(有头根结点.next)-->方法体内先判断两个链表的node1,node2哪个结点为空,谁空了返回对方的值-->都不为空时node1.no与node2.no比较,谁小赋值给node3,再将node3.next通过递归赋值,递归的参数为 小的node.next和大的node。该方法前提两个链表是顺序链表
public static Node cobineByOrder(Node linkValue1,Node linkValue2){
if (linkValue1==null){
return linkValue2;
}
if (linkValue2==null){
return linkValue1;
}
Node linkValue3=null;//混合顺序链表
//递归形成混合顺序链表
if (linkValue1.no < linkValue2.no){
linkValue3=linkValue1;
linkValue3.next=cobineByOrder(linkValue1.next,linkValue2);
}else {
linkValue3=linkValue2;
linkValue3.next=cobineByOrder(linkValue1,linkValue2.next);
}
return linkValue3;
}
单向链表已经可以实现不少功能了,但单向链表只能正向打印,而实现逆向打印很麻烦,且无法实现节点自我删除,要依靠辅助结点指向要删除结点的上一个再删除。解决方案:双向链表
【双向链表】
所谓双向,只不过是Node结点中多了一个pre用来存储上一个结点,下面以 双向-普通类-循环-有头 链表为例:
结构分析:
1,show,modify方法不变
2,add方法要添加 newNode.pre=temp 来连接上一个结点。addByOrder方法在插入结点时要先进行空判断(防止在链末添加结点时,下一个结点为null没有pre,再连pre时引起的空指向异常),非空 temp.next.pre=newNode; newNode.pre=temp 来连接上一个结点
3,新增reverseShow方法,用于逆向打印:非空-->遍历到最后-->利用pre向前遍历
4,remove方法与addByOrder方法一样在插入结点时要先进行空判断,非空 temp.next.next.pre=temp.pre 来覆盖上一个结点连接
package cn.dataStructure.demo;
class DNode{//结点类
public int no;
public String data;
public DNode next;//保存下一个结点
public DNode pre;//保存上一个结点
public DNode(int no,String data){
this.no=no;
this.data =data;
}
@Override
public String toString() {
return "["+this.no+"]"+"["+this.data +"]";
}
}
class DoubleLinkedList{//双向-普通类-有头-循环
private DNode root=new DNode(0,"");//创建根节点
public DNode getRoot(){
return this.root;
}
public void add(DNode newNode){//普通add:不按序号存储
DNode temp=this.root;//root不能动,需要辅助结点
while (true){//查找链表最后结点
if (temp.next==null){
break;
}
temp=temp.next;//向后一位
}
temp.next=newNode;//尾结点双向添加
newNode.pre=temp;
}
public void addByOrder(DNode newNode){//按顺序add
DNode temp=root;
boolean flag=false;//是否为重复编号
while (true){
if (temp.next==null){//找到链表最后一位
break;
}
if (temp.next.no>newNode.no){//找到合适位置(升序)
break;
}
if (temp.next.no==newNode.no){//找到重复编号
flag=true;
break;
}
temp=temp.next;//向后一位
}
if (flag){
System.out.println("查找到重复数据,无法加入");
return;
}else {
//插入新结点
newNode.next=temp.next;
temp.next=newNode;
//在逆向连接的时候注意,若处于链尾时,next为空没有pre不能先前连,否则会空指向异常
if (temp.next!=null){
temp.next.pre=newNode;
newNode.pre=temp;
}else {
newNode.pre=temp;
}
}
}
public void show(){
if (root.next==null){//判断是否为空链表
System.out.println("链表为空");
return;
}
DNode temp=root;
while (true){//遍历打印
if (temp.next==null){
break;
}
System.out.println(temp.next.toString());
temp=temp.next;//向后一位
}
}
public void modify(DNode newNode){//根据DNode中的no来匹配要修改的结点
DNode temp=root;
boolean flag=false;//判断是否找到了序号一样DNode
while (true){
if (temp.next==null){//遍历完链表,没找到
break;
}
if (temp.next.no==newNode.no){//找到了序号一样的
flag=true;
break;
}
temp=temp.next;//后退一位
}
if (flag){
temp.next.data =newNode.data;
}else {
System.out.println("没有找到要修改结点");
}
}
public void remove(int no){//删除结点
DNode temp=root;//注意这里是头结点所以temp.next=temp.next.next没问题,如果不是头结点而是root.next,需要进行空判断
boolean flag=false;//判断是否找到了序号一样DNode
while (true){
if (temp.next==null){//遍历完链表,没找到
break;
}
if (temp.next.no==no){//找到了序号一样的
flag=true;
break;
}
temp=temp.next;//后退一位
}
if (flag){
temp.next=temp.next.next;//删除正向连接,这里不会空指向异常,因为有空的头结点
if (temp.next!=null){//不能是temp.next.next因为此处temp.next已经被上面的语句替换!
temp.next.next.pre=temp.pre;//删除逆向连接,这里会空指向异常,要空判断
}
}else {
System.out.println("没有找到要删除结点");
}
}
public void reverseShow(){//逆向打印
if (root.next==null){//判断是否为空链表
System.out.println("链表为空");
return;
}
DNode temp=root;
while (temp.next!=null){//遍历到链表最后
temp=temp.next;
}
while (temp!=root){//逆向打印
System.out.println(temp);
temp=temp.pre;
}
}
}
public class 双向链表_DoubleLinkedList {
public static void main(String[] args) {
DoubleLinkedList dlink=new DoubleLinkedList();
dlink.addByOrder(new DNode(1,"one"));
dlink.addByOrder(new DNode(4,"three"));
dlink.addByOrder(new DNode(2,"two"));
dlink.addByOrder(new DNode(3,"three"));
System.out.println("按顺序add:");
dlink.show();
System.out.println("修改结点:");
dlink.modify(new DNode(4,"four"));
dlink.show();
System.out.println("删除结点:");
dlink.remove(4);
dlink.show();
System.out.println("逆向打印:");
dlink.reverseShow();
}
}
按顺序add:
[1][one]
[2][two]
[3][three]
[4][three]
修改结点:
[1][one]
[2][two]
[3][three]
[4][four]
删除结点:
[1][one]
[2][two]
[3][three]
逆向打印:
[3][three]
[2][two]
[1][one]
【环形链表】
就是把非环形链表的尾结点的next域指向根节点,下面以 环形-单向-普通类-循环-无头 为例(顺手解决约瑟夫Josephu问题):
结构分析:
1,add方法:是否为第一个结点,是就自成一环-->不是就遍历到最后-->向环中插入结点
2,AutoAdd方法:也就是一个循环里面每次自动创建结点后调用add方法添加,建立从1-n的顺序环链
3,count方法:用于解决约瑟夫问题,通过建立两个辅助结点(temp,select)。先移动开始结点数-1次,使select移动到开始的结点处,后移动开始结点数-2次,使temp移动到select之后,通过定数循环,在每轮循环后打印select结点,并将select向后一位,利用temp删除刚刚被选中的结点即可
package cn.dataStructure.demo;
class CNode{
private int data;
private CNode next;
public CNode(int no){
this.data =no;
}
public void setNext(CNode next){
this.next=next;
}
public CNode getNext() {
return next;
}
public int getData() {
return data;
}
@Override
public String toString() {
return "["+this.data +"]";
}
}
class CirleLinkedList{
private CNode root;
public CNode getRoot(){
return root;
}
public void add(CNode newNode){
if (root==null){//当添加第一个结点时自成一环
root=newNode;
root.setNext(newNode);
}
CNode temp=root;
while (temp.getNext()!=root){//循环遍历到环的最后
temp=temp.getNext();
}
//向环中插入结点
temp.setNext(newNode);
temp.getNext().setNext(root);
}
public void show(){
CNode temp=root;
while (true){
System.out.println(temp);
if (temp.getNext()==root){//判断是否遍历完毕
break;
}
temp=temp.getNext();
}
}
public void AutoAdd(int num){
if (num<1){//不足1,无法形成环
return;
}
CNode node;
for (int i=1;i<=num;i++){
node=new CNode(i);
add(node);
}
}
/**
* 解决约瑟夫问题
* @param all 总人数
* @param startNo 从第几个人开始
* @param num 数多少下
*/
public void count(int all,int startNo,int num){
if (root==null||startNo<1||startNo>all){//输入验证
System.out.println("输入参数有误");
return;
}
//需要两个辅助结点,用于确定被选中人,和被选中人的前一个人,便于结点删除
CNode temp=root;
CNode select=root;
for (int i=0;i<startNo-1;i++){//先移动startNo-1次,使select移动到开始的结点处
select=select.getNext();
}
for (int i=0;i<startNo-2;i++){//后移动startNo-2次,使temp移动到select之后
temp=temp.getNext();
}
while (true){//模拟报数选人
if (temp==select){//只剩一个人了
System.out.printf("幸运儿为:%d",select.getData());
break;
}
for (int i=0;i<num-1;i++){//自己也要报数,所以-1
temp=temp.getNext();
select=select.getNext();
}
System.out.printf("编号%d的被选中\n",select.getData());
//删除被选中结点
select=select.getNext();
temp.setNext(select);
}
}
}
public class 环形单向链表_CirleLinkedList_约瑟夫问题 {
public static void main(String[] args) {
CirleLinkedList cLink=new CirleLinkedList();
// cLink.add(new CNode(1));
// cLink.add(new CNode(2));
// cLink.add(new CNode(3));
// cLink.add(new CNode(4));
cLink.AutoAdd(10);
// cLink.show();
cLink.count(10,2,3);
}
}
编号4的被选中
编号7的被选中
编号10的被选中
编号3的被选中
编号8的被选中
编号2的被选中
编号9的被选中
编号6的被选中
编号1的被选中
幸运儿为:5
【数据结构与算法整理总结目录 :>】<-- 宝藏在此(doge)