初见 链表

前言

在上篇文章重温数组——顺序表  http://t.csdnimg.cn/9DH9M 后,本篇文章让我们认识一种新的数据结构:链表

认识

概念:链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的。

何为链表?可以去和生活中的火车类比,每一节火车车厢都有下一节车厢的钥匙,这样一来,可以看下图,其中上面便是我们所存储的数据,而下面便是连接彼此的“钥匙”。

单拿一个出来看,这一个一个便是 节点,当前节点主要有两个部分组成:要保存的数据和保存下一个节点的地址,这边是连接的关键,需要保存下一个节点的地址才能找到下一个节点,

其中有这么一个节点--头结点head去指向我们所存储的第一个节点,因此头结点中无论存了什么数据都是没有意义的

链表的种类不止我所举例的这一种,可以根据三个特征,单向还是双向,带头还是不带头,循环还是不循环来分类,但本文章主要介绍的是单向 不带头 不循环的链表哦~

基本功能实现

我们类比着顺序表来学,来实现相同的功能

首先初始化一个链表

那就少不了头结点head,要存放的数据data,以及让节点间产生联系的引用next ,在构造方法中让next置为0,那样只有一个节点的链表就创建成功了~

    private Node head;//首先定义一个头结点(节点类型)
    private static class Node {
        int data; // 节点数据
        Node next; // 下一个节点的引用

        Node(int data) {//构造方法
            this.data = data;
            this.next = null;//表示当前节点是最后一个节点
        }
    }

打印链表

同样也是少不了遍历,那我们想想1.怎么从第一个节点走到第二个节点?2.遍历什么时候结束?

首先别忘记了head的作用!于是head=head.next便可以遍历全部节点,那最后一个节点没有指向(单向)不就是null吗?如此一来

 public void display(){
     Node current =head;//避免改变头指针的位置
     while(current!=null){
         System.out.println(current.data+' ');
         current=current.next;//遍历所有节点
     }
     System.out.println();
 }

这里要特别注意:为啥不能用current.next呢?那是因为next本就是指向下一个数据,这样的话就会少遍历一个数据,即是current本身

清空链表

既然要做一个空链表,那想想刚创建链表的时候head的作用,只要让head头结点置null,那是不是就空了?可不能又是head.next置空~

 public void clear(){
     head=null;
 }
获得单链表的长度
public int size(){
    int count = 0;//用于记录链表长度
    Node current = head;//避免改变head的位置
    while(current != null){
        count++;
        current = current.next;
    }
    return count;
}

四大功能的实现

4.1 数据的添加之头插法

先给新节点new一个对象,让新节点的next指向原本的头,新节点设为新的头,结合图解和代码就更好理解了~

public void addFirst(int data){
    Node node = new Node(data);
    node.next = head; // 将新节点的next指向原来的头节点
    head = node; // 更新head节点为新插入的节点
}

4.2尾插法

有头插必有尾插,首先要找到链表的尾巴,找到后让next等于新插入的节点即可,不过这里就要考虑特殊情况了,假设是一个空链表,那头插和尾插其实就没区别了

 //尾插法
 public void addLast(int data) {
     Node newNode = new Node(data);
     if (head == null) {//如果头是空
         head = newNode;
     } else {
         Node current = head;
         while (current.next != null) {
             current = current.next;//遍历链表
         }
         current.next = newNode;//添加进去
     }
 }

4.3 任意位置插入

既然插入的是任意位置,那头插和尾插都要考虑,还有插入位置的合法性等等,这些都是特殊情况,那一般的插入配合画图就好理解了。遍历的过程中找到要插入的位置的前一个节点方便插入,因为链表插入是不需要移动元素的,改变next的指向就行

    public void addIndex(int index, int data) {
        Node current = head; // 定义current代替head去遍历
        if (index < 0 || index > size()) { // 判断插入位置是否合法
            return ; // 如果位置不合法,直接返回
        } else if (index == 0) { // 插入位置为链表头部
            addFirst(data); // 调用addFirst方法添加到链表头部
        } else if (index == size()) { // 插入位置为链表尾部
            addLast(data); // 调用addLast方法添加到链表尾部
        } else {
            int count = 0; // 记录当前节点位置
            while (count != index - 1) { // 找到指定位置的前一个节点
                current = current.next; // 移动到下一个节点
                count++;//这样就会逐步趋近index-1
            }
            Node node = new Node(data); // 创建新节点
            node.next = current.next; // 新节点的next指向原来链表中该位置的节点
            current.next = node; // 原来该位置节点的next指向新节点,完成插入操作
        }
    }

 在图中,本来current.next是指向0x89的,但是赋值给新node.next,这样就完成了第一步

第二步就改变current.next为node就行

所以在插入的过程中,发现了一个规律:先绑定后面的指向,再修改前面的指向。这样就保证了连贯性!

4.4删除第一次出现关键字key的节点

找到要删除的前一个节点,这样就不会丢失current了,同样的,一些极端情况也要考虑,如果第一个节点就是要删的,那么直接head赋值成head.next ,这样头结点就被删掉了

public void remove(int key) {
    while (head != null && head.data == key) {
        head = head.next; // 处理头节点为 key 的情况
    }

    Node current = head;
    while (current != null && current.next != null) {
        if (current.next.data == key) {
            current.next = current.next.next; // 删除当前节点的下一个节点
        } else {
            current = current.next;
        }
    }
}

4.5删除所有关键字key的节点

在删除第一个的基础上继续循环,这样便能实现啦!

public void removeAllKey(int key) {
    // 处理头节点为 key 的情况,一直删除直到头节点不为 key
    while (head != null && head.data == key) {
        head = head.next;
    }

    Node current = head;
    while (current != null && current.next != null) {
        // 判断当前节点的下一个节点是否为 key
        if (current.next.data == key) {
            // 将当前节点的 next 指针指向下一个节点的下一个节点,跳过要删除的节点
            current.next = current.next.next;
        } else {
            // 如果下一个节点不为 key,继续遍历链表,直到删完为止
            current = current.next;
        }
    }
}

4.6查询链表是否包含指定的关键字key

对于关键字的匹配和打印差不多,加个判断条件就行,代码奉上~

 //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        Node current=head;
        while(current!=null){
            if(current.data==key){
                return true;
            }
            current=current.next;
        }
        return false;
    }

链表的基础知识就分享到这里,希望看到这里的你收获满满~

  • 43
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值