链表
链表是一种物理存储单元上非连续,非顺序的存储结构,数据元素之间的逻辑顺序是通过节点相互链接的,由若干个节点链接构成的对象就是链表。
一.单链表
1.概念
链表的节点存储的只有下一个节点的地址,整个链表只能从链表头部走到尾部,这种链表称为单向链表。
举个相似的例子,单链表就像是火车一样,火车的不同车厢就相当于链表的节点,车厢之间通过挂钩连接,挂钩脱离则两个车厢就没有任何联系了,这挂钩就相当于单链表节点存储的下一个节点的地址。
2.定义一个链表
(1)节点
节点可以用类去定义,每个节点都有保存的元素(数值),和下一个节点的地址,尾部节点存储的地址为null。
class Node{
int val;//节点所保存的元素(数值)
Node next;//当前节点保存的下一个节点的地址
}
(2)链表对象
定义一个链表对象需要定义两个属性,一个是链表头节点,以及当前链表的节点个数。其中节点需要用一个Node类来定义,这个类中有节点保存的值和下一个节点的地址。
public class SingleLinkedList{
private Node head;//定义头节点
private int size;//定义链表节点个数
}
class Node{
int val;//节点所保存的元素(数值)
Node next;//当前节点保存的下一个节点的地址
}
3.单链表的增删改查
(1)增加元素
单链表的头插法
//链表的头插法
public void addFirst(int val){
Node node = new Node();
node.val = val;
//当链表为空,则插入的节点为头节点,否则依次头插
if(head != null){
node.next = head;
}
head = node;
size++;
}
在单链表中索引为index的位置插入元素val
public void add(int index,int val){
//判断索引是否合法(索引与数组的相同,从0开始,尾节点索引为链表节点个数size - 1)
if(index < 0 || index > size){
System.err.println("你索引异常,不合法");
return;
}
if(index == 0){
//索引为0即为插入头节点位置,所以直接引用头插法方法
addFirst(val);
}else {
//给新节点赋值
Node newNode = new Node();
newNode.val = val;
Node prev = head;
//找到插入位置的前一个节点,即为前驱节点
for (int i = 0;i < index - 1;i++){
prev = prev.next;
}
//先让插入节点链接前驱节点的下一个节点,再让前驱节点链接新节点,防止前驱节点后面的节点丢失
newNode.next = prev.next;
prev.next = newNode;
size++;
}
}
(2)查找元素
根据val值去查找单链表的对应元素的索引
public int getByValue(int val){
//设置一个索引
int index = 0;
//从头节点开始遍历链表,找到要查找的值。
for (Node x = head;x != null;x = x.next){
//找到则返还索引
if(x.val == val){
return index;
}
//索引跟链表遍历,一直对应相应的节点
index++;
}
//没找到返还-1
return -1;
}
查询索引为index的单链表中的元素值为多少
public int get(int index) {
//线判断索引合不合法,其中indexIsRight方法为自己定义的判断索引是否合法的方法,返还值为boolean类型
if(indexIsRight(index)){
Node x = head;
//遍历链表找到要查询索引处的节点
for (int i = 0; i < index;i++){
x = x.next;
}
return x.val;
}
System.err.println("索引不合法");
return -1;
}
查询链表是否包含指定值val的节点。
public boolean contain(int val){
//直接将val的值放入getByValue即可判断是否存在该val
return getByValue(val) != -1;
}
(3)修改元素
将链表中索引为index的元素值修改为val
public int set(int index,int newVal){
//判断索引是否越界
if(indexIsRight(index)) {
Node x = head;
//遍历链表,找到索引为index处的节点
for (int i = 0;i < index;i++){
x = x.next;
}
//将该节点原先的值保存下来
int oldVal = x.val;
//修改index处节点的值
x.val = newVal;
//返还原先的值
return oldVal;
}
System.err.println("索引不合法");
return -1;
}
修改头节点,尾节点
//头节点即为索引为0的节点,将其放入set方法,即可修改
public int setFirst(int val){
return set(0,val);
}
//尾节点即为索引为 size-1 的节点,将其放入set方法,即可修改
public int setLast(int val){
return set(size - 1,val);
}
(4)删除元素
删除单链表中索引为index位置的节点,返还节点值
public int remove(int index){
//判断索引是否越界
if(indexIsRight(index)){
//当删除索引处节点为头节点时
if(index == 0){
//设置x节点存储头节点,防止头节点丢失,然后头节点后移
Node x = head;
head = head.next;
x.next = null;
return x.val;
}else {
//当删除节点不为头节点时,设置前驱节点prev,存储头节点
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;
}
}
System.err.println("索引不合法");
return -1;
}
删除链表中第一个值为val的节点(操作图片如上图)****
public void delete(int val) {
//判断边界值,即链表是否为空
if (head == null) {
System.out.println("该链表为空,删除失败");
return;
}
//删除值为头节点时
if (head.val == val) {
Node x = head;
head = head.next;
x.next = null;
size--;
} else {
Node prev = head;
//遍历链表,找到对应值val的节点,进行删除操作
while (head.next != null) {
if (prev.next.val == val) {
Node node = prev.next;
prev.next = node.next;
node.next = null;
size--;
return;
}
//没找到则节点向后遍历,直到下一个节点为空
prev = prev.next;
}
}
}
删除链表中所有值为val的节点
###头节点为val
###头节点不是val
public void deleteAllVal(int val) {
//当头节点为val的时,将头节点后移,后移后前部分节点指向null
while (head != null && head.val == val) {
Node x = head;
head = head.next;
x.next = null;
size--;
}
//头节点为空时,链表为空,无法删除,退出方法
if (head == null) {
return;
} else {
//设置遍历节点
Node prev = head;
while (prev.next != null) {
//当找到val后,进行删除操作
if (prev.next.val == val) {
Node node = prev.next;
prev.next = node.next;
node.next = null;
size--;
} else {
//直到prev.next的值不等于val,将prev节点链接第一个不等于val的节点
prev = prev.next;
}
}
}
}
``