题外话: 篇幅停了一下,特意去看看其他人写的类似的内容;然后发现类似博主喜欢画图,喜欢讲解原理。

(于是我就在想了,理解数据结构的确需要画图,但我的文章写给懂得人看,只配少量图即可,省事儿)

下面正题开始


一般性的,都能想到 dummy head 的技巧以及Java中LinkedList(底层是双向(循环)链表)。

Leetcode 返回一个头结点对象,就算返回整个链表了,而我们自己实现一般会 new 一个链表对象实例,然后调用该实例的各类方法来操作整个链表。

单链表

基本认识

之前写的动态数组并非真正动态,因为其内部封装的是一个容量不可变的静态数组。

而这里的链表则是真正的动态数据结构(不需要处理固定容量问题,即增删效率高,但由于不知道实际地址/索引,所以也丧失了随机访能力)。

辅助其他数据结构:二分搜索树,AVL/红黑树,它们基于链表实现。

基本构成: 节点 + 指针。

  1. class Node {
  2. E e;
  3. Node next;
  4. }
  • 最后一个节点一般指向 null

为了方便或者统一操作,一般会有 Node head,头结点。

  • 头结点的存在一般是为了在头部操作 (就像动态数组的新元素索引始终是 size 位置)
  • 一般直接用头结点指向首个节点(第一个节点即 head,但它不存储元素) dummy head

之所以用 dummy head 的原因,其实是为了操作简便。(不用也可以,但实现上的写法就...)

  • 打个比方,你要删除/增加某个节点时,一般情况而言,一定要知道删除节点的前一个节点(在头部则没有必要);一般都是通过循环遍历往后先找到特定节点,但是如果没有 dummy head,那么就要区分是在头结点还是中间节点操作(在脑海中想一下就知道了)。

有了 dummy head,头结点前面也有节点了,所以整个操作行为是统一的,一致的,不需要再做情况区分。

(下面有案例)

实现框架

先把实现的框架列一下,大致如下:

  1. package linkedlist;
  2. public class LinkedList<E> {
  3. //定义一个内部类,作为节点类
  4. private class Node {
  5. public E e;
  6. public Node next; //便于 LinkedList 访问
  7. public Node(E e, Node next) {
  8. this.e = e;
  9. this.next = next;
  10. }
  11. public Node(E e){
  12. this(e, null);
  13. }
  14. public Node(){
  15. this(null, null);
  16. }
  17. @Override
  18. public String toString() {
  19. return e.toString();
  20. }
  21. }
  22. //操作链表的辅助变量
  23. private int size;
  24. private Node head; //头结点
  25. //构造函数
  26. public LinkedList() {
  27. head = null;
  28. size = 0;
  29. }
  30. public int getSize() {
  31. return size;
  32. }
  33. public boolean isEmpty() {
  34. return size == 0;
  35. }
  36. }

然后再来实现其中的增删改查,此时先不设置虚拟头节点。

添加操作

这里实现的头部添加 (后续再扩展其他添加):

  1. public void addFirst(E e) {
  2. /*
  3. Node node = new Node(e);
  4. node.next = head;
  5. head = node;
  6. */
  7. //简写
  8. head.next = new Node(e, head);
  9. //维护链表长度
  10. size++;
  11. }

某个位置插入元素:

  • 情况1: 链表中间的节点,先找到相应位置前一个节点,然后创建新节点,插入

  • 情况2: 如果是第一个节点,那么是不存在前一个节点的。直接用 addFirst 的方式

  1. //指定的 index 位置添加元素 (先要找到 index 前一个位置)
  2. // index 从 0 ~ size-1
  3. public void add(int index, E e) {
  4. // 索引有问题
  5. if (index < 0 || index > size) { //当 index == size 时,表示在末尾添加
  6. throw new IllegalArgumentException("Add Failed, Illegal index");
  7. }
  8. if (index == 0) {
  9. addFirst(e);
  10. } else {
  11. Node prev = head;
  12. //找到指定位置前一个节点
  13. for (int i = 0; i < index - 1; i++) {
  14. prev = prev.next;
  15. }
  16. //创建一个新节点
  17. /*Node node = new Node(e);
  18. node.next = prev.next;
  19. prev.next = node;*/
  20. //简写
  21. prev = new Node(e, prev.next);
  22. size++;
  23. }
  24. }

