链表
链表以结点的方式来存储元素
每个结点包含data域和next域(指向下一个结点)
链表在内存中不一定连续存储
链表分为有头节点和没有头结点的,根据实际需求来确定
1. 单向链表
import java.util.Stack;
public class SingleLinkedList<E> {
// 先初始化一个头结点,头结点不存放具体数据
Node<E> head = new Node<>(null, null);
// 定义结点
private static class Node<E> {
E item;
Node<E> next;
public Node(E item, Node<E> next) {
this.item = item;
this.next = next;
}
}
// 判断链表是否为空
public boolean isEmpty() {
return head.next == null;
}
// 头插法添加结点
public void addFirst(E item) {
Node<E> first = head.next;
// 创建一个结点,其next指向第一个结点
Node<E> node = new Node<>(item, first);
// head的next指向新创建的结点
head.next = node;
}
// 尾插法添加结点
public void addLast(E item) {
Node<E> tmp = head;
// 若当前结点的next为空,则说明是尾结点,退出循环
while (tmp.next != null) {
tmp = tmp.next;
}
tmp.next = new Node<>(item, null);
}
// 获取单链表中结点数量
public int size() {
int size = 0;
Node<E> tmp = head;
// 若当前结点的next为空,则说明是尾结点,退出循环
while (tmp.next != null) {
tmp = tmp.next;
size++;
}
return size;
}
// 获取第index个结点
private Node<E> getNode(int index) {
if (index < 0 || index >= size())
return null;
Node<E> tmp = head.next;
while (tmp != null && index > 0) {
tmp = tmp.next;
index--;
}
return tmp;
}
// 获取第index个结点的值
public E get(int index) {
Node<E> node = getNode(index);
if (node == null)
return null;
return node.item;
}
// 获取指定元素的索引,若不存在,则返回-1
public int indexOf(Object o) {
int index = 0;
if (o != null) {
Node<E> tmp = head.next;
while (tmp != null) {
if (o.equals(tmp.item))
return index;
index++;
tmp = tmp.next;
}
}
return -1;
}
// 在指定索引处插入元素
public boolean insert(int index, E element) {
// 获取插入位置的前驱结点
Node<E> prev = getNode(index - 1);
if (prev == null)
return false;
// 创建待插入结点,使其next指向prev的下一个
Node<E> ele = new Node<>(element, prev.next);
// 让prev的下一个指向待插入结点
prev.next = ele;
return true;
}
// 删除指定索引元素
public E remove(int index) {
Node<E> del;
if (index == 0) {
del = head.next;
head.next = del.next;
return del.item;
}
// 获取删除位置的前驱结点
Node<E> prev = getNode(index - 1);
if (prev == null)
return null;
del = prev.next;
// 让prev的next指向待删除结点后面的结点
prev.next = del.next;
return del.item;
}
// 删除指定的第一个结点
public E remove(Object o) {
return remove(indexOf(o));
}
// 单链表反转
public void reverse() {
// 如果为空,或只有一个元素,则不需要反转
if (isEmpty() || head.next.next == null)
return;
Node reverseHead = new Node(0, null);
// cur指向第一个结点
Node cur = head.next;
while (cur != null) {
// 在head中,每次摘掉第一个结点cur
head.next = cur.next;
// 将摘掉的cur采用头插法,添加到reverseHead
cur.next = reverseHead.next;
reverseHead.next = cur;
// 让cur重新指向当前的第一个结点
cur = head.next;
}
head = reverseHead;
}
// 遍历链表
public void show() {
// 判断链表是否为空
if (isEmpty())
return;
// 头结点不能动,因此找一个辅助变量
Node tmp = head.next;
while (tmp != null) {
System.out.println(tmp.item);
tmp = tmp.next;
}
}
// 递归逆序/后序遍历
private void postorderTraversal(Node node) {
if (node == null)
return;
postorderTraversal(node.next);
System.out.println(node.item);
}
// 调用递归逆序/后序遍历
public void inverseShowByRecursion() {
postorderTraversal(head.next);
}
// 使用栈逆序遍历
public void inverseShowByStack() {
if (isEmpty())
return;
Stack<Node> stack = new Stack<>();
Node cur = head.next;
// 先前序遍历原链表,将遍历出的每一个结点入栈
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
// 依次出栈
while (!stack.empty()) {
System.out.println(stack.pop().item);
}
}
}
2. 双向链表
public class DoubleLinkedList<E> {
// 先初始化一个头结点,头结点不存放具体数据
public Node head = new Node(0, null, null);
// 定义结点
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
public Node(E item, Node<E> next, Node<E> prev) {
this.item = item;
this.next = next;
this.prev = prev;
}
}
// 判断链表是否为空
public boolean isEmpty() {
return head.next == null;
}
// 添加数据到双链表尾部
public void addLast(E item) {
Node tmp = head;
// 若当前结点的next为空,则说明是尾结点,退出循环
while (tmp.next != null) {
tmp = tmp.next;
}
// 创建一个结点,使其prev为tmp;并将其连接到tmp后面
tmp.next = new Node(item, null, tmp);
}
// 删除第一个指定的date结点,返回是否删除成功
public boolean remove(E item) {
// 判断链表是否为空
if (isEmpty())
return false;
// tmp指向待删除结点
Node tmp = head.next;
// 遍历链表,找待删除结点
while (tmp != null) {
// 如果找到
if (tmp.item == item) {
// 让tmp的前一个结点的next,指向tmp的后一个结点
tmp.prev.next = tmp.next;
// 如果是最后一个结点,就不需要执行
if (tmp.next != null)
// 让tmp的后一个结点的prev,指向tmp的前一个结点
tmp.next.prev = tmp.prev;
return true;
}
tmp = tmp.next;
}
return false;
}
// 遍历双向链表
public void show() {
// 判断链表是否为空
if (isEmpty()) {
System.out.println("空链表");
return;
}
// 头结点不能动,因此找一个辅助变量
Node tmp = head.next;
while (tmp != null) {
System.out.println(tmp.item);
tmp = tmp.next;
}
}
}
3. 循环单链表解决Josephus问题
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
class CircleSingleLinkedList {
// 先创建一个first结点
public Node first = null;
// 结点数量
public int size;
// 定义结点
private static class Node<E> {
E item;
Node<E> next;
public Node(E item, Node<E> next) {
this.item = item;
this.next = next;
}
}
// 添加数据,构成不带头结点的单向环形链表
public boolean addSum(int size) {
if (size < 1)
return false;
this.size = size;
Node<Integer> curNode = null;
for (int i = 1; i <= size; i++) {
// 创建结点,使其next指向first
Node<Integer> node = new Node<>(i, null);
// 如果是第一个结点
if (i == 1) {
// 让next指向自身,即自身构成一个环
node.next = node;
first = node;
curNode = node;
} else {
// 让next指向first,构成环
node.next = first;
curNode.next = node;
curNode = curNode.next;
}
}
return true;
}
// 根据s,计算出圈顺序,star为从哪一个开始,offset为偏移量
public void goOutOrder(int star, int offset) {
if (first == null || star < 1 || star > size) {
System.out.println("参数有误");
return;
}
// 将firs移动到star
for (int i = 0; i < star - 1; i++) {
first = first.next;
}
Node<Integer> firstPre = first;
// 将firstPre移动到firs前面
while (true) {
if (firstPre.next == first)
break;
firstPre = firstPre.next;
}
while (firstPre != first) {
// 让firstPre和first移动offs - 1次
for (int i = 0; i < offset - 1; i++) {
first = first.next;
firstPre = firstPre.next;
}
// 这时first指向的结点就是该出圈的结点
System.out.print(first.item + "-->");
first = first.next;
firstPre.next = first;
}
// 当firstPre == first是,就是最后一个结点
System.out.println(first.item);
}
}
public class Josephus {
public static void main(String[] args) {
CircleSingleLinkedList csll = new CircleSingleLinkedList();
System.out.println("约瑟夫问题,从1开始,数到第3个人出列:");
csll.addSum(41);
// 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
csll.goOutOrder(1, 3);
}
}