链表基础知识点(Java)

链表介绍

链表一般分为

  1. 单向链表
  2. 双向链表
  3. 环形链表

链表属于线性结构,有且只有一个根节点,且每个节点最多有一个直接前驱和一个直接后继的非空数据结构

链表是有序的列表,在内存中是如下存储的
在这里插入图片描述

  1. 链表是以节点的方式来存储的,是链式存储
  2. 每个节点包含data域,next域:指向下一个节点
  3. 如图: 链表的各个节点不一定是连续存储
  4. 链表分带头节点的链表和没有头节点的链表,需要根据实际的需求来确定
    在这里插入图片描述

链表和数组的比较

数组的优缺点
数组的优势在于可以方便的遍历查询所需要的数据,比如说(查询数组中第3个元素的位置),只需要1次操作即可,时间复杂度为(O(1)),因为数组在内存中是连续存储的,可以非常方便的找到指定元素,但是数组在插入和删除的时候,需要移动数组中大量的元素,而且数组是静态内存分配,定义数组的时候必须指定数组的长度,因此空间效率差。

链表的优缺点
链表的优势在于可以方便的插入和删除数据,时间复杂度为(o1),链表在内存中是不连续存储的,因此链表能节省很多空间。但是链表在查询数据的时候很麻烦,时间复杂度为O(n)。

单链表

这里我们使用带head头结点来实现单向链表
head结点说明

  1. 不存放具体的数据
  2. 作用就是表示单链表表的头节点

单链表之添加节点

节点类

//创建节点 我们通过节点来链表中存储数据的

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;
    }

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

第一种方法:在添加数据的时候,直接到数据添加到链表的尾部,代码如下

 //添加节点 思路 不按编号进行添加
    // 1.通过一个变量temp来接收头节点(因为头节点是不能变的), 用来找到最后一个节点
    // 2. 更新temp的指向
    // 3.将要添加的节点 添加到最后一个节点的next变量上
    public  void add(HeroNode heroNode){
        HeroNode temp = head;
        while (true){
            //如果temp中的next 是null 的话 那么就找到了最后一个节点
            if(temp.next == null){
                break;
            }
            //如果temp中的next 不是null的话,那么就需要更改temp的指向
            temp = temp.next;
        }
        //遍历完后 temp是指向最后一个节点的, 更新最后节点next的指向
        temp.next = heroNode;
    }

第二种方法: 根据no属性,从小到大按顺序添加节点

//添加节点  按编号添加节点 思路
    //1.先通过一个变量temp接收头节点
    //2.遍历所有节点  用来找到当前这个节点编号的位置  用两个变量来保存当前要插入节点的上一个节点(before) 和下一个节点(after)
    //3.before.next 就指向当前插入的节点  当前插入的节点的next 用来指向 after
    public  void add1(HeroNode heroNode){
        HeroNode temp = head;
        HeroNode after_temp = null; //用来保存需要插入节点next指向 需要初始化为null 防止当前链表中只有一个节点
        HeroNode before_temp = head;
        while (true){
            //temp就指向了最后一个节点 就直接插入到这个节点的后面
            if(temp.next == null){
                break;
            }

            //更新节点的位置
            temp = temp.next;
            //如果temp.next后面还有节点,我们就需要根据编号来进行插入
            //如果当前节点的编号 小于需要插入节点的编号 那么就将不断的更新after_temp和before_temp的指向
            if(temp.no < heroNode.no){
                //这个after_temp节点是用来赋值给需要插入的节点.next
                after_temp = temp.next;
                before_temp = temp;
            }else{
            	//如果插入节点小于当前节点,说明已经找到了after_temp和before_temp 就立即退出循环
            	break;
            }
        }
        //循环结束后 temp表示 需要插入节点的上一个节点 after_temp表示需要插入节点的下一个节点
        before_temp.next = heroNode;
        heroNode.next = after_temp;
    }

遍历节点

 //遍历节点 思路
    //1. 我们也需要用一个变量temp来接收头节点,因为头节点是不能变的
    //2. 更新temp的指向
    //3. 输出每一个节点
    public  void showList(){
        if(head.next == null){
            System.out.println("链表没有下一个节点了");
            return;
        }
        HeroNode temp = head.next;
        while (true){
            if(temp == null){
                break;
            }

            //输出当前的节点
            System.out.println(temp);
            //改变temp的指向
            temp = temp.next;
        }
    }

