总结了一下网上的常用单链表操作(JAVA),然后每个操作都在自己本地跑过一遍,然后会自己写几个自己想到的链表操作,例如删除回环链表中的节点,以后就直接看自己整理的链表操作就可以拉
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class LinkedList {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一个链表
int[] nodes = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
int[] nodes2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
Node head = null;
Node temp = null;
for(int i : nodes){
if(i == 1){
head = new Node(1);
temp = head;
}else{
temp.next = new Node(i);
temp = temp.next;
//if(i == 11) temp.next = head;//回环
}
}
Node head2 = null;
Node temp2 = null;
for(int i : nodes2){
if(i == 1){
head2 = new Node(1);
temp2 = head2;
}else{
temp2.next = new Node(i);
temp2 = temp2.next;
}
}
//创建回环链表
Node cycel = null;
Node cur = null;
Node cycelStart = null;
for(int i : nodes2){
if(i == 1){
cycel = new Node(1);
cur = cycel;
}else{
cur.next = new Node(i);
cur = cur.next;
if(i == 4){
//创建回环点
cycelStart = cur;
}
if(i == 11){
cur.next = cycelStart;
}
}
}
//打印链表
//printList(head);
// System.out.println(getListLength(head));//链表的个数
// printList(reverseList(head));//翻转链表
//获取倒数第n个节点
//System.out.println(getReKthNodeUniversal(head, 9).data);
// System.out.println(getReKthNode(head, 2).data);
//获取链表的中间节点
// List<Node> middleNodes = getMiddleNode(head);
// for(Node node : middleNodes){
// System.out.println(node.data);
// }
//反序输出链表
// reversePrintListRec(head);//递归
// reversePrintList(head);//非递归
//合并2个有序的链表
// Node mHead = mergeSortListRec(head, head2);//递归
// Node mHead = mergeSortList(head, head2);
// printList(mHead);
// System.out.println(isCycle(head));//证明是否回环,快慢指针
// System.out.println(getFirstNodeInCycel(cycel).data);//获取环入口节点,快慢指针
// System.out.println(getFirstNodeInCycelList(cycel).data);//集合求环入口节点
// System.out.println(isIntersect(head, head2));
// Node intersect = getCommonFirstNode(head, head2);
// System.out.println(intersect == null ? "没有相交的节点" : intersect.data);
//删除链表的节点 时间复杂度O(1) 无环链表
// int count = 1;
// Node del = head;
// while(count != 10){
// del = del.next;
// count++;
// }
// //删除第10个节点
// delete(head, del);
// printList(head);
// //删除最后一个节点
// delete(head, del);
// System.out.println("");
// printList(head);
//删除有环的链表
//打印删除前的回环链表
printList(cycel);
//删除回环点
//deleteInCycle(cycel, cycelStart);
//删除回环点的前驱节点
Node delOut = cycel;
//回环外的前驱节点
while(delOut.next != cycelStart){
delOut = delOut.next;
}
//回环内的前驱节点
Node delIn = delOut;
delIn = delIn.next;
while(delIn.next != cycelStart){
delIn = delIn.next;
}
//deleteInCycle(cycel, delOut);//删除回环外的前驱节点
deleteInCycle(cycel, delIn);//删除回环内的前驱节点
System.out.println("");
System.out.println("新的回环起始点: "+getFirstNodeInCycel(cycel).data);
//打印删除节点之后的回环链表
System.out.println("");
printList(cycel);
}
static class Node{
int data;
Node next;
Node(){}
Node(int data){
this.data = data;
}
}
/**
* 打印链表
* @param head
*/
public static void printList(Node head){
if(head== null) return;
Node cur = head;
//首先判断是否回环,因为回环打印会出现死循环
Node cycelStart = getFirstNodeInCycel(head);
if(cycelStart == null){
//无环,简单
while(cur != null){
System.out.print(cur.data+" ");
cur = cur.next;
}
}else{
//有环
//循环打印回环点前的节点
while(cur != cycelStart){
System.out.print(cur.data+" ");
cur = cur.next;
}
//回环点单独处理
System.out.print("回环起始点( "+cur.data+" )");
cur = cur.next;
//循环打印回环点后的节点,当再次到回环其实点后,结束打印
while(cur != cycelStart){
System.out.print(cur.data+" ");
cur = cur.next;
}
}
}
/**
* 获取链表的个数
* @return
*/
public static int getListLength(Node head){
if(head == null) return 0;
Node cur = head;
int count = 0;
while(cur != null){
count++;
cur = cur.next;
}
return count;
}
/**
* 单链表翻转
* 思路:3个指针记录位置
* 翻转后的链表头,当前链表头,当前头节点的下一个节点
* @param head
* @return
*/
public static Node reverseList(Node head){
if(head == null || head.next == null) return head;
Node rHead = null;
Node curNode = head;
Node nextNode = null;
while(curNode != null){
nextNode = curNode.next;
curNode.next = rHead;
rHead = curNode;
curNode = nextNode;
}
return rHead;
}
/**
* 获取倒数第k个节点
* 普通做法
* 获取链表的个数n, 然后在获取顺数第n-k+1个
* @param head
* @param k
* @return
*/
public static Node getReKthNodeUniversal(Node head, int k){
int length = getListLength(head);
if(length == 0){
return null;
}else if(length - k +1 < 1){
return null;
}else{
int count = 1;
Node curNode = head;
while(count < length - k +1){
curNode = curNode.next;
count++;
}
return curNode;
}
}
/**
* 获取倒数第k个节点
* 思路:快慢指针
* 定义2个指针,快指针先移动到第k个节点,这样,慢指针是快指针的倒数第K个
* 然后快慢指针一起向后移动,当快指针移动到链表最后一个节点,慢指针就是倒数第K个节点
* @param head
* @param k
* @return
*/
public static Node getReKthNode(Node head, int k){
if(head == null || k == 0) return null;
Node fastNode = head;
Node slowNode = head;
int count = 1;
while(count < k && fastNode != null){
fastNode = fastNode.next;
count++;
}
if(fastNode == null){
return null;
}
while(fastNode.next != null){
slowNode = slowNode.next;
fastNode = fastNode.next;
}
return slowNode;
}
/**
* 获取链表的中间节点
* 思路:
* 慢指针指向头节点开始,慢指针一次走一步
* 快指针指向第二个节点开始,一次走2步
* 当快指针当前指向的对象为空,那么链表个数为奇数,那么慢指针所指向的为中间节点
* 当快指针下一个节点为空,那么俩表个数为偶数,那么慢指针当前节点和下一个节点为中间几点
* @param head
* @return
*/
public static List<Node> getMiddleNode(Node head){
List<Node> nodes = new ArrayList<Node>();
if(head == null || head.next == null){
nodes.add(head);
return nodes;
}
Node fastNode = head.next;
Node slowNode = head;
while(true){
slowNode = slowNode.next;
fastNode = fastNode.next.next;
if(fastNode == null){
//奇数,慢指针指向中间节点
nodes.add(slowNode);
return nodes;
}else if(fastNode.next == null){
//偶数,慢指针和慢指针的下一个节点都为中间节点
nodes.add(slowNode);
nodes.add(slowNode.next);
return nodes;
}
}
}
/**
* 反序打印链表
* 思路:递归
* 当前节点为空,那么直接返回空
* 当前节点不为空,那么递归去处理下一个节点,处理完了打印当前节点
* 不太好想
* @param head
*/
public static void reversePrintListRec(Node head){
if(head == null) return;
else
{
reversePrintListRec(head.next);
System.out.print(head.data+" ");
}
}
/**
* 反序输出链表
* 思路:非递归
* 反序应该想到栈
* @param head
*/
public static void reversePrintList(Node head){
if(head == null) return;
Stack<Node> stack = new Stack<Node>();
Node curNode = head;
while(curNode != null){
stack.push(curNode);
curNode = curNode.next;
}
while(!stack.isEmpty()){
Node node = stack.pop();
System.out.print(node.data+" ");
}
}
/**
* 合并2个有序链表
* 这里为2个从小到大排序的链表
* 如果2个链表排序不一样,那么我们可以使其中的一个链表翻转,再合并
* @param h1
* @param h2
* @return
*/
public static Node mergeSortList(Node h1, Node h2){
if(h1 == null) return h2;
if(h2 == null) return h1;
Node mHead = null;
Node mCur = null;
//决定头节点
if(h1.data > h2.data){
mHead = h2;
mCur = h2;
h2 = h2.next;
mHead.next = null;
}else{
mHead = h1;
mCur = h1;
h1 = h1.next;
mHead.next = null;
}
//开始并归,取当前小的节点放入新链表
Node curNode = null;
while(h1 != null && h2 != null){
//确定要放入新链表的当前节点
if(h1.data < h2.data){
curNode = h1;
h1 = h1.next;
curNode.next = null;//斩断与当前链表的关系
}else{
curNode = h2;
h2 = h2.next;
curNode.next = null;//斩断与当前链表的关系
}
//将节点插入新链表的尾部
mCur.next = curNode;
mCur = mCur.next;
}
if(h1 == null){
//直接将h2拼接在表尾
while(h2 != null){
curNode = h2;
h2 = h2.next;
mCur.next = curNode;
mCur = mCur.next;
curNode.next = null;//斩断与当前链表的关系
}
}else{
//h1拼接在表尾
while(h1 != null){
curNode = h1;
h1 = h1.next;
mCur.next = curNode;
mCur = mCur.next;
curNode.next = null;
}
}
return mHead;
}
/**
* 递归合并2个有序链表
* 思路:
* 先解决第一个节点,后面的节点就递归处理
* 寻找小的节点,将新链表的头节点指向小的节点,以此确定了新链表的头节点,然后头节点的下一个节点是递归处理2个剩下的链表
* @param h1
* @param h2
* @return
*/
public static Node mergeSortListRec(Node h1, Node h2){
if(h1 == null) return h2;
if(h2 == null) return h1;
Node mHead = null;
if(h1.data < h2.data){
mHead = h1;
//连接已经合并好的子问题
mHead.next = mergeSortListRec(h1.next, h2);
}else{
mHead = h2;
mHead.next = mergeSortListRec(h1, h2.next);
}
return mHead;
}
/**
* 判断链表是否有环
* 思路:快慢指针同时跑,快指针比慢指针每次快一步,如果有环,那么快指针会追上慢指针,若没有换,那么快指针会提前结束
* @param head
* @return
*/
public static boolean isCycle(Node head){
if(head == null) return false;
Node fastNode = head;
Node slowNode = head;
while(true){
fastNode = fastNode.next.next;
slowNode = slowNode.next;
if(fastNode == slowNode){
//快的追上了慢的,有环
return true;
}
if(fastNode == null || fastNode.next == null){
//快节点提前结束,没有环
return false;
}
}
}
/**
* 判断链表是否相交
* 思路:相交的话,从相交点开始所有的节点是相等的,所以它们的尾节点也必然相等
* @param h1
* @param h2
* @return
*/
public static boolean isIntersect(Node h1, Node h2){
if(h1 == null || h2 == null) return false;
Node tail1 = h1;
Node tail2 = h2;
while(tail1.next != null){
tail1 = tail1.next;
}
while(tail2.next != null){
tail2 = tail2.next;
}
if(tail1.data == tail2.data) return true;
return false;
}
/**
* 获取链表相交的第一个节点
* 思路:
* 先判断是否相交,如果相交的话,再求第一个相交点
* 第一个相交点的求法:
* 因为相交的话,从相交点开始所有节点都是相同的,那么较长的链表长出来的部分肯定不在考虑范围之内,所以较长的链表先移动到较短链表一致的起始点,然后再逐个比较
* 所以就要先求出2个链表的长度,当求第一个链表的长度的时候,记录最后一个节点的指针,目的是为了判断2个链表是否相交
* @param h1
* @param h2
* @return
*/
public static Node getCommonFirstNode(Node h1, Node h2){
if(h1 == null || h2 == null) return null;
int length1 = 1;
int length2 = 1;
Node cur1 = h1;
Node tail1 = null;
Node cur2 = h2;
while(cur1.next != null){
cur1 = cur1.next;
length1++;
if(cur1.next == null){
//记录最后一个节点,目的是为了在求长度的同时,判断2个链表是否相交
tail1 = cur1;
}
}
while(cur2.next != null){
cur2 = cur2.next;
length2++;
if(cur2.next == null){
//最后一个节点,若不相交,则返回空
if(cur2.data != tail1.data) return null;
}
}
cur1 = h1;
cur2 = h2;
if(length1 > length2){
int temp = length1 - length2;
while(temp != 0){
temp--;
cur1 = cur1.next;
}
}else{
int temp = length2 - length1;
while(temp != 0){
cur2 = cur2.next;
}
}
while(cur1 != null){
if(cur1.data == cur2.data) return cur1;
cur1 = cur1.next;
cur2 = cur2.next;
}
return null;
}
/**
* 获取环入口节点
* 思路:
* 快指针和慢指针第一次相遇的时候,相遇点到回环起始点的距离与链表头到回环起始点的距离相等
* 接下来就可以求回环点的位置了
* 慢指针指向链表头,快指针指向相遇点,然后以同样的速度移动,由于相遇点到回环起始点的距离与链表头到回环起始点的距离相等,那么快指针从相遇点走到回环起始点时,慢指针也会从链表头走到回环点,
* 也就是第二次相遇
* @param head
* @return
*/
public static Node getFirstNodeInCycel(Node head){
if(head == null) return null;
Node fast = head;
Node slow = head;
//找到相遇的点
while(!(fast == null || fast.next == null)){
fast = fast.next.next;
slow = slow.next;
if(fast == slow) break;
}
if(fast == null || fast.next == null){
//不是回环
return null;
}
//寻找回环点
slow = head;
while(slow != fast){
slow = slow.next;
fast = fast.next;
}
return slow;
}
/**
* 使用集合来来判断链表是否有环,环的入口
* 当链表回环,那么在我们循环链表的时候,肯定最终会再次遇见相同的节点,那么第一次遇上相同的节点,那么就代表该链表回环,该节点也就是回环点
* 一些java内置的工具类有提供比较2个元素是否相等,当我们存值之前,利用工具类判断该元素是否已经存在与集合中,然后决定是否存放。
* ArrayList判断2个节点是否相等是这样的:根据当前节点去求节点所在的位置 indexOf(Object obj);而这个方法是去遍历数组用equals(Object obj)去找位置,而该方法默认是使用
* Object的equals();所以只要指针一样,那么会被认为相同,所以不需要复写Node的equals方法
* 如果使用HashMap,将每个节点作为键值,只要引用相等,键值的equals和hashCode也必然相等
* 结论是:只要有提供检查是否包含某元素的方法的集合,都可以用来判断链表是否有环,环的入口
* @param head
* @return
*/
public static Node getFirstNodeInCycelList(Node head){
if(head == null) return null;
List<Node> list = new ArrayList<Node>();
Node cur = head;
while(cur != null){
if(!list.contains(cur)){
list.add(cur);
cur = cur.next;
}else{
return cur;
}
}
return cur;
}
/**
* 时间复杂度为O(1)删除指定节点
* 思路:
* 1)当不是以后一个节点,直接复制当前要删除的节点的下一个节点的值到当前要删除的节点上,然后删除下一个节点O(1)
* 2) 当是要删除最后一个节点,只能遍历链表找到删除节点的前驱节点,然后删除指定节点
* 总体为O(1)
* @param head
* @param toBeDeleted
*/
public static void delete(Node head, Node toBeDeleted){
if(head == null || toBeDeleted == null) return;
if(toBeDeleted.next != null){
//将下一个节点的数据复制到当前要删除的节点上,然后删除下一个节点即可,时间复杂度O(1)
toBeDeleted.data = toBeDeleted.next.data;
toBeDeleted.next = toBeDeleted.next.next;
return ;
}else{
//最后一个节点的情况,只能遍历链表找到删除节点的前驱即节点
Node cur = head;
while(cur.next != toBeDeleted){
cur = cur.next;
}
cur.next = cur.next.next;
return;
}
}
/**
* 删除有环的链表的指定节点
* O(1)
* @param head
*/
public static void deleteInCycle(Node head, Node del){
Node cycleStartNode = getFirstNodeInCycelList(head);
if(cycleStartNode == del.next){
//删除的节点的下一个节点是回环的起始点,较复杂
//因为回环起始点的前驱节点有2个,所以当删除回环起始点的时候,也要考虑到指向它的另一个前驱节点。所以必须找到另一个前驱节点
//因为不确定指定的删除节点是否在回环内,所以是必须2个前驱节点都要去找
//从头节点开始找
//当找到的前驱节点与当前del节点不同时,那么能确定另一个节点就是del节点
//当找到的前驱节点与当前del节点相同时,那么只能去回环里边找另一个前驱节点了
Node cur = head;
while(cur.next != cycleStartNode){
cur = cur.next;
}
if(cur != del){
//找到了另一个前驱节点就为del
//删除操作
//将回环起始点复制到del节点,然后删除回环起始点
del.data = del.next.data;
del.next = del.next.next;
//注意,此时的回环起始点应该是del,因为前边的复制行为将回环其实点往前挪了
// cur.next = cur.next.next;
cur.next = del;
}else{
//去回环里边找另一个前驱节点,这里cur == del,所以cur没用用了,
cur = cycleStartNode;
while(cur.next != cycleStartNode){
cur = cur.next;
}
//删除操作
del.data = del.next.data;
del.next = del.next.next;
// cur.next = cur.next.next;
cur.next = del;
}
}else{
//删除的是一般的节点,并且该节点肯定是有后继节点的,所以以O(1)去删除
del.data = del.next.data;
del.next = del.next.next;
return ;
}
}
}