今天的算法是链表篇,这篇比较简单,总体是之前完成的手写链表,几乎就是链表的大部分知识了,所以今天算是一个复习内容了。
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
所以第一步要设计一个节点类,如下:
public class Node<E> {//节点内的数据E data;//节点指向的下一个对象Node<E> next;public Node(E data, Node<E> next) {this.data = data;this.next = next;}
}
下面我们开始手写单向链表。
1.尾部添加
链接:设计链表
首先构建一个自己的链表,属性为 链表的长度,链表的头节点
对于尾部添加需要注意的是,需要知道链表的尾部节点是什么,所以第一步应该写一个方法,遍历链表找到尾节点。我们只需要判断每个节点的下一个指针是否为空,空的就是尾部节点。如下
private Node<E> findEnd() {Node<E> node = first;while (node.next != null){node = node.next;}return node;}
然后就是尾部添加方法的实现。
思路:
(1)判断链表是否为空,如果为空,则该节点为头节点
(2)调用查询尾部节点的方法,拿到尾部节点
(3)尾部添加,长度加一
代码如下:
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,就可以了
代码如下:
//寻找对于索引处的节点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)
1. 将a的节点指针,指向c
2. 将b的数据和节点指针置空
3. size减1
代码如下:
//删除对应元素public void remove(int index) {//一:遍历链表找到对应节点和上一节点 (头节点的判断)//二:将上一节点的指向改为删除节点的指向//三:将连接的属性设置为null,size减1if (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)不为头节点时,需要遍历整个链表,找到与删除数据一样的节点,并进行删除逻辑:
1. 依旧需要从头节点开始遍历,因为头节点已经判断过了,所以去判断头节点的下一个节点,也就是node.next
2. 如果是其数据为删除的数据,那么找到该节点的下一个节点(node.next.next),并将该节点置空(node.next),然后头节点更新节点指针,指向node.next.next,size减1
3.如果不为删除的数据,更新node节点,node=node.next;
代码如下:
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
到这里反转完成了,下面需要移动指针
上一个指针指向就变成了当前节点
下一个指针就变成了当前节点的下一个节点
代码如下:
//链表反转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,则找到交点。
否则循环退出返回空指针。
代码如下:
//链表相交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;}
测试:
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());// 删除元素2myLink.removeValue(2);// 打印链表System.out.println("删除元素2后的链表内容:");System.out.println(myLink.printList()); // 应该输出: "1 3 4"System.out.println(4/2);}}
结果:
成功!还有一些我就不详细去说了,思路都是一样的,大家可以看我之前的手写单向链表,这篇更多也是复习,我自己再写一遍了,今天就到这里了!
完整代码:
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减1if (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;}}