数据结构与算法学习笔记(JAVA)——第四章 链表

159 阅读7分钟

一、介绍

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

链表1.PNG

1.2 链表的特点

  1. 链表以节点方式来存储
  2. 每个节点包含data域,next域(指向下一个节点)
  3. 链表的节点不是连续存储的
  4. 链表分为有头结点的链表和无头结点的链表,按照需求来定
  5. 有头结点的链表逻辑结构如下图

链表2.PNG

二、应用

2.1 单向链表

描述

使用有头节点的单向链表实现对水浒英雄排行榜的管理。主要考察链表的插入、删除和修改等基本的功能。步骤拆解如下:

1) 添加节点(表尾)

思路:

链表3.PNG

代码:

package com.company.linkedList;

public class SingleLinkedListDemo {
    public static void main(String[] args){
        // 先创建节点
        HeroNode hero1 = new HeroNode(1,"宋江","及时雨");
        HeroNode hero2 = new HeroNode(1,"卢","玉麒麟");
        HeroNode hero3 = new HeroNode(1,"无用","智多星");

        //创建一个单链表
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        // 添加一个节点
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        // 展示节点
        singleLinkedList.show();
    }
}

//定义一个单链表对象
class SingleLinkedList{
    //定义一个头节点
    private HeroNode head = new HeroNode(0, "", "");

    //定义添加节点方法
    //1.确定最后一个节点的位置
    //2.将最后一个节点的next指向新节点
    public void add(HeroNode heroNode){
        //定义一个辅助变量temp,用于遍历链表
        HeroNode temp = head;
        while (true){
            if(temp.next == null){
                break;
            }
            temp = temp.next;
        }
        temp.next = heroNode;
    }

    //定义一个显示链表节点的方法
    public void show(){
        // 判断链表是否为空
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        // 定义一个辅助变量temp,用于遍历
        HeroNode temp = head.next;
        while (true){
            if(temp == null){
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }
}


// 定义一个Hero对象,代表一个节点
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;
    }

    //重新toString方法
    @Override
    public String toString(){
        return "HeroNode [no=" + no +",name=" + name + ",nickname=" + nickname + "]";
    }
}

2) 添加节点(顺序添加)

思路:

链表4.PNG

代码:

// 定义一个按照顺序添加节点的方法
// 如果链表中已经有相同的序号,抛出异常
// 1.找到插入节点的位置
// 2.将新节点.next = temp.next ; temp.next = 新节点
public void addByOrder(HeroNode heroNode){
    // 定义一个辅助节点,用于遍历
    HeroNode temp = head;
    Boolean flag = false; // 标志添加的节点序号是否存在
    while (true){
        // 判断是否已经到链表尾
        if(temp.next == null){
            break;
        }
        if(temp.next.no == heroNode.no) {
            flag = true;
            break;
        } else if(temp.next.no > heroNode.no){
            // 位置已找到
            break;
        }
        temp = temp.next;
    }
    if(flag){
        System.out.printf("准备插入的节点编号%d已经存在了\n",heroNode.no);
    }else{
        heroNode.next = temp.next;
        temp.next = heroNode;
    }
}

3) 修改节点

思路:

(1)通过遍历找到该节点 (2)更新节点的name和nickname temp.name = newHeroNode.name; temp.nickname = newHeroNode.nickname;

代码:

// 定义一个更新链表节点的方法
// 1. 找到该节点
// 2. 更新该节点的信息
public void update(HeroNode newHeroNode){
    // 判断链表是否为空
    if(head.next == null){
        System.out.println("链表为空");
        return;
    }
    // 定义一个辅助变量temp,用于遍历
    HeroNode temp = head.next;
    Boolean flag = false; // 标识节点已找到
    while (true){
        //
        if(temp == null){
            // 到达链表的尾部
            break;
        }
        if(temp.no == newHeroNode.no){
            flag = true;
            break;
        }
        temp = temp.next;
    }
    if(flag){
        temp.name = newHeroNode.name;
        temp.nickname = newHeroNode.nickname;
    }else {
        System.out.println("没有找到对应的节点");
    }

}

