环形单链表其实跟普通单链表相识,只是最后一个元素会指向第一个元素;而普通的单链表最后的一个元素只是指向 null.
不过在实现的时候需要注意一下这个环形链表的遍历的退出条件,以及在插入与删除元素的时候,怎么去操作其中的节点。(与普通单链表会存在细微差别。)
然后,简单介绍一下约瑟夫问题:
问题描述
约瑟夫环问题(Joseph)又称丢手绢问题:已知 m 个人围坐成一圈,由某人起头,下一个人开始从 1 递增报数,报到数字 n 的那个人出列,他的下一个人又从 1 开始报数,数到 n 的那个人又出列;依此规律重复下去,直到 m 个人全部出列约瑟夫环结束。如果从 0 ~ (m-1) 给这 m 个人编号,请输出这 m 个人的出列顺序。
————————————————
版权声明:本文为优快云博主「李春春_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/zhongkelee/article/details/40594147
这个问题描述看起来清楚明了。直接在纸上画能很容易的求解。但是如果 n,很大的话,就不好弄了。所以,需要一个通用的解决方案。
刚好环形链表可以解决这个问题。把这 n个人当成一个环形链表,然后就是根据 k, m 找到对应的位置,去删除对应的元素,依次反复,直到最后一个元素被删除。
这里直接给出实现:
为了便于 插入与删除。定义 head 和 tail, 固定指向链表的头结点和尾结点。
import java.util.Arrays;
import java.util.StringJoiner;
/**
* 环形单链表的实现.
*/
class AroundList<Z> {
private static class Node<K> {
K data;
Node<K> next;
}
private final Node<Z> head = new Node<>();
private final Node<Z> tail = new Node<>();
private int length = 0;
public AroundList() {
head.data = null;
head.next = null;
}
public int size() {
return length;
}
public boolean isEmpty() {
return length == 0;
}
public boolean add(Z data) {
return insert(0, data);
}
public boolean addLast(Z data) {
return insert(length, data);
}
public boolean replace(int index, Z data) {
if (index < 0 || index >= length) {
return false;
}
int pos = -1;
Node<Z> h = head;
do {
if (index - 1 == pos) {
break;
}
pos++;
h = h.next;
} while (h.next != head.next);
h.next.data = data;
return true;
}
// a [k] b c d
public boolean insert(int index, Z data) {
if (index < 0 || index > length) {
return false;
}
if (length == 0) {
Node<Z> young = new Node<>();
young.data = data;
young.next = young;
head.next = young;
tail.next = young;
} else {
int pos = -1;
Node<Z> h = head;
do {
if (index - 1 == pos) {
break;
}
pos++;
h = h.next;
} while (h.next != head.next);
if (pos == length) {
return false;
}
Node<Z> old = h.next;
Node<Z> young = new Node<>();
young.data = data;
young.next = old;
h.next = young;
if (pos == -1) {
// let tail.next == young
tail.next.next = young;
} else if (pos == length - 1) {
tail.next = young;
}
}
length++;
return true;
}
public int indexOf(Z data) {
int pos = -1;
if (data == null) {
return pos;
}
Node<Z> h = head;
boolean found = false;
do {
pos++;
h = h.next;
if (data.equals(h.data)) {
found = true;
break;
}
} while (h.next != head.next);
return found ? pos : -1;
}
public Z get(int index) {
if (index < 0 || index >= length) {
return null;
}
int pos = -1;
Node<Z> h = head;
do {
if (index - 1 == pos) {
break;
}
pos++;
h = h.next;
} while (h.next != head.next);
return h.next.data;
}
public boolean remove(Z data) {
return remove(indexOf(data));
}
public boolean removeLast() {
return remove(length - 1);
}
public boolean removeFirst() {
return remove(0);
}
public boolean remove(int index) {
if (index < 0 || index >= length) {
return false;
}
int pos = -1;
Node<Z> h = head;
do {
if (index - 1 == pos) {
break;
}
pos++;
h = h.next;
} while (h.next != head.next);
if (pos == length) {
return false;
}
// now h' pos == index-1
Node<Z> target = h.next;
h.next.data = null; // remove target
h.next = target.next;
if (pos == -1) {
// remove the first ele
tail.next.next = h.next;
} else if (pos == length - 2) {
// remove the last ele
tail.next = h;
}
length--;
return true;
}
public boolean clear() {
while (length > 0) {
removeFirst();
}
return true;
}
public void listReverse() {
// _ 3 8 9 7 6
// + 8 3
if (length <= 1) {
return;
}
Node<Z> rh = new Node<>();
Node<Z> next;
Node<Z> tt = null;
Node<Z> h = head.next;
do {
next = h.next;
h.next = rh.next;
rh.next = h;
if (tt == null) {
tt = h;
}
h = next;
} while (h != head.next);
tail.next = tt;
tail.next.next = rh.next;
head.next = rh.next;
}
/**
* @param order 0== no sort; 1== 3,2,1 , -1 == 1,2,3.
*/
public <T extends Comparable<T>> AroundList<T> merge(AroundList<T> another, int order) {
AroundList<T> list = new AroundList<>();
if (0 == order) {
for (int i = 0; i < size(); ++i) {
list.addLast((T) get(i));
}
if (another != null) {
for (int i = 0; i < another.size(); ++i) {
list.addLast(another.get(i));
}
}
} else {
for (int i = 0; i < size(); ++i) {
// list.addLast((T) get(i));
T data = (T) get(i);
int j;
for (j = 0; j < list.size(); ++j) {
T z = list.get(j);
if (order < 0
? z.compareTo(data) > 0 :
z.compareTo(data) < 0) {
break;
}
}
list.insert(j, data);
}
if (another != null) {
for (int i = 0; i < another.size(); ++i) {
T data = another.get(i);
int j;
for (j = 0; j < list.size(); ++j) {
T z = list.get(j);
if (order < 0
? z.compareTo(data) > 0 :
z.compareTo(data) < 0) {
break;
}
}
list.insert(j, data);
}
}
}
return list;
}
public String format() {
int pos = -1;
StringBuilder sb = new StringBuilder(getClass().getSimpleName() + ": {");
if (length > 0) {
Node<Z> h = head;
do {
h = h.next;
pos++;
sb.append("[").append(pos).append("]=").append(h.data).append(", ");
} while (h.next != head.next);
}
int last = sb.lastIndexOf(", ");
if (last != -1) {
sb.delete(last, last + 2);
}
sb.append("} | size=").append(length).append(" ##|");
return sb.toString();
}
@Override
public String toString() {
return new StringJoiner(", ",
AroundList.class.getSimpleName() + "[", "]")
.add(format()).toString();
}
public static void main(String[] args) {
joseph(5, 1, 2);
System.out.println("@@@@");
joseph(5, 3, 2);
// if (1 == 1) return;
AroundList<Integer> list = new AroundList<>();
System.out.println(list);
list.add(1);
System.out.println(list);
list.removeFirst();
System.out.println("--rr-" + list);
// if (1 == 1) return;
list.add(2);
list.add(3);
System.out.println("#######" + list);
list.listReverse();
System.out.println("r-r-r-r? " + list);
list.addLast(4);
list.addLast(5);
list.addLast(6);
list.add(-1);
System.out.println("add... " + list);
list.insert(3, 44);
System.out.println("is: " + list);
list.remove(list.size() - 1);
System.out.println("rs-1?" + list);
list.clear();
System.out.println("clear---------" + list);
list.add(12);
System.out.println("----??? " + list);
list.add(13);
list.addLast(14);
System.out.println(list);
list.remove(0);
System.out.println("r0? " + list);
list.remove(1);
System.out.println("r1? " + list);
list.replace(0, -11);
list.replace(1, -22);
System.out.println("rp? " + list);
for (int i = 0; i < list.size(); ++i) {
Integer value = list.get(i);
System.out.println(" get=-- [" + i + "] = " + value);
System.out.println("index of " + list.indexOf(value));
}
System.out.println(list.indexOf(Integer.MIN_VALUE));
System.out.println(list.indexOf(Integer.MAX_VALUE));
joseph(6, 2, 4);
}
private static void joseph(int count, int start, int times) {
if (start > count || times > count
|| count <= 0 || start <= 0 || times <= 0) {
return;
}
// 5, 1, 2
AroundList<Integer> list = new AroundList<>();
for (int i = 0; i < count; ++i) {
list.addLast(i + 1);
}
int[] results = new int[count];
int offset = -99;
for (int i = 0; i < count; ++i) {
int size = list.size();
int begin = i == 0 ? start - 1 : offset;
offset = (begin + times - 1) % size;
Integer value = list.get(offset);
boolean rm = list.remove(offset);
// System.out.printf("b=%d,offset=[%d], value=%d, rm=%s\n", begin, offset, value, rm);
results[i] = value;
}
System.out.println("results: " + Arrays.toString(results));
System.out.println("list--" + list);
}
}
这里需要关注 insert(index, data)
及 remove(index, data)
的逻辑。环形链表的问题在于,最后一个元素始终会指向第一个元素,所以在 插入到链表头,及插入到链表尾;删除头元素,删除尾元素 的时候需要特别注意。
然后是约瑟夫问题的解决:
private static void joseph(int count, int start, int times) {
if (start > count || times > count
|| count <= 0 || start <= 0 || times <= 0) {
return;
}
// 5, 1, 2
AroundList<Integer> list = new AroundList<>();
for (int i = 0; i < count; ++i) {
list.addLast(i + 1);
}
int[] results = new int[count];
int offset = -99;
for (int i = 0; i < count; ++i) {
int size = list.size();
int begin = i == 0 ? start - 1 : offset;
offset = (begin + times - 1) % size;
Integer value = list.get(offset);
boolean rm = list.remove(offset);
// System.out.printf("b=%d,offset=[%d], value=%d, rm=%s\n", begin, offset, value, rm);
results[i] = value;
}
System.out.println("results: " + Arrays.toString(results));
System.out.println("list--" + list);
}
在已经实现了环形链表的前提下,约瑟夫问题就变成了找到要删除的元素的位置,然后删除该元素。 不过要注意,找这个元素的逻辑。就是怎么去计算对应的索引值的偏移量。
看一下输出结果:
results: [2, 4, 1, 5, 3]
list--AroundList[AroundList: {} | size=0 ##|]
@@@@
results: [1, 5, 2, 4, 3]
...
可以看到,如果是5个人,从第1个开始数数,每数2个,就踢出;那么输出的顺序是2, 4, 1, 5, 3
; 如果是5个人,从第3个开始数数,每数4个,就踢出;那么输出的顺序是1, 5, 2, 4, 3
。
数的时候要包含当前的位置,自己也要被数一下。