算法与数据结构(三)

 第三章 数据结构

目录

 第三章 数据结构

3.1 线性表

3.1.1 顺序表

3.1.2 链表

单向链表

双向链表 

 3.1.3 顺序表与链表比较

3.1.4 链表的反转

单链表反转

3.1.5 快慢指针

中间值问题

单链表是否有环问题 

 循环链表 

 约瑟夫问题 

3.1 线性表

线性表是最基本、最常见的一种数据结结构,线性表是n个具有相同特征的数据元素的有限序列

常见场景排队 :

前驱元素

     若A元素在B元素的前面,则称A为B的前驱元素

后继元素

    若B元素在A元素的后面,则称A为B的后继元素

线性表特性 : 数据元素之间具有1对1的逻辑关系

  1. 第一个元素没有前驱元素,称为头节点
  2. 最后一个元素没有后继元素, 称为尾节点
  3. 除了第一个元素和最后一个元素,其他元素有且仅有一个前驱和一个后继

线性表用数学语言来定:  a1 ,a2 , ..., ai-1 , ai,ai+1,..., an

 线性表的分类 : 

顺序存储结构  : 顺序表

链式存储结构 : 链表

  

顺序表存储数据时,会提前申请一整块足够大小的物理空间,然后将数据依次存储起来,存储时做到数据元素之间不留一丝缝隙。

链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。

3.1.1 顺序表

顺序表的存储元素之间地址是连续的,数组刚好是这种存储结构, 所以使用数组实现顺序表

顺序表存储数据之前,除了要申请足够大小的物理空间之外,为了方便后期使用表中的数据,顺序表还需要实时记录以下 2 项数据:

  1. 顺序表申请的存储容量;
  2. 顺序表的长度,也就是表中存储数据元素的个数;

顺序表的API设计

类名SequenceList<T>
构造方法SequenceList(int capacity) : 创建容量为capacity的SequeceList对象
成员方法
  1. public void clear() :  置空线性表
  2. public boolean isEmpty() : 判断线性表是否为空, 为空返回true ,否返回false
  3. pubilc int length(): 获取线性表中元素的个数
  4. public T get(int i) : 获取线性表中索引i的元素
  5. public void insert (int i,T t) : 在索引i的元素素之前插入t元素
  6. public void insert (T t):线性表中添加一个元素,在数组末端
  7. public T remove(int i) :删除线性表中索引i的元素,并返回删除元素
  8. public int indexOf(Tt):返回线性表中首次出现t元素的位置索引,若不存在返回-1
成员变量
  1. private T[]  eles;  记录线性表的元素
  2. private int N: 记录线性表的个数

代码实现 :

/**
 * 数组实现 顺序表
 * 实现Iterable可以通过这种方式迭代 for(ele:eles)
 *
 * 支持扩容: 增加元素-扩容, 删除元素-缩容
 *    扩容,增加数据之前判断,容量不够,创建新数组是原来数组的2倍,同时copy原来数组数据
 *    缩容,移除数据之后,如果长度<容量的1/4,就创建容量的一半缩容数组,将原来的数据copy过来
 *
 * 时间复杂度
 *  get方法指定索引,复杂度O(1)
 *  insert 插入,如果插入是第一个元素,就要全部移动,复杂度是O(n)
 *  remove 删除复杂都也是O(n)
 *  扩缩容 : 如果插入某个元素的时候触发了扩缩容,这个时候插入数据前需要申请内存,消耗的时间就不是线性的了,会突然消耗很多时间;
 * @param <T>
 */
public class SequenceList<T>  implements Iterable<T>{

    private T[] eles;
    private int N;

    public SequenceList(int capacity) {
       this.eles = (T[]) new Object[capacity];
       this.N = 0;
    }

    /**
     * 清空线性表
     */
    public void clear() {
        this.N = 0;
    }

    /**
     * 判断线性表是否为空
     * @return
     */
    public boolean isEmpty() {
       return  this.N == 0;
    }

    /**
     * 返回线性表长度
     * @return
     */
    public int length() {
        return this.N;
    }

    /**
     * 返回第i个元素
     * @param i
     * @return
     */
    public T get(int i) {
        if (i>N || i<0 ) return null;
        return eles[i];
    }

    /**
     * 插入一个元素,默认是线性表的最后面
     * @param t
     */
    public void insert(T t) {
        if (N == eles.length) {
            resize(2* eles.length);
        }
        eles[N++] = t;
    }

