`

如何检查一个单向链表上是否有环

 
阅读更多

1, 最简单的方法, 用一个指针遍历链表, 每遇到一个节点就把他的内存地址(java中可以用object.hashcode())做为key放在一个HashMap中. 这样当HashMap中出现重复key的时候说明此链表上有环. 这个方法的时间复杂度为O(n), 空间同样为O(n)。

2, 使用反转指针的方法, 每过一个节点就把该节点的指针反向,如果单链表存在环,那么遍历指针会回到头节点,可以通过判断遍历指针来判断。这个算法的时间复杂度是O(n),空间为O(1)。但是缺点是会破坏原来的链表结构。

3、使用快慢指针追逐,快指针每次遍历前进2步,慢指针每次前进1步,如果单链表存在环,那么快指针肯定能追上慢指针,可以直接判断指针是否相同即可。这个算法的时间复杂度是O(n),空间为O(1)。这个算是最优秀的判定方法。

 

示例代码如下:

 

package com.xx.dataStructure.linklist;

import java.util.HashMap;
import java.util.Map;


//节点定义
class Node {
	public int data;
	public Node next;
	
	Node (int data,Node next){
		this.data = data;
		this.next = next;
	}
	
	@Override
	public int hashCode(){
		return data ;
	}
	
	@Override
	public boolean equals(Object o){
		if (o == null) 
			return false;
		if (this == o)
			return true;
		if (o instanceof Node){
			Node node = (Node)o;
			return this.data == node.data;
		}
		return false;
	}
}

//使用策略模式
interface IsCycle {
	boolean isCycle(Node h);
}

abstract class AbstractCycle implements IsCycle{
	protected String name ;
	
	public String getName(){
		return name;
	}
}

//hash计数法
class HashCalculate extends AbstractCycle {
	{
		name = " HashCalculate algorithm ";
	}
	
	@Override
	public boolean isCycle(Node h) {
		if (h == null || h.next == null) 
			return false;
		boolean result = false;
		Node p = h.next;
		if (p.next == null) 
			return true;
		if (p.next == p) 
			return true;
		
		//循环计数
		Map<Integer, Node> map = new HashMap<Integer,Node>(0);
		while(p != null){
			Integer hashCode = p.hashCode();
			if (!map.containsKey(hashCode)){
				map.put(hashCode, p);
			}else {
				result = true;
				break;
			}
			p = p.next;
		}
		
		return result;
	}
}

//逆序链表法
class ReverseLinkedList extends AbstractCycle {
	
	{
		name = " Reverse algorithm ";
	}
	
	@Override
	public boolean isCycle(Node h) {
		if (h == null || h.next == null) 
			return false;
		boolean result = false;
		Node p = h.next;
		Node startNode = p;
		h.next = null;
		if (p.next == p) 
			return true;
		if (p.next == null)
			return false;
		int i = 0;
		while(p != null ){
			Node cNode = h.next;
			h.next = p;
			p = p.next;
			h.next.next = cNode;
			i++;
			if (h.next == startNode && i > 1){
				result = true;
				break;
			}
		}
		return result;
	}
}

//快慢指针追逐判定法
class PointChase extends AbstractCycle {
	
	{
		name = " PointChase algorithm ";
	}
	
	@Override
	public boolean isCycle(Node h) {
		if (h == null || h.next == null) 
			return false;
		boolean result = false;
		Node p = h.next;
		if (p.next == null)
			return false;
		if (p.next == p) 
			return true;
		Node slow = p, fast = p.next;
		
		while( slow  != null && fast.next != null){
			if (slow == fast){
				result = true;
				break;
			}else {
				slow = slow.next;
				fast = fast.next.next;
			}
		}
		return result;
	}
}

public class LinkedList {
	
	static Node h1 = null;
	
	static Node h2 = new Node(-1,null);
	
	static Node h3 = new Node(-1,new Node(0,new Node(1,null)));
	
	static Node h4 = null;
	
	static Node tailNode = null;
	
	static Node cycleBeginNode = null;
	
	static {
		tailNode = new Node(99,null);
		cycleBeginNode = new Node(8,new Node(9,tailNode));
		tailNode.next = cycleBeginNode;
		h4 = new Node(-1,new Node(0,new Node(1,new Node(2,cycleBeginNode))));
	}
	
	static Node h5 = new Node(-1,new Node(0,new Node(1,new Node(2,cycleBeginNode))));
	 
	
	public static void main(String [] args){
		AbstractCycle[] methods = {
				new HashCalculate(),
				new PointChase(),
				new ReverseLinkedList()
		};
		
		for(AbstractCycle method : methods){
			System.out.println(method.getName() + ":"+  method.isCycle(h1)); //false
			System.out.println(method.getName() + ":"+  method.isCycle(h2)); //false
			System.out.println(method.getName() + ":"+  method.isCycle(h3)); //false
			System.out.println(method.getName() + ":"+  method.isCycle(h4)); //true
		}
	}
}
 扩展问题:

 

1、如果单链表有环,如何找到相交节点?
     这个问题使用快慢指针算法也非常容易,假设程序循环了N次,那么慢指针走的路程为N,快指针走的路程为2N, 由于快慢指针第一次相交,那么快指针走的路程比慢指针多环的周长,因此环的周长是N,可以在循环内部加一个计数器即可。第二步定义2个指针A,B,A从表头开始遍历,B从表头第(N)个节点开始遍历,遍历的步长均为1,那么A、B第一次相遇时的节点即为单链表的相交节点。假定相交节点到头结点的距离为X,A,B第一次在相交节点相遇的条件是A遍历的步长是X,而B距离头结点的距离是X+N,AB相遇。
2、如何解环?
    A、B指针相交后,A指针继续遍历(N-1)次,到达链表尾节点,直接把尾节点的next指针置空即可。
上一篇日志中的判断单链表是否相交的算法中,就需要先判断单链表是否有环,并解环之后才能使算法正确运行。
分享到:
评论

相关推荐

    单向循环链表实现约瑟夫环.zip

    约瑟夫环问题是一个理论上的问题,源自古罗马的一个传说,其中涉及到人们站成一个圈,然后按照一定的规则逐个剔除,直到剩下最后一个人为止。 首先,我们需要理解单向循环链表的基本构造。在链表中,每个节点包含两...

    C/C++经典约瑟夫环问题——带头结点的单向循环链表

    与普通的单向链表不同,它在第一个元素(头结点)之前额外添加了一个结点,通常这个结点不存储实际数据,而是用作链表的标记。这样做的好处是可以简化对链表的处理,比如在插入和删除操作时,不必区分头结点和普通...

    单向链表的功能

    单向链表的功能远不止这些,还包括合并两个已排序的链表、判断链表是否有环、获取链表中间节点等。理解和熟练掌握单向链表的操作对于编程学习至关重要,特别是在处理动态数据结构和优化算法效率时。通过实践和练习,...

    单向链表结点的逐个删除-C语言教程

    单向链表作为一种基础的数据结构,其插入、删除、创建和遍历操作是每个程序员必须熟练掌握的技能。本文将详细介绍如何使用C语言实现单向链表结点的逐个删除。 首先,我们要了解单向链表的基本概念。单向链表是由一...

    VC++采用单向循环链表实现约瑟夫环

    现在我们有了一个表示约瑟夫环的链表,接下来要实现报数和剔除节点的过程。可以创建一个函数,接受链表头、报数起始值和报数间隔作为参数。该函数将遍历链表,每报数到指定间隔就删除当前节点,然后继续从下一个节点...

    用单向循环链表模拟约瑟夫环

    用C语言编写的约瑟夫环问题解决程序,利用单向循环链表存储结构模拟此过程

    数据结构单向链表和双向链表

    双向链表与单向链表的主要区别在于每个节点不仅有一个指向下一个节点的指针,还有一个指向前一个节点的指针。这种结构提供了更灵活的操作,但也增加了存储需求。 1. **节点结构**:双向链表的节点包含三个字段:...

    C语言实现单向链表的创建、插入,删除节点,和2个链表合并

    总结,本篇文章介绍了C语言实现单向链表的基本操作,包括创建链表、插入节点、删除节点、判断链表是否有环以及合并两个已排序的链表。通过理解这些基本操作,可以为更复杂的数据结构和算法打下坚实的基础。在VC6.0...

    判断一个链表是否有环,并指出换的头结点(O(n)时间复杂度)

    供大家交流使用,欢迎大家交流指对。。。欢迎大家下载。。

    单向链表,双向链表示例

    双向链表则比单向链表更复杂一些,每个节点除了数据域外,还有两个指针域,分别指向它的前一个节点和后一个节点。这意味着双向链表允许我们从任一方向遍历链表,增加了灵活性,但也带来了额外的存储开销。在C语言中...

    lianbiao.rar_单向链表

    单向链表是一种基本的数据结构,它在计算机科学和编程中有着广泛的应用。与数组不同,链表中的元素不是在内存中连续存储的,而是通过指针连接起来,每个节点包含数据部分和指向下一个节点的指针部分。这种存储方式...

    数据结构:单向循环链表源码

    与普通单向链表不同,循环链表的最后一个节点的指针不是空的,而是指向链表的第一个节点,形成一个闭合的环。这种设计使得遍历链表更为方便,因为没有明显的"结束"标志。 **单向循环链表特性** 1. **无头结点**:与...

    约瑟夫环单循环链表C语言实现

    单向循环链表是一种特殊的线性表存储结构,其中每个节点包含一个指向下一个节点的指针,最后一个节点的指针指向链表的头节点,形成一个闭环。在约瑟夫环问题中,单向循环链表非常适合模拟游戏参与者围成一圈的情形。...

    单向循环链表.zip

    6. **检查元素存在**:`contains(Object o)` 检查链表是否包含特定对象。 7. **链表大小**:`size()` 返回链表中的元素数量。 8. **清空链表**:`clear()` 删除链表的所有元素。 9. **迭代器**:`iterator()` 提供一...

    单向循环链表-仿学生管理系统[详尽注释]

    与普通单向链表不同,循环链表的最后一个节点指向第一个节点,形成一个闭合的环,使得遍历更加方便。接下来我们将深入探讨这个主题。 首先,我们要理解链表的基本概念。链表不同于数组,它的元素(节点)在内存中...

    算法:给定一个链表,判断链表中是否存在环

    // 构建一个有环的链表用于测试 ListNode node1 = new ListNode(1); ListNode node2 = new ListNode(2); ListNode node3 = new ListNode(3); ListNode node4 = new ListNode(4); ListNode node5 = new ...

    C语言单向链表的基本操作12个

    9. **判断链表循环**: 使用Floyd判圈法(快慢指针)检查链表是否有环。快指针每次移动两步,慢指针每次移动一步,如果两者相遇,链表有环;若快指针到达NULL,链表无环。 10. **链表排序**: 可以使用插入排序、归并...

    两列单向链表相同值查询设计

    本篇文章将详细介绍如何设计并实现一个程序,该程序能构建两个单向链表,并根据第一个链表中的值来查询第二个链表中是否存在相同的值及其位置。该任务属于基础强化训练的一部分,涉及到的数据结构与算法知识主要包括...

    java编写的循环链表来实现约瑟夫环

    循环链表 实现约瑟夫环 java 自己写的 测试通过 有注释

Global site tag (gtag.js) - Google Analytics