Java数据结构之链表

Posted 月上贺兰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java数据结构之链表相关的知识,希望对你有一定的参考价值。

链表是一个有序的列表,它分为单向链表,双向链表,单向环形链表

一.单向链表

链表在内存中的存储如下:

                       

 

 

 它有如下特性:

    1.链表是以节点的方式,是链式存储

    2.每个节点都包含data域,next域来指向下一个节点

    3.每个链表在内存中的分布不一定是连续的

    4.链表分为有头结点的链表和没有头结点的链表,根据实际的情况决定

链表的逻辑结构示意图

            

 

单链表可以用来存储数据,也可以对数据进行增删改查的操作

  1.创建一个单链表

 

class HeroNode{
    public int no;
    public String name;
    public String nickName;
    public HeroNode next;    // 指向下一个节点的指针

    public HeroNode(int no, String name, String nickName) {
        this.no = no;
        this.name = name;
        this.nickName = nickName;
    }

    public HeroNode() {
    }

    @Override
    public String toString() {
        return "HeroNode{" +
                "no=" + no +
                ", name=\'" + name + \'\\\'\' +
                ", nickName=\'" + nickName + \'\\\'\' +
                \'}\';
    }
}

  2.单链表的添加元素

public void add(HeroNode node){
        /*添加操作*/
        /*1.找到当前链表的最后一个节点*/
        /*2.将最后这个节点的next指向这个新的节点*/

        HeroNode temp = head;   // 定义辅助指针来遍历链表
        while (true){
            if(temp.next == null){  // 找到链表的最后,退出循环
                break;
            }
            temp = temp.next;   // 如果没有找到最后,将链表后移
        }
        // 当退出while循环时,temp就指向了链表的最后
        // 将最后这个节点的next指向新的节点
        temp.next = node;
    }    

  3.单链表按照顺序添加元素