4) 删除节点

思路:

链表5.PNG

代码:

// 定义一个删除节点的方法
// 1.找到该节点的节点的前一个节点temp
// 2. temp.next = temp.next.next
public void delete(int no){
    //判断链表是否为空
    if(head.next == null){
        System.out.println("链表为空");
        return;
    }
    //定义一个辅助变量用于遍历
    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.println("没有找到对应的节点");
    }
}

2.2 双向链表

1) 添加节点(链表尾部)

思路:

  1. 找到链表的最后一个节点
  2. temp.next = newHeroNode
  3. newHeroNode.pre = temp

代码:

//定义添加节点方法
//1. 找到链表的末尾
//2. temp.next = newHeroNode
//3. newHeroNode.pre = temp
public void add(DoubleHeroNode heroNode){
    //定义一个辅助变量temp,用于遍历链表
    DoubleHeroNode temp = head;
    while (true){
        if(temp.next == null){
            break;
        }
        temp = temp.next;
    }
    temp.next = heroNode;
    heroNode.pre = temp;
}

2) 添加节点(顺序)

3) 修改节点

思路: 与单链表一样

  1. 找到需要修改的节点,利于temp
  2. 修改节点的属性

代码:

// 定义一个更新链表节点的方法
// 1. 找到该节点
// 2. 更新该节点的信息
public void update(DoubleHeroNode newHeroNode){
    // 判断链表是否为空
    if(head.next == null){
        System.out.println("链表为空");
        return;
    }
    // 定义一个辅助变量temp,用于遍历
    DoubleHeroNode temp = head.next;
    Boolean flag = false; // 标识节点已找到
    while (true){
        //
        if(temp == null){
            // 到达链表的尾部
            break;
        }
        if(temp.no == newHeroNode.no){
            flag = true;
            break;
        }
        temp = temp.next;
    }
    if(flag){
        temp.name = newHeroNode.name;
        temp.nickname = newHeroNode.nickname;
    }else {
        System.out.println("没有找到对应的节点");
    }

}

4) 删除节点

思路:

  1. 找到需要删除的节点,例如temp
  2. temp.pre.next = temp.next
  3. temp.next.pre = temp.pre

链表6.PNG

代码:

// 定义一个删除节点的方法
//1. 找到需要删除的节点,例如temp
//2. temp.pre.next = temp.next
//3. temp.next.pre = temp.pre
public void delete(int no){
    //判断链表是否为空
    if(head.next == null){
        System.out.println("链表为空");
        return;
    }
    //定义一个辅助变量用于遍历
    DoubleHeroNode temp = head.next;
    // 标识是否找到该节点
    Boolean flag = false;
    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("没有找到对应的节点");
    }
}

5) 附录

双向链表的完整代码:

package com.company.linkedList;

public class DoubleLinkedListDemo {
    public static void main(String[] args){
        // 先创建节点
        DoubleHeroNode hero1 = new DoubleHeroNode(1,"宋江","及时雨");
        DoubleHeroNode hero2 = new DoubleHeroNode(2,"卢","玉麒麟");
        DoubleHeroNode hero3 = new DoubleHeroNode(3,"无用","智多星");
        DoubleHeroNode hero4 = new DoubleHeroNode(4,"林冲","豹子头");

        //创建一个单链表
        DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
        // 添加一个节点
        doubleLinkedList.add(hero1);
        doubleLinkedList.add(hero4);
        doubleLinkedList.add(hero2);
        doubleLinkedList.add(hero3);

//        // 顺序添加一个节点
//        doubleLinkedList.addByOrder(hero1);
//        doubleLinkedList.addByOrder(hero3);
//        doubleLinkedList.addByOrder(hero2);
//        doubleLinkedList.addByOrder(hero4);

        // 展示节点
        doubleLinkedList.show();

        // 测试修改节点代码
        DoubleHeroNode hero5 = new DoubleHeroNode(4,"林冲111","豹子头111");
        doubleLinkedList.update(hero5);

        System.out.println("修改后的节点");

        // 展示节点
        doubleLinkedList.show();

        System.out.println("删除节点前");
        doubleLinkedList.delete(1);
        doubleLinkedList.delete(2);
        doubleLinkedList.delete(3);

        System.out.println("删除节点后");

        // 展示节点
        doubleLinkedList.show();

        doubleLinkedList.delete(4);

        System.out.println("删除节点后");

        // 展示节点
        doubleLinkedList.show();

        // 添加一个节点
        doubleLinkedList.add(hero1);

        System.out.println("删除节点后再添加");

        // 展示节点
        doubleLinkedList.show();
    }
}

