目录
2.6.修改链表中任意位置index处的节点值,返回修改前的节点值
2.11.判断链表的index是否合法(针对查询,删除,修改)
- 结构简单,一般不会单独用来存储数据,更多是作为其他数据结构的子结构,如哈希桶(盛放不同key链表的容器),图的邻接表等。
- 笔试,面试中很多。
1.车厢类
/**
* 车厢类
*/
class Node {
//存储具体元素
int data;
//存储下一个节点的地址
Node next;//next是自定义的Node引用类型,存储Node类型的对象的地址,即车厢地址
//所有类以及数组都是引用类型,所有引用数据类型存储的都是一个类的对象的地址(起个别名)
//所有引用类型的默认值都是NULL
//当new一个对象时,它才有意义
//构造方法 alt+insert 但此电脑无法使用
public Node(int data) {
this.data = data;
}
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
}
2.火车类
/**
* 火车类 实际上是多个车厢的组合
*/
public class SingleLinkedList {
//当前火车中车厢的个数(实际存储的元素个数)
private int size;
//当前火车的第一个节点(单链表只能从前向后遍历)
private Node head;
//具体方法
...
}
2.1.在链表的头部添加节点
/**
* 在链表的头部添加节点
* @param data
*/
public void addFirst(int data) {
//判断当前火车是否为空
//一个节点都没有,要插入的节点就是第一个节点
if(size == 0) {
Node node = new Node(data); //引用数据类型,存地址
head = node;
size++;
} else {
//当前火车已经存在节点了
Node node = new Node(data);
node.next = head; //这两行代码不能互换,否则是自己指向自己,node->node,就与原链表扯不上关系了,联系不上了
head = node; //这两行代码不能互换
size++;
}
}
2.2.在链表的任意位置index处添加节点
/**
* 在链表的任意位置index处添加节点
* @param index
* @param data
*/
public void addIndex(int index, int data) {
//判断边界,注意合法性
if(index < 0 || index > size) {
System.err.println("add index illegal!");
return;
}
//头 无前驱
if(index == 0) {
//头插
addFirst(data);
return;
}
//中间和尾 有前驱
//此时需要找到待插入位置index的前驱节点,单链表只能从前向后遍历
Node node = new Node(data);
Node prev = head;
for(int i = 0; i < index - 1; i++) {
prev = prev.next;
}
//prev引用指向当前插入index的前驱节点(此时prev指向index - 1处节点)
node.next = prev.next;
prev.next = node;
size++;
}
2.3.在链表的尾部添加节点
/**
* 在链表尾部添加元素
* @param data
*/
public void addLast(int data) {
addIndex(size, data);
}
2.4.查询链表的任意位置index处的节点值
/**
* 查询链表的任意位置index处的节点值
* @param index
* @return
*/
public int get(int index) {
if(rangeCheck(index)) {
Node node = head;
for(int i = 0; i < index; i++) {
node = node.next;
}
//此时node指向待查找元素的索引
int data = node.data;
return data;
} else {
System.err.println("get index illegal!");
return -1;
}
}
2.5.判断链表中是否包含值为data的节点
/**
* 判断链表中是否包含值为data的节点
* @param data
* @return
*/
public boolean contains(int data) {
Node node = head;
while(node != null) {
if(node.data == data) {
System.out.println("找到元素");
return true;
}
node = node.next;
}
System.out.println("没有找到该元素");
return false;
}
2.6.修改链表中任意位置index处的节点值,返回修改前的节点值
/**
* 修改链表中任意位置index处的节点值,返回修改前的节点值
* @param index
* @param data
* @return
*/
public int set(int index, int data) {
if(rangeCheck(index)) {
Node node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
//此时走到index处
int oldData = node.data;
node.data = data;
return oldData;
} else {
System.err.println("set index illegal!");
return -1;
}
}
2.7.删除链表的头节点
/**
* 删除链表的头节点
*/
public void removeFirst() {
// //判断边界条件
// if(head == null || head.next == null) {
// //删除完链表为空链表
// return;
// }
Node node = head;
head = head.next;
node.next = null;
size--;
}
2.8.删除链表中任意位置index处节点
/**
* 删除链表中任意位置index处节点
* @param index
*/
public void removeIndex(int index) {
if(rangeCheck(index)) {
if(index == 0) {
//删除头节点 无前驱
removeFirst();
} else {
//删除中间位置节点 有前驱
Node prev = head;
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
//此时prev指向待删除节点的前驱节点
//node就是待删除的节点
Node node = prev.next;
//链接前驱节点和后继节点
prev.next = node.next; //①②
//将当前节点的next引用置为空,脱钩操作
node.next = null; //③
//先连接后脱钩
size--;
}
} else {
System.err.println("remove index illegal!");
}
}
2.9.删除链表中第一次出现某个值的节点
/**
* 删除链表中第一次出现某个值的节点
* @param data
*/
public void removeValueOnce(int data) {
//先要找到待删除元素的节点
//先判断头节点的情况 无前驱
if (head.data == data) {
//此时头节点就是第一个待删除的节点
removeFirst();
} else {
//此时头节点一定不是要删除的节点
//从头节点开始找到待删除元素的节点前驱
Node prev = head;
//此时要看下一个节点的情况
while (prev.next != null) {
//找到待删除的节点
if (prev.next.data == data) {
//此时prev就是待删除节点的前驱,node就是待删除节点
Node node = prev.next;
//将prev跳过当前node
prev.next = node.next;
node.next = null;
size--;
break;
} else {
//此时prev的下一个节点不是待删除的元素节点
//继续向下一个节点寻找
prev = prev.next;
}
}
}
}
2.10.删除链表中值为data的所有节点
/**
* 删除链表中值为data的所有节点
* @param data
*/
public void removeAllValue(int data) {
//判断头节点的情况
//头节点以及之后出现了多个连续的待删除的节点
while(head != null && head.data == data) {
Node node = head;
head = head.next;
node.next = null;
size--;
}
//此时head一定不是待删除的节点 head.data!=data;
if(head == null) {
//此时链表全是待删除的元素节点,已经删除完毕
return;
} else {
//此时头节点已经处理完毕,并且链表也不为空,向下一个节点开始判断
Node prev = head;
while(prev.next != null) {
if(prev.next.data == data) {
Node node = prev.next;
prev.next = node.next;
node.next = null;
size--; //当处理完当前删除节点后,需要继续判断下一个节点是否也要删除,此时prev不能移动
} else {
prev = prev.next; //此时prev的下一个节点不是要删除的节点,prev继续向后走一个单位
}
}
}
}
2.11.判断链表的index是否合法(针对查询,删除,修改)
/**
* 判断链表的index是否合法(针对查询,删除,修改)
* @param index
* @return
*/
private boolean rangeCheck(int index) {
if(index < 0 || index >= size) {
return false;
}
return true;
}
2.12.toString()方法
@Override
public String toString() {
String ret = "";
//为啥此时要有一个临时变量node来存储head的值?防止遍历完head不见了,这个链表就不能用了。有一个临时变量node来存储head的值,来保护head
Node node = head;
while(node != null) {
ret += node.data + " -> ";
//继续访问下一节车厢
node = node.next;
}
ret += "NULL";
return ret;
}
3.总代码实现
//一个类中可以有多个普通类,
// 但只有一个主类,带public,主类方法与这个类命名相同
/**
* 车厢类
*/
class Node {
//存储具体元素
int data;
//存储下一个节点的地址
Node next;//next是自定义的Node引用类型,存储Node类型的对象的地址,即车厢地址
//所有类以及数组都是引用类型,所有引用数据类型存储的都是一个类的对象的地址(起个别名)
//所有引用类型的默认值都是NULL
//当new一个对象时,它才有意义
//构造方法 alt+insert 但此电脑无法使用
public Node(int data) {
this.data=data;
}
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
}
/**
* 火车类 实际上是多个车厢的组合
*/
public class SingleLinkedList {
//当前火车中车厢的个数(实际存储的元素个数)
private int size;
//当前火车的第一个节点(单链表只能从前向后遍历)
private Node head;
/**
* 在火车头部添加元素
* @param data
*/
public void addFirst(int data) {
//判断当前火车是否为空
//一个节点都没有,要插入的节点就是第一个节点
if(size == 0) {
Node node = new Node(data); //引用数据类型,存地址
head = node;
size++;
} else {
//当前火车已经存在节点了
Node node = new Node(data);
node.next = head; //这两行代码不能互换,否则是自己指向自己,node->node,就与原链表扯不上关系了,联系不上了
head = node; //这两行代码不能互换
size++;
}
}
/**
* 在链表的任意位置index处添加节点
* @param index
* @param data
*/
public void addIndex(int index, int data) {
//判断边界,注意合法性
if(index < 0 || index > size) {
System.err.println("add index illegal!");
return;
}
//头 无前驱
if(index == 0) {
//头插
addFirst(data);
return;
}
//中间和尾 有前驱
//此时需要找到待插入位置index的前驱节点,单链表只能从前向后遍历
Node node = new Node(data);
Node prev = head;
for(int i = 0; i < index - 1; i++) {
prev = prev.next;
}
//prev引用指向当前插入index的前驱节点(此时prev指向index - 1处节点)
node.next = prev.next;
prev.next = node;
size++;
}
/**
* 在链表尾部添加元素
* @param data
*/
public void addLast(int data) {
addIndex(size, data);
}
/**
* 查询链表的任意位置index处的节点值
* @param index
* @return
*/
public int get(int index) {
if(rangeCheck(index)) {
Node node = head;
for(int i = 0; i < index; i++) {
node = node.next;
}
//此时node指向待查找元素的索引
int data = node.data;
return data;
} else {
System.err.println("get index illegal!");
return -1;
}
}
/**
* 判断链表中是否包含值为data的节点
* @param data
* @return
*/
public boolean contains(int data) {
Node node = head;
while(node != null) {
if(node.data == data) {
System.out.println("找到元素");
return true;
}
node = node.next;
}
System.out.println("没有找到该元素");
return false;
}
/**
* 修改链表中任意位置index处的节点值,返回修改前的节点值
* @param index
* @param data
* @return
*/
public int set(int index, int data) {
if(rangeCheck(index)) {
Node node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
//此时走到index处
int oldData = node.data;
node.data = data;
return oldData;
} else {
System.err.println("set index illegal!");
return -1;
}
}
/**
* 删除链表的头节点
*/
public void removeFirst() {
// //判断边界条件
// if(head == null || head.next == null) {
// //删除完链表为空链表
// return;
// }
Node node = head;
head = head.next;
node.next = null;
size--;
}
/**
* 删除链表中任意位置index处节点
* @param index
*/
public void removeIndex(int index) {
if(rangeCheck(index)) {
if(index == 0) {
//删除头节点 无前驱
removeFirst();
} else {
//删除中间位置节点 有前驱
Node prev = head;
for (int i = 0; i < index - 1; i++) {
prev = prev.next;
}
//此时prev指向待删除节点的前驱节点
//node就是待删除的节点
Node node = prev.next;
//链接前驱节点和后继节点
prev.next = node.next; //①②
//将当前节点的next引用置为空,脱钩操作
node.next = null; //③
//先连接后脱钩
size--;
}
} else {
System.err.println("remove index illegal!");
}
}
/**
* 删除链表中第一次出现某个值的节点
* @param data
*/
public void removeValueOnce(int data) {
//先要找到待删除元素的节点
//先判断头节点的情况 无前驱
if (head.data == data) {
//此时头节点就是第一个待删除的节点
removeFirst();
} else {
//此时头节点一定不是要删除的节点
//从头节点开始找到待删除元素的节点前驱
Node prev = head;
//此时要看下一个节点的情况
while (prev.next != null) {
//找到待删除的节点
if (prev.next.data == data) {
//此时prev就是待删除节点的前驱,node就是待删除节点
Node node = prev.next;
//将prev跳过当前node
prev.next = node.next;
node.next = null;
size--;
break;
} else {
//此时prev的下一个节点不是待删除的元素节点
//继续向下一个节点寻找
prev = prev.next;
}
}
}
}
/**
* 删除链表中值为data的所有节点
* @param data
*/
public void removeAllValue(int data) {
//判断头节点的情况
//头节点以及之后出现了多个连续的待删除的节点
while(head != null && head.data == data) {
Node node = head;
head = head.next;
node.next = null;
size--;
}
//此时head一定不是待删除的节点 head.data!=data;
if(head == null) {
//此时链表全是待删除的元素节点,已经删除完毕
return;
} else {
//此时头节点已经处理完毕,并且链表也不为空,向下一个节点开始判断
Node prev = head;
while(prev.next != null) {
if(prev.next.data == data) {
Node node = prev.next;
prev.next = node.next;
node.next = null;
size--;
} else {
prev = prev.next;
}
}
}
}
@Override
public String toString() {
String ret = "";
//为啥此时要有一个临时变量node来存储head的值?防止遍历完head不见了,这个链表就不能用了。有一个临时变量node来存储head的值,来保护head
Node node = head;
while(node != null) {
ret += node.data + " -> ";
//继续访问下一节车厢
node = node.next;
}
ret += "NULL";
return ret;
}
/**
* 判断链表的index是否合法(针对查询,删除,修改)
* @param index
* @return
*/
private boolean rangeCheck(int index) {
if(index < 0 || index >= size) {
return false;
}
return true;
}
public static void main(String[] args) {
SingleLinkedList singleLinkedList=new SingleLinkedList();
singleLinkedList.addFirst(1);
singleLinkedList.addFirst(2);
singleLinkedList.addFirst(2);
singleLinkedList.addFirst(2);
singleLinkedList.addFirst(2);
singleLinkedList.addFirst(2);
singleLinkedList.addFirst(2);
singleLinkedList.addLast(4);
singleLinkedList.addLast(6);
//2->2->2->2->2->2->1->4->6->NULL
singleLinkedList.addIndex(2,10);
//2->2->10->2->2->2->2->1->4->6->NULL
System.out.println(singleLinkedList.get(3));
//2
singleLinkedList.set(2,99);
//2->2->99->2->2->2->2->1->4->6->NULL
singleLinkedList.contains(6);
//true
singleLinkedList.contains(88);
//false
singleLinkedList.removeFirst();
//2->99->2->2->2->2->1->4->6->NULL
singleLinkedList.removeIndex(1);
//2->2->2->2->2->1->4->6->NULL
singleLinkedList.removeValueOnce(2);
//2->2->2->2->1->4->6->NULL
singleLinkedList.removeAllValue(2);
//1->4->6->NULL
System.out.println(singleLinkedList);
}
}
4.此种数据结构的缺点和解决方案
缺点:
由于单链表的插入与删除操作都要找前驱节点,但头节点不存在前驱节点,因此在处理时,要额外考虑头节点。
解决方案:
引入一个虚拟头节点dummyHead当火车头,不存储元素。这样在插入和删除时,不用考虑头节点的特殊性,所有节点一律一视同仁,都有前驱节点。