    /**
     * 第i个元素前插入,就是ele[i]
     * @param i
     * @param t
     */
    public void insert(int i, T t) {
        if (N == eles.length) {
            resize(2* eles.length);
        }
        //i处元素和后面元素都向后移
        for (int index = N; index >i; index--) {
            eles[index]=eles[index-1];
        }
        //赋值
        eles[i] =t;
        N++;
    }

    /***
     * 删除元素,
     * @param i
     * @return
     */
    public T remove(int i) {


        T t = eles[i];
        for (int index = i; index < N ; index++) {
            eles[index] = eles[index+1];
        }
        N--;

        if (N < eles.length/4){
            resize(eles.length/2);
        }
        return t;
    }

    /**
     * 返回线性表中首次出现t元素的位置索引,若不存在返回-1
     * @param t
     * @return
     */
    public int indexOf(T t) {
        for (int i = 0; i < eles.length; i++) {
            t.equals(eles[i]);
            return i;
        }
        return -1;
    }

    /**
     *  根据newsize重新更新数组
     * @param newSize
     */
    public void  resize(int newSize) {
        //定义临时数组,指向原数组
        T[]  temps = eles;
        //创建新数组
        eles = (T[]) new Object[newSize];
        //把原来数组的输copy到新数组
        for (int i = 0; i < N; i++) {
            eles[i] = temps[i];
        }
    }


    /**
     * 迭代返回
     * @return
     */
    public Iterator<T> iterator() {

        return new SIterator();
    }

    /**
     * 内部类实现
     *
     */
    private class SIterator implements Iterator {

        private int cursor;

        public SIterator() {
            this.cursor = 0;
        }

        public boolean hasNext() {
            return cursor < N;
        }

        public T next() {
            return eles[cursor++];
        }

        public void remove() {

        }

    }
}

 Java的ArrayList 就是顺序表的实现,底层也是通过数组实现的而且提供了自动扩容方法;

  •   为什么自己要实现?

   Java本身的顺序表的实现是考虑各种情况, 代码也是比较多,比较通用, 但是也比较臃肿, 在实际过程中,有可能ArrayList不能 ,可以通过自己的实现的顺序表,帮助解决特殊问题。自己写的代码不一定就比ArrayList差。

3.1.2 链表

 链表中每个数据的存储都由以下两部分组成:

  1. 数据元素本身,其所在的区域称为数据域;
  2. 指向直接后继元素的指针,所在的区域称为指针域;

 链表的存储结构在链表中称为节点。也就是说,链表实际存储的是一个一个的节点,真正的数据元素包含在这些节点中,如图 4 所示:

头节点,头指针和首元节点

其实,图 4 所示的链表结构并不完整。一个完整的链表需要由以下几部分构成:

  1. 头指针:一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;
  2. 节点:链表中的节点又细分为头节点、首元节点和其他节点:
    • 头节点:其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题;
    • 首元节点:由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。首元节点只是对链表中第一个存有数据节点的一个称谓,没有实际意义;
    • 其他节点:链表中其他的节点;

因此,一个存储 {1,2,3} 的完整链表结构如图 5 所示:


完整的链表示意图
                                     图 5 完整的链表示意图

注意:链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点:

 链表的插入

链表中插入元素的 3 种情况示意图

链表的删除:

  链表删除元素示意图链表删除元素示意图

 结点的类设计

类名Node<T>   结点类
构造方法Node(T t,Node next) : 创建node对象
成员方法

T item   存储的数据

Node next ; 指向下一个结点

代码实现

public class Node<T> {

    public   T t;
    public Node next;

    public Node(T t, Node next) {
        this.t = t;
        this.next = next;
    }
    
}

 简单测试  : 构建简单链表

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

        //构建结点
        Node<Integer> first = new Node<Integer>(12,null);
        Node<Integer> second = new Node<Integer>(13,null);
        Node<Integer> third = new Node<Integer>(14,null);
        Node<Integer> fourth = new Node<Integer>(15,null);
        Node<Integer> fifth = new Node<Integer>(15,null);

        //生成链表
        first.next =second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
    }
}

单向链表

 单向链表的API

类名LinkList<T>
构造方法LinkList() : 创建LinkList对象
成员方法
  1. public void clear() :  置空链表
  2. public boolean isEmpty() : 判断链表是否为空, 为空返回true ,否返回false
  3. pubilc int length(): 获取链表中元素的个数
  4. public T get(int i) : 获取链表中第i个元素
  5. public void insert (int i,T t) : 在链表 第i个元素之后插入t元素
  6. public void insert (T t):往链表中添加一个元素
  7. public T remove(int i) :删除链表中第i个元素,并返回删除元素
  8. public int indexOf(Tt):返回线性中首次出现t元素的位置索引,若不存在返回-1