(可以看到上面确实是区分不同的情况了的)

此时在末尾添加元素,即 index = size 的位置添加,直接调用 addLast 即可:

  1. //在末尾添加元素
  2. public void addLast(E e){
  3. add(size, e);
  4. }

头结点优化

不着急往后探索,这里先把头节点优化一下,即加入 dummy head,统一整个操作流程。

上面的操作 add ,由于链表头结点 head 并没有前面一个节点,所以插入的时候确实要特殊一些。(如果第一个节点之前有节点,那么整个操作就统一了)

优化方法,在头结点前面添加一个 虚拟节点,即不存储任意元素的节点。

内部机制,用户(client) 不知道虚拟节点的存在。(只是为了方便逻辑操作)。

相关修改:

构造函数需要修改,初始化 LinkedList 的时候就要创建一个节点

  1. public LinkedList1() {
  2. dummyHead = new Node(null, null);
  3. size = 0;
  4. }

添加元素可以统一用 add,然后让 addFirst 和 addLast 调用 add 方法即可。

  1. //指定的 index 位置添加元素 (先要找到 index 前一个位置)
  2. // index 从 0 ~ size-1
  3. public void add(int index, E e) {
  4. // 索引有问题
  5. if (index < 0 || index > size) { //当 index == size 时,表示在末尾添加
  6. throw new IllegalArgumentException("Add Failed, Illegal index");
  7. }
  8. //因为在实际 index 取值范围内,总能找到相关节点的前一个节点
  9. Node prev = dummyHead;
  10. //找 index 之前的节点
  11. for(int i = 0; i < index; i++){
  12. prev = prev.next;
  13. }
  14. prev = new Node(e, prev.next);
  15. size++;
  16. }
  17. //头部插入
  18. public void addFirst(E e) {
  19. add(0, e);
  20. }
  21. //在末尾添加元素
  22. public void addLast(E e){
  23. add(size, e);
  24. }

虚拟头结点的引入,方便了其他许多链表的操作(只要涉及类似的遍历查找)

获取操作

  1. //获取某元素
  2. public E get(int index) {
  3. //先检查索引的合法性
  4. if(index<0 || index > size-1) {
  5. throw new IllegalArgumentException("Get Failed, Illegal index");
  6. }
  7. // 和前面找 index 节点前一个节点不同(那里是从第一个节点前面的虚拟节点开始)
  8. // 这里就要找 index 节点,索引从 dummyHead.next 开始,即真正的第一个节点开始
  9. Node ret = dummyHead.next;
  10. for(int i =0; i < index; i++) {
  11. ret = ret.next;
  12. }
  13. return ret.e;
  14. }

获取第一个元素,最后一个:

  1. //获取第一个
  2. public E getFirst() {
  3. return get(0);
  4. }
  5. //获取最后一个
  6. public E getLast() {
  7. return get(size -1);
  8. }

修改元素

把 index 位置的元素修改为 E。

(找到节点,然后替换里面的元素 e)

  1. public void set(int index, E e) {
  2. //先检查索引的合法性
  3. if (index < 0 || index > size - 1) {
  4. throw new IllegalArgumentException("Get Failed, Illegal index");
  5. }
  6. //找到节点,然后替换里面的元素
  7. Node curr = dummyHead.next;
  8. for (int i = 0; i < index; i++) {
  9. curr = curr.next;
  10. }
  11. curr.e = e;
  12. }

查找元素

