2021.2.1 随笔
随笔高大上的名字,其实就是日记
单链表的操作
手写一个实现基本增删查改功能操作的单链表
public class MyLinked1 {
Node top;
int size;
// 添加
public boolean add(String str) {
// 判断链表是否为空
if (top == null || size == 0) {
// 代表是一个空链表
top = new Node(str, null);
size++;
return true;
}
// 查找尾结点
Node mid = top;
while (mid.next != null) {
mid = mid.next;
}
// 上述循环的跳出条件,就是 mid 是尾结点
mid.next = new Node(str, null);
size++;
return true;
}
// 删除
public String delete(String str) {
// TODO:参数校验:
if (top == null || size == 0) {
throw new RuntimeException("linked id null");
}
if (top.value.equals(str)) {
// 要删除的结点是头结点
top = top.next;
size--;
return str;
}
// 删除的不是头结点
Node mid = top;
// 向后查找要删除的结点
// 如果查找的结点不指向 null 和该结点下一个结点的值不是 str ,就向后查找
while (mid.next != null && !mid.next.value.equals(str)) {
mid = mid.next;
}
// 意味着
// 1.mid.next=null 没有删除的元素
// 2.mid.next 就是要删除的结点
if (mid.next == null) {
// 没找到
return null;
}
// mid.next 要找到结点
mid.next = mid.next.next;
size--;
return str;
}
// 修改
public boolean set(String oldStr, String newStr) {
// TODO:参数校验:
if (top == null || size == 0) {
throw new RuntimeException("linked id null");
}
// 修改的是否是头结点
if (top.value.equals(oldStr)) {
top.value = newStr;
return true;
}
// 修改的不是头结点
Node mid = top;
// 这个循环就是查找要替换的元素
while (mid.next != null && !mid.next.value.equals(oldStr)) {
mid = mid.next;
}
// 没找到
if (mid.next == null) {
return false;
}
// 必然找到
mid.next.value = newStr;
return true;
}
// 查找
public boolean find(String str) {
// TODO:参数校验
if (top == null || size == 0) {
throw new RuntimeException("linked is null");
}
// 查找的是不是头结点
if (top.value.equals(str)) {
return true;
}
// 不是头结点,就向后遍历
Node mid = top;
// 如果下一个不是null,并且下一个的值不是str
while (mid.next != null && !mid.next.value.equals(str)) {
mid = mid.next;
}
// 没找到
if (mid.next == null) {
return false;
}
// 必然找到
return true;
}
// 根据下标删除
public String idDelete(int id) {
// TODO:参数校验
if (top == null || size == 0) {
throw new RuntimeException("linked is null");
}
if (id < 0 || id >= size) {
throw new IllegalArgumentException("超出范围!");
}
// 判断是否是头结点
if (id == 0) {
String oldValue = top.value;
top = top.next;
size--;
return oldValue;
}
Node mid = top;
// 不是头结点,就向后遍历,查找被删除结点的前一个结点
int i = 0;
// 下面或许可以换成(id!=i)实现应该是一样的。
while (mid.next != null && i < id - 1) {
// 循环条件为小于 下标为 id-1 的 结点,
mid = mid.next;
i++;
}
if (mid.next == null) {
// 写上保险,想法更全面。
}
// 被删除结点前一个结点的指针指向下下个结点。
String oldStr = mid.next.value;
mid.next = mid.next.next;
size--;
return oldStr;
}
// 选做:根据下标添加
public boolean idAdd(String str, int id) {
// 超出范围
// 这里不能等于 size ,因为可以在尾结点添加啊
if (id < 0 || id > size) {
throw new IndexOutOfBoundsException("超出范围!");
}
// 判断空表,这段可能不太需要。
if (top == null || size == 0) {
top = new Node(str, null);
size++;
return true;
}
// 判断头结点
if (id == 0) {
Node newNode = new Node(str, top);
top = newNode;
size++;
return true;
}
Node mid = top;
int i = 1;
// 这里为什么要从1开始?,1--id 或者 0开始--(id-1)
// 遍历找被添加结点的前一个结点。
while (i < id ) { // 等价于 i!=id
mid = mid.next;
i++;
}
// 必然添加
Node newNode = new Node(str, mid.next);
mid.next = newNode;
size++;
return true;
}
class Node {
String value;
Node next;
public Node(String value, Node next) {
this.value = value;
this.next = next;
}
@Override
public String toString() {
return "{" +
"value=" + value +
"}" + next;
}
}
@Override
public String toString() {
return "" + top;
}
}
class Demo {
public static void main(String[] args) {
MyLinked1 myLinked1 = new MyLinked1();
myLinked1.add("zs");
myLinked1.add("ls");
myLinked1.add("ww");
myLinked1.add("zz");
myLinked1.add("ss");
myLinked1.add("mm");
// System.out.println(myLinked1);
// myLinked1.delete("ww");
// myLinked1.set("ls", "ohou");
// System.out.println(myLinked1.find("ssss")); // 查找
// myLinked1.idDelete(0); // 头
// System.out.println(myLinked1);
// myLinked1.idDelete(3); // 中间
// System.out.println(myLinked1);
// myLinked1.idDelete(5); // 尾
// System.out.println(myLinked1);
//
// myLinked1.idAdd("萌萌",0); // 头
// System.out.println(myLinked1);
// myLinked1.idAdd("萌萌",3); // 中间
// System.out.println(myLinked1);
myLinked1.idAdd("萌萌", 6); // 最后
System.out.println(myLinked1);
}
}
总结:最初开始接触链表也有点懵,纯按自己的理解写了一个很low的代码,还没改,同时,发现了很多缺点,代码冗余等等
双链表的操作
/**
* @program: day3
* @description: 双向链表
* @author: Mr.Mengmeng
* @create: 2021-02-01 20:32
**/
/*
双向链表的操作
*/
// 测试
public class Test {
public static void main(String[] args) {
MyDBNode myDBNode = new MyDBNode();
myDBNode.add("z1");
myDBNode.add("z2");
myDBNode.add("z3");
myDBNode.add("z4");
myDBNode.add("z5");
myDBNode.add("z6");
// System.out.println(myDBNode);
System.out.println(myDBNode.indexToDelete(6));
System.out.println(myDBNode);
}
}
class DBNode { // 对于每一个结点来说,都有值域,前指针,后指针。
String value;
DBNode pre;
DBNode next;
public DBNode(String value, DBNode pre, DBNode next) {
this.value = value;
this.pre = pre;
this.next = next;
}
@Override
public String toString() {
return "{" +
"value='" + value + '\'' +
", next=" + next +
'}';
}
}
class MyDBNode {
// 对于每一个双向链表,都有头结点,尾结点,链表的大小。
DBNode top;
DBNode end;
int size;
// 添加方法
public boolean add(String str) {
// 首先判断该表是否是空表
if (top == null || size == 0) {
top = new DBNode(str, null, null);
end = top;
size++;
return true;
}
// 链表不为空
DBNode newDBNode = new DBNode(str, end, null);
end.next = newDBNode;
end = newDBNode;
size++;
return true;
}
// 选做:根据下标添加元素
public String indexAdd(int index, String newStr) {
// 判断下标是否越界
if (index < 0 || index > size) throw new IllegalArgumentException("下标越界");
// 判断空表
if (top == null || size == 0) {
top = new DBNode(newStr, null, null);
end = top;
size++;
return newStr;
}
// 链表不为空,判断头结点
if (index == 0) {
DBNode newdbNode = new DBNode(newStr, null, top);
top.pre = newdbNode;
top = newdbNode;
size++;
return newStr;
}
// 判断 null 结点,尾结点之后添加
if (index == size) {
DBNode newDBNode = new DBNode(newStr, end, null);
end.next = newDBNode;
end = newDBNode;
size++;
return newStr;
}
// 判断其他结点,遍历找到被添加结点的前一个结点
DBNode mid = top;
int i = 1;
while (i < index) {
mid = mid.next;
i++;
}
DBNode newDBNode = new DBNode(newStr, mid, mid.next);
mid.next = newDBNode;
mid.next.pre = newDBNode;
size++;
return newStr;
}
// 删除,根据内容删除
public String deleteStr(String str) {
// 判断空表
if (top == null || size == 0) {
throw new RuntimeException("MyDBNode is null");
}
// 判断删除的是否是唯一元素
if (size == 1) {
String oldStr = top.value;
top = null;
end = top;
size--;
return oldStr;
}
// 判断删除的是头结点的内容
if (top.value.equals(str)) {
top = top.next;
top.next.pre = null;
size--;
return str;
}
// 判断删除的是尾结点的内容
if (end.value.equals(str)) {
String oldStr = end.value;
end.pre.next = null;
size--;
return oldStr;
}
// 判断删除的是中间结点的内容,遍历删除内容的前一个结点为 mid
DBNode mid = top;
while (mid.next != null && !mid.next.equals(str)) {
mid = mid.next;
}
// 没找到
if (mid.next == null) {
return null;
}
// 找到了,先标记一下
String oldStr = mid.next.value;
mid.next = mid.next.next;
mid.next.next.pre = mid;
size--;
return oldStr;
}
// 选做:根据下标删除
public String indexToDelete(int index) {
// 判断空表
if (top == null || size == 0) throw new RuntimeException("DBNode is null");
// 判断下标是否越界
if (index < 0 || index >= size) throw new IllegalArgumentException("下标越界");
// 判断删除的是否是唯一元素
if (size == 1) {
String oldStr = top.value;
top = null;
end = top;
size--;
return oldStr;
}
// 判断头结点
if (index == 0) {
String oldStr = top.value;
top = top.next;
top.next.pre = null;
size--;
return oldStr;
}
// 判断尾结点
if (index == size - 1) {
String oldStr = end.value;
end.pre.next = null;
end = end.pre;
size--;
return oldStr;
}
// 判断其他结点
DBNode mid = top;
int i = 1;
while (i<index) {
mid = mid.next;
i++;
}
DBNode oldNode = mid.next;
oldNode.pre.next=oldNode.next;
oldNode.next.pre=oldNode.pre;
size--;
return oldNode.value;
}
// 修改,根据内容修改
public String set(String oldStr, String newStr) {
// 判断空表
if (top == null || size == 0) throw new RuntimeException("MyDBNode is null");
// 判断头结点
if (top.value.equals(oldStr)) {
top.value = newStr;
return newStr;
}
// 判断中间结点
DBNode mid = top;
while (mid.next != null && !mid.next.value.equals(oldStr)) {
mid = mid.next;
}
// 没找到
if (mid.next == null) {
return null;
}
// 找到了
mid.next.value = newStr;
return newStr;
}
// 选做:根据下标修改值
public String indexSet(int index, String newStr) {
// 判断空表
if (top == null || size == 0) throw new RuntimeException("DBNode is null");
// 判断下标是否越界
if (index < 0 || index >= size) throw new IllegalArgumentException("下标越界");
// 判断头结点
if (index == 0) {
top.value = newStr;
return newStr;
}
// 判断其他结点
DBNode mid = top;
int i = 1;
while (index != i) {
mid = mid.next;
i++;
}
// 没找到
if (mid.next == null) {
return null;
}
// 找到了
mid.next.value = newStr;
return newStr;
}
// 查找
public boolean find(String str) {
// 判断空表
if (top == null || size == 0) throw new RuntimeException("MyDBNode is null");
// 判断头结点
if (top.value.equals(str)) {
return true;
}
// 不是头结点,向后遍历,找到被查询结点的前一个结点
DBNode mid = top;
while (mid.next != null && !mid.next.value.equals(str)) {
mid = mid.next;
}
// 没找到
if (mid.next == null) {
return false;
}
// 找到了
return true;
}
// 选做:根据下标查找值
public String indexToValue(int index) {
// 判断空表
if (top == null || size == 0) throw new RuntimeException("MyDBNode is null");
// 判断下标越界
if (index < 0 || index >= size) throw new IllegalArgumentException("下标越界");
// 判断头结点
if (index == 0) {
return top.value;
}
// 判断其他结点
DBNode mid = top;
int i = 1;
while (index != i) {
mid = mid.next;
i++;
}
// 没找到
if (mid.next == null) {
return null;
}
// 找到了
return mid.next.value;
}
// 选做:根据内容查找下标
public int strToIndex(String str) {
// 判断空表
if (top == null || size == 0) throw new RuntimeException("MyDBNode is null");
// 判断头结点
if (top.value.equals(str)) {
return 0;
}
// 判断其他结点
DBNode mid = top;
int i = 1;
while (mid.next != null && !mid.next.value.equals(str)) {
mid = mid.next;
i++;
}
// 没找到
if (mid.next == null) {
return -1;
}
return i;
}
@Override
public String toString() {
return "{" +
"top=" + top +
'}';
}
}
可能写的啰嗦了一点,但是只有自己去动手实现更好的理解链表。
快慢指针
(第一次听到这个名字,便去学习了一下)
快慢指针,双指针,表示的是,有两个指针,一个是快指针,每次向后移动两步,一个是慢指针,每次向后移动一步。快慢指针一般应用在带环的链表中。
那么,我愚蠢的问题又来了...为什么快指针不一次性走三步呢?
然后就 google 搜索,在 leecode 第141道题中找到解。快指针可以走很多步,那么,同样,遍历次数就会增多,慢指针取1,所以快指针取2,可以最小化算法的运行时间。
三道应用题:
1.求链表的中间元素
public class Demo1 {
public static void main(String[] args) {
Node node1 = new Node("zs", null);
Node node2 = new Node("ls", node1);
Node node3 = new Node("ww", node2);
Node node4 = new Node("zl", node2);
// zl --》 wu --》 ls --》 zs
// List list = new ArrayList();
// int size = list.size();
// int tag = size / 2;
// System.out.println(list.get(tag));
// 太慢了,可以用快慢指针
Node f = node4;
Node l = node4;
while (f.next != null && f.next.next != null) {
// 快指针走两步,慢指针走一步
f=f.next.next;
l=l.next;
}
// 跳出循环的条件,f 已经接近于链表的尾部
// l 已经是要查找的中间结点
}
}
class Node {
String value;
Node next;
public Node(String value, Node next) {
this.value = value;
this.next = next;
}
}
2.判断链表中是否有环(circle),判断环的起始位置
/*
判断链表中是否有环
*/
public class Demo2 {
public static void main(String[] args) {
Node node1 = new Node("1", null);
Node node2 = new Node("2", node1);
Node node3 = new Node("3", node2);
Node node4 = new Node("4", node3);
Node node5 = new Node("5", node4);
Node node6 = new Node("6", node5);
Node node7 = new Node("7", node6);
Node node8 = new Node("8", node7);
// 8 7 6 5 4 3 2 1
node1.next = node5;
Node f = node7;
Node l = node7;
while (f.next != null && f.next.next != null) {
f = f.next.next;
l = l.next;
if (f == l) {
// 代表,快指针和慢指针重合
break;
}
}
if (f == l) {
System.out.println("有环");
} else { // 要么就是走完了
System.out.println("没环");
}
f = node7;
while (f != l) {
f = f.next;
l = l.next;
}
// 环的起始位置
System.out.println(f.value);
}
}
3.反转单链表
/*
反转单链表
*/
public class Demo3 {
public static void main(String[] args) {
Node node1 = new Node("1", null);
Node node2 = new Node("2", node1);
Node node3 = new Node("3", node2);
Node node4 = new Node("4", node3);
Node node5 = new Node("5", node4);
Node node6 = new Node("6", node5);
Node node7 = new Node("7", node6);
// 7 6 5 4 3 2 1
// 1 2 3 4 5 6 7
// 头插法 尾插法
Node relinked = reLinked(node7);
System.out.println(relinked.toString());
}
private static Node reLinked(Node node7) {
Node relinked = null;
// 遍历结点
Node mid = node7;
// 遍历结点的下一个结点不为空
while (mid.next != null) {
// 记录遍历结点的下一个结点,(保存正序序列,以免丢失)
Node linkedNext = mid.next; // 维护原来的链表
// 遍历的结点采用“头插法”,插入反序链表
mid.next = relinked;
// 反序链表的头结点前移
relinked = mid;
// mid 指向正序链表
mid = linkedNext;
}
// 处理最后一个正序结点
mid.next = relinked;
relinked = mid;
return relinked;
}
}