链表是什么
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(Node)(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域(data),另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间。
![0c479c99c2f815b81687d3bca50420ad.png](https://i-blog.csdnimg.cn/blog_migrate/45c794f6971ae1b430c5bd2d8935efc7.jpeg)
链表的分类
线性表的链式存储结构
头节点和头指针
1、头节点指的是链表中的第一个节点,有真实头节点和虚拟头节点。
1.1 真实头节点:
![970c63645fd46e5acd89a993483be5fb.png](https://i-blog.csdnimg.cn/blog_migrate/89afa0833917f3c654a579b7e29c16bc.jpeg)
真实头节点的第一个节点存有数据
1.2 虚拟头节点
![f283ca530da6bacfb8a7bff11f5fceb8.png](https://i-blog.csdnimg.cn/blog_migrate/c7794c56880bae87eca7cac1251aed56.jpeg)
虚拟头节点的第一个节点不能存放数据
2、头指针:存放头节点的地址
插入元素(头插法)
头插法就是在虚拟头节点的后一个插入新的元素,每次插入的新元素,都在最前面,最开始虚拟头节点的next指向为NULL。
![b75733930ac73e4067393b9ca4bf0168.png](https://i-blog.csdnimg.cn/blog_migrate/c60da06c0c8dd32ab6b196d1e50f91d8.jpeg)
当有新的元素进来时,它的指针域将会给新节点的指针域指向一下个。而虚拟头节点的指针域则指向新元素的数据域地址。同时rear(尾指针)指向新插入的元素。
![861b5da5b3e451b49a2c95c99249ddec.png](https://i-blog.csdnimg.cn/blog_migrate/bf7c69575727afa5991a2a01213c0cf0.jpeg)
再插入一个元素B,首先封装成Node节点,然后头节点的next给B的next指向A,然后头节点next再指向新元素。
![85f8278bab1c9f55005440ed9f274eb9.png](https://i-blog.csdnimg.cn/blog_migrate/950f7cfbb31e4b7505a57252f939cf49.jpeg)
可以看出rear(尾指针)依旧指向最开始进来的元素A,所以可知,第一个头插法进来的元素绝对是尾指针。 它指向的元素是尾节点。
插入元素(尾插法)
尾插法就是在元素的后面插入一个新元素,然后上一节点的指针域指向新元素,同时新元素的指针域指向下一个元素。
![79306aa082df449746b0bbd41a3e6ee2.png](https://i-blog.csdnimg.cn/blog_migrate/0405d3f5cd53e5845e000b707d52f59f.jpeg)
![e6d882efc2012732cfb30a9cc1b32989.png](https://i-blog.csdnimg.cn/blog_migrate/6ffd0def1c2010d524b4a29c5024b10d.jpeg)
头插尾插结合
当插入第一个元素A时,进行头插法,头节点的next指向元素A,A的next指向下一个,同时rear指向A。当插入B元素时,头插,B的next指向A,头节点指向B的地址。插入C是采取尾插,A的next给C的next。同时A的next指向C。rear(尾指针)指向C。头插法插入D时,D的next指向B,头节点的next指向D。
![14e06d13bb6c809d088ed0f4f3557abd.gif](https://i-blog.csdnimg.cn/blog_migrate/98b217005d4fbefd505198ca21e58c24.gif)
一般插入
如图:在B和A之间插入元素C
![ec9c3a67c19054cdd297ced17ca8083d.png](https://i-blog.csdnimg.cn/blog_migrate/25712866135ebf808f4475e65160b089.jpeg)
删除元素(头删)
![b1fd8c9d1adb6697692f99431fd4a88b.png](https://i-blog.csdnimg.cn/blog_migrate/40844d71961dfcd7be602346376cd9f0.jpeg)
如图:若果想要删除A元素,那么可以让头节点的指针域指向B,但是A的指针域此时也指向B,所以要真正删除A元素,还需要让的指针域指向NULL。
![741ab9931f2d90bdc60a5fb43698825e.png](https://i-blog.csdnimg.cn/blog_migrate/0643279cd9e3365af586461e04a2fdaf.jpeg)
![0f4a5d5f7c8415dbd7bb1c76494a3d88.png](https://i-blog.csdnimg.cn/blog_migrate/4c8e60cc351f37c4e30ad0bbf3f658e7.jpeg)
假设此时只有A一个元素,想要删除掉他的话,头指针和尾指针会怎么变化呢?
![35edb8825dbdaa9c2753d616246fa2fd.png](https://i-blog.csdnimg.cn/blog_migrate/3c562635e89994ed8dcd4163d401f286.jpeg)
图中可以看出链表中有两个节点一个元素A。如果要删除A的话,head的next指向为空,然后将rear(尾指针重置为0的状态即谁也不指,则已删除元素A)
![1d4feaa5632dc9f5cd4cb36430625680.png](https://i-blog.csdnimg.cn/blog_migrate/bb26326354eb0fab2426e2fca25b377b.jpeg)
删除元素(尾删)
现在有三个元素A,B,C,对他们依次进行尾删。
![72019de7fb8b2b2591694726cbf983d1.png](https://i-blog.csdnimg.cn/blog_migrate/79eeeb240c669673bf33f501e3ff08f8.jpeg)
首先删除A元素(size-1),我们可以先将尾指针rear指向C,然后将C的next指向NULL,来删除A,删除C时,将rear指向B,B的next指向NULL,删除B时,rear指向虚拟头节点,然后头节点的next指向NULL。
![49ea84fb1480faed06a96c440a218809.png](https://i-blog.csdnimg.cn/blog_migrate/5f957f2d8d3eb8f13a7f0f78aff47926.jpeg)
删除元素(一般删除)
![52cf0afb4514d5f047deb5bdd81acc2e.png](https://i-blog.csdnimg.cn/blog_migrate/21cfb7803db85c501805809c0ebe8f16.jpeg)
在上面三个元素,四个节点中删除C元素,首先要找到C元素的位置,找到之后,让B的next指向A,同时将C的next置为NULL,这样就可以删除C元素了。
![6714df22deb5465b7034ef6d2821f97a.png](https://i-blog.csdnimg.cn/blog_migrate/ae80454ef6802b764b5bf1a00e0a4002.jpeg)
LinkedList实现的也是List接口,所以它得实现List中的方法。
其中,需要有一个Node类作为内部类,来对元素进行封装,包含它的数据域(data),指针域(next)
private class Node{ E data; //数据域 Node next; //指针域 public Node(){ this(null,null); } public Node(E data,Node next){ this.data = data; this.next = next; } @Override public String toString() { return data.toString(); } }
定义三个变量,head指的是头指针,rear是尾指针,size是其中元素的个数,创建一个无参的构造方法,初始化一个节点,包括它的虚拟头节点,它的尾指针和头指针指向同一个节点对象,此时里面的元素为0
public class LinkedList implements List {//内部类Node,结点信息 private class Node{ E data; //数据域 Node next; //指针域 public Node(){ this(null,null); } public Node(E data,Node next){ this.data = data; this.next = next; } @Override public String toString() { return data.toString(); } }private Node head; //指向头结点的头指针 private Node rear; //指向尾结点的尾指针 private int size; //记录元素的个数public LinkedList(){ head = new Node(); rear = head; size=0; } public LinkedList(E[] arr){ head = new Node(); rear = head; size =0; }}
getSize()
获取链表中的元素个数,可以借助Arrays中的getSize()方法。因为链表也是线性表的一种。
@Override public int getSize() { return size; }
isEmpty()
链表为空时,它的next指向为NULL,同时里面的元素个数为0,所以它的判空条件size=0,head.next=null即如图:
![479fb0ad752006fd76800091ca74fa33.png](https://i-blog.csdnimg.cn/blog_migrate/2f39015add465762e578a261c1dff1b2.jpeg)
@Override public boolean isEmpty() { return size==0&&head.next==null; }
add(int index,E e)
首先要对index的位置进行判断是否合法,然后看是头插还是尾插或者是一般插入,当是头插法的时候,将head的next给插入节点的next然后在让head指向node。当元素个数为0的时候,让尾指针指向新的元素。
public void add(int index, E e) { if(index<0||index>size){ throw new IllegalArgumentException("插入角标非法!"); } //头插 Node node = new Node(e, null); if(index==0){ node.next = head.next; head.next = node; rear.next = node; }if(size==0){ rear = node; }
当插入元素是最后一个,则采用尾插法rear.next指向新插入的元素,然后尾指针后移
else if(index==size){rear.next = node;rear = rear.next;}
当使用一般插入的时候,首先你要找到你要插入位置的前驱的节点。找到它之后,将它的next给插入的元素然后指向下一个元素,再让它的前驱节点和它连接在一起。
else{Node p = head;for(int i=0;i
addFirst(E e)
直接用add(int index,E e)
public void addFirst(E e){add(0,e);}
addLast(E e)
public void addLast(E e){add(size,e);}
get(int index)
获取在index出的元素值,首先判断index的合法性,当index==0时,直接返回虚拟头节点next里面的data
如果index= =size-1时,直接返回尾指针里的data。如果是一般情况的话,首先要找到它的前驱节点,然后再获取它里面的data。
public E get(int index){if(index<0||index>=size){throw new IllegalArgumentExcepiton("查找角标非法");}if(index==0){return head.next.data;}else if(index==size-1){return rear.next;}else{Node p =head;for(int i=0;i<=index;i++){p = p.next;}return p.data;}}
getFirst()
public E getFirst(){return get(0);}
getLast()
public E getLast(){return get(size-1);}
set(int index,E e)
将index出的元素的值更新为e,Node o = head 让他找到index的前驱的元素,使p.next指向找到的元素p.data=e就完成了值的更新。
public void set(int index,E e){if(index<0||index>=size){throw new IllegalArgumentException("index参数不合法");}if(index==0){head.next.data = e;}else if(index==size-1){rear.data = e;}else{Node p = head;for(int i=0;i
find(E e)
寻找e的位置,这里没有给具体的位置,所以需要使用到while循环,当Node p = head,循环的条件是当p走到最后一个位置时就寻找结束,即当p.next=null;时跳出循环,p的起始位置是在虚拟头节点出,index=-1,所以它在第一个有效元素的时候,index=index+1;当找到对应的元素条件应满足p.data=e;
public int find(E e){int index = -1;if(isEmpty()){return -1;}Node p = head;while(p.next!=null){p = p.next;index++;if(p.data=e){return index;}}return -1;}
boolean contains(E e)
public boolean contains(E e){return find(e)!=-1;}
remove(int index)
删除元素,一共有三种情况,第一种是头元素,第二种是尾元素,第三种就是一般删除
当头删时,Node p 来存放head.next(即删除的元素),然后head.next=p.next将head和删除元素的一下个进行连接,然后断开删除元素和下一个元素的连接并将p置空,尾删:找到删除元素的前一个,然后前驱的next置为null,并将rear指针移动回来,一般删除,创建一个Node p用来移动,当找到元素的前驱时,让前驱的next指向删除元素的next,并将删除元素和下一个元素的连接断开。
public E remove(int index){if(index=size){throw new IllegalArgumentException("index参数不合法");}E temp = null;//用来存储删除元素的dataif(index == 0){Node p = head.next;temp = p.data;head.next = p.next;p.next = null;p = null;if(size==1){rear=head;}}else if(index==size-1){Node p = head;temp = rear.data;while(p.next!=rear){p=p.next;}p.next=null;rear = p;}else{Node p = head;for(int i=0;i
removeFirst()
public E removeFirst(){return remove(0);}123
removeLast()
public E removeLast(){return removeLast(size-1);}
removeElement(E e)
通过使用find(e)来找到对应的位置,然后remove(index)
publc void removeElement(E e){int index = find(e);if(index==-1){throw new IllegalArgumentException("元素不存在");}remove(index);}
clear()
清空,只需将head.next置空,让rear和head相同,size=0;
public void clear(){head.next = null;rear = head;size = 0;}
测试
public class Main { public static void main(String[] args) { LinkedList list = new LinkedList(); for (int i = 1; i <= 10; i++) { list.addFirst(i); } System.out.println(list); for (int i = 11; i <=15 ; i++) { list.addLast(i); } System.out.println(list); System.out.println(list.getFirst()); System.out.println(list.getLast()); for (int i = 1; i <= 5 ; i++) { list.removeFirst(); list.removeLast(); } System.out.println(list); list.removeFirst(); list.removeFirst(); list.removeFirst(); list.removeFirst(); list.removeFirst(); System.out.println(list); }}
运行结果
![9065fa231d55bb763f6ef237403b3525.png](https://i-blog.csdnimg.cn/blog_migrate/9bab6527e508b1fbc2fe5ce22a855900.jpeg)
欢迎访问我的博客