一直遍历到元素末尾,然后寻找尾巴。

  1. //查找元素
  2. public boolean contains(E e) {
  3. Boolean ret = false;
  4. //在 size 范围内遍历查找
  5. Node curr = dummyHead.next;
  6. /*for(int i=0; i<size; i++){
  7. if(curr.e.equals(e)){
  8. ret = true;
  9. break;
  10. }
  11. curr = curr.next;
  12. }*/
  13. //其实可以用 while 循环 (多判断一次 size 位置)
  14. while(curr != null) {
  15. //当前节点是有效节点
  16. if(curr.e.equals(e)){
  17. ret = true;
  18. break;
  19. }
  20. curr = curr.next;
  21. }
  22. return ret;
  23. }

遍历打印

多种循环的写法:

  1. //打印方法
  2. @Override
  3. public String toString() {
  4. StringBuilder res = new StringBuilder();
  5. //从头遍历到尾巴
  6. /*Node curr = dummyHead.next;
  7. while(curr != null) {
  8. res.append(curr + "->");
  9. curr = curr.next;
  10. }*/
  11. //简写
  12. for(Node curr = dummyHead.next; curr != null; curr = curr.next) {
  13. res.append(curr + "->");
  14. }
  15. res.append("null");
  16. return res.toString();
  17. }

简单测试一下:

  1. //测试元素
  2. public static void main(String[] args) {
  3. LinkedList1<Integer> linkedlist = new LinkedList1<>();
  4. //放入元素 0, 1, 2, 3, 4
  5. for(int i =0; i < 5; i++) {
  6. linkedlist.addFirst(i); //O(1)
  7. System.out.println(linkedlist);
  8. }
  9. System.out.println(linkedlist);
  10. //尝试插入一个元素
  11. linkedlist.add(1, 100); // 4, 100, 2, 3, 1, 0, null
  12. System.out.println(linkedlist);
  13. }

打印结果:

  1. 0->null
  2. 1->0->null
  3. 2->1->0->null
  4. 3->2->1->0->null
  5. 4->3->2->1->0->null
  6. 4->3->2->1->0->null
  7. 4->100->3->2->1->0->null

删除元素

还是要 先找到前一个节点 。(也就是说还是借助虚拟头结点)

简单一句话,然 delNode 和原来的链表脱离。(delNode 置空非必须)

编码实现:

  1. //删除元素
  2. public E remove(int index){
  3. if (index < 0 || index > size - 1) {
  4. throw new IllegalArgumentException("Delete Failed, Illegal index");
  5. }
  6. //找到相关节点的前一个节点
  7. Node curr = dummyHead;
  8. for(int i = 0; i < index; i++) {
  9. curr = curr.next;
  10. }
  11. Node delNode = curr.next;
  12. //删除
  13. curr.next = delNode.next;
  14. delNode.next = null;
  15. //必须维护 size
  16. size--;
  17. return delNode.e;
  18. }
  19. //删除第一个节点
  20. public E removeFirst() {
  21. return remove(0);
  22. }
  23. //删除最后一个节点
  24. public E removeLast() {
  25. return remove(size-1);
  26. }
  27. //删除指定元素
  28. public void removeElem(E e) {
  29. //从 dummyHead 开始找,找到就删除,否则就不删除
  30. Node curr = dummyHead;
  31. boolean found = false;
  32. while (curr.next != null) {
  33. if (curr.next.e.equals(e)) {
  34. found = true;
  35. //删除操作
  36. Node delNode = curr.next;
  37. curr.next = delNode.next;
  38. delNode.next = null;
  39. size--;
  40. break;
  41. }
  42. curr = curr.next;
  43. }
  44. if (!found) {
  45. throw new RuntimeException("要删除的元素不存在");
  46. }
  47. }

