手把手教你用java实现数据结构(六)---------(链表)算法题

1,093 阅读7分钟

如何判断单向链表是否有环?

前言:链表在开发过程中属于出现频次十分高的一种数据结构,在java中,比如我们熟知的LinkedList、HashMap底层结构、LinkedHashMap、AQS等都使用到了链表,关于单向链表有几个经典问题???

1:如何判断链表有环

2:如果有环,找出入环的节点

3:环的长度是多少?

本篇博客就围绕这三个问题来展开讨论

目录

一:如何判断单向链表有环?

二:如果有环,找出入环的节点

三:环的长度是多少?

四:完整算法代码

五:完整测试代码

六:总结

问题一:如何判断单向链表有环?

首先我们来画一个普通的单向链表和环状链表的结构图:

可以看出在环形单向链表的EFGH形成了一个环状,那么如何用程序判断它成环呢?

这里要借助一个跑道的思想:假如有一个环形的跑道,跑道上有两个人P和Q,假设P的速度是1km/10分钟,Q的速度是2km/10分钟,速度恒定不变。如果这个跑道是环型的,他们同时出发,起初Q领先,而在某一个时刻,Q终将从后面追上过P,他们两一定会相遇,而如果是直线跑道,P和Q一定不会相遇。借助于这个思想,我们可以设置快慢指针去绕着环状链表去走,如果两个指针相遇,那么它肯定是环形的。

下面是java版的实现:通过设定两个不同速度的快慢指针来遍历整个链表,如果快慢相遇,则整个链表一定有环:

程序的具体实现:

package com.baoji.lianbiao_suanfati;
/**
 * 链表节点
 * @author LinChi
 *
 */
public class Node {
	//节点的值
	private String value;
	//下一个节点
	private Node next;
	public String getValue() {
		return value;
	}
	public void setValue(String value) {
		this.value = value;
	}
	public Node getNext() {
		return next;
	}
	public void setNext(Node next) {
		this.next = next;
	}

判断链表是否有环

程序代码实现:

/**
	 * 判断链表是否循环
	 * @param sourceNode 当前节点
	 * @return  返回结果
	 */
	public static boolean hasCircle(Node sourceNode) {
		//先来判断该节点是否为null,如果为null,错误结果返回;
		if(sourceNode == null) {
			return false;
		}
		//判断该节点的下一个是否有节点,如果没有下一个节点,错误结果返回;
		if(sourceNode.next == null) {
			return false;
		}
		//慢指针
		Node slowPointer = sourceNode;
		//快指针
		Node fastPointer = sourceNode;
		while(fastPointer != null) {
			//慢指针每次走一个链表格
			slowPointer = slowPointer.next;
			//快指针每次走两个链表格
			fastPointer = fastPointer.next.next;
			//当快指针和慢指针相等时,则相遇
			if(fastPointer == slowPointer) {
				return true;
			}
		}
		return false;
	}

问题二:如果有环,找出入环的节点

假设环形链表的长度是L,相遇点在M,在相遇之后,只需要将fast指针指向开始的节点,然后和slow指针保持同一的速度遍历(相当于此时不分快慢,每个指针的每次步长为1),下一次两个节点相遇的时候就是链表的环形入口:关于此结论的数学证明:

zhuanlan.zhihu.com/p/33663488

程序代码实现:

