Java数据结构-线性表之链表应用-检测链表是否有环

??如何检测一个链表是否有环?这个是一个出现频率较高的面试题。

??如下是一个含有环的链表。

(图片来自http://www.nowamagic.net/librarys/veda/detail/2245

一个有很多关于数据结构的文章的网站,还有其他的资料,可以看看)

我这里解题的方法有三种:

  1. 快慢指针方法:两个速度不一样的指针遍历总会相遇;
  2. 利用环的顶点数和边相等的关系;
  3. 两个指针遍历判断步数是否相等。

??为了实现检查链表是否含有环的情况,我们需要先构建出一个含有环的链表。

??于是乎我在之前源码的基础上增加了两个方法,代码如下:

/**
 * @TODO 设置成循环链表
 */
public void setLoop(){
    FOLinkedNode<E> p = new FOLinkedNode<E>();
    p=this.header;
    FOLinkedNode<E> q = new FOLinkedNode<E>();
    q = this.last(p);
    q.addNext(p);
}
/**
 * @TODO 指定某个位置来设置链表还有环
 * @param index 链表的某个位置
 */
public void setIndexLoop(int index){
    validateIndex(index);
    FOLinkedNode<E> p = new FOLinkedNode<E>();
    p=get(index);
    FOLinkedNode<E> q = new FOLinkedNode<E>();
    q=last(this.header);
    q.addNext(p);
}

1.快慢指针方法:

代码如下:

/**
 * @TODO 判断链表是否含有环(快慢指针法)
 * @return true or false
 */
public boolean hasLoop(){
    FOLinkedNode<E> p = new FOLinkedNode<E>();
    FOLinkedNode<E> q = new FOLinkedNode<E>();
    p=this.header;
    if(p!=null){
        q = p.next;
        while(p!=null && q!=null && q.next!=null){
            if(p==q || p==q.next){
                return true;
            }
            q =q.next.next;
            p = p.next;
        }
    }
    return false;
}

2.环的顶点数和边相等的关系。

??这是我想到的一种方法,由于是链表,所以我最初假设的是单链表,而我的单链表是设置了长度的,这样在进行判断是否含有环的时候可以利用长度来进行。

??将指针是否为空和长度每次遍历减1操作来进行while循环,这样如果到长度等于0的时候而指针却不为空,此时就说明此链表含有环。这利用了环的顶点数和边相等的关系。

代码如下:

/**
 * @TODO 利用环的顶点数和边数相等的关系进行判断是否含有环
 * @return true or false
 */
public boolean hasLoop2(){
    FOLinkedNode<E> temp = new FOLinkedNode<E>();
    temp = this.header.next;
    int tempSize = this.size();
    while (temp != null) {
        tempSize--;
        if(tempSize<=0){
            return true;
        }
        temp = temp.next;
    }
    return false;
}

??这个方法我其实是在打印链表的时候想到的。如果是单链表,那么打印的话只要判断指针是否为null就行;而如果是循环链表或者是含有环的链表的时候,如果要打印则会陷入死循环之中,于是乎就想到用链表的长度进行判断来打印链表。这样当需要判断链表是否含有环的时候还需要再增加一个条件,就是指针是否为空的情况,于是乎问题就有解了。

顺便贴上改进后的toString方法的代码:

@Override
public String toString() {
    return "[" + this.NodesToString(this) + "]";
}
/**
 * @param foLinkedList
 * @TODO 设置单链表的长度
 * @return 单链表的节点字符串序列
 */
private String NodesToString(FOLinkedList<E> foll) {
    StringBuffer sb = new StringBuffer();
    if (foll.header != null) {
        sb.append(foll.header.getE());
        FOLinkedNode<E> temp = new FOLinkedNode<E>();
        temp = foll.header.next;
        int tempSize = foll.size() - 1;
        while (temp != null && tempSize!=0) {
            sb.append(", " + temp.getE());
            temp = temp.next;
            tempSize--;
        }
    }
    return sb.toString();
}

