赞
踩
缺点:
优点:
在给定下标进行查找时,时间复杂度为O(1)。
总结:顺序表比较时候在给定下标的时候进行查找。
首先要明白:
链表是由一个个结点组成,而这些结点又是由数据域和next域构成(双向链表还会多一个prev域)。
其中,data域存放数据,next域存放下一个结点的地址(prev域则存放前一个结点的地址)。
分别是:单项/双向带头循环、单项/双向不带头循环、单项/双向带头非循环、单项/双向不带头非循环。
什么是“头”?
“头”,就是头节点的意思,它的组成如图:
头节点的data域是不存放元素的。
至于带不带头可以简单理解为有人驾驶的列车和无人驾驶的列车,有人驾驶的列车不能在火车头前面加车厢,第一节车厢永远是驾驶室;而无人驾驶的列车则可以在最前面加车厢,哪节车厢在最前面哪节就是头。即head只是用来标识。
什么是循环?
判断是不是循环就看最后一个结点的next域存放的是null还是第一个结点的地址。
此处主要演示单项不带头非循环链表。
public interface ILinked { //头插法 void addFirst(int data); //尾插法 void addLast(int data); //任意位置插入,第一个数据节点为0号下标 void addIndex(int index,int data); //查找是否包含关键字key是否在单链表当中 boolean contains(int key); //删除第一次出现关键字为key的节点 void remove(int key); //删除所有值为key的节点 void removeAllKey(int key); //得到单链表的长度 int size(); void clear(); void display(); }
class MySecendLink implements ILinked{ static class ListNode { private int val; private ListNode next; public ListNode(int val) { this.val = val; } } private ListNode head; @Override public void addFirst(int data) { } @Override public void addLast(int data) { } @Override public void addIndex(int index, int data) { } @Override public boolean contains(int key) { return false; } @Override public void remove(int key) { } @Override public void removeAllKey(int key) { } @Override public int size() { return 0; } @Override public void clear() { } @Override public void display() { } }
【解释】
ListNode
修饰:因为next所指向的是下一个结点的地址,而结点始终是ListNode
类型。思路:从头结点开始遍历,若该结点不等于null就打印输出该结点的元素。
问题:
@Override
public void display() {
ListNode cur = this.head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
【解释】:为什么创建一个cur代替head:如果直接用head遍历那么当打印输出结束后head就为null,相当于链表就空了,因此用cur代替head。
思路:定义一个计数器,从头开始遍历链表,每走一个结点计数器加一,最后返回计数器结果。
@Override
public int size() {
ListNode cur = this.head;
int count = 0;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
思路:遍历链表,每走一步判断结点的元素是否等于key,等于则直接返回true,若遍历完成仍没有则返回false
@Override
public boolean contains(int key) {
ListNode cur = this.head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
思路:
@Override
public void addFirst(int data) {
ListNode node = new ListNode(data);
if (head == null) {
this.head = node;
}else {
node.next = this.head;
this.head = node;
}
}
思路:
@Override
public void addLast(int data) {
ListNode node = new ListNode(data);
if (head == null) {
this.head = node;
}else {
ListNode cur = this.head;
while (cur.next != null) {//cur.next代表走到最后一个元素
cur = cur.next;
}
cur.next = node;
}
}
思路:实例化一个结点,让index位置前一个结点next指向该结点,该结点的next指向index位置的结点。
问题:**如何获取index位置前一个结点?:**不能像之前一样直接让cur走到index位置,因为单向链表无法获取前一个结点的信息。
解决:cur走到index-1步,然后:
node.next = cur.next;
cur.next = node;
注意:必须先绑定后面的结点再绑定前面的结点,若
cur.next = node;
node.next = cur.next;
因为此时cur.next已经变成了node自己,所以会出现node自己绑定自己。
代码实现:
查找index-1位置
private ListNode SearchPrev(int index) {
ListNode cur = this.head;
int count = 0;
while (count != index-1) {
cur = cur.next;
count++;
}
return cur;
}
addIndex
@Override public void addIndex(int index, int data) { ListNode node = new ListNode(data); //判断index的合法性 if (index < 0 || index > size()) { throw new IllegalIndex("下标不合法!");//自定义异常 } if (index == 0) { addFirst(data); }else if (index == size()) { addLast(data); }else { ListNode cur = SearchPrev(index); node.next = cur.next; cur.next = node; } }
思路:如果链表为空,则返回“链表为空”;如果链表没有对应的元素则返回“没找到对应的元素”;如果找到了:
前一个结点的next指向删除结点del的next。
问题:如何查找前一个结点?
private ListNode findPrev(int key) {
ListNode cur = this.head;
while (cur.next != null) {
if (cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;
}
@Override public void remove(int key) { if (this.head == null) { System.out.println("链表为空,无法删除!"); } if (this.head.val == key) { this.head = this.head.next; return; } //找到前驱 ListNode PrevNode = findPrev(key); //判断是否为空 if (PrevNode == null) { System.out.println("没有你要删除的元素."); return; } //删除元素 ListNode del = PrevNode.next; PrevNode.next = del.next; }
这种最简单易想到的做法是直接遍历链表当结点元素满足要求就删除,但是这样遍历链表再加上查找前驱结点,时间复杂度就很高了。怎么把时间复杂度控制在O(n)呢?
思路:定义两个结点:
ListNode prevNode = this.head;
ListNode cur = this.head.next;
当结点元素等于要删除的数据时删除结点,否则
prevNode = cur;
cur = cur.next;
代码实现:
@Override public void removeAllKey(int key) { if (this.head == null) { return; } ListNode prevNode = this.head; ListNode cur = this.head.next; while (cur != null) { if (cur.val == key) { prevNode.next = cur.next; cur = cur.next; }else { prevNode = cur; cur = cur.next; } } if (head.val == key) { this.head = this.head.next; } }
思路,把每个结点都制空
@Override
public void clear() {
ListNode cur =head;
while (head != null) {
ListNode curNext = cur.next;
//cur.val = null 引用数据类型加上这句
cur.next = null;
cur = curNext;
}
head = null;
}
双向链表相对于单链表多了一个前驱结点prev存放前一个结点地址和尾结点last
这个因为多了个前驱节点,双向链表在实现各种方法时的时间复杂度相对于单链表更低
因为双向链表的各方法实现与单向链表大致相同且有些更为简单,所以就不一一细讲了
public interface IDoubleLink { //头插法 void addFirst(int data); //尾插法 void addLast(int data); //任意位置插入,第一个数据节点为0号下标 void addIndex(int index,int data); //查找是否包含关键字key是否在单链表当中 boolean contains(int key); //删除第一次出现关键字为key的节点 void remove(int key); //删除所有值为key的节点 void removeAllKey(int key); //得到单链表的长度 int size(); void clear(); void display(); }
public class MyDoubleLinked implements IDoubleLink{ static class ListNode { private ListNode next; private ListNode prev; private int val; public ListNode(int val) { this.val = val; } } private ListNode head; private ListNode last; @Override public void addFirst(int data) { ListNode node = new ListNode(data); if (this.head == null) { this.head = node; this.last = head; }else { node.next = this.head; this.head.prev = node; } } @Override public void addLast(int data) { ListNode node = new ListNode(data); if (this.last == null) { this.head = node; this.last = head; }else { this.last.next = node; node.prev = this.last; } } @Override public void addIndex(int index, int data) { ListNode node = new ListNode(data); if (index < 0 || index > size()) { throw new IllegalIndex("输入的下标不在范围内."); } ListNode cur = this.head; int count = 0; if (index == 0) { addFirst(data); } else if (index == size()) { addLast(data); }else { while (index-1 != count) { cur = cur.next; count++; } node.next = cur.next; cur.next.prev = node; cur.next = node; node.prev = cur; } } @Override public boolean contains(int key) { ListNode cur = this.head; while (cur != null) { if (cur.val == key) { return true; } cur = cur.next; } return false; } @Override public void remove(int key) { if (this.head == null) { System.out.println("没有元素删除!"); return; } ListNode cur = this.head; while (cur != null) { if (cur.val == key) { cur.prev.next = cur.next; cur.next.prev = cur.prev; return; } cur = cur.next; } System.out.println("没有你要删除的数据!"); } @Override public void removeAllKey(int key) { ListNode cur =this.head; while (cur != null) { if (cur.val == key) { cur.prev.next = cur.next; cur.next.prev = cur.prev; } cur = cur.next; } } @Override public int size() { ListNode cur = this.head; int count = 0; while (cur != null) { count++; cur = cur.next; } return count; } @Override public void clear() { ListNode cur =head; while (head != null) { ListNode curNext = cur.next; //cur.val = null 引用数据类型 cur.next = null; cur = curNext; } head = null; } @Override public void display() { ListNode cur = this.head; while (cur != null) { System.out.print(cur.val + " "); cur = cur.next; } System.out.println(); } }
LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节
点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
在集合框架种,LinkedList也实现了List接口:
【说明】
方法 | 解释 |
---|---|
Linkedlist(); | 无参构造 |
public LinkedList(Collection<? extends E>C) | 使用其他集合容器中元素构造List |
public static void main(String[] args) {
// 构造一个空的LinkedList
List<Integer> list1 = new LinkedList<>();
List<String> list2 = new ArrayList<>();
list2.add("JavaSE");
list2.add("JavaWeb");
list2.add("JavaEE");
// 使用ArrayList构造LinkedList
List<String> list3 = new LinkedList<>(list2);
}
方法 | 解释 |
---|---|
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List subList(int fromIndex, int toIndex) | 截取部分 list |
public static void main(String[] args) { LinkedList<Integer> list = new LinkedList<>(); list.add(1);//add(elem)表示尾插 list.add(2); list.add(3); list.add(4); list.add(5); System.out.println(list.size()); //foreach遍历 for (int e:list) { System.out.print(e + " "); } System.out.println(); //使用迭代器遍历——正向遍历 ListIterator<Integer> it = list.listIterator(); whiel (it.hasNext()) { System.out.print(it.next+" "); } //使用迭代器遍历——反向遍历 ListIterator<Integer> rit = list.listIterator(list.size()); while (rit.hasPrevious()) { System.out.print(rit.previous()+" "); } System.out.println(); }
不同点 | ArrayList | LinkedList |
---|---|---|
存储空间上 | 物理上一定连续 | 逻辑上连续,但物理上不一定连续 |
随机访问 | 支持,O(1) | 不支持:O(n) |
头插 | 需要搬移元素,效率低:O(n) | 只需修改引用的指向:O(1) |
尾插 | 空间不够时需要扩容 | 没有容量概念 |
应用场景 | 元素高效存储+频繁访问 | 任意位置插入和删除频繁 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。