看了前三篇博客,相信大家对于动态数组会有一点新的思考,现在一起再来看另一种重要的线性数据结构--链表。
既然已经有了一种线性数据结构,为什么又要在引入另一种数据结构呢,相信大家已经发现,在向数组插入元素的时候,时间复杂度是O(n)级别的,如果现在需要执行频繁的插入操作,那么使用数组会导致程序性能很差,这时就需要使用到链表这一结构,因为链表是依靠引用把一个个分散在内存中的节点串起来的,在插入操作上,性能优于数组。
下面来一起看看具体的代码实现
public class LinkedList<E> {
private class Node {
public E e;
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
//虚拟头节点
private Node dummyHead;
private int size;
public LinkedList() {
dummyHead = new Node(null, null);
size = 0;
}
//返回链表个数
public int getSize() {
return size;
}
//判断链表是否为空
public boolean isEmpty() {
return size == 0;
}
//在链表头添加新的元素e
public void addFirst(E e) {
add(0, e);
}
public void addLast(E e) {
add(size, e);
}
// 在链表的index位置添加新的元素e
// 在链表中不是一个常用的操作,练习用
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed. Illegal index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
// Node node = new Node(e);
// node.next = prev.next;
// prev.next = node;
}
prev.next = new Node(e, prev.next);
size++;
}
// 获取链表index位置的元素
// 在链表中不是一个常用的操作,练习用
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed. Illegal index.");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
// 获得链表的第一个元素
public E getFist() {
return get(0);
}
public E getLast() {
return get(size - 1);
}
// 修改链表index位置的元素
// 在链表中不是一个常用的操作,练习用
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed. Illegal index.");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
//查找链表中是否有元素e
public boolean contains(E e) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
// 删除链表index位置的元素
// 在链表中不是一个常用的操作,练习用
public E remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed. Illegal index.");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node retNode = prev.next;
prev.next = retNode.next;
retNode.next = null;
size--;
return retNode.e;
}
// 从链表中删除元素e
public void removeElement(E e) {
Node prev = dummyHead;
while (prev.next != null) {
if (prev.next.e.equals(e)) {
break;
}
prev = prev.next;
}
if (prev.next != null) {
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
}
}
public E removeFirst() {
return remove(0);
}
public E removeLast() {
return remove(size - 1);
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
// Node cur = dummyHead.next;
// while (cur != null) {
// res.append(cur + "->");
// cur = cur.next;
// }
for (Node cur = dummyHead.next; cur != null; cur = cur.next) {
res.append(cur + "->");
}
res.append("NULL");
return res.toString();
}
}
几个注意点
1.在这里使用了dummyHead这个虚拟头节点,使用这个虚拟头节点,虽然浪费了一个空间,但是在增删相关操作上,简化了逻辑。
2.一般在使用链表这种数据结构的时候,删除操作其实是不常涉及到的一个操作,但是通过删除操作,可以有助于我们更好的理解掌握链表。
3.链式结构具有天然的递归性质,在后面的博客中,递归将作为一个专题和大家进行分享。
4.数组在知道索引的情况下,访问时间复杂度是O(1)级别,而链表需要根据头节点去找,时间复杂度为O(n),在实际业务中,数据的使用频率会较高,但是链表作为一种最基础的链式结构,一定要掌握,特别在后面分享树和图相关时,会很有帮助。
同学们可能会想到,既然基于动态数组可以实现栈和队列,那么基于链表呢?答案是肯定的,接下来的一篇博客,将和大家一起分享基于链表的栈和队列。感谢阅读~
还请需要转载的同学注明出处:https://blog.csdn.net/sinat_33150417/article/details/82260318