链表的初步认识

        什么是链表?链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 就如现实中的火车或铁链一般,环环相扣。当我们到达一个节点时,就可以通过这个节点找到下一个节点。链表与顺序表一样,都是线性结构,如果你没学过数据结构,那么可能会问,既然有了顺序表,为什么还要链表呢?那是因为它们的作用范围不同,如顺序表它的底层便是一个数组,既然是数组,那么它便不可避免的具有数组的小毛病,那就是不好删除和插入。我猜可能会有人说:“哎哎哎,谁说数组不好删除和插入了,我几个代码就将它给做到了。”,对你来说是几行代码的事情,但是对计算机来说,就得重复n遍了,倘若数组的成员有许多,那么不久拖累了效率了吗?那么有没有一下就能做好这件事的呢?当然有,那就是链表,毕竟你往铁链里面插入一个节点,只需在其位置断开后面的,然后将新的节点接在后面,同时把断下来的部分接在新节点后面就行,而不是如数组一样,将后面一个一个往前面覆盖。

链表的初步认识_第1张图片

         就连火车都分动铁和高铁等样式,链表自然也多种多样,共有着8种链表。为什么是8种,因为链表有着如下特点,将它们组合起来就只有8种。

1. 单向或者双向
2. 带头或者不带头//所谓头是指该节点
3. 循环或者非循环
虽然有这么多的链表的结构,但是我们重点掌握两种 :
无头单向非循环链表 结构简单 ,一般不会单独用来存数据。实际中更多是作为 其他数据结构的子结构 ,如哈希桶、图的邻接表等等。
无头双向链表 :在 Java 的集合框架库中 LinkedList 底层实现就是无头双向循环链表。
既然我们已经知道了链表的概念,那么我们能不能自己设计链表呢?当然能,并且它还要使用到内部类,并且内部类中要有着一个引用变量,用来指向下一个节点。
内部类:

public class ListNode {

      int val;

      ListNode next;

      ListNode() {}

      ListNode(int val) { this.val = val; }

      ListNode(int val, ListNode next) { this.val = val; this.next = next; }

  }

既然我们知道了什么是链表,那么我们就得学会使用它,不仅要会使用链表,还得学会自己造轮子。那么我们就开始自己写一个链表吧。我写的是无头单向链表。
既然要写一个链表,那么就要知道链表有什么功能,并且它的返回值与传参都要一一考虑,如头插法,我们只需要它要插入的内容就行,因此只需要一个参数,至于是否需要返回值,则根据要求来。当我们全部考虑好后,便只需要填写内容就行。
     //头插法
     public void addFirst(int data);
     //尾插法
     public void addLast(int data);
     //任意位置插入,第一个数据节点为0号下标
     public boolean addIndex(int index,int data);
     //查找是否包含关键字key是否在单链表当中
     public boolean contains(int key);
     //删除第一次出现关键字为key的节点
     public void remove(int key);
     //删除所有值为key的节点
     public void removeAllKey(int key);
     //得到单链表的长度
     public int size();
     public void display();
     public void clear();
头插法应该是最简单的了,我们只需要为要插入的数据创建一个节点,然后将该节点的next指向原来的头节点,然后将头节点重新指向该节点就行。有人或许会问,不是无头的吗?哪来的头节点。所谓的有头无头指的是是否有固定的头节点,若有,则有头,无则无头。
public void addFirst(int value){
        if(this.head == null){
            head = new Node(value);
        }else {
            Node node = new Node(value);
            node.setNext(head);
            head = node;
        }
        size++;//这是用来统计链表的长度的。
    }

而尾插法也与它大同小异,唯一的问题就是如何找到原尾节点在哪。我们要想找到尾节点的话,就要知道它与其它节点的不同,它的后面没有链接任何东西,也就是它的next为空,因此,我们只需遍历整个链表尾节点了,那么怎么遍历呢?我们可以先定义一个变量记录头节点的位置,因为我们要遍历的话,就必然要顺着链条往下走,如果我们不记录头节点的位置,那么头节点的位置也会随着往下走,那岂不是前面的节点都找不到了。因此我们得定义一个变量,当我们定义好后,就该开始遍历了,我们得判断当前变量的next是不是为空,倘若是,则停止遍历,倘如不是,则让该变量指向该变量的next,然后重复以上操作,直到找到尾节点为止。

public void addLast(int value){
        if(this.head == null){
            head = new Node(value);
        }else {
            Node cur = head;
            while (cur.Next != null){
                cur = cur.Next;
            }
            cur.next = new Node(value);
        }
        size++;
    }

展示链表也是通过遍历来进行的,不过是将寻找尾节点的过程中的所有数据都展示出来罢了,而添加也较为简单,我们可以先确定要添加的位置,然后通过双指针遍历找到该位置与该位置的上一个节点,然后与头插法一样,将其添加进去,然后将用双指针找到的该位置的上一个节点的next指向要添加的数据节点,并让该数据节点的next指向原该位置(也通过双指针找到了)。删除也与之大差不差,就不一一写了。那么我们来写我认为是链表中最难的,就是删除所有值为key(我们要删的)的节点,也许有人会说,这也太简单了,只要将删除的操作循环不就行。没错·,这样也行,但这样我们得循环好多次,如果该链表全是我们要删除的,那得循环多少次才行,因此我们得在一次遍历中就将它全部删除。我们还是用双指针来解决,与删除一样,当我们通过快指针找到要删除的节点后,慢指针刚好指向该节点的前一个,然后我们将快指针指向要删除节点的后面,慢指针指向快指针,然后继续遍历,直到快指针为空就全部删除完。

public void removeAll(int value){
        if(head == null){
            return;
        }
        int count = 0;//记录删除节点的个数
        Node prev = head;
        Node cur = prev.next;
        while(cur != null){
            if(cur.data == value){
                count++;
                cur = cur.next;
                if(cur == null){
                    prev.next = null;
                }
                continue;
            }
            prev.next = null;
            prev = cur;
            cur = cur.next;
        }
        if(head.data == value){//如果头节点也要删的话
            head = head.next;
            count++;
        }
        size = size - count;//计算此时链表的长度
    }

剩下的就没太大难度了,就不罗嗦了。

你可能感兴趣的:(链表,数据结构)