成员变量
  1. private Node  head;  记录首结点
  2. private int N: 记链表的长度
成员内部类 private class Node<T>  结点类 

代码实现:

/**
 * 时间复杂度
 *  get 方法指定索引,复杂度O(n)
 *  insert 插入,如果插入是第一个元素,就要全部移动,复杂度是O(n)
 *  remove 删除复杂都也是O(n)
 */
public class LinkList<T> implements Iterable<T> {

    private Node head;
    private int N;

    public LinkList() {

        this.head = new Node(null,null);
        this.N = 0;

    }

    public void clear() {
        this.head.next =  null;
        this.N = 0 ;
    }

    public int length() {
        return this.N;
    }

    public boolean isEmpty() {
        return this.N == 0 ;
    }

    /**
     * 获取指定位置i的元素
     * @param i
     * @return
     */
    public T get(int i) {

        Node node = head.next;

        for (int j = 0; j < i; j++) {
            node = node.next;
        }
        return node.t;
    }

    /**
     * 添加一个元素,默认链表最后
     * @param t
     */
    public void insert(T t) {

        Node newNode = new Node(t, null);

        Node node = head;
        while (node.next != null) {
            node = node.next;
        }
        node.next = newNode;
        N++;
    }

    /**
     * 指定位置,添加一个元素
     * @param t
     */
    public void insert(int i,T t) {

        //前面的元素
        Node prior = head;
        for (int j = 0; j < i ; j++) {
            prior  = prior.next;
        }

        //后面的元素
        Node next = prior.next;

        //建立联系
        Node newNode = new Node(t,next);
        prior.next = newNode;
        N++;
    }

    /**
     * 删除i元素
     * @param i
     * @return
     */
    public T remove(int i) {

        //前面的元素
        Node prior = head;
        for (int j = 0; j < i ; j++) {
            prior  = prior.next;
        }

        //要删除的元素
        Node current = prior.next;

        prior.next = current.next;

        N--;

        return current.t;
    }

    /**
     * 返回T的位置i
     * @param t
     * @return
     */
    public int  indexOf(T t) {
        Node node = head.next;
        for (int i = 0; node.next != null ; i++) {
          if (t.equals(node.t)) return i;
          node = node.next;
        }
        return -1;
    }



    private class Node {

        public T t;
        public Node next;

        public Node(T t, Node next) {
            this.t = t;
            this.next = next;
        }
    }

    public Iterator<T> iterator() {
        return new LinkIterator();
    }

    private class LinkIterator implements Iterator {

        private Node node;


        public LinkIterator() {
            this.node = head;
        }

        public boolean hasNext() {
            return node.next != null ;
        }

        public T next() {
            node = node.next;
            return node.t;
        }

        public void remove() {

        }
    }
}

测试代码

public class TestLinkList {

    public static void main(String[] args) {
        LinkList<Integer> integerLinkList = new LinkList<Integer>();
        integerLinkList.insert(100);
        integerLinkList.insert(200);
        integerLinkList.insert(300);
        for (int i = 0; i < integerLinkList.length(); i++) {
            System.out.println(integerLinkList.get(i));
        }
        integerLinkList.insert(1,250);
        System.out.println("---------insert-------");
        for (int i = 0; i < integerLinkList.length(); i++) {
            System.out.println(integerLinkList.get(i));
        }
        integerLinkList.remove(2);
        System.out.println("--------remove--------");
        for (int i = 0; i < integerLinkList.length(); i++) {
            System.out.println(integerLinkList.get(i));
        }
        System.out.println(integerLinkList.indexOf(100));
        System.out.println("--------遍历--------");
        for (Integer integer : integerLinkList) {
            System.out.println(integer);
        }
    }
}

双向链表 

单链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,某场景中需要大量地查找某结点的前趋结点,这种情况下使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,"从后往前" 找并不是它的强项。

对于逆向查找(从后往前)相关的问题,双向链表,会更加事半功倍。

双向链表结构示意图:

双向链表结构示意图

双向链表结点结构

双向链表的节点构成

结点类设计  

类名Node<T>   结点类
构造方法Node(T t,Nnode prior, Node next) : 创建node对象
成员方法