单链表之修改节点

//修改节点 思路
    //1. 先使用一个变量 保存头节点的下一个节点
    //2. 遍历所有节点 从链表中找到与需要修改节点相同的编号
    //3. 进行修改  newHeroNode是修改之后的值
    public  void update(HeroNode newHeroNode){
        if(head.next == null){
            return;  //如果到达链表尾部
        }
        HeroNode temp = head.next; //直接指向头节点的下一个节点
        boolean flag = false; //用来标识是否找到了相同编号的节点
        while (true){
            if(temp == null){
                break;
            }
            if(temp.no == newHeroNode.no){
                flag = true;
                break;
            }
            //更新节点的指向
            temp = temp.next;
        }
        //当flag等于true temp就是我们需要修改的节点
        if(flag){
            temp.name = newHeroNode.name;
            temp.nickName = newHeroNode.nickName;
        }else{
            System.out.println("没有找到我们要修改的节点");
        }
    }

单链表之删除节点

//删除节点 思路
    //1. 用一个遍历来接收头节点 temp
    //2. 遍历找到我们要删除的节点 更新temp的指向
    //3. 记录我们要删除节点的上一个节点和下一个节点
    public  void  delete(int id){
        //如果链表里面没有数据 就直接退出
        if(head.next == null){
            return;
        }

        HeroNode temp = head;
        boolean flag = false;//用来标识是否有我们要删除的节点
        HeroNode before_temp = head; //记录要删除节点的前一个节点
        HeroNode after_temp = null;//记录要删除节点的后一个节点
        while (true){
            if(temp == null){
                break;
            }
            //找到了我们要删除的节点
            if(temp.next.no == id){
                flag = true ;
                before_temp = temp;
                //判断待删除节点的下一个节点是否为空
                //防止空指针异常
                if(temp.next.next == null){
                    after_temp = null;
                }else{
                    after_temp = temp.next.next;
                }
                break;
            }

            //更新位置
            temp = temp.next;
        }
        //需要删除的节点指向null
        temp.next = null;
        //删除节点前一个节点指向删除节点后一个节点
        before_temp.next = after_temp;
    }

单链表之获取有效节点个数

//返回单链表中有效节点的个数
    public  int getLength(){
        if(head.next == null){
            return  0;//空链表
        }
        HeroNode temp  = head.next;
        int length = 0;
        while (temp!=null){
            length++;
            temp = temp.next;
        }
        return length;
    }

单链表之查询倒数第K个结点

//查询链表中倒是第K个节点 思路
    //1. 传入一个index index是查询的倒数第index个节点
    //2. 遍历所有的节点,找到所有的有效节点个数n
    //3. n-index  就是我们要查询的节点
    public HeroNode getNode(int index){
        int length = getLength();
        int node_index = length-index;

        HeroNode node = head.next; //存储我们要查询的节点
        if(index<0 || index > length){
            return  null;
        }else{
            for (int i = 0; i < node_index; i++) {
                node = node.next;
            }
        }
        return  node;
    }

至此为止,单链表就说到这里了。单链表完整的代码如下:

package day03;

import java.util.Stack;

public class singleLinkedListDemo {
    public static void main(String[] args) {

        //模拟单向链表
        //1.初始化节点
        HeroNode hero1 = new HeroNode(1, "小心心", "可爱");
        HeroNode hero2 = new HeroNode(2, "小心心1", "性感");
        HeroNode hero3 = new HeroNode(3, "小心心2", "身材好");
        HeroNode hero4 = new HeroNode(4, "小心心3", "淑女");
        HeroNode hero5 = new HeroNode(5, "小心心4", "漂亮");
        HeroNode hero6 = new HeroNode(7, "小心心4", "漂亮");
        //2.创建链表管理节点
        singleLinkedList list = new singleLinkedList();
        //3.直接往链表里面添加节点
//        list.add(hero1);
//        list.add(hero2);
//        list.add(hero3);
//        list.add(hero4);

        //4.按照编号添加节点
        list.add1(hero1);
        list.add1(hero6);
        list.add1(hero4);
        list.add1(hero2);
        list.add1(hero5);
        list.add1(hero3);

        //5.遍历节点
        list.showList();
        //修改节点信息测试
        HeroNode newHero = new HeroNode(2, "小鑫鑫", "性感大美女");
        list.update(newHero);
        //5.遍历节点
        System.out.println("修改之后的节点");
        list.showList();

        //测试删除的节点
        list.delete(1);
        list.delete(4);
        System.out.println("删除之后的节点");
        list.showList();


        //测试返回单链表的有效个数
        System.out.println(list.getLength());
        
        //测试链表中倒数第k个节点
        System.out.println(list.getNode(1));
        list.showList();
      
    }
}