3.两个指针遍历判断步数是否相等

思路:

??设两个工作指针p、q,p总是向前走,但q每次都从头开始走,对于每个节点,看p走的步数是否和q一样。比如p从A走到D,用了4步,而q则用了14步。因而步数不等,出现矛盾,存在环。

??这个方法相比于前面的两种方法就略次一些了,这里就没有进行代码编写了。

??最后贴上测试的代码,注释的代码是测试之前的代码用的,可以忽略,也可以自行尝试。

    public static void main(String[] args) {
//      FOLinkedList<String> fol = new FOLinkedList<String>();
//      for (int i = 1; i <= 8; i++) {
//          fol.add("元素"+i);
//      }
//      fol.add("元素"+2);
//      fol.add("元素"+7);
//      fol.add("元素"+2);
//      fol.add("元素"+2);
//      fol.add("元素"+3);
//      fol.add("元素"+11);
//      fol.add("元素"+1);
//      fol.add("元素"+9);
//      System.out.println(fol);
//      fol = FOLinkedList.removeRepeatElement(fol);
//      System.out.println(fol);
//      System.out.println(fol.size());
//      fol.insert( 9,"xxx");
//      System.out.println(fol);
//      System.out.println(fol.size());
//      fol.set(9,"元素9");
//      System.out.println(fol);
//      FOLinkedNode<String> e = fol.remove(6);
//      System.out.println(e);
//      System.out.println(fol);
//      System.out.println(fol.size());
//      FOLinkedList<String> newFol = new FOLinkedList<String>();
//      for (int i = 1; i <= fol.size(); i++) {
//          newFol.addFirst(fol.get(i).getE());
//      }
//      System.out.println(newFol);
//      FOLinkedList<Integer> a = new FOLinkedList<Integer>();
//      FOLinkedList<Integer> b = new FOLinkedList<Integer>();
//      for (int i = 0,j=1; i < 10; i++,j=j+2) {
//          a.add(j);
//      }
//      for (int i = 0,j=0; i < 10; i++,j=j+3) {
//          b.add(j);
//      }
//      System.out.println(a);
//      System.out.println(b);
//      FOLinkedList<Integer> c = a.merge(b);
//      System.out.println(c);

        //测试链表是否含有环
        FOLinkedList<Integer> x = new FOLinkedList<Integer>();
        for (int i = 1; i <= 10; i++) {
            x.add(i);
        }
        System.out.println(x);
        System.out.println(x.size());
        System.out.println("hasLoop2:"+x.hasLoop2());
        System.out.println("hasLoop:"+x.hasLoop());
        x.setIndexLoop(10);
        System.out.println(x);
        System.out.println(x.size());
        System.out.println("hasLoop2:"+x.hasLoop2());
        System.out.println("hasLoop:"+x.hasLoop());
    }

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-09 16:25:48

Java数据结构-线性表之链表应用-检测链表是否有环的相关文章

Java数据结构-线性表之单链表LinkedList

线性表的链式存储结构,也称之为链式表,链表:链表的存储单元可以连续也可以不连续. 链表中的节点包含数据域和指针域,数据域为存储数据元素信息的域,指针域为存储直接后继位置(一般称为指针)的域. 注意一个头结点和头指针的区别: 头指针: 指向链表的第一个节点的指针,若链表有头结点,则是指向头结点的指针: 头指针具有标识作用,所以常用头指针作为链表的名字: 不论链表是否为空,头指针都不为空: 是链表的必要元素. 头结点: 头结点是为了操作的统一和方便而设立的,放在第一个元素节点的前面,其数据域一般无意

Java数据结构-线性表之静态链表

静态链表的定义: 节点由一个一维数组和一个指针域组成,数组用来存放数据元素,而指针域里面的指针(又称游标)用来指向下一个节点的数组下标.这样的链表称之为静态链表. 链表中的数组第一个和最后一个位置需要特殊处理,不存数据.第一个位置(即数组0下标)的节点的指针用来存放备用链表的第一个节点的数组下标.最后一个位置(即数组长度MaxSize-1下标)的节点的指针用来存放指向有数值的第一个数据元素的数组下标,类似于单链表的头结点. 静态链表的示例图: 下面举一个摘抄自<大话数据结构>的例子,来解释一下