//定义一个单链表对象
class DoubleLinkedList{
    //定义一个头节点
    private DoubleHeroNode head = new DoubleHeroNode(0, "", "");

    //定义添加节点方法
    //1. 找到链表的末尾
    //2. temp.next = newHeroNode
    //3. newHeroNode.pre = temp
    public void add(DoubleHeroNode heroNode){
        //定义一个辅助变量temp,用于遍历链表
        DoubleHeroNode temp = head;
        while (true){
            if(temp.next == null){
                break;
            }
            temp = temp.next;
        }
        temp.next = heroNode;
        heroNode.pre = temp;
    }

    // 定义一个按照顺序添加节点的方法
    // 如果链表中已经有相同的序号,抛出异常
    // 1.找到插入节点的位置
    // 2.将新节点.next = temp.next ; temp.next = 新节点
    public void addByOrder(DoubleHeroNode heroNode){
        // 定义一个辅助节点,用于遍历
        DoubleHeroNode temp = head;
        Boolean flag = false; // 标志添加的节点序号是否存在
        while (true){
            // 判断是否已经到链表尾
            if(temp.next == null){
                break;
            }
            if(temp.next.no == heroNode.no) {
                flag = true;
                break;
            } else if(temp.next.no > heroNode.no){
                // 位置已找到
                break;
            }
            temp = temp.next;
        }
        if(flag){
            System.out.printf("准备插入的节点编号%d已经存在了\n",heroNode.no);
        }else{
            heroNode.next = temp.next;
            temp.next = heroNode;
        }
    }

    // 定义一个更新链表节点的方法
    // 1. 找到该节点
    // 2. 更新该节点的信息
    public void update(DoubleHeroNode newHeroNode){
        // 判断链表是否为空
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        // 定义一个辅助变量temp,用于遍历
        DoubleHeroNode temp = head.next;
        Boolean flag = false; // 标识节点已找到
        while (true){
            //
            if(temp == null){
                // 到达链表的尾部
                break;
            }
            if(temp.no == newHeroNode.no){
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if(flag){
            temp.name = newHeroNode.name;
            temp.nickname = newHeroNode.nickname;
        }else {
            System.out.println("没有找到对应的节点");
        }

    }

    // 定义一个删除节点的方法
    //1. 找到需要删除的节点,例如temp
    //2. temp.pre.next = temp.next
    //3. temp.next.pre = temp.pre
    public void delete(int no){
        //判断链表是否为空
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        //定义一个辅助变量用于遍历
        DoubleHeroNode temp = head.next;
        // 标识是否找到该节点
        Boolean flag = false;
        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("没有找到对应的节点");
        }
    }


    //定义一个显示链表节点的方法
    public void show(){
        // 判断链表是否为空
        if(head.next == null){
            System.out.println("链表为空");
            return;
        }
        // 定义一个辅助变量temp,用于遍历
        DoubleHeroNode temp = head.next;
        while (true){
            if(temp == null){
                break;
            }
            System.out.println(temp);
            temp = temp.next;
        }
    }
}


// 定义一个Hero对象,代表一个节点
class DoubleHeroNode{
    public int no;
    public  String name;
    public  String nickname;
    public DoubleHeroNode next;
    public DoubleHeroNode pre;

    //定义一个构造函数
    public  DoubleHeroNode(int no, String name, String nickname){
        this.no = no;
        this.name = name;
        this.nickname = nickname;
    }

    //重新toString方法
    @Override
    public String toString(){
        return "DoubleHeroNode [no=" + no +",name=" + name + ",nickname=" + nickname + "]";
    }
}