拜托,别再问我什么是链表了!!!

链表(Linked List)

上一篇文章分析了List源码,这一篇文章本来要分析Set的源码,发现Set的底层是使用HashMap实现的,于是准备先分析Map,但是发现map的实现类的底层数据结构是数组,链表,红黑树,撤了撤了,先讲一波数据结构,数组大家都比较熟悉,所以我们主要讲链表和树结构,这一篇文章先学习链表。

链表的定义

链表是以节点(node)存储的链式存储结构,一个node包含一个data域(存放数据)和一个next域(存放下一个node的指针),链表的各个节点不一定是连续的,它可以分为带头结点和不带头结点。头结点仅包含next域。

下面我们主要讲解单向链表双向链表单向循环链表(约瑟夫问题)

1、单向链表

单向链表的特点是链接方向是单向的,对链表的访问要从头部开始进行顺序读取,链表中的每个结点仅有一个指向下一个结点的指针。

1.1构造链表

  • 创建节点类,包含了data和next,next指向下一个节点对象

class PersonNode {
public int number;//每个结点的序号,方便我们进行排序和查找
public String name;//结点的名称
public String nickName;//结点的别名
public PersonNode next;//指向下一结点的指针

public PersonNode(int number, String name, String nickName) {
this.number = number;
this.name = name;
this.nickName = nickName;
}

@Override
public String toString() {
return "PersonNode{" +
      "number=" + number +
      ", name='" + name + '\'' +
      ", nickName='" + nickName + '\'' +
      '}';
}
}
  • 创建单向链表类,里面包含了增删改查的方法

class SingleLinkedList {
    //创建头结点
    private PersonNode headNode = new PersonNode(0, "", "");

    public PersonNode getHeadNode() {
        return headNode;
    }
    
//添加节点的单向链表

    /**
     * 在链表尾部插入
     */

    public void addNode(PersonNode personNode) {
      ......
    }

    /**
     * 按照number顺序插入
     */
    public void addByOrder(PersonNode personNode) {
       ......
    }

    /**
     * 修改节点信息
     */

    public void update(PersonNode personNode) {
      ......
    }

    /**
     * 删除某一个节点
     * 先根据number查找,在删除
     */
    public void delete(PersonNode personNode) {
        ......
    }

    /**
     * 遍历节点
     */
    //显示链表中的有效节点
    public void show(PersonNode personNode) {
       ......
    }

1.2单向链表增删改查的实现

我们将会以图解的方式讲解,

  • 在链表中增加一个节点


确定要插入的节点位置,循环遍历到该位置节点的上一个节点。

S->next=P->next
P->next=S

代码实现

  /**
     * 按照number顺序插入
     */
    public void addByOrder(PersonNode personNode) {
        //定义插入标识符
        boolean flag = false;
        //定义替代变量
        PersonNode temp = headNode;
        while (true) {
            if (temp.next == null) {
                break;
            }
            //满足条件
            if (temp.next.number > personNode.number) {
                break;
            } else if (temp.next.number == personNode.number) {
                flag = true;
                break;
            }
            //后移
            temp = temp.next;
        }
        //插入节点
        if (flag) {
            System.out.println("节点已经存在~~~~");
        } else {
            personNode.next = temp.next;
            temp.next = personNode;
        }
    }
  • 在链表中删除一个节点

确定删除的位置,循环遍历到该位置节点的上一个节点

P->next=p->next->next

代码实现

  /**
     * 删除某一个节点
     * 先根据number查找,在删除
     */
    public void delete(PersonNode personNode) {
        boolean flag = false;
        PersonNode temp = headNode;
        while (true) {
            if (temp.next == null) {
                System.out.println("链表为空");
                break;
            }
            if (temp.next.number == personNode.number) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.next = temp.next.next;
        } else {
            System.out.println("找不到该节点的信息");
        }

    }
  • 修改链表中的一个节点信息

确定要修改节点的位置,先遍历找到该节点,修改该节点的信息。

  /**
     * 修改节点信息
     */

    public void update(PersonNode personNode) {
        boolean flag = false;
        PersonNode temp = headNode;
        //根据编号找到要修改的节点
        while (true) {
            if (temp.next == null) {
                System.out.println("链表为空");
                break;
            }
            if (temp.next.number == personNode.number) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        //修改节点信息
        if (flag) {
            temp.next.nickName = personNode.nickName;
            temp.next.name = personNode.name;
        } else {
            System.out.println("找不到该节点的信息");
        }

    }
  • 遍历链表

/**
  * 遍历节点
  */
    //显示链表中的有效节点
    public void show(PersonNode personNode) {
        if (personNode.next == null) {
            System.out.println("该链表为空~~~");
        }
        PersonNode temp = personNode;
        while (true) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
            System.out.println(temp);
        }
    }

2、双向链表

双向链表的定义

双向链表中的数据结点有两个指针,分别指向直接后继和直接前驱,所以从双向链表中的任一结点开始,都可以很方便的访问它的前驱结点和后继结点。

2.1构造双向链表

它和单向链表很相似,仅仅是增加了一个直接前驱结点

public int number;
public String name;
public String nickName;
public PersonNode next;
public PersonNode pre;

2.2双向链表增删改查的实现

  • 双向链表的遍历

双向链表的遍历与单向链表相同,可以按照两个方向遍历,在这里就不在代码实现了。