测试一下:

  1. //测试元素
  2. public static void main(String[] args) {
  3. LinkedList1<Integer> linkedlist = new LinkedList1<>();
  4. //放入元素 0, 1, 2, 3, 4
  5. for(int i =0; i < 5; i++) {
  6. linkedlist.addFirst(i); //O(1)
  7. System.out.println(linkedlist);
  8. }
  9. System.out.println(linkedlist);
  10. //尝试插入一个元素
  11. linkedlist.add(1, 100); // 4, 100, 2, 3, 1, 0, null
  12. System.out.println(linkedlist);
  13. //尝试删除 index = 1 位置的 100
  14. linkedlist.remove(1);
  15. System.out.println(linkedlist); //4->3->2->1->0->null
  16. //删除最后一个元素 0
  17. linkedlist.removeLast();
  18. System.out.println(linkedlist); //4->3->2->1->null
  19. //删除第一个元素
  20. linkedlist.removeFirst();
  21. System.out.println(linkedlist); //3->2->1->null
  22. //删除指定元素
  23. linkedlist.removeElem(3);
  24. linkedlist.removeElem(1);
  25. //linkedlist.removeElem(null);
  26. System.out.println(linkedlist);
  27. }

时间复杂度

链表虽然不移动元素,但是涉及到从前往后找到(检查)相应的位置/元素。

添加操作:

  • addFirst(), O(1) 因为采用的是头插法
  • addLast(), O(n) 涉及循环遍历到尾部,然后插入
  • add(), O(n) 其实是 O(n/2) 即 O(n)

删除操作:

同上。

修改操作: O(n)。

查找操作:

get(), contains(), find() 一律 O(n),因为并不支持随机访问呀。

单链表应用

链栈

上面也说了,如果只在链表头增删时,它的整体复杂度是 O(1),这不正好用于栈么?

  • 简单记忆一下,同侧操作
  • 栈的底层实现是链表,而不是动态数组了
  1. package stack;
  2. import linkedlist.LinkedList1; //这是有 dummy head优化的链表实现
  3. public class LinkedListStack<E> implements Stack<E>{
  4. //链栈内部实际采用链表存储
  5. private LinkedList1<E> list;
  6. public LinkedListStack(){
  7. list = new LinkedList1<>();
  8. }
  9. @Override
  10. public boolean isEmpty() {
  11. return list.isEmpty();
  12. }
  13. @Override
  14. public int getSize() {
  15. return list.getSize();
  16. }
  17. @Override
  18. public E pop() {
  19. return list.removeFirst();
  20. }
  21. @Override
  22. public E peek() {
  23. return list.getFirst();
  24. }
  25. @Override
  26. public void push(E e) {
  27. list.addFirst(e);
  28. }
  29. @Override
  30. public String toString() {
  31. StringBuilder res = new StringBuilder();
  32. res.append("Stack: top [");
  33. res.append(list);
  34. res.append("]");
  35. return res.toString();
  36. }
  37. public static void main(String[] args) {
  38. LinkedListStack<Integer> stack = new LinkedListStack<>();
  39. //放入元素 0, 1, 2, 3, 4
  40. for(int i =0; i < 5; i++) {
  41. stack.push(i); //O(1)
  42. System.out.println(stack);
  43. }
  44. System.out.println(stack);
  45. System.out.println(stack.peek());
  46. //弹出一个元素
  47. stack.pop();
  48. System.out.println(stack);
  49. }
  50. }

测试结果:

  1. Stack: top [0->null]
  2. Stack: top [1->0->null]
  3. Stack: top [2->1->0->null]
  4. Stack: top [3->2->1->0->null]
  5. Stack: top [4->3->2->1->0->null]
  6. Stack: top [4->3->2->1->0->null]
  7. 4
  8. Stack: top [3->2->1->0->null]

和数组实现的栈的不同,数组是在尾巴上插入,可能涉及动态扩容,均摊复杂度是 O(1),而链栈始终就是O(1)。

  • 但是 linkedlist 的 new 操作时非常耗时的 (特别是大量对象创建)
  • 真实运行结果是不确定的 (ArrayStack VS LinkedListStack),因为数量级一致

链队列

