链表学习笔记
链表是一种使用十分频繁的数据结构,它的优点在于可以将大量的数据存储在分散的空间内,当需要插入或修改节点的时候,只需要修改节点之间的指针即可。
与之相反,数组的存储则需要连续的空间,当需要向数组中插入数据或者修改顺序的时候,需要对整个空间进行处理。
所以:我们常说 如果查找的比较频繁就使用数组,如果修改比较频繁那么建议使用链表
链表是以节点的方式来进行数据存储的,它分为带头节点的链表和不带头节点的链表
单向链表
每一个节点中都分为data域和next域 ,data域存放数据 ,next域指向下一个节点
代码实现
public class SingleLinkedList {
/**
* 头节点中没有任何数据 它只是一个辅助节点
*/
private SingleLinkedNode head;
public SingleLinkedList() {
head = new SingleLinkedNode(-1, null);
}
/**
* 添加节点
*
* @param node
*/
public void add(SingleLinkedNode node) {
SingleLinkedNode temp = head;
while (true) {
if (temp.next == null) {
break;
}
temp = temp.next;
}
temp.next = node;
}
/**
* 按排序添加
*
* @param node
*/
public void addByRank(SingleLinkedNode node) {
SingleLinkedNode temp = head;
while (true) {
if (temp.next != null) {
if (temp.next.rank > node.rank) {
node.next = temp.next;
temp.next = node;
break;
} else {
temp = temp.next;
}
} else {
temp.next = node;
break;
}
}
}
/**
* 移除节点
*
* @param rank
*/
public SingleLinkedNode remove(int rank) {
SingleLinkedNode temp = head;
SingleLinkedNode removed = null;
while (true) {
if (temp.next != null) {
if (temp.next.rank == rank) {
removed = temp.next;
temp.next = temp.next.next;
break;
}
// 往后面移动一位
temp = temp.next;
} else {
// 不存在当前移除的key
break;
}
}
return removed;
}
public void print() {
// 打印
SingleLinkedNode lastNode = head.next;
System.out.println("head");
while (lastNode != null) {
System.out.println(" -> " + lastNode);
lastNode = lastNode.next;
}
}
/**
* 从尾部开始打印
* 利用栈 先进后出的原则
*/
public void printFromEnd() {
Stack<String> stack = new Stack<>();
SingleLinkedNode lastNode = head.next;
while (lastNode != null) {
stack.add(" -> " + lastNode);
lastNode = lastNode.next;
}
System.out.println("printFromEnd");
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
}
/**
* 链表的有效长度
*
* @return
*/
public int size() {
int len = 0;
SingleLinkedNode temp = head;
while (temp.next != null) {
len++;
temp = temp.next;
}
return len;
}
/**
* 从尾部开始查找到第n个节点
*
* @param index 下标都是从0开始计算
* @return
*/
public SingleLinkedNode lastIndexOf(int index) {
// 先获取到真正的长度
int size = size();
if (index >= size) {
throw new RuntimeException("out of bound:" + index + "[" + size + "]");
}
// 计算正向的下标
int realIndex = size - 1 - index;
SingleLinkedNode temp = head;
int curIndex = -1;
while (temp.next != null) {
curIndex++;
temp = temp.next;
if (realIndex == curIndex) {
return temp;
}
}
return null;
}
/**
* 反转
* 原链表是 head - 1 -> 2 -> 5
* 反转之后 head - 5 -> 2 -> 1
* <p>
* 思路:
* tempHead -> 1
* tempHead -> 2 -> 1
* tempHead -> 5 -> 2 -> 1
*/
public void reverse() {
if (head.next == null) {
return;
}
// 当前循环遍历的节点
SingleLinkedNode cur = head.next;
// 当前循环的节点的下一个节点
SingleLinkedNode next;
// 反转的临时头节点
SingleLinkedNode reverseHead = new SingleLinkedNode(0, "");
while (cur != null) {
// 先获取到下一个节点
next = cur.next;
// 断开原本当前节点和下一个节点之间的关系 并构建新的关系
// 这一句很巧妙 实现了将1挂在2的后面
cur.next = reverseHead.next;
// 将当前节点设置为反转链表的第一个节点
reverseHead.next = cur;
// 链表向后移动
cur = next;
}
head.next = reverseHead.next;
}
/**
* 合并2个有序的链表
* @param another
* @return
*/
public SingleLinkedList merge( SingleLinkedList another){
SingleLinkedNode aTemp = this.head.next;
SingleLinkedNode bTemp = another.head.next;
SingleLinkedList mergeList = new SingleLinkedList();
SingleLinkedNode mergeNode = mergeList.head ;
while(aTemp != null && bTemp != null){
// 比较
if(aTemp.rank > bTemp.rank){
mergeNode.next = aTemp;
aTemp = aTemp.next;
}else{
mergeNode.next = bTemp;
bTemp = bTemp.next;
}
mergeNode = mergeNode.next;
}
if(aTemp == null){
mergeNode.next = bTemp;
}else {
mergeNode.next = aTemp;
}
return mergeList;
}
static class SingleLinkedNode {
private int rank;
private String name;
private SingleLinkedNode next;
public SingleLinkedNode(int rank, String name) {
this.rank = rank;
this.name = name;
}
@Override
public String toString() {
return "SingleLinkedNode{" +
"rank=" + rank +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(new SingleLinkedNode(1, "zhangsan"));
singleLinkedList.add(new SingleLinkedNode(3, "lisi"));
singleLinkedList.add(new SingleLinkedNode(5, "wangwu"));
singleLinkedList.print();
singleLinkedList.remove(4);
singleLinkedList.print();
singleLinkedList.addByRank(new SingleLinkedNode(4, "haha"));
singleLinkedList.print();
singleLinkedList.remove(4);
singleLinkedList.print();
System.out.println("size:" + singleLinkedList.size());
System.out.println("\tlastIndexOf(2):" + singleLinkedList.lastIndexOf(2));
System.out.println("\tlastIndexOf(1):" + singleLinkedList.lastIndexOf(1));
System.out.println("\tlastIndexOf(0):" + singleLinkedList.lastIndexOf(0));
System.out.println("reverse:");
singleLinkedList.reverse();
singleLinkedList.print();
singleLinkedList.printFromEnd();
SingleLinkedList singleLinkedList2 = new SingleLinkedList();
singleLinkedList2.add(new SingleLinkedNode(8, "zhaoliu"));
singleLinkedList2.add(new SingleLinkedNode(3, "songqi"));
singleLinkedList2.add(new SingleLinkedNode(2, "songqi2"));
System.out.println("merge:");
singleLinkedList.merge(singleLinkedList2).print();
}
}
双向链表
和单向链表不同的点在于双向链表除了data域和next域以外还有一个pre域,它指向当前节点的上一个节点
所以:双向链表可以自删除,可以反向查找
代码实现
public class DoubleLinkedList {
private DoubleLinkedNode head;
public DoubleLinkedList() {
head = new DoubleLinkedNode(-1, "头节点");
}
public void add(DoubleLinkedNode node) {
// 找到最后一个节点
DoubleLinkedNode temp = head;
while (temp.next != null) {
temp = temp.next;
}
// 双向绑定
temp.next = node;
node.pre = temp;
}
public void addByRank(DoubleLinkedNode node) {
DoubleLinkedNode temp = head;
while (temp.next != null) {
if (temp.next.rank > node.rank) {
break;
}
temp = temp.next;
}
if (temp.next != null) {
temp.next.pre = node;
node.next = temp.next;
}
// 双向绑定
temp.next = node;
node.pre = temp;
}
/**
* 移除节点
*
* @param rank
*/
public DoubleLinkedNode remove(int rank) {
DoubleLinkedNode temp = head;
DoubleLinkedNode removed = null;
while (true) {
if (temp.next != null) {
if (temp.next.rank == rank) {
removed = temp.next;
DoubleLinkedNode preNode = removed.pre;
DoubleLinkedNode nextNode = removed.next;
preNode.next = nextNode;
if (nextNode != null) {
nextNode.pre = preNode;
}
break;
}
// 往后面移动一位
temp = temp.next;
} else {
// 不存在当前移除的key
break;
}
}
return removed;
}
/**
* 打印数据
*/
public void print() {
System.out.println("head:");
DoubleLinkedNode temp = head;
while (temp.next != null) {
System.out.println("\t -> " + temp.next);
temp = temp.next;
}
System.out.println();
}
/**
* 从尾部开始打印
*/
public void printFromEnd() {
// 首先找到最后一个节点
DoubleLinkedNode temp = head;
// 向后查找
while (temp.next != null) {
temp = temp.next;
}
System.out.println("printFromEnd");
// 向前查找
while (temp.pre != null) {
System.out.println("\t -> " + temp);
temp = temp.pre;
}
}
/**
* 链表的有效长度
* @return
*/
public int size() {
int len = 0;
DoubleLinkedNode temp = head;
while (temp.next != null) {
len++;
temp = temp.next;
}
return len;
}
/**
* 从尾部开始查找到第n个节点
*
* @param index 下标都是从0开始计算
* @return
*/
public DoubleLinkedNode lastIndexOf(int index) {
// 先获取到最后一个节点
DoubleLinkedNode temp = head;
int len = 0;
while (temp.next != null) {
temp = temp.next;
len++;
}
if (index >= len) {
throw new RuntimeException("out of bound:" + index + "[" + len + "]");
}
// 向前找
while (index > 0) {
temp = temp.pre;
index--;
}
return temp;
}
/**
* 反转
*/
public void reverse() {
if(head.next == null){
return;
}
// 首先需要一个临时的反转节点
DoubleLinkedNode reverseHead = new DoubleLinkedNode(-1, "");
// 循环原链表
DoubleLinkedNode cur = head.next;
while(cur!= null){
// 先获取到下一个节点
DoubleLinkedNode next = cur.next;
// 反转pre和当前节点的关系
cur.pre = cur;
// 处理next的关系
cur.next = reverseHead.next;
// 将当前节点绑到新的头节点上
reverseHead.next = cur;
// 向后移动
cur = next;
}
// 重新绑定头节点
head.next = reverseHead.next;
reverseHead.next.pre = head;
}
static class DoubleLinkedNode {
private int rank;
private String name;
private DoubleLinkedNode pre;
private DoubleLinkedNode next;
public DoubleLinkedNode(int rank, String name) {
this.rank = rank;
this.name = name;
}
@Override
public String toString() {
return "DoubleLinkedNode{" +
"rank=" + rank +
", name='" + name + '\'' +
'}';
}
}
public static void main(String[] args) {
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
doubleLinkedList.add(new DoubleLinkedNode(1, "zhangsan"));
doubleLinkedList.add(new DoubleLinkedNode(3, "lisi"));
doubleLinkedList.add(new DoubleLinkedNode(5, "wangwu"));
doubleLinkedList.print();
doubleLinkedList.remove(3);
doubleLinkedList.print();
doubleLinkedList.addByRank(new DoubleLinkedNode(4, "zhaoliu"));
doubleLinkedList.print();
doubleLinkedList.printFromEnd();
System.out.println("size:" + doubleLinkedList.size());
System.out.println("\tlastIndexOf(2):" + doubleLinkedList.lastIndexOf(2));
System.out.println("\tlastIndexOf(1):" + doubleLinkedList.lastIndexOf(1));
System.out.println("\tlastIndexOf(0):" + doubleLinkedList.lastIndexOf(0));
System.out.println("reverse:");
doubleLinkedList.reverse();
doubleLinkedList.print();
}
}
使用不带头节点的单向环形链表实现约瑟夫环
单向循环链表
如图所示,不带头节点的单线环形链表中 最后一个节点的next域会指向第一个节点,所以我们可以通过这个数据结构实现约瑟夫环
约瑟夫环
代码实现
public class Josephu {
static class Children {
private Child first;
public Children(int nums) {
addChild(nums);
}
public void addChild(int nums) {
first = new Child(1);
Child temp = first;
for (int i = 2; i <= nums; i++) {
Child child = new Child(i);
temp.next = child;
temp = child;
}
temp.next = first;
}
public void print() {
System.out.println("head:");
Child temp = first;
if (temp != null) {
System.out.println("\t -> " + temp);
while (temp.next != first) {
temp = temp.next;
System.out.println("\t -> " + temp);
}
}
}
public void calculate(int num) {
System.out.println("calculate split:" + num);
int size = size();
int splitSpace = (num - 1) % size;
if(splitSpace == 0){
splitSpace = size;
}
Child cur = first;
Child next;
int curIndex = 0;
while (size > 1) {
curIndex++;
if (curIndex == splitSpace) {
// 需要移除下一个
next = cur.next;
System.out.println("\t -> "+next);
cur.next = next.next;
// 长度-1
size--;
// 重新计算间隔位置
splitSpace = (num - 1) % size;
if(splitSpace == 0){
splitSpace = size;
}
// 给当前节点当作最后一个
curIndex = 0;
}
cur = cur.next;
}
System.out.println("\t -> "+cur);
}
public int size() {
if (first == null) {
return 0;
}
int len = 1;
Child temp = first;
while (temp.next != first) {
temp = temp.next;
len++;
}
return len;
}
}
static class Child {
int no;
Child next;
public Child(int no) {
this.no = no;
}
@Override
public String toString() {
return "Child{" +
"no=" + no +
'}';
}
}
public static void main(String[] args) {
Children children = new Children(5);
children.print();
// 1 2 3 4 5 的结果应该是 2 -> 4 -> 1 -> 5 -> 3
children.calculate(7);
}
}