链表(Linked List)---单向链表(超详细)

746 阅读4分钟

这是我参与8月更文挑战的第七天,活动详情查看:8月更文挑战

链表介绍

  • 链表是一系列的存储数据元素的单元通过指针串接起来形成的,因此每个单元至少有两个域,一个域用于数据元素的存储,另一个或两个域是指向其他单元的指针。这里具有一个数据域和多个指针域的存储单元通常称为节点(node)。
  • 链表的第一个节点和最后一个节点,分别称为链表的头节点和尾节点。尾节点的特征是其 next 引用为空(null)。链表中每个节点的 next 引用都相当于一个指针,指向另一个节点,借助这些 next 引用,我们可以从链表的头节点移动到尾节点。

链表是有序的列表,但是他在内存中式存储如下:

image.png

小结

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

2)每个节点包含data域,next域(指向下一个节点)

3)如图所示,发现链表的各个节点不一定是连续存储

4)链表分带头结点的链表和没有头节点的链表,根据实际的需求来确定

链表数据结构中主要包含单向链表、双向链表及循环链表:

-------------------------------今天我们先学习单向链表------------------------------------

单链表介绍

单链表(带头结点)逻辑结构示意图如下:

image.png

单向链表中,每个节点的数据域都是通过一个 Object 类的对象引用来指向数据元素的,与数组类似,单向链表中的节点也具有一个线性次序,即如果节点 a1 的 next 引用指向节点 a2,则 a1 就是 a2 的直接前驱,a2 是 a1 的直接后续。只能通过前驱节点找到后续节点,而无法从后续节点找到前驱节点。

特点

  • 数据元素的存储对应的是不连续的存储空间,每个存储结点对应一个需要存储的数据元素。每个结点是由数据域和指针域组成。 元素之间的逻辑关系通过存储节点之间的链接关系反映出来。
  • 逻辑上相邻的节点物理上不必相邻。

缺点

1、比顺序存储结构的存储密度小 (每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序比链式存储更多)。

2、查找结点时链式存储要比顺序存储慢(每个节点地址不连续、无规律,导致按照索引查询效率低下)。

优点

1、插入、删除灵活 (不必移动节点,只要改变节点中的指针,但是需要先定位到元素上)。

2、有元素才会分配结点空间,不会有闲置的结点。

代码实现

    //先初始化一个头结点,头结点不要动,不存放具体数据
    private HeroNode head= new HeroNode(0,"","");

    //添加节点到单向链表
    //思路,当不考虑编号顺序时
    //1.找到当前链表的最后节点
    //2.将最后这个节点的next指向新的节点
    public void add(HeroNode heroNode){
        //因为head节点不能动,因此我们需要一个辅助遍历temp
        HeroNode temp = head;
        //遍历链表,找到最后
        while(true){
            if(temp.next == null){
                break;
            }
            //如果没有找到最后,temp就后移
            temp = temp.next;
        }
//        while(temp.next != null){
//            temp = temp.next;
//        }
//如果这样写,temp就直接成了temp的next,那退出循环时指向的就是temp的next的next

// 后面描述如果用这种方式来遍历链表,结果会不同!!!!!
        
        //当退出while循环时,temp就指向了链表的最后
        //将最后这个节点的next指向新的节点
        temp.next = heroNode;
    }

    //显示链表【遍历】
    public void list(){
        //判断链表是否为空
        if(head.next == null){
            System.out.println("链表为空");
            return ;
        }
        //因为头结点不能动,我们需要一个辅助变量来遍历
        HeroNode temp = head.next;
//        while(temp != null){
        while(true){
            if(temp == null){
                break;
            }
            //输出节点信息
            System.out.println(temp);
            //并将temp后移,不然是死循环!!
            temp = temp.next;
        }
    }
}

class HeroNode{
    private int no;
    private String name;
    private 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 + '\'' +
                '}';
    }
}

测试数据


public class SingleLinkedListDemo {
    public static void main(String[] args) {
        HeroNode hero1 = new HeroNode(1,"宋江","及时雨");
        HeroNode hero2 = new HeroNode(2,"卢俊义","玉麒麟");
        HeroNode hero3 = new HeroNode(3,"吴用","智多星");
        HeroNode hero4 = new HeroNode(4,"林冲","豹子头");

        SingleLinkedList singleLinkedList = new SingleLinkedList();
        singleLinkedList.add(hero1);
        singleLinkedList.add(hero2);
        singleLinkedList.add(hero3);
        singleLinkedList.add(hero4);
        
        //显示链表
        singleLinkedList.list();
    }
}

测试结果

image.png

若当中遍历链表时用以下方法


//        while(temp.next != null){
//            temp = temp.next;
//        }
//如果这样写,temp就直接成了temp的next,那退出循环时指向的就是temp的nextnext

测试结果

image.png