因为队列涉及头和尾的操作,所以如果用链表,那一般要添加一个尾指针。

因为 head 和 tail 都是指针,所以入队和出队相当于改变指向那么简单,但谁做头谁做尾巴?(相当于 head, tail 指针往哪个方向移动)

如果要删除 tail 元素并不容易(无法做到O(1)),因为删除元素要知道 tail 前面一个元素。但是 tail 增加,则可以直接添加。(head不用管, 它的增删都比较容易)

所以结论显而易见:

  • tail 用作队尾 (即用于增加元素, tail 指针右移)
  • head 用作队首 (删除元素,出队)

此时还需要 dummy head 么,分析上面的 tail, head,显然不需要操作统一了,所以不需要哑结点。

这里就不复用 LinkedList 了,而是专门再在内部实现链式存储。(Node 内部类还是需要的)

特别注意:

  • 链表为空的情况
  • 只有一个元素的情况,此时即便是出队,也要 head = tail = null;
  1. //内部采用链式存储的队列
  2. public class LinkedQueue<E> implements Queue<E> {
  3. //定义一个内部类,作为节点类
  4. private class Node {
  5. public E e;
  6. public Node next; //便于 LinkedList 访问
  7. public Node(E e, Node next) {
  8. this.e = e;
  9. this.next = next;
  10. }
  11. public Node(E e) {
  12. this(e, null);
  13. }
  14. public Node() {
  15. this(null, null);
  16. }
  17. @Override
  18. public String toString() {
  19. return e.toString();
  20. }
  21. }
  22. private Node head, tail;
  23. private int size;
  24. //构造器
  25. public LinkedQueue() {
  26. head = tail = null;
  27. size = 0;
  28. }
  29. @Override
  30. public boolean isEmpty() {
  31. return size == 0;
  32. }
  33. @Override
  34. public int getSize() {
  35. return size;
  36. }
  37. @Override
  38. public E dequeue() {
  39. //出队操作,在队首
  40. //没有元素肯定就不能出队
  41. if (isEmpty()) {
  42. //或者 head = null
  43. throw new IllegalArgumentException("Cannot dequeue from an empty queue");
  44. }
  45. //正常出队,提取 head
  46. Node retNode = head; //tail,考虑只有一个元素的队列
  47. head = retNode.next;
  48. retNode.next = null;//游离对象
  49. //仅在只有一个元素的队列,需要维护 tail
  50. if (head == null) {
  51. tail = null;
  52. }
  53. size--;
  54. return retNode.e;
  55. }
  56. @Override
  57. public E getFront() {
  58. if (isEmpty()) {
  59. //或者 head = null
  60. throw new IllegalArgumentException("Cannot dequeue from an empty queue");
  61. }
  62. return head.e; // 返回队首即可
  63. }
  64. @Override
  65. public void enqueue(E e) {
  66. //入队操作,在尾部操作
  67. if (tail == null) { //说明此时队列是空的,即 tail 和 head 都为空
  68. tail = new Node(e);
  69. head = tail;
  70. } else {
  71. tail.next = new Node(e);
  72. tail = tail.next;
  73. }
  74. size++;
  75. }
  76. @Override
  77. public String toString() {
  78. StringBuilder res = new StringBuilder();
  79. res.append("Queue: front[ ");
  80. for(Node curr = head; curr != null; curr = curr.next){
  81. res.append(curr.e + "->");
  82. }
  83. res.append("null ] tail");
  84. return res.toString();
  85. }
  86. public static void main(String[] args) {
  87. LinkedQueue<Integer> queue = new LinkedQueue<>();
  88. //存储 11 个元素看看
  89. for(int i=0; i<11; i++){
  90. queue.enqueue(i);
  91. System.out.println(queue); // 在 10 个元素满的时候回扩容
  92. }
  93. //出队试试
  94. System.out.println("------出队");
  95. queue.dequeue();
  96. System.out.println(queue);
  97. }
  98. }

