链表的概念+MySingleList的实现


链表

顺序存储:顺序表/ArrayList

  • 优点:给定下标的时候,查找速度快 o(1)
  • 缺点:插入和删除时要移动元素o(n) 、每次扩容会浪费资源

由于ArrayList的缺点,我们引入链式存储:链表

一、 链表的概念

1.概念

在这里插入图片描述

  • 链表在物理层面是非连续的存储结构,在逻辑上是连续的,通过引用链接次序来实现它的逻辑顺序
  • 链表由一个个结点组成,结点从堆上申请
  • 一个结点起码包含两个域,val域存储数组、next域存储下一个结点的地址

2. 结构

链表由多种结构:由头、无头、单向、双向、循环、非循环
排列组合后有八种,重点了解无头单向非循环链表和无头双向链表

二、MySingleList的实现

无头单向非循环链表的实现

1 .定义内部类

public class MySingleList {
    //链表是由一个一个的结点所组成的,可以把Node定义成一个内部类
    static class Node {
        public int val;//存储的数据
        public Node next;//存储下一个结点的地址
        public Node(int val) {//先不设置next,创建一个新的结点时,还不知道下一个结点是什么
            this.val = val;
        }
    }
}

链表是由一个个的结点所组成的,可以把Node定义成一个内部类

  • 定义一个val变量存储数据
  • 定义一个Node类型的next变量存储下一个结点的地址
  public Node head;//代表当前链表的头结点的引用

head 代表当前链表的头结点的引用

  • 因为现在写的是不带头结点的单链表,通过一个变量head,来引用当前链表的头结点
  • head引用哪个结点,哪个就是当前链表的头结点

2 .创建链表

public void creatList(){//创建一个链表
        Node node1 = new Node(12);
        Node node2 = new Node(86);
        Node node3 = new Node(33);
        Node node4 = new Node(45);
        node1.next =node2;
        node2.next =node3;
        node3.next =node4;
        head=node1;//创建头结点
    }

创建各个结点,给定val值,每个结点的next依次引用下一个结点的地址
确定头结点,head引用node1所引用的地址

在这里插入图片描述

3. 遍历链表并打印

