链表——从0实现一个单向循环链表 & 并且实现约瑟夫问题

目录

一、什么是约瑟夫问题(Josephus Problem)?
二、从0手写一个单向循环链表
三、实现约瑟夫问题

一、什么是约瑟夫问题(Josephus Problem)?

  • 百度百科-约瑟夫问题
  • 以下图为例, 箭头从1开始循环数数, 具体数到几自己规定, 下图为例从1数到3, 然后3淘汰, 将箭头指向被淘汰数的下一个, 然后继续数, 淘汰6, 以此类推…
    在这里插入图片描述

上图淘汰顺序为: 3 6 1 5 2 8 4 7

二、从0手写一个单向循环链表

  • 该实现直接使用单向循环链表的内容, 建议从链表开始看;
    在这里插入图片描述
    下面代码为单向循环链表的具体实现
1、自定义List接口实现
package com.zy;

/**
 * Description: 定义动态数组和链表的公共接口
 *
 * @author guizy
 * @date 2020/3/19 21:50
 */
public interface List<E> {

    /**
     * 找不到元素返回-1
     */
    // 默认就是 public static final修饰
    int ELEMENT_NOT_FOUNT = -1;

    /**
     * 清除所有元素
     */
    void clear();

    /**
     * 元素的数量
     *
     * @return
     */
    int size();

    /**
     * 是否为空
     *
     * @return
     */
    boolean inEmpty();

    /**
     * 是否包含某个元素
     *
     * @param element
     * @return
     */
    boolean contains(E element);

    /**
     * 添加元素到尾部
     *
     * @param element
     */
    void add(E element);

    /**
     * 获取index位置的元素
     *
     * @param index
     * @return
     */
    E get(int index);

    /**
     * 设置index位置的元素
     *
     * @param index
     * @param element
     * @return 原来的元素
     */
    E set(int index, E element);

    /**
     * 在index位置插入一个元素
     *
     * @param index
     * @param element
     */
    void add(int index, E element);


    /**
     * 删除index位置的元素
     *
     * @param index
     * @return 被删除的元素
     */
    E remove(int index);

    /**
     * 删除传入的元素
     *
     * @param element
     */
    void remove(E element);

    /**
     * 查看元素的索引
     *
     * @param element
     * @return
     */
    int indexOf(E element);
}
2、自定义AbstractList实现上面的List
package com.zy;

/**
 * Description: 该类不对外公开,只是为了抽取一些公共的代码
 *
 * @author guizy
 * @date 2020/3/19 22:13
 */
/*
        抽象类是不能被实例化的,所以没有要求实现所有的方法。但是没写出的方法还是隐式的存在的。
    当你在定义一个非abstract类继承那个类的话,就一定要全部实现了。
 */
public abstract class AbstractList<E> implements List<E> {

    /**
     * 元素的数量
     */
    protected int size;

    /**
     * 元素的数量
     *
     * @return
     */
    public int size() {
        return size;
    }

    /**
     * 是否为空
     *
     * @return
     */
    public boolean inEmpty() {
        return size == 0;
    }

    /**
     * 是否包含某个元素
     *
     * @param element
     * @return
     */
    public boolean contains(E element) {
        // 如果element元素可以找到
        return indexOf(element) != ELEMENT_NOT_FOUNT;
    }

    /**
     * 添加元素到尾部
     *
     * @param element
     */
    public void add(E element) {
        // elements[size++] = element;
        // 传入数组数量(相当于在最后插入元素)
        add(size, element);
    }

    /**
     * 封装数组越界异常
     *
     * @param index
     */
    protected void indexOutOfBounds(int index) {
        throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
    }

    /**
     * 检查get,remove传入的index是否有效
     *
     * @param index
     */
    protected void rangeCheck(int index) {
        if (index < 0 || index >= size) {
            indexOutOfBounds(index);
        }
    }

    /**
     * 根据index插入元素时,判断index是否有效
     *
     * @param index
     */
    protected void rangeCheckForAdd(int index) {
        if (index < 0 || index > size) {
            indexOutOfBounds(index);
        }
    }
}
3、SingleCircleLinkedList继承AbstractList
package com.zy.single;

import com.zy.AbstractList;

/**
 * Description: 单向循环链表实现
 *
 * @author guizy
 * @date 2020/3/19 21:22
 */
public class SingleCircleLinkedList<E> extends AbstractList<E> {

    /*
        单向循环链表, 相对于单链表; 我们只需要更高 add、remove方法即可!
     */

    /**
     * 指向第一个结点
     */
    protected Node<E> first;


    /**
     * 结点类;为链表的内部类
     *
     * @param <E>
     */
    protected static class Node<E> {

        //存储元素的信息
        E element;

        //指向下一个结点
        Node<E> next;

        // 结点构造器
        public Node(E element, Node<E> next) {
            this.element = element;
            this.next = next;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append(element).append("_").append(next.element);
            return sb.toString();
        }
    }

    @Override
    public void clear() {
        size = 0;
        first = null;
    }