运行结果如下:

  1. Queue: front[ 0->null ] tail
  2. Queue: front[ 0->1->null ] tail
  3. Queue: front[ 0->1->2->null ] tail
  4. Queue: front[ 0->1->2->3->null ] tail
  5. Queue: front[ 0->1->2->3->4->null ] tail
  6. Queue: front[ 0->1->2->3->4->5->null ] tail
  7. Queue: front[ 0->1->2->3->4->5->6->null ] tail
  8. Queue: front[ 0->1->2->3->4->5->6->7->null ] tail
  9. Queue: front[ 0->1->2->3->4->5->6->7->8->null ] tail
  10. Queue: front[ 0->1->2->3->4->5->6->7->8->9->null ] tail
  11. Queue: front[ 0->1->2->3->4->5->6->7->8->9->10->null ] tail
  12. ------出队
  13. Queue: front[ 1->2->3->4->5->6->7->8->9->10->null ] tail

到这里,单链表基本探究完毕了。

其他链表

下面说的这些链表其实也很常用,但是个人要去实现的话,就费事儿啊

(除非你是大学教师,或者学生,或者自由作家,有的是时间耐得住寂寞,磨啊)

双向链表

这个维护代价其实有点大,有点就是节点之间的联系更加方便了。(单链表时也会维护尾指针)

  • 比如尾端删除,不用从头开始找尾端前一个元素了,避免了 O(n) 复杂度

没有对比就没有伤害,要找我前一个节点是吧,直接给你(不要循环了)。其他操作则没有太多变化(需要头结点优化)。由于有额外的变量需要维护,所以并不见得简单。

  1. class Node {
  2. E e;
  3. Node prev, next;
  4. }

循环链表

jdk 中 linkedlist 貌似经过一阵子去环优化,可能,因为不要环效率也不差。

循环链表一般都是基于双向链表的

不用画图了,直接认为尾部元素直接指向 dummy head 即可。

此时不需要 tail,因为在 dummyHead 的前面添加一个元素,就相当于在结尾添加元素了。

(引入的环会导致操作有些许变化,比如遍历)

数组链表

  • 数组中除了存储值,还存储了下一个节点的索引,那么就相当于数组链表了。
  • 不依赖数组本身的 index,而依赖于自身存储的数字索引。

有点儿类似于数据库存储设计中的无限级字段,即某个元素要存储其父元素位置(parentId)。


毕竟还是基础数据结构,没有太复杂;这种 link 的思想用于树(二叉树,多叉树)很平常。

老规矩,代码参考的话,我放在了 gayhub, FYI。


纯数据结构Java实现(3/11)(链表)的更多相关文章

  1. 纯数据结构Java实现(5/11)(Set&Map)

    纯数据结构Java实现(5/11)(Set&Map) Set 和 Map 都是抽象或者高级数据结构,至于底层是采用树还是散列则根据需要而定. 可以细想一下 TreeMap/HashMap, T ...

  2. 纯数据结构Java实现(0/11)(开篇)

    为嘛要写 本来按照我的风格,其实很不喜欢去写这些细节的东西,因为笔记上直接带过了. 本来按照我的风格,如果要写,那也是直接上来就干,根本不解释这些大纲,参考依据. 本来按照我的风格,不想太显山露水,但 ...

  3. 纯数据结构Java实现(1/11)(动态数组)

    我怕说这部分内容太简单后,突然蹦出来一个大佬把我虐到哭,还是悠着点,踏实写 大致内容有: 增删改查,泛型支持,扩容支持,复杂度分析.(铺垫: Java语言中的数组) 基础铺垫 其实没啥好介绍的,顺序存 ...

  4. 纯数据结构Java实现(2/11)(栈与队列)

    栈和队列的应用非常多,但是起实现嘛,其实很少人关心. 但问题是,虽然苹果一直宣传什么最小年龄的编程者,它试图把编程大众化,弱智化,但真正的复杂问题,需要抽丝剥茧的时候,还是要 PRO 人士出场,所以知 ...

  5. 纯数据结构Java实现(6/11)(二叉堆&优先队列)

    堆其实也是树结构(或者说基于树结构),一般可以用堆实现优先队列. 二叉堆 堆可以用于实现其他高层数据结构,比如优先队列 而要实现一个堆,可以借助二叉树,其实现称为: 二叉堆 (使用二叉树表示的堆). ...

  6. 纯数据结构Java实现(4/11)(BST)

    个人感觉,BST(二叉查找树)应该是众多常见树的爸爸,而不是弟弟,尽管相比较而言,它比较简单. 二叉树基础 理论定义,代码定义,满,完全等定义 不同于线性结构,树结构用于存储的话,通常操作效率更高.就 ...

  7. 纯数据结构Java实现(10/11)(2-3树&红黑树)

    欢迎访问我的自建博客: CH-YK Blog.

  8. 纯数据结构Java实现(9/11)(AVL)

    欢迎访问我的自建博客: CH-YK Blog.

  9. 纯数据结构Java实现(8/11)(Trie)

    欢迎访问我的自建博客: CH-YK Blog.