public void disPlay() {//不要改变head
        Node cur = head;//定义一个cur,让cur移动,head不动
        //链表遍历完,head==null
        //遍历到尾部,不打印最后一个,head.next==null
        while (cur!=null){
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

1.通过cur来代替head,保证head不会发生改变
2.循环的判断依据是cur!=null,这样能遍历完整
cur.next!=null会少打印最后一个

4.查找单链表中是否包含关键字key

public boolean contains(int key) {
        Node cur = head;
        while (cur != null) {//当cur为空时,遍历完
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;//cur向后移动
        }
        return false;
    }

5.得到链表的长度

 public int size(){
        int count = 0;
        Node cur = head;
        while (cur!=null) {//遍历一遍链表 o(n)
            count++;
            cur=cur.next;
        }
        return count ;
    }

遍历数组,直到cur为null跳出循环,说明已经走完整个链表,返回记录的次数

6.头插法

在这里插入图片描述

 public void addFirst(int data){
        Node node = new Node(data);//新建一个结点
        node.next=head;
        head = node;
    }

1.创建新的结点
2.将新结点的next域存放原本头结点的地址值
3.让新结点成为新的头结点
这样就完成了头插法的实现

7. 尾插法

在这里插入图片描述

 public void addList(int data) {
        Node node = new Node(data);
        if (head == null) {//判断头结点为空的情况
            head = node;//让新建的结点成为头节点
            return;//
        }

        Node cur = head;//让cur代替head
        while (cur.next != null) {
            cur = cur.next;
        }//当cur的next为null时,找到了最后一个结点
        //找到最后一个结点后,插入node结点
        cur.next = node;
    }

1.创建一个新结点
2.判断头结点是否为空
3.遍历链表找到当前最后一个结点
4.将新结点插到末尾
链表的插入只是修改指向

8.任意位置插入

在这里插入图片描述

public void addIndex(int index, int data) throws IndexOutOfException {
        checkIndex(index);//先检查index的值是否合法
        if (index == 0) {//如果index=0,调用头插法
            addFirst(data);
            return;
        }
        if (index == size()) {//如果index的值为链表长度,调用尾插法
            addList(data);
            return;
        }
        Node cur = findIndexSubOne(index);//找到index的前一个元素
        Node node = new Node(data);
        node.next = cur.next;
        cur.next = node;
    }

    private void checkIndex(int index) {
        if (index < 0 || index > size()) {
            throw new IndexOutOfException("index位置不合法");
        }
    }

    //走index-1步,返回当前索引的地址
    private Node findIndexSubOne(int index) {
        Node cur = head;
        int count = 0;
        while (count != index - 1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }

1.调用checkIndex方法先检查index的值是否合法,不合法抛出异常
2.判断索引,如果是0,调用头插法,如果等于链表长,调用尾插法
3.调用findIndexSubOne方法,找到index的前一个节点,返回地址值
4.创建一个新结点,使新结点的next域的值等于前一个结点next域的值
5.再让前一个结点的next域,引用新结点的地址值。

8.删除结点

删除第一次出现关键字为key的结点
在这里插入图片描述

 public void remove(int key) {
        if (head == null) {//判断是否是空节点
            return; //一个结点都没有
        }
        if (head.val == key) {//如果当前头结点的元素等于要删除的元素
            head = head.next;//头结点向后移动
            return;
        }
        Node cur = searchPrev(key);//找到key的前驱结点
        if (cur == null) {//没有要删除的key
            return;
        }
        Node del = cur.next;//要删除的结点
        cur.next = del.next;
        //or cur.next = cur.next.next;
    }

    private Node searchPrev(int key) {//找到key的前一个结点

        Node cur = head;
        while (cur.next != null) { //cur.next==null,说明cur已经走到最后一个结点
            if (cur.next.val == key) {//如果cur下一个结点的值等于key
                return cur;//找到key的前一个结点
            }
            cur = cur.next;
        }
        return null;//没有你要删除的结点
    }

1.先定义searchPrev方法,遍历链表,如果cur的下一个结点的值=key,cur就为key的前驱结点
2.判断:如果头结点为空,链表中没有元素,直接返回
3.如果头结点的值,就是要删除的元素,头结点后移,直接返回
4.都不是,进入searchPrev方法,返回前驱结点cur;
5.如果返回的cur==null,说明遍历完链表,没有要删除的元素
6.要删除的结点del就是cur的下一个结点
7.让cur结点的next域引用del的下一个结点的地址;
完成删除

删除所有值为key的节点

在这里插入图片描述

 public void removeAllKey(int key) {
        if (head == null) {//如果头结点为空,直接返回
            return ;
        }
        Node prev = head;
        Node cur = head.next;
        while (cur != null) {
            if (cur.val == key) {
                prev.next = cur.next;
                //cur = cur.next;
            } else {
                prev = cur;
                //cur = cur.next;
            }
            cur = cur.next;
        }
        if (head.val == key) {//最后处理头结点
            //如果头结点的值等于key,头结点后移
            head = head.next;
        }
    }

1.判断头结点是否为空
2.设置prev为头结点,cur为下一个结点(头结点key的情况最后考虑)
3.遍历链表,直到cur==null为止
4.如果cur的值等于key,prev的next域引用cur下一个结点的地址,cur后移一位
5.否则,prev移动到cur,cur后移一位
6.最后处理头结点,如果头结点刚好等于key,将头结点后移一位。

清空
   public void clear(){//清空,让链表中所有的结点都不被引用
        head =null;
    }

清空,让链表中所有的结点都不被引用,head置为空

点击移步博客主页,欢迎光临~

偷cyk的图

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值