T item   存储的数据

Node prior : 指向上一个结点

Node next ; 指向下一个结点

 双向链表的API设计 :

 TwoWayLinkList

类名TwoWayLinkList<T>  
构造方法TwoWayLinkList() : 创建LinkList对象
成员方法
  1. public void clear() :  置空链表
  2. public boolean isEmpty() : 判断链表是否为空, 为空返回true ,否返回false
  3. pubilc int length(): 获取链表中元素的个数
  4. public T get(int i) : 获取链表中第i个元素
  5. public void insert (int i,T t) : 在链表 第i个元素之后插入t元素
  6. public void insert (T t):往链表中添加一个元素
  7. public T remove(int i) :删除链表中第i个元素,并返回删除元素
  8. public int indexOf(Tt):返回线性中首次出现t元素的位置索引,若不存在返回-1
  9. public T getFirst(): 获取第一个元素
  10. public T getLast ():获取最后一个元素
成员变量
  1. private Node  head;  记录首结点
  2. privat Node last; 记录尾结点
  3. private int N: 记链表的长度
成员内部类 private class Node<T>  结点类 

 代码实现 : 

public class TwoWayLinkList<T>  implements Iterable<T> {

     private Node head;
     private Node last;
     private int N;

    public TwoWayLinkList() {
        this.head = new Node(null,null,null);
        this.last = null;
        this.N = 0;
    }

    /**
     * 清空链表
     */
    public void clear() {
        this.head.next =  null;
        this.last.prior = null;
        this.N = 0 ;
    }

    public int length() {
        return this.N;
    }

    public boolean isEmpty() {
        return this.N == 0 ;
    }

    /**
     * 获取指定位置i的元素
     * @param i
     * @return
     */
    public T get(int i) {

        Node node = head;
        for (int j = 0; j < i; j++) {
            node=node.next;
        }
        return node.next.t;
    }

    /**
     * 获取第一个元素
     * @return
     */
    public T getFirst() {
        if (isEmpty()) return null;
        return head.next.t;
    }

    /**
     * 获取最后一个元素
     * @return
     */
    public T getLast() {
        if (isEmpty()) return null;
        return last.t;

    }


    /**
     * 添加一个元素,默认链表最后
     * @param t
     */
    public void insert(T t) {

        Node newNode;
        if(isEmpty()) {
            newNode = new Node(t, head, null);
            head.next = newNode;
        } else {
            newNode = new Node(t, last, null);
            last.next = newNode;
        }
        last = newNode;
        N++;
    }

    /**
     * 指定位置添加一个元素
     * @param t
     */
    public void insert(int i,T t) {

        if (i>length() || i< 0) {
            return;
        }

        //找到上一个结点
        Node prior = head;
        for (int j = 0; j < i; j++) {
            prior = prior.next;
        }

        //下一个结点
        Node next = prior.next;

        //当前结点
        Node current = new Node(t,prior,next);

        //更新上一个结点的next
        prior.next = current;

        //更新下一个结点的prior
        next.prior = current;

        N++;
    }

    /**
     * 删除指定位置的结点,返回删除的结点
     * @param i
     * @return
     */
    public T remove(int i) {

        //找到上一个结点
        Node prior = head;
        for (int j = 0; j < i; j++) {
            prior = prior.next;
        }

        //要删除的结点
        Node current= prior.next;

        //要删除结点的下一个结点
        Node next = current.next;

        //要删除的上一个结点的next指向next
        prior.next = next;
        //要删除的下一个结点的prior指向prior
        next.prior = prior;

        N--;

        return current.t;
    }


    /**
     * 返回T的位置i
     * @param t
     * @return
     */
    public int  indexOf(T t) {
        Node node = head.next;
        for (int i = 0; node.next != null; i++) {
            if (t.equals(node.t)) return i;
            node = node.next;
        }
        return -1;
    }


    private class Node {

        public T t;
        public Node prior;
        public Node  next;

        public Node(T t, Node prior, Node next) {
            this.t = t;
            this.prior = prior;
            this.next = next;
        }
    }

    /**
     * 提供遍历
     * @return
     */

    public Iterator<T> iterator() {
        return new TwoLinkIterator();
    }

    public class TwoLinkIterator implements Iterator{

        private Node node;

        public TwoLinkIterator() {
            this.node = head;
        }

        public boolean hasNext() {
            return  node.next !=null;
        }

        public T next() {
            node = node.next;
            return node.t;
        }