//创建一个链表用来管理节点的
class  singleLinkedList{
    //初始化头节点
    private  HeroNode head = new HeroNode(0,"小鑫","大美女");

    //添加节点 思路 不按编号进行添加
    // 1.通过一个变量temp来接收头节点(因为头节点是不能变的), 用来找到最后一个节点
    // 2. 更新temp的指向
    // 3.将要添加的节点 添加到最后一个节点的next变量上
    public  void add(HeroNode heroNode){
        HeroNode temp = head;
        while (true){
            //如果temp中的next 是null 的话 那么就找到了最后一个节点
            if(temp.next == null){
                break;
            }
            //如果temp中的next 不是null的话,那么就需要更改temp的指向
            temp = temp.next;
        }
        //遍历完后 temp是指向最后一个节点的, 更新最后节点next的指向
        temp.next = heroNode;
    }

    //添加节点  按编号添加节点 思路
    //1.先通过一个变量temp接收头节点
    //2.遍历所有节点  用来找到当前这个节点编号的位置  用两个变量来保存当前要插入节点的上一个节点(before) 和下一个节点(after)
    //3.before.next 就指向当前插入的节点  当前插入的节点的next 用来指向 after
    public  void add1(HeroNode heroNode){
        HeroNode temp = head;
        HeroNode after_temp = null; //用来保存需要插入节点next指向 需要初始化为null 防止当前链表中只有一个节点
        HeroNode before_temp = head;
        while (true){
            //temp就指向了最后一个节点 就直接插入到这个节点的后面
            if(temp.next == null){
                break;
            }

            //更新节点的位置
            temp = temp.next;
            //如果temp.next后面还有节点,我们就需要根据编号来进行插入
            //如果当前节点的编号 小于需要插入节点的编号 那么就将需要插入的节点 插入到当前节点的后面
            if(temp.no < heroNode.no){
                //这个after_temp节点是用来赋值给需要插入的节点.next
                after_temp = temp.next;
                before_temp = temp;
            }else{
                break;
            }


        }
        //循环结束后 temp表示 需要插入节点的上一个节点 after_temp表示需要插入节点的下一个节点
        before_temp.next = heroNode;
        heroNode.next = after_temp;
    }


    //修改节点 思路
    //1. 先使用一个变量 保存头节点的下一个节点
    //2. 遍历所有节点 从链表中找到与需要修改节点相同的编号
    //3. 进行修改
    public  void update(HeroNode newHeroNode){
        if(head.next == null){
            return;  //如果到达链表尾部
        }
        HeroNode temp = head.next; //直接指向头节点的下一个节点
        boolean flag = false; //用来标识是否找到了相同编号的节点
        while (true){
            if(temp == null){
                break;
            }
            if(temp.no == newHeroNode.no){
                flag = true;
                break;
            }
            //更新节点的指向
            temp = temp.next;
        }
        //当flag等于true temp就是我们需要修改的节点
        if(flag){
            temp.name = newHeroNode.name;
            temp.nickName = newHeroNode.nickName;
        }else{
            System.out.println("没有找到我们要修改的节点");
        }


    }

    //删除节点 思路
    //1. 用一个遍历来接收头节点 temp
    //2. 遍历找到我们要删除的节点 更新temp的指向
    //3. 记录我们要删除节点的上一个节点和下一个节点
    public  void  delete(int id){
        //如果链表里面没有数据 就直接退出
        if(head.next == null){
            return;
        }

        HeroNode temp = head;
        boolean flag = false;//用来标识是否有我们要删除的节点
        HeroNode before_temp = head; //记录要删除节点的前一个节点
        HeroNode after_temp = null;//记录要删除节点的后一个节点
        while (true){
            if(temp == null){
                break;
            }
            //找到了我们要删除的节点
            if(temp.next.no == id){
                flag = true ;
                before_temp = temp;
                if(temp.next.next == null){
                    after_temp = null;
                }else{
                    after_temp = temp.next.next;
                }
                break;
            }

            //更新位置
            temp = temp.next;
        }
        // 0 1 4 5
        temp.next = null;
        before_temp.next = after_temp;
    }