    @Override
    public E get(int index) {
        // 通过node方法找到结点,通过结点的element来获取元素
        return node(index).element;
    }

    @Override
    public E set(int index, E element) {
        // 获取index处的结点
        Node<E> node = node(index);
        E oldElement = node.element;
        node.element = element;
        // 返回的是没覆盖之前的结点元素信息
        return oldElement;
    }

    @Override
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        // 当index传0的时候,就会报异常,此时要特殊处理
        if (index == 0) {
            Node<E> newFirst = new Node<>(element, first);

            // 拿到最后一个结点; 判断只有一个结点的情况
            Node<E> last = (size == 0) ? newFirst : node(size - 1);
            // 最后一个结点的next指向头结点
            last.next = newFirst;
            first = newFirst;

        } else {
            // 在index处插入元素,首先要找到它前面的结点
            Node<E> preNode = node(index - 1);
            // 让前面的结点的next指向新创建的结点,新创建的结点指向index处的结点即可
            // 此时index处的结点,就是它前面的结点的next,就是指向index处的结点
            Node<E> newNode = new Node<>(element, preNode.next);
            // 前面的结点的next再指向新的结点
            preNode.next = newNode;
        }
        size++;
    }

    @Override
    /**
     * 返回的是被删除元素的element
     */
    public E remove(int index) {
        // 假如被删除的结点为第一个结点
        Node<E> node = first;
        if (index == 0) {
            if (size == 1) { // 链表中只有一个元素的时候
                first = null;
            } else {
                Node<E> last = node(size - 1);
                // first已经指向了第一个结点,如果要删除第一个结点,此时要将first指向第一个结点的next结点
                first = first.next;
                last.next = first; // 最后一个结点指向新的第一个结点
            }
        } else {
            Node<E> preNode = node(index - 1);
            node = preNode.next; // 这个node就是当前被删除的结点
            //preNode.next = preNode.next.next;
            preNode.next = node.next;
        }
        size--;
        return node.element;
    }

    @Override
    public void remove(E element) {
        remove(indexOf(element));
    }

    @Override
    /**
     * element是保存在node里的,传入element,返回该结点的索引
     */
    public int indexOf(E element) {
        Node<E> node = first;
        if (element == null) {
            // 因为element都在node中,所以通过next来遍历node
            for (int i = 0; i < size; i++) {
                if (node.element == null) return i;
                node = node.next;
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (element.equals(node.element)) return i;
                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUNT;
    }

    /**
     * 传入一个index,返回该index位置的结点对象
     *
     * @param index
     * @return
     */
    private Node<E> node(int index) {
        // 当传入非法index就会报异常
        rangeCheck(index);
        /*
            通过first去寻找结点,看需要next几次,找到该结点;
            可以发现,next的次数和index有关系,index为几,就需要next几次
         */
        Node<E> node = first;
        for (int i = 0; i < index; i++) {
            node = node.next;
        }
        return node;
    }

    @Override
    public String toString() {
        // 打印格式: size=3, [10, 20, 30]
        // 使用StringBuilder 效率高一些
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            string.append(node.toString());
            if (i != size - 1) {
                string.append(", ");
            }
            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
}

三、实现约瑟夫问题

在上面单向循环链表的实现基础上, 增加1个成员变量, 3个方法

在这里插入图片描述

在这里插入图片描述
核心实现:

  /**
   * 指向某一节点
   */
  private Node<E> current;
  

  /**
   * 让current节点重置到first节点
   */
  public void reset() {
      current = first;
  }

  /**
   * 移动current节点, 指向下一个节点
   * @return
   */
  public E next() {
      if (current == null) return null;

      current = current.next;
      return current.element;
  }

  /**
   * 删除current指向的节点, 删除成功后, 指向下一个节点
   * @return 被删除节点的element
   */
  public E remove() {
      // 当current没有指向first的时候, 初始化为null
      if (current == null) return null;

      // 删除之前要拿到其下一个节点; 如果只有一个节点的时候, current.next还是current本身
      Node<E> next = current.next;
      // 删除当前current所指向的节点
      E element = remove(indexOf(current.element));
      // 当只有一个节点的时候, current.next指向的还是它本身(因为是循环链表), 所以current一直被引用着, 没有被销毁
      if (size == 0) {
          current = null;
      } else {
          // 如果只有一个节点了, current指向的就是它自己, 它就不会被销毁, 所以上面做处理
          current = next; // 删除后, current指向它下一个
      }
      return element;
  }

测试:

 static void testJosephusProblem() {
     SingleCircleLinkedList<Integer> linkedList = new SingleCircleLinkedList<>();
     for (int i = 1; i <= 8; i++) {
         linkedList.add(i);
     }

     //指向头结点
     linkedList.reset();

     while (!linkedList.inEmpty()) {
         linkedList.next();
         linkedList.next();
         System.out.print(linkedList.remove() + " ");
     }
 }

输出:

和开篇图示顺序一致~

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

white camel

感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值