        public void remove() {

        }
    }
}

测试代码:

public class TestTwoLinkList {

    public static void main(String[] args) {
        TwoWayLinkList<Integer> twoLinkList = new TwoWayLinkList<Integer>();
        twoLinkList.insert(100);
        twoLinkList.insert(200);
        twoLinkList.insert(300);
        for (int i = 0; i < twoLinkList.length(); i++) {
            System.out.println(twoLinkList.get(i));
        }
        twoLinkList.insert(0,250);
        System.out.println("---------insert-------");
        for (int i = 0; i < twoLinkList.length(); i++) {
            System.out.println(twoLinkList.get(i));
        }

        Integer remove = twoLinkList.remove(1);
        System.out.println("---------remove-------" + remove);
        for (int i = 0; i < twoLinkList.length(); i++) {
            System.out.println(twoLinkList.get(i));
        }
        System.out.println("---------indexOf-------");
        System.out.println(twoLinkList.indexOf(200));
        System.out.println(twoLinkList.indexOf(100));

        System.out.println("--------遍历--------");
        for (Integer integer : twoLinkList) {
            System.out.println(integer);
        }

        System.out.println("--------first last--------");
        System.out.println(twoLinkList.getFirst());
        System.out.println(twoLinkList.getLast());
    }
}

 3.1.3 顺序表与链表比较

时间复杂度比较 :

数据结构insert时间复杂度get时间复杂度remove时间复杂度
顺序表O(n)O(1)O(n)
链表O(n)O(n)O(n)

结论: 顺序表获取元素是最快,那是因为顺序表的存储地址是连续的,通过索引一次可以获取到需要的元素。如果数据查询比较多,使用顺序表比较好。相对而言,顺序表的插入和删除性能不好。而链表的插入和删除性能更好;

我们都应该听过说,链表的插入和删除是比顺序表性能好,但是他们的时间复杂度都是O(n) ,为什么说链表更好 ?

原因 :链表指定位置,插入也要一个一个开始找位置,n个元素插入,最坏情况每次插入最后一个,也是要找n次,但是链表的n次操作主要在循环比较找位置,没有发生数据交换,而顺序表最坏的情况,就是每次插入都是第0个元素,其他所有元素后移,n次操作主要是n次移动数据,移动数据有内存开销,内存开销影响性能,n越来越大,数组需要的内存空间更大,如果遇到需要扩容性能更差,虽然复杂度都是O(n),n代表的含义不太一样,所以链表的性能更好,删除也是同样道理;

3.1.4 链表的反转

单链表反转

API设计:  

public void  reverse(); 整个链表反转

public Node reverse(Node node) ; 反转指定结点,A->B, 反转为B->A, 这里反转是指下一个元素反转入参是A,返回是B

   /**
     * 反转整个链表
     * 这里是递归调用的,不太好理解
     */
    public void reverse() {
        if (isEmpty() || length() == 1) {
            return;
        }
        reverse(head.next);
    }

    /**
     * 反转链表,指定结点反转,返回值是反转之后的上一个结点
     * @param current
     * @return
     */
    public Node reverse(Node current) {
        if (current.next == null) {
            head.next = current;
            return current;
        }
        //递归调用,返回值是链表反转后的当前结点的上一个结点
        Node prior =  reverse(current.next);

        prior.next = current;
        //当前结点的下一个结点为null
        current.next = null;
        //返回上一个结点
        return current;
    }

3.1.5 快慢指针

定义两个指针,这两个指针的移动速度一快一慢,以此可以制造出自己想要的差值,这个差值可以让我们找到链表上相应的结点,一般情况下,快指针的移动步长为慢指针的两倍。 

中间值问题

    快指针移动两次, 慢指针移动一次, 快指针结束时,慢指针指向中间值;

public class FastSlow {

    public static void main(String[] args) {
        //构建结点
        Node<Integer> first = new Node<Integer>(12,null);
        Node<Integer> second = new Node<Integer>(13,null);
        Node<Integer> third = new Node<Integer>(14,null);
        Node<Integer> fourth = new Node<Integer>(15,null);
        Node<Integer> fifth = new Node<Integer>(16,null);
        Node<Integer> six = new Node<Integer>(17,null);

        //生成链表
        first.next =second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;


        //中间值
        Integer mid= getMid(first);
        System.out.println(mid);
        
    }