    //返回单链表中有效节点的个数
    public  int getLength(){
        if(head.next == null){
            return  0;//空链表
        }
        HeroNode temp  = head.next;
        int length = 0;
        while (temp!=null){
            length++;
            temp = temp.next;
        }
        return length;
    }

    //查询链表中倒是第K个节点 思路
    //1. 传入一个index index是查询的倒数第index个节点
    //2. 遍历所有的节点,找到所有的有效节点个数n
    //3. n-index  就是我们要查询的节点
    public HeroNode getNode(int index){
        int length = getLength();
        int node_index = length-index;

        HeroNode node = head.next; //存储我们要查询的节点
        if(index<0 || index > length){
            return  null;
        }else{
            for (int i = 0; i < node_index; i++) {
                node = node.next;
            }
        }
        return  node;
    }

    //遍历节点 思路
    //1. 我们也需要用一个变量temp来接收头节点,因为头节点是不能变的
    //2. 更新temp的指向
    //3. 输出每一个节点
    public  void showList(){
        if(head.next == null){
            System.out.println("链表没有下一个节点了");
            return;
        }
        HeroNode temp = head.next;
        while (true){
            if(temp == null){
                break;
            }

            //输出当前的节点
            System.out.println(temp);
            //改变temp的指向
            temp = temp.next;
        }
    }
}




//创建节点 我们通过节点来链表中存储数据的

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;
    }

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

双向链表

管理单向链表的缺点分析

  1. 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找
  2. 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,

双向链表之添加节点

节点类