随机推荐

  1. redis集群(单机6节点实现)

    Redis集群搭建与简单使用 1.介绍安装环境与版本: 1)Redis使用的是Redis-3.2.8版本. 2)用一台虚拟机模拟6个节点,三个master节点,三个slave节点.虚拟机使用CentO ...

  2. mysql -h139.129.205.80 -p test_db_dzpk < db.dump

    mysqldump -h139.129.205.80 -uroot -p db_a > db_dzpk.dump mysql -h139.129.205.80 -p test_db< db ...

  3. 个人永久性免费-Excel催化剂功能第36波-新增序列函数用于生成规律性的循环重复或间隔序列

    啃过Excel函数的表哥表姐们,一定对函数的嵌套.数组公式等高级的应用有很深的体会,威力是大,但也烧死不少脑细胞,不少人就在这样的绕函数中光荣地牺牲了,走向从入门到放弃.Excel催化剂的创立,初衷就 ...

  4. Mysql 时间相关

    -- 当前时间SELECT NOW(), SYSDATE(), CURRENT_TIMESTAMP(), LOCALTIME(), LOCALTIMESTAMP();SELECT CURDATE(), ...

  5. VUE动态(自动)Loading【绑定到URL】,同页面多个Loading互不冲突

    需求来源:当使用React时,使用 umi loading 很方便,页面对http请求发生改变时,也会自动改变loading的相关状态(true/false) 对VUE插件进行找寻,发现没找到合适内容 ...

  6. 【MySQL】(七)事务

    我的个人博客 http://www.haxianhe.com/ 数据库系统引入事务的主要目的:事务会把数据库从一种状态转换为另一种一致状态.在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所 ...

  7. VS Code 安装 LeetCode 插件

    练习算法绕不开的一个网站就是力扣,很多小伙伴为了拿到大厂 offer,刷题都刷到吐了. 然而如果直接在 LeetCode 上写代码,那是很痛苦的一件事,那就相当于用 txt 写代码一样,没有 IDE ...

  8. css清除select默认的样式

    select在firefox与chrome的显示是不一样的,我们一般选择通过css清除掉css的默认样式,然后再增添自定义的样式来解决,css我们一般用这么几行代码来清除默认样式: 1 select ...

  9. 【Intellij】Hot Swap Failed & class reloaded

    用 Intellij IDEA 编译程序时遇到了这个问题,如下图所示: 对结果貌似没什么影响,但暂时没找到出现这个情况的原因……

  10. poj 2503 Babelfish(字典树或map或哈希或排序二分)

    输入若干组对应关系,然后输入应该单词,输出对应的单词,如果没有对应的输出eh 此题的做法非常多,很多人用了字典树,还有有用hash的,也有用了排序加二分的(感觉这种方法时间效率最差了),这里我参考了M ...