	/**
	 * 获取入口节点  
	 * @param sourceNode  当前节点
	 * @return
	 */
	public static Node getEnterNode(Node sourceNode) {
			//先来找到相遇的节点处
			//先来判断该节点是否为null,如果为null,返回null;
				if(sourceNode == null) {
					return null;
				}
				//判断该节点的下一个是否有节点,如果没有下一个节点,返回null;
				if(sourceNode.next == null) {
					return null;
				}
				//慢指针
				Node slowPointer = sourceNode;
				//快指针
				Node fastPointer = sourceNode;
				while(fastPointer != null) {
					//慢指针每次走一个链表格
					slowPointer = slowPointer.next;
					//快指针每次走两个链表格
					fastPointer = fastPointer.next.next;
					//当快指针和慢指针相等时,则相遇,return true;
					if(fastPointer == slowPointer) {
						//当找到成环位置,立即停止
						break;
					}
				}
					//打印相遇点的值
					System.out.println("相遇点:"+fastPointer.getValue());
					//将快指针指向开始节点处
					fastPointer = sourceNode;
					while(fastPointer != null) {
						//让快节点和慢节点以同样的步长走,当下一次相遇处即为链表的环形入口
						fastPointer = fastPointer.next;
						slowPointer = slowPointer.next;
						//判断是否相遇
						if(fastPointer == slowPointer) {
							return fastPointer;
						}
					}
					return null;
				}

问题三:环的长度是多少?

这个问题比较简单,既然我们已经知道了环的入口节点,只需要新增一个指针,顺着环依次循环一遍用一个变量进行累加,每次的步长设为一,然后直到和入口节点相遇(环入口的节点位置保持不变)那么环的长度也就统计出来了:

程序代码实现:

/**
	 * 获取环的长度
	 * @param sourceNode
	 * @return
	 */
	public static int getCirCleLength(Node sourceNode) {
		if(sourceNode == null) {
			return 0;
		}
		//新增节点,让该节点从环形节点处开始计数,每次步长为1,则统计出环的长度
		final Node enterNode = getEnterNode(sourceNode);
		//定义环的下一个指针
		Node circleSecondNode = enterNode.next;
		//定义环的长度
		int length = 1;
		//当环的下一个指针不等于成环的节点
		while(circleSecondNode != enterNode) {
			//长度+1
			length++;
			//让环的下一个指针一直向下指
			circleSecondNode = circleSecondNode.next;
		}
		return length;
	}

四.完整算法代码

package com.baoji.lianbiao_suanfati;
/**
 * 链表节点
 * @author LinChi
 *
 */
public class Node {
	//节点的值
	private String value;
	//下一个节点
	private Node next;
	public String getValue() {
		return value;
	}
	public void setValue(String value) {
		this.value = value;
	}
	public Node getNext() {
		return next;
	}
	public void setNext(Node next) {
		this.next = next;
	}
	/**
	 * 判断链表是否循环
	 * @param sourceNode 当前节点
	 * @return  返回结果
	 */
	public static boolean hasCircle(Node sourceNode) {
		//先来判断该节点是否为null,如果为null,return flase;
		if(sourceNode == null) {
			return false;
		}
		//判断该节点的下一个是否有节点,如果没有下一个节点,return flase;
		if(sourceNode.next == null) {
			return false;
		}
		//慢指针
		Node slowPointer = sourceNode;
		//快指针
		Node fastPointer = sourceNode;
		while(fastPointer != null) {
			//慢指针每次走一个链表格
			slowPointer = slowPointer.next;
			//快指针每次走两个链表格
			fastPointer = fastPointer.next.next;
			//当快指针和慢指针相等时,则相遇,return true;
			if(fastPointer == slowPointer) {
				return true;
			}
		}
		return false;
	}
	/**
	 * 获取入口节点  
	 * @param sourceNode  当前节点
	 * @return
	 */
	public static Node getEnterNode(Node sourceNode) {
			//先来找到相遇的节点处
			//先来判断该节点是否为null,如果为null,返回null;
				if(sourceNode == null) {
					return null;
				}
				//判断该节点的下一个是否有节点,如果没有下一个节点,返回null;
				if(sourceNode.next == null) {
					return null;
				}
				//慢指针
				Node slowPointer = sourceNode;
				//快指针
				Node fastPointer = sourceNode;
				while(fastPointer != null) {
					//慢指针每次走一个链表格
					slowPointer = slowPointer.next;
					//快指针每次走两个链表格
					fastPointer = fastPointer.next.next;
					//当快指针和慢指针相等时,则相遇,return true;
					if(fastPointer == slowPointer) {
						//当找到成环位置,立即停止
						break;
					}
				}
					//打印相遇点的值
					System.out.println("相遇点:"+fastPointer.getValue());
					//将快指针指向开始节点处
					fastPointer = sourceNode;
					while(fastPointer != null) {
						//让快节点和慢节点以同样的步长走,当下一次相遇处即为链表的环形入口
						fastPointer = fastPointer.next;
						slowPointer = slowPointer.next;
						//判断是否相遇
						if(fastPointer == slowPointer) {
							return fastPointer;
						}
					}
					return null;
				}
	/**
	 * 获取环的长度
	 * @param sourceNode
	 * @return
	 */
	public static int getCirCleLength(Node sourceNode) {
		if(sourceNode == null) {
			return 0;
		}
		//新增节点,让该节点从环形节点处开始计数,每次步长为1,则统计出环的长度
		final Node enterNode = getEnterNode(sourceNode);
		//定义环的下一个指针
		Node circleSecondNode = enterNode.next;
		//定义环的长度
		int length = 1;
		//当环的下一个指针不等于成环的节点
		while(circleSecondNode != enterNode) {
			//长度+1
			length++;
			//让环的下一个指针一直向下指
			circleSecondNode = circleSecondNode.next;
		}
		return length;
	}
}

五.测试

我们来写一个测试方法来模拟一下上面的环状节点,然后测试一下。

测试完整代码:

package com.baoji.lianbiao_suanfati;

public class Test {
	public static void main(String[] args) {

        final Node node = new Node();
        node.setValue("A");
        final Node node2 = new Node();
        node2.setValue("B");
        final Node node3 = new Node();
        node3.setValue("C");
        final Node node4 = new Node();
        node4.setValue("D");
        final Node node5 = new Node();
        node5.setValue("E");
        final Node node6 = new Node();
        node6.setValue("F");
        final Node node7 = new Node();
        node7.setValue("G");
        final Node node8 = new Node();
        node8.setValue("H");
        node.setNext(node2);
        node2.setNext(node3);
        node3.setNext(node4);
        node4.setNext(node5);
        node5.setNext(node6);
        node6.setNext(node7);
        node7.setNext(node8);
        node8.setNext(node5);
        final boolean hasCircle = Node.hasCircle(node);
        System.out.println("是否是环形链表:"+hasCircle);

        final Node enterNode = Node.getEnterNode(node);
        System.out.println("相遇节点是:"+enterNode.getValue());

        final int cirCleLength = Node.getCirCleLength(node);
        System.out.println("环状长度:"+cirCleLength);
        
    }
}

程序输出如下:


六.总结

本次主要分析了环形链表的一些问题,并给出了示例代码,通过此篇博客可以学习到关于链表的一些东西,快慢指针的基本思想,以及如何求相遇节点和环的长度两个问题,如何用java求解,并熟悉链表这种数据结构,在实际学习中可以加深对环形链表的一些理解。


记得点赞+关注👉:本人github地址:github.com/Lmobject