//创建节点
    static class HeroNode{
        public  int no; //编号
        public  String name; //名称
        public  String nickName;//别名
        public  HeroNode next; //指向下一个节点
        public  HeroNode pre;//指向上一个节点

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

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

第一种方法默认添加到双向链表的最后,代码如下

        //添加节点  添加到链表末尾(不按编号添加)
        public  void add(HeroNode heroNode){
            HeroNode temp = head;
            while (true){
                if(temp.next == null){
                    break;
                }
                temp = temp.next;
            }
            //遍历完 就找到了最后一个节点 temp
            temp.next = heroNode;
            //因为是双向链表,添加节点的pre要指向temp
            heroNode.pre = temp;
        }

第二种方法 从小到大 按顺序添加节点 代码如下

//添加节点 按编号添加节点
        public  void add1(HeroNode heroNode){
            HeroNode temp = head;
            while (true){
                if(temp.next == null){
                    break;
                }
                if(heroNode.no<temp.next.no){
                    break; // heroNode 要插入到temp的前一个位置
                }
                temp = temp.next;
            }
            //循环结束 要插入的节点就再temp节点的后面
            //要先使当前要插入的节点的next指向 temp节点的下一个节点 不能先更新temp.next 不然会陷入死循环
            heroNode.next = temp.next;
            //先判断下一个节点为空 如果为空的话 temp.next.pre会报错
            //如果temp是尾节点的话 temp.next就会是空的
            if(temp.next !=null){
                temp.next.pre = heroNode;
            }
            temp.next = heroNode;
            heroNode.pre = temp;
        }

双向链表之修改节点

修改节点的思路和单向链表一样,这里就不多说了,直接就上代码

//修改节点
        public  void  update(HeroNode heroNode){
            if(head.next == null){
                return;
            }
            HeroNode temp = head.next;
            boolean flag = false;
            while (temp!=null){
                if(temp.no == heroNode.no){
                    flag = true;
                    break;
                }
                temp = temp.next;
            }
            //循环结束后 temp就是我要修改的节点
            if(flag){
                temp.name = heroNode.name;
                temp.nickName = heroNode.nickName;
            }else {
                System.out.println("你修改的节点不存在");
            }
        }

双向链表之删除节点

思路如下

  1. 因为是双向链表,因此,我们可以实现自我删除某个节点
  2. 直接找到要删除的这个节点,比如temp
  3. temp.pre.next = temp.next
  4. 要先判断temp.next是否为空,否则会出现空指针异常(比如temp是最后一个节点) temp.next.pre = temp.pre;

代码如下

//删除节点
        public  void delete(HeroNode heroNode){
            if(head.next == null){
                return;
            }
            HeroNode temp = head.next;
            boolean flag = false;
            //循环遍历找到要删除的节点
            while (temp!=null){
                if(temp.no == heroNode.no){
                    //那么temp就是我们要删除的节点
                    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  showList(){
            if(head.next == null){
                return;
            }
            HeroNode temp = head.next;
            while (temp!=null){
                System.out.println(temp);
                temp = temp.next;
            }

        }

好了,双向链表基础的知识点就到这里。 双向链表完整的代码如下

package day03;

//双向链表的实现
public class twoSingleLinkedListDemo {
    public static void main(String[] args) {
        //验证添加节点
        twoSingleLinkedList list = new twoSingleLinkedList();

        HeroNode hero1 = new HeroNode(1, "小心心", "可爱");
        HeroNode hero2 = new HeroNode(2, "小心心1", "性感");
        HeroNode hero5 = new HeroNode(5, "小心心4", "漂亮");
        HeroNode hero3 = new HeroNode(3, "小心心2", "身材好");
        HeroNode hero4 = new HeroNode(4, "小心心3", "淑女");


//        list.add(hero1);
//        list.add(hero2);
//        list.add(hero3);
//        list.add(hero4);
//        list.add(hero5);
//
//        list.showList();
//
        //测试按编号添加节点
        System.out.println("按顺序添加节点");
        list.add1(hero1);
        list.add1(hero4);
        list.add1(hero5);
        list.add1(hero2);
        list.add1(hero3);
        list.showList();

        //测试删除节点
        System.out.println("删除之后的链表");
        list.delete(hero5);
        list.showList();

        //测试修改节点
        HeroNode updateNode = new HeroNode(4, "小鑫鑫", "漂亮又可爱");
        list.update(updateNode);
        System.out.println("修改之后的节点");
        list.showList();
    }

    //创建节点
    static class HeroNode{
        public  int no; //编号
        public  String name; //名称
        public  String nickName;//别名
        public  HeroNode next; //指向下一个节点
        public  HeroNode pre;//指向上一个节点

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

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

    //创建双向链表
    static class  twoSingleLinkedList{
        //定义一个头节点
       private HeroNode head = new HeroNode(0,"","");

        //添加节点  添加到链表末尾(不按编号添加)
        public  void add(HeroNode heroNode){
            HeroNode temp = head;
            while (true){
                if(temp.next == null){
                    break;
                }
                temp = temp.next;
            }
            //遍历完 就找到了最后一个节点
            temp.next = heroNode;
            heroNode.pre = temp;
        }

        //添加节点 按编号添加节点
        public  void add1(HeroNode heroNode){
            HeroNode temp = head;
            while (true){
                if(temp.next == null){
                    break;
                }
                if(heroNode.no<temp.next.no){
                    break; // heroNode 要插入到temp的前一个位置
                }
                temp = temp.next;
            }
            //循环结束 要插入的节点就再temp节点的后面
            //要先使当前要插入的节点的next指向 temp节点的下一个节点 不能先更新temp.next 不然会陷入死循环
            heroNode.next = temp.next;
            //先判断下一个节点为空 如果为空的话 temp.next.pre会报错
            //如果temp是尾节点的话 temp.next就会是空的
            if(temp.next !=null){
                temp.next.pre = heroNode;
            }
            temp.next = heroNode;
            heroNode.pre = temp;
        }

        //删除节点
        public  void delete(HeroNode heroNode){
            if(head.next == null){
                return;
            }
            HeroNode temp = head.next;
            boolean flag = false;
            //循环遍历找到要删除的节点
            while (temp!=null){
                if(temp.no == heroNode.no){
                    //那么temp就是我们要删除的节点
                    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  update(HeroNode heroNode){
            if(head.next == null){
                return;
            }
            HeroNode temp = head.next;
            boolean flag = false;
            while (temp!=null){
                if(temp.no == heroNode.no){
                    flag = true;
                    break;
                }
                temp = temp.next;
            }
            //循环结束后 temp就是我要修改的节点
            if(flag){
                temp.name = heroNode.name;
                temp.nickName = heroNode.nickName;
            }else {
                System.out.println("你修改的节点不存在");
            }
        }

        //遍历节点
        public void  showList(){
            if(head.next == null){
                return;
            }
            HeroNode temp = head.next;
            while (temp!=null){
                System.out.println(temp);
                temp = temp.next;
            }

        }
    }

}

好了,单向链表和双向链表的基础就说到这里。希望大家共同学习进步,不足之处望指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值