public void addByOrder(HeroNode node){
        /*按照顺序添加*/
        /*因为是单链表,所以temp是要添加指针的前一个节点*/
        boolean flag = false;  // 表示英雄的编号是否存在,默认为false
        HeroNode temp = head; //辅助指针
        while (true){
            if (temp.next == null){
                break;
            }
            if (temp.next.no > node.no){ // 位置已经找到,就在temp后面插入新节点
                break;
            }else if (temp.no == node.no){ // 添加的新的节点已经存在
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            System.out.printf("不能添加相同的英雄,%d",node.no);
            return;
        }else {   // 将新元素插入到temp的后面
            node.next = temp.next;
            temp.next = node;
        }
    }

  4.修改元素

public void update(HeroNode newNode){
        /*根据英雄的编号修改节点*/
        HeroNode temp = head;
        boolean flag = false; // 是否找到
        while (true){
            if (temp.next == null){
                break;
            }
            if (temp.no == newNode.no){ // 已经找到需要修改的节点
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            temp.name = newNode.name;
            temp.nickName = newNode.nickName;
        }else {
            System.out.printf("没有找到编号为%d的节点",newNode.no);
            return;
        }

    }

  5.删除元素

public void delete(int no){
        /*根据编号删除*/
        /*找到被删除节点的前一个节点,将引用指向它后面的节点即可*/
        HeroNode temp = head;
        boolean flag = false;  // 是否找到待删除的节点
        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.printf("没有找到编号为 %d的节点",no);
            return;
        }
    }    

  6.单链表元素显示

public void show(){
        /*显示链表,直接遍历即可*/
        HeroNode temp = head.next;
        while (true){
            if (temp == null){
                System.out.println("链表为空");
                return;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }

  7.单链表面试题

    7.1 查看单链表中有效数据的个数

public int size(HeroNode head){
        /*计算链表中有效数据的个数,不算头结点*/
        if (head.next == null){  // 链表为空,返回0
            return 0;
        }
        int length = 0;  // 定义有效数据的个数
        HeroNode temp = head.next;
        while (temp != null){ // 遍历,++,返回
            length++;
            temp = temp.next;
        }
        return length;
    }

    7.2 返回单链表中倒数第k个节点

 public HeroNode printLastIndexNode(HeroNode head,int index){
        /*打印倒数第k个节点*/
        /*index表示倒数第几个节点*/
        /*1.先获取链表的总的长度size*/
        /*2.得到size后,从链表的第一个遍历到size-index个,返回该节点即可*/
        if (head.next == null){ // 如果链表为空,返回null
            return null;
        }
        int size = size(head);
        if (index <= 0 || index > size){  // 数据校验,如果输入的index不合法,返回null
            return null;
        }
        HeroNode temp = head.next;
        for (int i = 0; i < size-index;i++){ // for循环定义倒数的节点个数,然后返回节点
            temp = temp.next;
        }
        return temp;
    }

    7.3 反转单链表

public void reverse(HeroNode head){
        /*反转单链表*/
        /*1.从头到尾遍历原先的链表,每遍历一次将其取出,放在新链表的最前端*/
        /*2.原来链表的head.next 指向reserveHead.next即可*/
        // 如果链表中没有元素,或者只有一个元素直接return
        if (head.next==null || head.next.next == null){
            return;
        }
        HeroNode cur = head.next;   // 定义辅助节点,遍历原链表
        HeroNode next = null;          // 定义当前节点的下一个节点
        HeroNode reverseHead = new HeroNode(0,"",""); // 反转链表的头节点
        while (cur != null){
            next = cur.next;   //保存当前节点的下一个节点
            cur.next = reverseHead.next; // 将后面一个链表的元素挂到cur的前面
            reverseHead.next = cur;   // 将reserveHead的头连接到原来的链表上
            cur = next;   // cur下移
        }
        head.next = reverseHead.next;
    }

    7.4 反向打印输出单链表

public void reversePrint(HeroNode head){
        /*反转打印单链表,不破坏原有的结构*/
        /*使用栈这种数据结构就能简单的实现*/
        if (head.next == null){ // 如果链表为空,直接return
            return;
        }
        HeroNode cur = head.next;
        // 创建一个栈,将元素压入到栈中
        Stack<HeroNode> stack = new Stack<>();
        while (cur!=null){ // 遍历链表,把所有的节点压进栈
            stack.push(cur);
            cur = cur.next;
        }
        while (stack.size() > 0){ // 节点出栈
            System.out.println(stack.pop());
        }
    }

 测试代码

  public static void main(String[] args) {
        // 生成英雄的节点
        HeroNode h1 = new HeroNode(1,"宋江","及时雨");
        HeroNode h2 = new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode h3 = new HeroNode(3,"吴用","智多星");
        HeroNode h4 = new HeroNode(4,"林冲","豹子头");

        SingleLinkedList singleLinkedList = new SingleLinkedList();
          // 添加
        singleLinkedList.add(h1);
        singleLinkedList.add(h2);
        singleLinkedList.add(h3);
        singleLinkedList.add(h4);
        
        // 按照no添加
        singleLinkedList.addByOrder(h1);
        singleLinkedList.addByOrder(h4);
        singleLinkedList.addByOrder(h2);
        singleLinkedList.addByOrder(h3);
        singleLinkedList.show();
         
        //修改
        HeroNode h5 = new HeroNode(2,"小卢卢","小麒麟");
        singleLinkedList.update(h5);
        singleLinkedList.show();
         
          //删除
        singleLinkedList.delete(1);
        singleLinkedList.delete(4);
        singleLinkedList.show();


        // 有效节点的个数
        System.out.printf("一共有%d个有效节点",singleLinkedList.size(singleLinkedList.getHead()));
        // 倒数第k个节点
        System.out.println("倒数第1个节点是: "+singleLinkedList.printLastIndexNode(singleLinkedList.getHead(),1));
       
       // 反转单链表
       singleLinkedList.reverse(singleLinkedList.getHead());
       // 反转打印单链表
       singleLinkedList.reversePrint(singleLinkedList.getHead());
    }

 二.单向环形链表(Josephu问题)

  设编号为1,2,3,...n的人围坐一圈,约定编号为k的人从1开始报数,数到m的人出列,它的下一个又从1开始报数,数到m的人又出列,以此类推,直到所有的人都出列

 为止.

        示意图:

                      

 

 

代码实现:

1.创建节点

class Child{
    /*创建小孩节点*/
    private int no;
    private Child next;

    public Child(int no) {
        this.no = no;
    }

    public Child() {
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public Child getNext() {
        return next;
    }

    public void setNext(Child next) {
        this.next = next;
    }

    @Override
    public String toString() {
        return "Child{" +
                "no=" + no +
                \'}\';
    }
}

2.操作节点

public void list(){
        /*遍历环形链表*/
        if (first == null){ //判断链表是否为空,如果为空直接return
            System.out.println("链表为空");
            return;
        }
        Child cur = first;   // 头节点不能使用,需要创建辅助节点
        while (true){
            System.out.println("当前小孩的编号"+cur.getNo());
            if (cur.getNext() == first){ //如果已经到老了最后节点,则退出循环
                break;
            }
            cur = cur.getNext();   // 节点下移
        }
    }
public void addChildren(int nums){
        /*添加节点*/
        /*1.先创建第一个节点,然后让first指向该节点,构成环*/
        /*2.每创建一个新的节点,将其加入到环中即可*/
        if (nums < 1){ // 数据校验,不合法直接return
            System.out.println("添加的数要大于1");
            return;
        }
        Child cur = null;  // 使用辅助节点遍历
        for (int i = 1; i <= nums; i++) {
            Child child = new Child(i); //根据编号创建小孩节点
            if (i == 1){   // 如果是第一个小孩
                first = new Child(1);
                first.setNext(first); // 构成环
                cur = first;   //让cur指向first
            }else {
                cur.setNext(child);  // 将新的节点添加到环中
                child.setNext(first);
                cur = child; // 让cur指向下一个节点
            }
        }
    }
 /**
     * 
     * @param startNo 表示从第几个小孩开始数数
     * @param countNum 表示数几下
     * @param nums  表示最初有多少个小孩在圈中
      如果  startNo = 1,countNum = 2,num = 5,最后的顺序是2->4->1->5->3
     */
    public void countChildren(int startNo,int countNum,int nums){
        /*计算小孩出圈的队列*/
        // 校验参数是否合法
        if(startNo < 1 || nums < countNum || first == null){
            System.out.println("输入的参数错误");
            return;
        }
        //定义辅助节点
        Child helper = first;
        while (true){ // 该节点需要指向当前链表的最后一个节点
            if (helper.getNext() == first){
                break;
            }
            helper = helper.getNext();
        }

        //报数前,需要移动k-1次
        for (int i = 0; i < startNo-1;i++){
            first = first.getNext();
            helper = helper.getNext();
        }
        
        while (true){
            if (helper == first){ // 只有一个节点
                break;
            }
            // 当小孩报数时,辅助节点和first节点同时移动countNum-1次,然后出圈
            for (int i = 0; i < countNum-1; i++) {
                first = first.getNext();
                helper = helper.getNext();
            }
            // 此时first就是要出圈的节点
            System.out.println("小孩出圈的编号是"+first.getNo());
            first = first.getNext();
            helper.setNext(first);
        }
        System.out.println("最终留下的小孩的编号是"+first.getNo());

    }

测试代码:

 public static void main(String[] args) {

        CircleLinkedList circleLinkedList = new CircleLinkedList();
        circleLinkedList.addChildren(5);
        circleLinkedList.list();

        circleLinkedList.countChildren(1,2,5);
    }

 

三.双向链表

  单向链表的缺点:

  1.查找的方向只能是一个方向,双向链表可以向前或向后进行查找

  2.单向链表不能自我删除,需要借助辅助节点,而双向链表可以自我删除

双向链表的操作(增删改查)

  创建双向链表

class HeroNode2{
    public int no;
    public String name;
    public String nickName;
    public HeroNode2 next;  //指向后一个节点的指针
    public HeroNode2 pre;   // 指向前一个节点的指针

    public HeroNode2(int no, String name, String nickName) {
        this.no = no;
        this.name = name;
        this.nickName = nickName;
    }

    public HeroNode2() {
    }

    @Override
    public String toString() {
        return "HeroNode2{" +
                "no=" + no +
                ", name=\'" + name + \'\\\'\' +
                ", nickName=\'" + nickName + \'\\\'\' +
                \'}\';
    }


}

  遍历双向链表(同单向链表一样)

 public void list(){

        if (head.next == null){
            System.out.println("链表为空");
            return;
        }
        HeroNode2 temp = head; // 定义头结点的辅助节点
        while (true){
            if (temp.next == null){
                break;
            }
            temp = temp.next;
            System.out.println(temp);
        }
    }

  新增节点

public void add(HeroNode2 node){
        HeroNode2 temp = head;
       // 不停的遍历链表,找到链表的最后一个节点,退出
        while (true){
            if (temp.next == null){
                break;
            }
            temp = temp.next;
        }
        // 当退出循环时,temp就指向了链表的最后
        temp.next = node; // 将新的节点加入到temp后面
        node.pre = temp;
    }

  修改节点(同单向链表)

public void update(HeroNode2 node){
        boolean flag = false;
        if (head.next == null){
            System.out.println("链表为空,不能修改");
            return;
        }
        HeroNode2 temp = head.next;
        while (true){
            if (temp == null){
                break;
            }
            if (temp.no == node.no){
                flag=true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            temp.name = node.name;
            temp.nickName = node.nickName;
        }else {
            System.out.println("没有找到要修改的节点");
            return;
        }
    }

  删除节点

 public void delete(int no){
        /*双向链表的删除可以直接找到要删除的节点,找到后,自我删除即可*/
        if (head.next == null){
            System.out.println("不能删除空链表");
            return;
        }
        boolean flag = false;
        HeroNode2 temp = head.next;
        while (true){
            if (temp == null){
                break;
            }
            if (temp.no == no){// 已经找到待删除的节点,退出
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag){
            temp.pre.next = temp.next;  // 把当前节点的前一个指针指向后一个
            if (temp.next !=null){ 
               // 如果不是最后一个节点,需要把当后一个节点指向前一个
                temp.next.pre = temp.pre;
            }
        }else {
            System.out.println("没有找到待删除的节点"+no);
            return;
        }
    }

 

以上是关于Java数据结构之链表的主要内容,如果未能解决你的问题,请参考以下文章

Java数据结构线性表之链表

java数据结构之链表(java核心卷Ⅰ读书笔记)

第二节1:Java集合框架之链表及其实现

Java数据结构之链表

Java数据结构之链表

数据结构与算法(Java)之链表