数据结构 之 链表LinkedList
在���学习顺序表之后,我就立马开始了链表的学习,但是在学习链表之前,我就有一个疑问,为什么明明有了顺序表这一种数据结构为什么我们还要有链表这一种数据结构呢?
1. ArrayList的缺陷:
通过对ArrayList的简单了解,我们知道,其实顺序表的底层是由数组来实现的,他是一段连续的空间,所以,当ArrayList在增删元素的时候,通过计算我们发现,他的时间复杂度为O(n),效率比较低下,如果数据很大的情况下,使用顺序表进行增删操作,会浪费非常多的时间,所以,就引入了链表这一种数据结构!!!
2. 链表:
2.1 链表的概念及结构:
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
在现实生活中,火车的结构就类似于我们的链表
链表的结构多种多样:
1. 单向或者双向:
2. 带头或者不带头:
3.循环或者不循环:
以上就是链表的结构,所以一共有八种链表:
单向不带头不循环链表;
单向带头不循环链表;
单向不带头循环链表;
单向带头循环链表;
双向不带头不循环链表;
双向带头不循环链表;
双向不带头循环链表;
双向带头循环链表;
3. 链表的使用和模拟实现:
3.1 构造方法:
链表源码提供了两个构造方法:
这是不带参数的构造方法;
这是带一个参数的构造方法,将c中的全部元素都拷贝到链表中;
3.2 模拟实现:
首先,我们需要创建一个My_LinkedList类:(我们以单向不带头不循环链表为例)
在这个类中创建一个静态内部类,称为ListNode,一共有两个成员,一个是value用来存放该节点的值的,一个是next,用来存放下一个节点的地址,同时我们还可以写一个构造方法;
再实例化一个头节点
public class My_LinkedList { static class ListNode { public int val;//数值域 public ListNode next;//存储下一个节点的地址 public ListNode(int val) { this.val = val; } } public ListNode head = new ListNode(0); //实例化头节点 public My_LinkedList(int val) { head.val = val; //构造方法 } }
addFirst方法:
头插法,在链表的第一个节点插入新的节点:
假如我们在如图所示的链表中头插一个node节点
为了防止我们的首节点丢失,我们需要先将首节点的地址传给新的节点,再将新节点更新为head
/** * 头插法 * addFrist */ public void addFirst(int data) { ListNode node = new ListNode(data); //实例化一个节点node /*if(this.head == null) { this.head = node; }else { node.next = this.head; this.head = node; }*/ node.next = this.head; //将首节点的地址赋给新的节点 this.head = node; //将新的节点更新为head }
addLast方法:
尾插法,将新节点插入链表末端:
public void addLast(int data) { ListNode node = new ListNode(data); //实例化一个node节点 if (head == null) { head = node; //若链表为空,则直接将node更新为head } else { ListNode cur = head; //实例化一个cur节点 while (cur.next != null) { //用cur节点去遍历链表,知道cur节点为最后一个节点 cur = cur.next; } //cur.next == null; cur.next = node; //将node赋值给cur.next,也就是将node节点放在了链表的最末端 } }
size方法:
得到并返回单链表的长度:
//得到单链表的长度 public int size() { int count = 0; //创建整形变量count作为计数器 ListNode cur = this.head; //实例化cur = 当前的头节点 while (cur != null) { //遍历整个链表 count++; //每遍历一个节点,则count++ cur = cur.next; //cur一直向后移动 } return count; //返回单链表的长度 }
add方法:
任意位置插入,将节点插入指定位置:
在插入之前,我先要检测给定的节点位置是否合理:
private void checkIndexAdd(int index) { if (index size()) { //如果位置不合理 throw new MySingleListIndexOutOfException("任意位置插入的时候,index不合法!"); } //若不合理,则抛出异常 }
MySingleListIndexOutOfException :
public class MySingleListIndexOutOfException extends RuntimeException{ public MySingleListIndexOutOfException() { super(); } public MySingleListIndexOutOfException(String str) { super(str); } }
public void add(int index, int data) throws MySingleListIndexOutOfException { checkIndexAdd(index); //检查index位置是否合法 if (index == 0) { //如果index为0 addFirst(data); //进行头插法 return; } if (index == size()) { //如果index = 单链表长度 addLast(data); //进行尾插法 return; } ListNode node = new ListNode(data); //实例化一个node节点,值为data ListNode cur = findIndexSubOne(index); //找到index下标的前一个节点 node.next = cur.next; //为了防止节点丢失,先将要插入节点的next更新为前一个节点的原来的下一个节点的地址 cur.next = node; //将cur的next更新为新的节点的地址 }
private ListNode findIndexSubOne(int index) { ListNode cur = this.head; //实例化一个cur = 头节点 while (index - 1 != 0) { //向后遍历index - 1次,找到index下标节点的前一个节点 cur = cur.next; index--; } return cur; //返回当前节点 }
contains方法:
查找是否包含关键子key在链表当中:
//查找是否包含关键字key是否在单链表当中 public boolean contains(int key) { if (head == null) { //如果head为空,则直接返回false return false; } ListNode cur = this.head; //实例化一个cur节点 = 头节点 //cur != null 说明 没有遍历完 链表 while (cur != null) { //cur不停向后遍历 if (cur.val == key) { //找到了key return true; //返回true } cur = cur.next; } return false; //循环结束,cur.next = null,说明没有找到key,返回false }
remove方法:
删除第一次出现的值为key的节点:
//删除第一次出现关键字为key的节点 public void remove(int key) { if (this.head == null) { //如果链表为空,则没有元素,无法删除 System.out.println("此时链表为空,不能进行删除!"); return; } if (this.head.val == key) { //判断 第一个节点是不是等于我要删除的节点this.head = this.head.next; return; } ListNode cur = this.head; //实例化一个cur = head while (cur.next != null) { //遍历整个链表,直到cur为最后一个节点为止 if (cur.next.val == key) { //如果找到了值为key的节点 //进行删除了 ListNode del = cur.next; //实例化del节点为cur节点的下一个节点 cur.next = del.next; //将前一个节点的next更新为后一个节点的地址 return; } cur = cur.next; } System.out.println("未找到值为key的节点"); //跳出循环,说明没有值为key的节点 }
removeAll方法:
//删除所有值为key的节点 public void removeAllKey(int key) { if (this.head == null) { //如果head == null,则链表为空,不能进行删除操作 return; } //单独处理了头节点,若头节点的值为key,则头节点向后移动 if(this.head.val == key) { head = head.next; } ListNode cur = this.head.next; //实例化cur节点 == head节点的下一个节点 ListNode prev = this.head; //实例化prev节点 == head节点 while (cur != null) { //cur节点不断向后遍历 if (cur.val == key) { //如果cur.val == key即找到了值为key的节点 prev.next = cur.next; //将prev节点即cur的前一个节点的next = cur.next, 即删除了cur位置的节点 cur = cur.next; //cur继续向后走,查找值为key的节点 } else { //若cur.val != key prev = cur; //prev和cur一起向后移动 cur = cur.next; } } }
clear方法:
clear方法的实现有两种方法:
第一种:
public void clear() { this.head = null; }
这种做法比较粗暴,直接将head置为空,由于之前的链表中的节点没有了引用,故会被系统给自动回收;
第二种:
/** * 当我们 执行clear这个函数的时候,会将这个链表当中的所有的节点回收 */ public void clear() { ListNode cur = this.head; //令cur = head ListNode curNext = null; //实例化一个curNext while (cur != null) { curNext = cur.next; //将curNext更新为cur.next cur.next = null; //再将cur节点的next置为空 cur = curNext; //再将cur更新为curNext,一个一个的去删除链表中的所有的节点 } head = null; //最后将head置为空即可 }
4. 源码分享:
在我的模拟实现源码中,我多写了createList方法和display方法,即创建链表和打印链表方法,为的是模拟实现后方便进行测试,以找出代码的不足!!!
public class My_LinkedList { static class ListNode { public int val;//数值域 public ListNode next;//存储下一个节点的地址 public ListNode(int val) { this.val = val; } } public ListNode head;//代表单链表的头结点的引用 /** * 这里只是简单的进行,链表的构造。 */ public void createList() { ListNode listNode1 = new ListNode(12); ListNode listNode2 = new ListNode(23); ListNode listNode3 = new ListNode(34); ListNode listNode4 = new ListNode(45); ListNode listNode5 = new ListNode(56); listNode1.next = listNode2; listNode2.next = listNode3; listNode3.next = listNode4; listNode4.next = listNode5; this.head = listNode1; } public void display() { ListNode cur = head; while (cur != null) { System.out.print(cur.val + " "); cur = cur.next; } System.out.println(); } /** * 从指定的节点开始答应 * * @param newHead */ public void display(ListNode newHead) { ListNode cur = newHead; while (cur != null) { System.out.print(cur.val + " "); cur = cur.next; } System.out.println(); } /** * 头插法 * addFrist */ public void addFirst(int data) { ListNode node = new ListNode(data); //实例化一个节点node /*if(this.head == null) { this.head = node; }else { node.next = this.head; this.head = node; }*/ node.next = this.head; //将首节点的地址赋给新的节点 this.head = node; //将新的节点更新为head } //尾插法 O(n) public void addLast(int data) { ListNode node = new ListNode(data); //实例化一个node节点 if (head == null) { head = node; //若链表为空,则直接将node更新为head } else { ListNode cur = head; //实例化一个cur节点 while (cur.next != null) { //用cur节点去遍历链表,知道cur节点为最后一个节点 cur = cur.next; } //cur.next == null; cur.next = node; //将node赋值给cur.next,也就是将node节点放在了链表的最末端 } } public void add(int index, int data) throws MySingleListIndexOutOfException { checkIndexAdd(index); //检查index位置是否合法 if (index == 0) { //如果index为0 addFirst(data); //进行头插法 return; } if (index == size()) { //如果index = 单链表长度 addLast(data); //进行尾插法 return; } ListNode node = new ListNode(data); //实例化一个node节点,值为data ListNode cur = findIndexSubOne(index); //找到index下标的前一个节点 node.next = cur.next; //为了防止节点丢失,先将要插入节点的next更新为前一个节点的原来的下一个节点的地址 cur.next = node; //将cur的next更新为新的节点的地址 } /** * 找到index-1位置的节点 * * @param index * @return 该节点的地址 */ private ListNode findIndexSubOne(int index) { ListNode cur = this.head; //实例化一个cur = 头节点 while (index - 1 != 0) { //向后遍历index - 1次,找到index下标节点的前一个节点 cur = cur.next; index--; } return cur; //返回当前节点 } private void checkIndexAdd(int index) { if (index size()) { //如果位置不合理 throw new MySingleListIndexOutOfException("任意位置插入的时候,index不合法!"); } //若不合理,则抛出异常 } //查找是否包含关键字key是否在单链表当中 public boolean contains(int key) { if (head == null) { //如果head为空,则直接返回false return false; } ListNode cur = this.head; //实例化一个cur节点 = 头节点 //cur != null 说明 没有遍历完 链表 while (cur != null) { //cur不停向后遍历 if (cur.val == key) { //找到了key return true; //返回true } cur = cur.next; } return false; //循环结束,cur.next = null,说明没有找到key,返回false } //删除第一次出现关键字为key的节点 public void remove(int key) { if (this.head == null) { //如果链表为空,则没有元素,无法删除 System.out.println("此时链表为空,不能进行删除!"); return; } if (this.head.val == key) { //判断 第一个节点是不是等于我要删除的节点this.head = this.head.next; return; } ListNode cur = this.head; //实例化一个cur = head while (cur.next != null) { //遍历整个链表,直到cur为最后一个节点为止 if (cur.next.val == key) { //如果找到了值为key的节点 //进行删除了 ListNode del = cur.next; //实例化del节点为cur节点的下一个节点 cur.next = del.next; //将前一个节点的next更新为后一个节点的地址 return; } cur = cur.next; } System.out.println("未找到值为key的节点"); //跳出循环,说明没有值为key的节点 } //删除所有值为key的节点 public void removeAllKey(int key) { if (this.head == null) { //如果head == null,则链表为空,不能进行删除操作 return; } //单独处理了头节点,若头节点的值为key,则头节点向后移动 if(this.head.val == key) { head = head.next; } ListNode cur = this.head.next; //实例化cur节点 == head节点的下一个节点 ListNode prev = this.head; //实例化prev节点 == head节点 while (cur != null) { //cur节点不断向后遍历 if (cur.val == key) { //如果cur.val == key即找到了值为key的节点 prev.next = cur.next; //将prev节点即cur的前一个节点的next = cur.next, 即删除了cur位置的节点 cur = cur.next; //cur继续向后走,查找值为key的节点 } else { //若cur.val != key prev = cur; //prev和cur一起向后移动 cur = cur.next; } } } //得到单链表的长度 public int size() { int count = 0; //创建整形变量count作为计数器 ListNode cur = this.head; //实例化cur = 当前的头节点 while (cur != null) { //遍历整个链表 count++; //每遍历一个节点,则count++ cur = cur.next; //cur一直向后移动 } return count; //返回单链表的长度 } /** * 当我们 执行clear这个函数的时候,会将这个链表当中的所有的节点回收 */ public void clear() { //this.head = null;//这种做法 比较粗暴! ListNode cur = this.head; //令cur = head ListNode curNext = null; //实例化一个curNext while (cur != null) { curNext = cur.next; //将curNext更新为cur.next cur.next = null; //再将cur节点的next置为空 cur = curNext; //再将cur更新为curNext,一个一个的去删除链表中的所有的节点 } head = null; //最后将head置为空即可 } }
以上就是链表的所有的内容了,感谢大家的观看!!!
制作不易,三连支持
谢谢!!!
以上的模拟实现代码未必是最优解,仅代表本人的思路,望多多理解,谢谢!!
最后送给大家一句话,同时也是对我自己的勉励:
在心里种花,人生才不会荒芜!!!!