什么是单链表?
元素之间逻辑连续,物理上不连续的线性表。
所谓的单链表叫单向链表只能从链表的第一个节点走到最后节点。
链表中最重要的四句代码:
Node node = new Node(1);
node.next = head
//当链表为空时,仍然可以这样写; node就是第一个节点,node.next = null
head = node
size ++;
一、单链表的增删查改
插入
头插:
//在当前链表的头部添加元素
public void addFirst(E val){
Node node = new Node(val);
node.next = head;
head = node;
size ++;
}
尾插(默认插入):
public void add(E element) {
add(size,element);
}
中间插入:
public void add(int index, E element) {
// 1.base case,边界判断
if (index < 0 || index > size) {
throw new IllegalArgumentException("add index illegal!");
}
// 2.判断没有前驱的情况
if (index == 0) {
addFirst(element);
return;
}
// 3.确实是中间位置的插入,寻找待插入位置的前驱!
Node prev = head;
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
// prev走到待插入位置的前驱
Node node = new Node(element);
node.next = prev.next;
prev.next = node;
size ++;
}
删除
用索引删除
public E removeByIndex(int index) {
// 1.base case
if (!rangeCheck(index)) {
throw new IllegalArgumentException("remove index illegal!");
}
// 2.判断头节点的删除问题
if (index == 0) {
Node node = head;
head = head.next;
node.next = null;
size --;
return node.val;
}
// 3.现在确实是中间位置的删除
Node prev = head;
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
Node node = prev.next;
prev.next = node.next;
node.next = null;
size --;
return node.val;
}
删除某一元素(有重复删除第一个)
待删除元素在链表头部
待删除元素在链表的中间位置
public void removeByValue(E element) {
// 1.base case
if (head == null) {
return;
}
// 2.判断头节点恰好是待删除的节点
if (head.val.equals(element)) {
head = head.next;
size --;
return;
}
// 3.此时头节点不为空其一定不是待删除的结点
Node prev = head;
while (prev.next != null) {
if (prev.next.val.equals(element)) {
// prev走到了待删除节点的前驱位置
prev.next = prev.next.next;
size --;
return;
}
prev = prev.next;
}
// 4.链表中没有待删除的节点
System.out.println("当前链表中不存在值为" + element + "的节点");
}
删除某一元素(有重复全部删除)
重复元素出现在头节点位置
重复元素出现在链表中间位置
public void removeAllValue(E element) {
// 1.base case
if(head == null) {
return;
}
// 2.若头节点就是待删除的节点且出现连续的待删除节点
while (head != null && head.val.equals(element)) {
head = head.next;
size --;
}
if (head == null) {
// 整个链表已经删除完了
return;
}
// 3.头节点一定不是待删除的节点且链表不为空!
// prev一定指向的不是待删除的结点~
Node prev = head;
while (prev.next != null) {
if (prev.next.val.equals(element)) {
// 此时prev就是待删除节点的前驱
prev.next = prev.next.next;
size --;
}else {
// 只有后继节点不是待删除的结点才能移动prev引用!
prev = prev.next;
}
}
}
修改
public E set(int index, E element) {
// 合法性校验
if (!rangeCheck(index)) {
throw new IllegalArgumentException("set index illegal!");
}
// 遍历走到index对应的元素
Node x = head;
for (int i = 0; i < index; i++) {
x = x.next;
}
// 此时x就落在了待修改的节点位置
E oldVal = x.val;
x.val = element;
return oldVal;
}
查询
public E get(int index) {
if (!rangeCheck(index)) {
throw new IllegalArgumentException("get index illegal!");
}
Node x = head;
for (int i = 0; i < index; i++) {
x = x.next;
}
return x.val;
}
二、带头单链表
通过上述代码发现,单链表的核心操作都在找前驱!但是头节点没有前驱,因此在各项操作之前都在处理头结点的情况。
所谓的带头单链表指的是,链表的头部节点不存储具体元素,就只是作为链表的头来使用,所有的存储具体值的结点一定都在这个头结点的后面。
不带头单链表指的是,链表头结点若存在,则头结点也存储具体元素,而带头单链表的头结点不存储具体元素。
增删查改
插入
//头插
public void addFirst(E element){
add(0,element);
}
//默认尾插
@Override
public void add(E element) {
add(size,element);
}
//中间位置插入
@Override
public void add(int index, E element) {
//合法性校验
if (index < 0 || index > size) {
throw new IllegalArgumentException("add index illegal!");
}
//定义前驱节点为虚拟头节点
Node prev = dummyHead;
//遍历链表,确定前驱节点位置,prev一定不指向代操作节点
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//创建新节点,存放待插入元素
Node node = new Node(element);
node.next = prev.next;
prev.next = node;
size ++;
}
删除
//删除索引为index的元素
@Override
public E removeByIndex(int index) {
// 1.base case
if (!rangeCheck(index)) {
throw new IllegalArgumentException("remove index illegal!");
}
SingleHeadList.Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
SingleHeadList.Node node = prev.next;
prev.next = node.next;
node.next = null;
size --;
return (E) node.val;
}
//删除元素值为element的第一个元素
@Override
public void removeByValue(E element) {
// 1.base case
if (dummyHead.next == null) {
return;
}
SingleHeadList.Node prev = dummyHead;
while (prev.next != null) {
if (prev.next.val.equals(element)) {
// prev走到了待删除节点的前驱位置
prev.next = prev.next.next;
size --;
return;
}
prev = prev.next;
}
// 链表中没有待删除的节点
System.out.println("当前链表中不存在值为" + element + "的节点");
}
//删除元素值为element的所有元素
@Override
public void removeAllValue(E element) {
// prev一定指向的不是待删除的节点
Node prev = dummyHead;
while (prev.next != null) {
if (prev.next.val.equals(element)) {
prev.next = prev.next.next;
size --;
}else {
prev = prev.next;
}
}
}
修改
//修改索引为index位置的元素为element
@Override
public E set(int index, E element) {
// 合法性校验
if (!rangeCheck(index)) {
throw new IllegalArgumentException("set index illegal!");
}
// 遍历走到index对应的元素
SingleHeadList.Node x = dummyHead.next;
for (int i = 0; i < index; i++) {
x = x.next;
}
// 此时x就落在了待修改的节点位置
E oldVal = (E) x.val;
x.val = element;
return oldVal;
}
查询
//获取索引为index位置的元素
@Override
public E get(int index) {
if (!rangeCheck(index)) {
throw new IllegalArgumentException("get index illegal!");
}
SingleHeadList.Node x = dummyHead.next;
for (int i = 0; i < index; i++) {
x = x.next;
}
return (E) x.val;
}
总结
对于单链表的学习主要在于理解,多画图有利于加深理解!