动态链表存储结构
这个版块主要学习以下的内容,本次讲解的主要是线性表的链式单向存储结构
链表的定义
为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了储存本省的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数元素信息的域称为数据域,把存储直接后继位置的域称为指针域,指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)
n个结点(ai的存储映像)链接成一个链表,即为线性表(a1,a2…an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
结点组成:数据域data,指针域next.
例如这个图称为单链表
头结点与头指针
头结点是指链表中的第一结点,有真实头结点和虚拟头结点之分
那么问题来了,何为真实头结点和虚拟头结点
真实头结点:其第一个结点用于存储数据
虚拟头结点:其第一个结点不允许存储数据(这里说明一下,本篇就采用虚拟头结点讲述单链表)
再用两个图来说明以下
此图为真实头结点,根据概念就可以看出来,该链表的头结点有数据;而虚拟头结点没有数据,如下图所示
通过两张图我们显而易见,可以直观的分清楚真实头结点和虚拟头结点。
头指针:仅仅是一个引用变量,存储头结点地址的指针而已,这里解释以下,当你创建一个结点时,相当于在堆内存中,创建一个链表对象,然后你在给这个对象赋值,也就是存储的数据元素。
尾指针:同头指针,不过尾指针是链表中最后一个结点的指针而已。
这里的head指头指针,而rear指尾指针。
线性表的链式存储结构LinkedList的定义
说明一点,LinkedList和Node里面的属性蓝色杠为私有属性。
LinkedList宏观讲为链表,但是它是由一堆结点链接起来的链表,存在结点说法,所以有一个Node类,那我们可以将Node当作一个内部类来写。
插入元素—头插法
头插法分两种情况:
第一种当此时链表为空,那么插入元素A,操作是将把头结点的下一跳给新结点下一跳,再将新元素的地址给头结点的下一跳,再将尾指针指向A
第二种此时链表不为空,那么插入元素B,操作是将头结点的下一跳给新结点的下一跳,再将B的地址给头结点的下一跳,此时尾指针不需要移动
区别在于:链表为空需要移动尾指针,而不为空咋不需要移动尾指针
代码实现:
/**
* 因为我们一般插入中,是不是当index=0时,就相当于头插
* 那么我们可以直接调用add方法,让它的index赋值0
* */
@Override
public void addFirst(E e) {
add(0,e);
}
插入元素—尾插法
尾插法操作,此时链表为空,先将新元素的地址给尾结点结点的下一跳,尾结点后移。再插入一个元素, 将此元素的地址给上一元素的下一跳,尾指针后移。
代码实现:
/**
* 同样,当我们尾插时是不是当index=size时,就相当于尾插
* 那么我们可以直接调用add方法,让它的index赋值size
* */
@Override
public void addLast(E e) {
add(size,e);
}
插入元素—头插尾插结合
头插:将头结点的下一跳给新元素下一跳,将新元素的地址指向头结点,此时链表不为空,若为空,尾结点需要后移。
尾插:将新元素的地址给尾指针下一跳,尾指针后移,此时链表不为空。
插入元素—一般插入
看图让我们把D元素插入到AB之间,操作将A节点的下一跳给D,再将D的地址给A下一跳,其实此时的操作类似于头插法,这里我们将A看作头结点,就完全是头插法。
代码实现:
/**
* 链表的一般插入
* 这里的index我们可以当作是一种链表的编号,链表不存在角标。
* */
@Override
public void add(int index, E e) {
if(index < 0||index > size) {
throw new IllegalArgumentException("插入角标非法");
}
Node n = new Node(e,null);
size++;
if(index == 0) { //相当于头插法
n.next = head.next;
head.next = n;
if(size == 0) {
rear = n;
}
} else if(index == size) {//相当于尾插法
rear.next = n;
rear = rear.next;
} else {
Node p = head;
for(int i=0;i<index;i++) {
p=p.next;
}
n.next = p.next;
p.next = n.next;
}
}
删除元素—删头
这里也分为两种情况:
第一种链表元素大于1,操作将A头删除,将A结点的下一跳给头节点的下一跳,当然这里A和B之间还存在关系,这里要清除AB最后的关系。
第二种链表元素只有A,操作A的下一跳指向头结点的下一跳,将尾指针前移。
代码实现:
/**
* 删除链表头元素
* */
@Override
public E removeFirst() {
return remove(0);
}
删除元素—删尾
这里删除尾,先找到尾结点前一个结点,将尾结点前移,再将尾结点的下一跳制空。
代码实现:
/**
* 删除链表尾元素
* */
@Override
public E removeLast() {
return remove(size-1);
}
删除元素—一般删除
删除B元素,先找到B元素的前一结点,然后将B结点的下一跳给A的下一跳,再将B的下一跳制空。
代码实现:
/**
* 链表元素的一般删除
* */
@Override
public E remove(int index) {
if(index < 0||index >= size) {
throw new IllegalArgumentException("删除角标非法");
}
E res = null;
if(index == 0) { //相当于头删除
Node p = head;
res = p.data;
head.next = p.next;
p.next = null;
p = null;
if(size == 1) {
rear = head;
}
size--;
return res;
} else if(index == size-1) { //相当于尾删除
Node p = head;
res = rear.data;
while(p.next != rear) {
p = p.next;
}
p.next = null;
rear = p;
size--;
return res;
} else {
Node p = head;
for(int i=0;i<index;i++) {
p = p.next;
}
Node del = p.next;
res = del.data;
p.next = del.next;
del.next = null;
del = null;
}
size--;
return res;
}
以上内容为详解思路,下面为具体代码实现,里面还存在清空链表,toString方法重写等。
实现代码:
package com.study.动态链表;
import com.study.shuzu.List;
public class LinkedList<E> implements List<E> {
/**
* 单向链表的结点类
* */
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;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return head.next == null&&size == 0;
}
/**
* 链表的一般插入
* 这里的index我们可以当作是一种链表的编号,链表不存在角标。
* */
@Override
public void add(int index, E e) {
if(index < 0||index > size) {
throw new IllegalArgumentException("插入角标非法");
}
Node n = new Node(e,null);
size++;
if(index == 0) { //相当于头插法
n.next = head.next;
head.next = n;
if(size == 0) {
rear = n;
}
} else if(index == size) {//相当于尾插法
rear.next = n;
rear = rear.next;
} else {
Node p = head;
for(int i=0;i<index;i++) {
p=p.next;
}
n.next = p.next;
p.next = n.next;
}
}
/**
* 因为我们一般插入中,是不是当index=0时,就相当于头插
* 那么我们可以直接调用add方法,让它的index赋值0
* */
@Override
public void addFirst(E e) {
add(0,e);
}
/**
* 同样,当我们尾插时是不是当index=size时,就相当于尾插
* 那么我们可以直接调用add方法,让它的index赋值size
* */
@Override
public void addLast(E e) {
add(size,e);
}
/**
* 获取指定位置的元素
* */
@Override
public E get(int index) {
if(index < 0||index >= size) {
throw new IllegalArgumentException("查找角标非法");
}
if(index == 0) {
return head.next.data;
} else if(index == size-1) {
return rear.data;
}
Node p = head;
for(int i=0;i<index;i++) {
p = p.next;
}
return p.data;
}
/**
* 获取链表首元素
* */
@Override
public E getFirst() {
return get(0);
}
/**
* 获取链表尾元素
* */
@Override
public E getLast() {
return get(size-1);
}
/**
* 修改链表指定位置的元素
* */
@Override
public void set(int index, E e) {
if(index < 0||index >= size) {
throw new IllegalArgumentException("修改角标非法");
}
if(index == 0) {
head.next.data = e;
} else if(index == size-1) {
rear.data = e;
}
Node p = head;
for(int i=0;i<index;i++) {
p = p.next;
}
p.data = e;
}
/**
* 看链表中是否包含某个元素
* */
@Override
public boolean contains(E e) {
return find(e) != -1;
}
/**
* 查询链表元素的位置
* */
@Override
public int find(E e) {
int index = -1;
if(isEmpty()) {
return index;
}
Node p = head;
while(p.next != null) {
p = p.next;
index++;
if(p.data == e) {
return index;
}
}
return -1;
}
/**
* 链表元素的一般删除
* */
@Override
public E remove(int index) {
if(index < 0||index >= size) {
throw new IllegalArgumentException("删除角标非法");
}
E res = null;
if(index == 0) { //相当于头删除
Node p = head;
res = p.data;
head.next = p.next;
p.next = null;
p = null;
if(size == 1) {
rear = head;
}
size--;
return res;
} else if(index == size-1) { //相当于尾删除
Node p = head;
res = rear.data;
while(p.next != rear) {
p = p.next;
}
p.next = null;
rear = p;
size--;
return res;
} else {
Node p = head;
for(int i=0;i<index;i++) {
p = p.next;
}
Node del = p.next;
res = del.data;
p.next = del.next;
del.next = null;
del = null;
}
size--;
return res;
}
/**
* 删除链表头元素
* */
@Override
public E removeFirst() {
return remove(0);
}
/**
* 删除链表尾元素
* */
@Override
public E removeLast() {
return remove(size-1);
}
/**
* 删除元素
* */
@Override
public void removeElement(E e) {
int index = find(e);
if(index==-1) {
throw new IllegalArgumentException("元素不存在");
}
remove(index);
}
/**
* 清空链表
* */
@Override
public void clear() {
head.next = null;
rear = head;
size = 0;
}
/**
* toString实现
* */
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("LinkedList:size="+getSize());
if(isEmpty()) {
sb.append("[]");
}else {
sb.append('[');
Node p = head;
while(p.next != null) {
p = p.next;
if(p == rear) {
sb.append(p.data + ",");
} else {
sb.append(']');
}
}
}
return sb.toString();
}
}