不就几种数据类型么?02-链表

上期回顾:上次我们学习数组,感觉也还好,并没有多少难度~(哈)本期我们学习链表,首先带着几个小问题来学习。

1、链表和数组有啥子区别

2、链表在内存中存储结构是啥样子的

3、它的增删改查的如何做的

1、链表

1.1概念:

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

2、链表的基本结构

2.1 单向链表

 

由图可见:单向链表每个节点都有两个属性,一个本身value,一个是指向下一个节点的指针。

private static class Node {
     private Object data;
     private Node next;
 }

链表的第一个节点被称为头结点,最后一个加点被称为尾节点,尾结点的next指针指向null。

与数组按照下标来随机寻找元素不同,对于链表的其中一个节点A,我们只能根据节点A的next指针来找到该节点的下一个节点B,依次往下寻找。

那么问题来了如何快速找到他的前一个节点你呢?不是下一个节点。

 

2.2双向链

双向链表比单向链表稍微复杂一些,他的每一个节点除了拥有data和next指针,还拥有指向前置节点的prev指针。

 

2.3链表环问题

判断是否有环

  定义一个快指针和一个慢指针,快指针一次走两步,慢指针一次走两步,会出现两种情况,情况一指针走到了空的位置,那就说明这个链表不带环。情况二两个指针相遇,说明这个链表带环。(后面讲到算法的时候会再学习具体实现的)

 

2.4链表的基本存储

接下来我们看看链表的存储方式。

如果说数组在内存中的存储方式是顺序存储,那么在链表在内存中存储方式就是随机存储。

啥子叫随机存储呢?

链表是采用“见缝插针”的方式,链表的每一个节点分布在内存的不同位置。依靠next指针关联起来。这样可以灵活有效的利用零碎的碎片空间。让我们看一看下面的两张图,对比一下数组和链表在内存中分配方式的不同。

 

图中的箭头代表链表节点的next指针。

 

3、链表的基本操作

3.1查找节点

在查找元素时,链表不像数组那样可以通过下标快速进行定位,只能从头结点开始向后一个一个节点逐一查找。

例如:给出一个链表,需要查找从头开始的第三个节点

 

1)、将查找的指针定位到头结点

2)、根据头节点的next的指针,定位到第2个节点。

3)、根据第二个 节点next指针,定位到第三个节点,查找完毕

一个小问题,链表节点的时间复杂度是多少呢?

很简单是吧,链表的数据只能按照顺序进行访问,最坏的时间复杂度是O(n)

 

3.2 更新节点

如果不考虑查找节点的过程,链表的更新速度还是可观的,因为他和数组一样直接把旧数据换成新数据即可。

如:我们要把一个链表的第二个节点数据更新。

 

3.3 插入节点

与数组类似,链表插入节点时,同样分为三种情况:

  • 尾部插入
  • 头部插入
  • 中间插入

1)、尾部插入,是最简单的情况,把最后欧一个节点的next指针指向新插入的节点即可。

 

2)、头部插入,可以分为两个步骤

第一步,把新的节点的next指针指向原先的节点。

第二步,把新的节点变为链表的头节点。

 

3)、中间插入,同样分为两个步骤

第一步,新节点的next指针,指向插入位置的节点。

第二本,插入位置的前置节点的next指针,指向新节点

 

如果内存允许,链表的长度可以是无限大的。

 

3.4 删除节点

同样和插入一样分为三种方式

  • 尾部删除
  • 头部删除
  • 中间删除

1)、尾部删除,是最简单的情况,把倒数第二个节点的next指针指向空即可

2)、头部删除也很简单,把链表的头结点设为原先头结点的next指针即可

3)、中间删除,同样很简单,把删除节点的前置节点的next指针,指向要删除元素的下一个节点即可。

这里有个小点:java有自动化回收机制,删除的节点会被回收。

 

链表的插入和删除不考虑查找元素的过程,只考虑纯粹的插入和删除操作,时间复杂度都是O(1);

完整代码如下:

public class linkTest {
    //头节点指针
    private Node head;
    //尾节点指针
    private Node last;
    //链表的实际长度
    private int size;
    /**
     * 链表插入元素
     * @Pram data 插入元素
     * @Pram index 插入位置
     */
    public void insert(Object data,int index) throws Exception {
        if (index<0||index>size){
            throw new IndexOutOfBoundsException("超出链表节点范围!");
        }
        Node insertNode = new Node(data);
        if (size==0){
            //空链表
            head=insertNode;
            last=insertNode;
        }else if (index==0){
            //插入头部
            insertNode.next=head;
            head=insertNode;
        }else if (size==index){
            //插入尾部
           last.next=insertNode;
           last=insertNode;
        }else {
            //插入中间
            //获取到第index-1的节点对象
            Node prevNode = get(index - 1);
            //将第index-1的node.next数据赋给insert.next
            insertNode.next=prevNode.next;
            //再将insertNode的信息赋给prevNode.next
            prevNode.next=insertNode;
        }
        size++;
    }
    /**
     * 删除元素
     * @Pram index 删除位置
     */
    public Node remove(int index) throws Exception{
        if (index<0||index>size){
            throw new IndexOutOfBoundsException("超出链表节点范围!");
        }
        Node removeNode=null;
        if (index==0){
            //删除头节点
            removeNode=head;
            head=head.next;
        }else if (index==size-1){
            //删除尾节点
            Node prevNode = get(index - 1);
            removeNode=prevNode.next;
            prevNode.next=null;
            last=prevNode;
        }else {
            //删除中间节点
            //获取到删除节点的前一个节点
            Node prevNode = get(index - 1);
            //获取到next.next目的将删除节点的前一个节点next指向删除节点的下一个节点
            Node nextNode=prevNode.next.next;
            //将要删除的节点赋给removeNode
            removeNode=prevNode.next;
            //删除节点的前一个节点的next指向删除节点的下一个节点
            prevNode.next=nextNode;
        }
        size--;
        return removeNode;
    }
    /**
     * 链表查询元素
     * @Pram index 查找元素的位置
     */
    public Node get(int index)throws Exception{
        if (index<0||index>size){
            throw new IndexOutOfBoundsException("超出链表节点范围!");
        }
        Node temp = this.head;
        for (int i = 0; i <index ; i++) {
            temp=temp.next;
        }
        return temp;
    }
    /**
     * 链表节点
     */
    private static class Node{
        Object data;
        Node next;
        Node(Object data){
            this.data=data;
        }
    }

    /**
     *输出链表
     */
    public void output(){
        Node temp=head;
        while (temp!=null){
            System.out.println(temp.data);
            temp=temp.next;
        }
    }

    public static void main(String[] args) throws Exception {
        linkTest linkTest = new linkTest();
        linkTest.insert("小明",0);
        linkTest.insert("小花",1);
        linkTest.insert("小红",2);
        linkTest.insert("小狗蛋",3);
        linkTest.insert("小王八",4);
        //执行删除
        linkTest.remove(3);
        linkTest.insert("小猫咪",3);
        linkTest.output();
    }
}

以上是对单链表相关操作的代码实现。为了尾部插入的方便,代码中额外增加了指定向链表尾节点的指针last。

到这里链表介绍结束,是不是也很简单,biu~

那么我们总结一下:

1.数据结构没有绝对的好与坏,数组和链表各有千秋。数组和链表的相关操作的性能对比如下图:

 

从表格可以看出,数组的优势在于快速定位元素,适用于读多写少的场景

反之,链表优势在于灵活的插入与删除。

 

下期预告:栈和队列的前世今生。。。。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值