  • 双向链表的添加

1、在末尾添加

在双向链表的末尾添加C节点(定位到节点B)

B->next=C
C->pre=B

代码实现

 /**
   * 1、在末尾添加节点
   */
    public void  add(PersonNode2 personNode2){
        PersonNode2 temp=headNode;

        while (true){
            if(temp.next==null){
                break;
            }
            temp=temp.next;
        }
        //构成双向链表
        temp.next=personNode2;
        personNode2.pre=temp;
    }

2、在两节点间添加

在节点B和节点C间添加D(程序定位到节点D),

顺序:先搞定D的前驱和后继节点,再搞定C的节点的前驱和B的后继节点。

D->pre=B
D->next=C
C->pre=D
B->next=D

代码实现

/**
*在两节间插入新的结点
*/
public void addByOrder(PersonNode2 personNode2){
        PersonNode2 temp=headNode;
        boolean flag=false;
        boolean flag2=false;
        boolean flag3=false;
        while (true){
            if (temp.next==null){
                flag2=true;
                break;
            }
            //加入两个节点间的条件
            if(temp.next.number>personNode2.number){
                    flag3=true;
                    break;
            }
            if(temp.next.number==personNode2.number){
                flag=true;
                break;
            }

            temp=temp.next;
        }
        if(flag){
            System.out.println("相同的节点已存在");
        }
        if(flag2){
            //在末尾添加构成双向链表
            temp.next=personNode2;
            personNode2.pre=temp;
        }
        if(flag3){
            //在两个有效节点间添加
            personNode2.pre= temp;
            personNode2.next=temp.next;
            temp.next.pre=personNode2;
            temp.next=personNode2;
        }
    }
  • 双向链表的修改

同单链表修改相同

  • 双向链表的删除

在两节点间删除(定位到节点S(D))

S->pre->next=S->next
S->next->pre=S->pre

代码实现

 /**
  * 删除节点
  */
    public  void delete(PersonNode2 personNode2){
        PersonNode2 temp=headNode;
        boolean flag=false;
        while (true){
            if(temp.next==null){
                break;
            }
            temp=temp.next;
            if(temp.number==personNode2.number){
                flag=true;
                break;
            }
        }
        if(flag){
            temp.pre.next=temp.next;
            if(temp.next!=null){
                temp.next.pre=temp.pre;
            }

        }else {
            System.out.println("找不到删除节点的信息~~");
        }

    }

3、单向循环链表(无头结点)

定义

单向循环链表是单链表的另一种形式,其结构特点是最后一个节点不再是结束标记,而是指向整个链表的第一个结点,从而形成一个环。

约瑟夫问题是单向循环链表的一个典型的应用,所以下面通过学习约瑟夫问题来学习单向循环链表。

  • 约瑟夫问题描述

约瑟夫问题是一个非常著名的趣题,即由n个小男孩坐成一圈,按顺时针由1开始给他们编号。然后由第一个人开始报数,数到m的人出局。现在需要求的是最后一个出局的人的编号。给定两个参数 n和m,分别代表游戏的人数和报数大小。请返回最后一个出局的人的编号.

问题分析:

约瑟夫问题可以简单分为两个步骤

构造Boy节点类

 private int number;
 private Boy next;
  • 构建一个单向循环链表

定义两个指针变量First和cur,first表示头结点,cur指向当前节点

/**
*构造第一个节点first
*/
first = boy;//第一个节点为first
first.setNext(first);//仅有一个节点时,first指向first,构成闭环
cur = first;//辅助变量指向first
/**
*构造其它节点
*/
cur.setNext(boy);//将新添加的节点设置为当前节点的后继节点
boy.setNext(first);//新添加的节点与first构成闭环
cur=boy;//将当前节点设置为新添加的节点
  • 遍历链表,根据约定的规则退出循环链表,直至仅有一个节点

定义两个辅助节点变量first和helper,first指向每一轮要开始的节点,helper指向最后一个节点,当first与helper指向同一个节点时,该循环链表仅剩一个节点

构造函数
moveCycle(int no,int number,int sum)
1、确定从第几个节点开始
for(int i=0;i<no-1;i++){
  first=first.getNext();
  helper=helper.getNext();
}
2、循环,找出节点,直至仅剩一个节点
while (true){
  if(first==helper){
      System.out.printf("最后一个节点时%d\n",first.getNumber());
      break;
  }
  //开始,每number下,出圈一次
  for(int j=0;j<number-1;j++){
      first=first.getNext();
      helper=helper.getNext();
  }
  System.out.printf("%d出圈\n",first.getNumber());
  first=first.getNext();
  helper.setNext(first);
}

往期推荐

【吊打面试官】Mysql大厂高频面试题!!!

Redis高频面试题及答案

大白话布隆过滤器,又能和面试官扯皮了~

天天用Redis,持久化方案有哪些你知道吗?

面试官:你知道哪几种事务失效的场景?

天天写 order by,你知道Mysql底层执行流程吗? 万字长文带你入门Zookeeper!!! 求你了,别再问我Zookeeper如何实现分布式锁了!!!

Mysql性能优化:为什么count(*)这么慢?

猜你喜欢

转载自blog.csdn.net/qq_34162294/article/details/105803969