今天的算法是链表篇,这篇比较简单,总体是之前完成的手写链表,几乎就是链表的大部分知识了,所以今天算是一个复习内容了。
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
所以第一步要设计一个节点类,如下:
java
public class Node<E> {
//节点内的数据
E data;
//节点指向的下一个对象
Node<E> next;
public Node(E data, Node<E> next) {
this.data = data;
this.next = next;
}
}
下面我们开始手写单向链表。
1.尾部添加
链接:设计链表
首先构建一个自己的链表,属性为 链表的长度,链表的头节点
对于尾部添加需要注意的是,需要知道链表的尾部节点是什么,所以第一步应该写一个方法,遍历链表找到尾节点。我们只需要判断每个节点的下一个指针是否为空,空的就是尾部节点。如下
java
private Node<E> findEnd() {
Node<E> node = first;
while (node.next != null){
node = node.next;
}
return node;
}
然后就是尾部添加方法的实现。
思路:
(1)判断链表是否为空,如果为空,则该节点为头节点
(2)调用查询尾部节点的方法,拿到尾部节点
(3)尾部添加,长度加一
代码如下:
java
public void add(E data) {
Node<E> node = new Node<>(data, null);
//首先判断头节点是否为空,如果为空则证明为空链表,第一次添加的节点为头节点
if (first == null) {
first = node;
size++;
return;
}
//找到链表的尾部
Node<E> endNode = findEnd();
endNode.next = node;
size++;
}
2.删除节点
链接:设计链表****
删除节点有两种方式,一种是根据索引进行删除,另一种是根据节点内的数据进行删除
索引删除:
无论是删除还是添加,我们都需要注意,要考虑到头节点的情况,并对其做出相应的处理
首先需要一个方法,根据索引返回对应的节点,该方法不需要考虑头节点,因为我们需要在真正执行删除的方法中添加对应的逻辑就好,该方法为通用方法
举例:需要找到索引值为2的节点,那么在不考虑头节点的情况下,只需要找到1索引值的节点,就可以根据其next的指针,找到索引值为2的节点
node(2) = node(1).next 所以只需要传入1,就可以了
代码如下:
java
//寻找对于索引处的节点
public Node<E> findNode(int index) {
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
下面开始删除节点的方法。
思路:
(1)如果索引为0,也就是头节点,首先获取头节点的下一个节点,将当前头节点的数据和节点指针置空,最后将链表的头节点属性改为下一个节点,size减1
(2)如果索引不为0,首先调用寻找节点的方法,获取到该节点的上一个节点,这样就可以拿到三个节点的信息
上一个节点(a) 当前节点(b) 下一个节点(c)
-
将a的节点指针,指向c
-
将b的数据和节点指针置空
-
size减1
代码如下:
java
//删除对应元素
public void remove(int index) {
//一:遍历链表找到对应节点和上一节点 (头节点的判断)
//二:将上一节点的指向改为删除节点的指向
//三:将连接的属性设置为null,size减1
if (first != null && index == 0) {
//1.获取头节点的下一节点,将头节点的属性清空
//2.头节点重新赋值
Node<E> next = first.next;
first.next = null;
first.data = null;
first = next;
} else {
//上一个节点
Node<E> prevNode = findNode(index - 1);
//当前节点
Node<E> node = prevNode.next;
prevNode.next = node.next;
node.next = null;
node.data = null;
}
size--;
}
节点数据删除:
对于节点数据删除来说,不能只删除一个节点,因为数据不是唯一的,可能多个节点的数据是相同的,这是一个注意的点,还有就是要考虑头节点删除的情况
思路:
(1)判断是否为头节点,如果删除的数据与头节点的数据相同的话,进行对应的逻辑:与索引删除的逻辑一样,只不过更改了判断条件
(2)不为头节点时,需要遍历整个链表,找到与删除数据一样的节点,并进行删除逻辑:
-
依旧需要从头节点开始遍历,因为头节点已经判断过了,所以去判断头节点的下一个节点,也就是node.next
-
如果是其数据为删除的数据,那么找到该节点的下一个节点(node.next.next),并将该节点置空(node.next),然后头节点更新节点指针,指向node.next.next,size减1
3.如果不为删除的数据,更新node节点,node=node.next;
代码如下:
java
public void removeValue(E data) {
// 删除头节点的情况
while (first != null && data.equals(first.data)) {
Node<E> nextNode = first.next;
first.next = null;
first.data = null;
first = nextNode;
size--;
}
// 删除其他节点的情况
Node<E> node = first;
while (node != null && node.next != null) {
if (data.equals(node.next.data)) {
//当前节点的下一个节点的下一个节点
Node<E> nextNode = node.next.next;
node.next.data = null; // 释放节点数据
node.next.next = null; // 释放节点指针
node.next = nextNode; // 更新指向
size--;
}else {
node = node.next;
}
}
}
3.链表反转
链接:反转链表
这里需要理解的就是双指针的概念了,定义两个指针,a指针在b指针前,两个指针分别记录了相邻的节点,可以通过一个中间值temp,来更改两个节点的指向
下一个概念就是虚拟头,可以在头节点前想象一个空节点,data为null,next为头节点,在开始时,让a指针指向这个虚拟头,b指针指向头节点。
比如有 1 -> 2 -> 3,反转需要做的是 1 <- 2 <- 3
第一步:将2的next更改为1。 第二步:将3的next更改为2
指针的移动为:a指向1 ,b指向2 ---> a指向2,b指向3
换成节点为:node=first
nextNode = first.next
node.next = preNode上一个节点信息,初始为null
到这里反转完成了,下面需要移动指针
上一个指针指向就变成了当前节点
下一个指针就变成了当前节点的下一个节点
代码如下:
java
//链表反转
public void reversal() {
//定义,上一个节点,当前节点,下一个节点
//从头节点开始
Node<E> prevNode = null;
Node<E> node = first;
Node<E> nextNode = null;
//遍历整个链表到尾部
while (node != null){
nextNode = node.next;
node.next = prevNode;
prevNode = node;
node = nextNode;
}
first = prevNode;//当遍历到尾节点的时候,已经为空节点了,获取其上一个节点
}
4.相交链表
链接:相交链表
简单来说,就是求两个链表交点节点的指针。
我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,这里不用纠结让A移动还是B移动,最终的结果两个链表的长度是一样的
此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。
否则循环退出返回空指针。
代码如下:
java
//链表相交
public Node<E> IntersectingLinkedList(Node<E> headA, Node<E> headB) {
Node<E> curA = headA;
Node<E> curB = headB;
int lenA = 0;
int lenB = 0;
//计算两个链表的长度
while (curA != null) {
lenA++;
curA = curA.next;
}
while (curB != null) {
lenB++;
curB = curB.next;
}
//计算差值
int gap = lenA - lenB;
if (gap > 0){
//证明A为长链表
for (int i = 0; i < gap; i++) {
headA = headA.next;
}
}else {
//证明B为长链表
gap = lenB - lenA;
for (int i = 0; i < gap; i++) {
headB = headB.next;
}
}
//无论A还是B链表,两个链表的长度是一样的,所以直接判断即可
while (headA != null) {
if (headA != headB){
headA = headA.next;
headB = headB.next;
}
//相等,返回
else {
return headA;
}
}
return null;
}
测试:
java
public class TestMyLink {
public static void main(String[] args) {
// 创建链表
MyLink<Integer> myLink = new MyLink<>();
// 添加元素
myLink.add(1);
myLink.add(2);
myLink.add(3);
myLink.add(4);
myLink.add(5);
System.out.println("链表初始化后的元素:");
System.out.println(myLink.printList());
// 获取元素
System.out.println("索引2处的元素:" + myLink.get(2)); // 应输出 3
// 更新元素
myLink.update(2, 10);
System.out.println("更新索引2处的元素后的链表:");
System.out.println(myLink.printList());
// 在指定位置插入元素
myLink.add(2, 7);
System.out.println("在索引2处插入7后的链表:");
System.out.println(myLink.printList());
// 删除元素
myLink.remove(0);
System.out.println("删除索引0处的元素后的链表:");
System.out.println(myLink.printList());
// 头插法
myLink.headAdd(0);
System.out.println("头插法插入0后的链表:");
System.out.println(myLink.printList());
// 链表反转
myLink.reversal();
System.out.println("链表反转后的元素:");
System.out.println(myLink.printList());
// 删除元素2
myLink.removeValue(2);
// 打印链表
System.out.println("删除元素2后的链表内容:");
System.out.println(myLink.printList()); // 应该输出: "1 3 4"
System.out.println(4/2);
}
}
结果:
成功!还有一些我就不详细去说了,思路都是一样的,大家可以看我之前的手写单向链表,这篇更多也是复习,我自己再写一遍了,今天就到这里了!
完整代码:
java
public class MyLink<E> {
//单向链表:链表的长度,节点,头节点
private int size;
//头节点
Node<E> first;
//尾插法,新增元素
public void add(E data) {
Node<E> node = new Node<>(data, null);
//首先判断头节点是否为空,如果为空则证明为空链表,第一次添加的节点为头节点
if (first == null) {
first = node;
size++;
return;
}
//找到链表的尾部
Node<E> endNode = findEnd();
endNode.next = node;
size++;
}
//寻找链表的尾部节点,节点内的指向下一个节点的属性为null,则为尾部节点
private Node<E> findEnd() {
//从头节点开始遍历
Node<E> node = first;
while (node.next != null) {
node = node.next;
}
return node;
}
//删除对应元素
public void remove(int index) {
//一:遍历链表找到对应节点和上一节点 (头节点的判断)
//二:将上一节点的指向改为删除节点的指向
//三:将连接的属性设置为null,size减1
if (index == 0) {
//1.获取头节点的下一节点,将头节点的属性清空
//2.头节点重新赋值
Node<E> next = first.next;
first.next = null;
first.data = null;
first = next;
} else {
Node<E> prevNode = findNode(index - 1);
Node<E> node = prevNode.next;
prevNode.next = node.next;
node.data = null;
node.next = null;
}
size--;
}
public void removeValue(E data) {
// 删除头节点的情况
while (first != null && data.equals(first.data)) {
Node<E> nextNode = first.next;
first.next = null;
first.data = null;
first = nextNode;
size--;
}
// 删除其他节点的情况
Node<E> node = first;
while (node != null && node.next != null) {
if (data.equals(node.next.data)) {
// 删除节点
Node<E> nextNode = node.next.next;
node.next.data = null; // 释放节点数据
node.next.next = null; // 释放节点指针
node.next = nextNode; // 更新指向
size--;
} else {
node = node.next;
}
}
}
//寻找对于索引处的节点
public Node<E> findNode(int index) {
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
//获取对应索引处的数据
public E get(int index) {
Node<E> node = findNode(index);
return node.data;
}
//修改对应索引处的数据
public void update(int index, E data) {
Node<E> node = findNode(index);
node.data = data;
}
//向指定位置添加一个元素
public void add(int index, E data) {
// 检查索引是否有效
if (index < 0 || index > size) {
System.out.println("索引越界");
return;
}
Node<E> node = new Node<>(data, null);
if (index == 0) {
// 头插法
headAdd(data);
} else if (index == size) {
// 尾插法
add(data); // 使用已有的尾插法
} else {
// 插入到指定位置
Node<E> prevNode = findNode(index - 1);
Node<E> nextNode = prevNode.next;
prevNode.next = node;
node.next = nextNode;
size++;
}
}
//头插法
public void headAdd(E data) {
//获取头节点,新节点指向头节点,并修改头节点变量
Node<E> node = new Node<>(data, null);
node.next = first;
first = node;
size++;
}
//链表反转
public void reversal() {
//定义,上一个节点,当前节点,下一个节点
//从头节点开始
Node<E> prevNode = null;
Node<E> node = first;
Node<E> nextNode = null;
//遍历到链表的尾部
while (node != null) {
//保存当前节点的指向
nextNode = node.next;
//开始反转
node.next = prevNode;//指向上一节点
prevNode = node;//将当前节点变为上一个节点
node = nextNode;//移动节点
}
first = prevNode;//当遍历到尾节点的时候,已经为空节点了
}
public String printList() {
Node<E> node = first;
StringBuilder list = new StringBuilder();
while (node != null) {
list.append(node.data);
list.append(" ");
node = node.next;
}
return list.toString();
}
//链表相交
public Node<E> IntersectingLinkedList(Node<E> headA, Node<E> headB) {
Node<E> curA = headA;
Node<E> curB = headB;
int lenA = 0;
int lenB = 0;
//计算两个链表的长度
while (curA != null) {
lenA++;
curA = curA.next;
}
while (curB != null) {
lenB++;
curB = curB.next;
}
//计算差值
int gap = lenA - lenB;
if (gap > 0){
//证明A为长链表
for (int i = 0; i < gap; i++) {
headA = headA.next;
}
}else {
//证明B为长链表
gap = lenB - lenA;
for (int i = 0; i < gap; i++) {
headB = headB.next;
}
}
//无论A还是B链表,两个链表的长度是一样的,所以直接判断即可
while (headA != null) {
if (headA != headB){
headA = headA.next;
headB = headB.next;
}
//相等,返回
else {
return headA;
}
}
return null;
}
}