    /**
     * 快慢指针解决中间值问题: 
     * 1 2 3 4 5 返回中间值3
     * 1 2 3 4 5 6 返回 4
     * @param first
     * @return
     */
    private static Integer getMid(Node<Integer> first) {
        //定义两个指针
        Node<Integer> fast = first;
        Node<Integer> slow = first;
        while(fast !=null && fast.next != null ) {
            fast = fast.next.next;
            slow = slow.next;
        }
        return slow.t;
    }
}

单链表是否有环问题 

有环链表 : 快指针会和慢指针相遇

    分析过程 :

public class CheckCircleLink {

    public static void main(String[] args) {
        //构建结点
        Node<Integer> first = new Node<Integer>(12,null);
        Node<Integer> second = new Node<Integer>(13,null);
        Node<Integer> third = new Node<Integer>(14,null);
        Node<Integer> fourth = new Node<Integer>(15,null);
        Node<Integer> fifth = new Node<Integer>(16,null);
        Node<Integer> six = new Node<Integer>(17,null);

        //生成链表
        first.next =second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;

        //加环
        six.next = fourth;

        //检查是否有环
        System.out.println(isCircle(first));

    }

    /**
     * 快慢指针判断是否有环
     * @param first
     * @return
     */
    private static boolean isCircle(Node<Integer> first) {
        //定义两个指针
        Node<Integer> fast = first;
        Node<Integer> slow = first;

        //如果两个指针指向对象一样就是有环
        while (fast !=null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (slow.equals(fast)) return true;
        }
        return false;
    }
}

环的入口问题:

 当快慢指针相遇的时候,确定链表有环, 此时重新设定新指针直指向链表的起点,且步长与慢指针一样为1, 则慢指针与新指针,相遇的地方就是环的入口。 为什么呢?证明需要数学知识,不太会。 

实际过程,看下图:

代码实现:

  /**
     * 查找环入口
     * @param first
     * @return
     */
    private static Node getEntrance (Node<Integer> first) {
        //定义两个指针
        Node<Integer> fast = first;
        Node<Integer> slow = first;
        Node<Integer> temp = null;

        //如果两个指针指向对象一样就是有环
        while (fast !=null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (temp !=null) {
                temp = temp.next;
                if (temp.equals(slow)) {
                    break;
                }
            }
            if (slow.equals(fast)) {
                temp = first;
                continue;
            }
        }
        return temp;
    }

 循环链表 

构建循环链表 : 头指向尾

    public static void main(String[] args) {
        //构建结点
        Node<Integer> first = new Node<Integer>(12,null);
        Node<Integer> second = new Node<Integer>(13,null);
        Node<Integer> third = new Node<Integer>(14,null);
        Node<Integer> fourth = new Node<Integer>(15,null);
        Node<Integer> fifth = new Node<Integer>(16,null);
        Node<Integer> six = new Node<Integer>(17,null);

        //生成链表
        first.next =second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;

        //加环
        six.next = first;
}

 约瑟夫问题 

 据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。

17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。

构建循环列表 : 

  1. 用41个结点,分别存储41个人
  2. 使用计数器count,记录当前报数的值
  3. 遍历链表,每次count++
  4. 判断count的值,如果是3,则从链表中删除这个结点,并打印,count重置为0
public class JosephusProblem {

    public static void main(String[] args) {

        //构建循环链表,存储41个数字
        Node<Integer> next = null;
        Node<Integer> first = null;
        Node<Integer> end = null;
        for (int i = 41; i > 0 ; i--) {
            Node<Integer>  node = new Node<Integer>(i,next);
            if ( i == 41) end = node;
            next = node;
        }
        first = next;
        end.next = first;



        //计数器
        int count=0;
        //当前结点
        Node current = first;
        //当前结点的上一个结点
        Node prior = null;

        //循环退出条件是自己指向自己
        while (current != current.next) {
            //模拟报数
            count++;
            //判断报数是不是3
            if (count == 3) {
                //如果是3就要删除当前结点,打印 重置count
                prior.next = current.next;
                System.out.print(current.t + " ");
                count = 0;
                current= current.next;
            }else {
                //将当前结点给上一个结点
                 prior = current;
                //当前结点下移动
                current = current.next;
            }
        }
        //打印最一个人
        System.out.println(current.t);
    }
}

/**
 输出结果
 3 6 9 12 15 18 21 24 27 30 33 36 39 1 5 10 14 19 23 28 32 37 41 7 13 20 26 34 40 8 17 29 38 11 25 2 22 4 35 16 31 
 */

   

 

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值