Java数据结构-线性表之栈(顺序栈和链栈)

栈的定义:(特殊的线性表) ??仅在表的一端进行插入和删除的线性表.允许插入.删除的这一端称为栈顶,另一端称为栈底.表中没有元素时称为空栈. ??被称为后进先出的线性表(Last In First Out),简称 LIFO表,或被称为先进后出的线性表(First In Last Out),简称 FILO表. ??栈更具存储方式的不同分为两种:顺序栈和链栈. 顺序栈: 和顺序表一样,顺序栈也采用数组来存放数据元素: 为了保证栈底位置的不变,采用数组下标为0的位置作为顺序栈的栈底. 而栈顶指针的最大

Java数据结构-线性表之队列

队列(Queue)的定义:只允许在一端进行插入另一端进行删除操作的线性表.允许插入的一端称为队尾(rear) ,允许删除的一端称为队头(front). 具有"先进先出"特点. 队列也是线性表,所以也存在顺序结构和链式结构. 顺序队列: 对于队列,入队操作的解释为: (是在队尾追加一个元素,不需要移动任何元素,因此时间复杂度为0(1).) 判断队列是否已满: 如果没满则先给队尾元素赋值; 然后将队尾指针后移一位(对队尾指针赋值,Q->rear = Q->rear+1 ). 出

Java数据结构-线性表之顺序表ArrayList

线性表的顺序存储结构,也称为顺序表,指用一段连续的存储单元依次存储线性表中的数据元素. 根据顺序表的特性,我们用数组来实现顺序表,下面是我通过数组实现的Java版本的顺序表. package com.phn.datestructure; /** * @author 潘海南 * @Email [email protected] * @TODO 顺序表 * @date 2015年7月16日 */ public class FOArrayList<E> { // 顺序表长度 private int

数据结构~~~线性表复习2(动态链表的合并)

// 线性表(链表).cpp: 定义控制台应用程序的入口点. // #include "stdafx.h" #include"iostream" using namespace std; template<class T> struct Node { T data; Node *next; }; template<class T> class listNode { private: Node<T> *Head; public: l

Java数据结构-线性表之栈的应用-递归及其应用

??递归函数的定义:把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数(递归函数必须有一个结束的条件,以免陷入无穷尽的递归中). 迭代和递归的区别是: ?(1).迭代使用的是循环结构,递归使用的是选择结构. ?(2).递归能使程序的结构更清晰.更简洁.更容易让人理解,从而减少读懂代码的时间.但是大量的递归调用会建立函数的副本,会耗费大量的时间和内存. ?(3).迭代则不需要反复调用函数和占用额外的内存.因此我们应该视不同情况选择不同的代码实现方式. 下面解释一下怎么使用栈

java实现数据结构-线性表-顺序表,实现插入,查找,删除,合并功能

package 顺序表; import java.util.ArrayList; import java.util.Scanner; public class OrderList { /** * @param args * @author 刘雁冰 * @2015-1-31 21:00 */ /* * (以下所谓"位置"不是从0开始的数组下标表示法,而是从1开始的表示法.) * (如12,13,14,15,16数据中,位置2上的数据即是13) * * 利用JAVA实现数据结构-线性表-顺

数据结构线性表链表的C语言实现

                                                                                      数据结构线性表链表的C语言实现      说明:线性表是一种最简单的线性结构,也是最基本的一种线性结构,所以它不仅是学习中的重点,也是应用开发非常常用的一种数据结构.它可以分为顺序表和链表.它的主要操作是数据元素的插入,删除,以及排序等.接下来,本篇文章将对线性表链表的基本操作和运用进行详细的说明(包含在源代码的注释中),并给