【算法通关村】链表基础经典问题解析
【算法通关村】链表基础&经典问题解析
一.什么是链表
链表是一种通过指针将多个节点串联在一起的线性结构,每一个节点(结点)都由两部分组成,一个是数据域(用来存储数据),一个是指针域(存放指向下一个节点的地址),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点。普通的单链表就是只给你一个指向链表头的指针head,如果想访问其他元素,就只能从head开始一个个向后找,遍历链表,最终会在访问尾结点之后如果继续访问,就会返回null。
从上图看,是不是觉得链表的存储方式有点像数组?实际上链表的各节点在内存中的存储次序是混乱的,它是通过指针域中的指针链接在内存中各个节点,即每个节点中都有记录它所链接的节点的地址。
二.链表的创建
- 要想创建链表,我们就必须先构建链表中的每个节点:
public class Node {public Object data; // 存储数据public Node next; // 存储所连接的下一个节点的地址Node(Object data) {this.data = data;this.next = null;}
}
- 接下来我们就可以创建链表结构:
public class Code {public static void main(String[] args) {Node head = new Node(1); // 创建头节点Node node1 = new Node(2); // 创建节点1head.next = node1; // 令头节点指向节点1Node node2 = new Node(3); // 创建节点2node1.next = node2; // 令节点1指向节点2Node node3 = new Node(4); // 创建节点3node2.next = node3; // 令节点2指向节点3Node node4 = new Node(5); // 创建节点4node3.next = node4; // 令节点3指向节点4}
}
- 通过debug调试我们可以看到已经成功创建出了链表结构:
三.链表的增删改查
(1) 遍历链表
由于链表的各节点在内存中的存储次序是混乱的,我们一般只知道它的头节点,因此对于单链表,不管进行什么操作,一定是从头开始逐个向后访问,直到null为止。所以操作之后是否还能找到表头节点非常重要,千万不要只顾当前位置而将标记表头的指针丢掉了。
/*** 遍历链表数据域内容并返回链表长度** @param head 链表头节点* @return 链表的长度*/public static int getLengthAndShow(Node head) {int length = 0;Node node = head; // 拷贝一份头节点地址进行操作,避免头节点指针丢失while (node != null) {System.out.print(node.data + "->");length++;node = node.next; // 根据指针域找到下一个节点}return length;}
示意图如下:
(2) 链表的插入
单链表的插入操作需要考虑三种情况:首部插入、中部插入和尾部插入。
(2.1) 首部插入
链表的首部插入非常简单,我们只需要将待插入节点的指针域指向头节点,然后更新头节点为待插入的节点即可。
/*** 插入节点在链表首部** @param head 头节点* @param insertNode 待插入的节点* @return 新的头节点*/public static Node insertTop(Node head, Node insertNode) {insertNode.next = head; // 令插入的节点指向头节点return insertNode; // 返回新的头节点}
示例图如下:
(2.2) 中部插入
链表的中部插入会相对麻烦一点,首先我们必须先遍历找到要插入的位置前驱节点,然后在当前位置与前驱结点和后继结点进行连接。
为什么要找到前驱节点而不是待插入的位置呢?这是由于链表的结构使然,链表的遍历就如时间的流逝,只能向后,无法回溯。如果我们遍历到待插入的位置,若未记录前驱节点,那么我们将无法再获取到前驱节点!
/*** 插入节点在链表中部** @param head 头节点* @param insertNode 待插入的节点* @param pos 插入的位置(从0开始)* (此处省略pos位于首部和尾部的处理,只考虑中部)*/public static void insertMiddle(Node head, Node insertNode, int pos) {// 1.找到插入位置的前驱节点while (pos > 1) {head = head.next;pos--;}// 2.先令待插入的节点指向后继节点insertNode.next = head.next;// 3.再令前驱节点指向待插入的节点head.next = insertNode;}
示意图如下:
为什么要先令待插入的节点指向后继节点再让前驱节点指向待插入的节点呢?
这是由于每个节点都只有一个next(即只能保存一份地址),倘若我们先令前驱节点指向待插入的节点,那么原本前驱节点与后继节点之间的链接将会断开,我们将丢失后续的节点。
(2.3) 尾部插入
尾部插入也是比较简单的,我们只需要遍历到尾部节点,再令尾部节点指向待插入的节点即可。
/*** 插入节点在链表尾部** @param head 头节点* @param insertNode 待插入的节点*/public static void insertEnd(Node head, Node insertNode) {// 1.找到尾部位置while (head.next != null) {head = head.next;}// 2.令尾部节点指向待插入的节点head.next = insertNode;}
示意图如下:
(3) 链表的删除
与插入情况类似,单链表的删除操作也需要考虑三种情况:首部删除、中部删除和尾部删除。
(3.1) 首部删除
删除头节点非常的简单,只需要将当前头节点更新为当前头节点指针域所指向的地址即可。 一般只要执行head=head.next
即可。
/*** 删除的节点在链表首部** @param head 头节点* @return 新的头节点*/public static Node removeTop(Node head) {head = head.next; // 将当前头节点更新为当前头节点指针域所指向的地址return head;}
示意图如下:
(3.2) 中部删除
删除中间结点与插入类似,也需要我们先找到待删除位置的前驱节点,然后再将前驱节点的指针域改为后继节点指针域中所保存的地址。
/*** 删除的节点在链表中部** @param head 头节点* @param pos 插入的位置(从0开始)* (此处省略pos位于首部和尾部的处理,只考虑中部)*/public static void removeMiddle(Node head, int pos) {// 1.找到删除位置的前驱节点while (pos > 1) {head = head.next;pos--;}// 2.将前驱节点的指针域改为后继节点指针域中所保存的地址head.next = head.next.next;}
示意图如下:
(3.3) 尾部删除
要删除尾部节点同样要找到尾部节点的前驱节点,如果我们知道尾部节点的位置,那么尾部节点的删除其实与中部节点的删除方法一致,否则我们便需要通过cur.next.next == null
来判断了。
/*** 删除的节点在链表尾部** @param head 头节点*/public static void removeEnd(Node head) {// 1.找到删除位置的前驱节点while (head.next.next != null) {head = head.next;}// 2.将前驱节点的指针域改为后继节点指针域中所保存的地址head.next = head.next.next; // 或者 head.next = null}
示意图如下:
四.双向链表
(1) 基本概念
上面我们所讲解的链表都为单向链表,即链表之间是单向连接的,都由前一个节点指向后一个节点,我们只能从某一个节点开始获取到它后面的节点,而无法获取到它前面的节点。
双向链表顾名思义就是既可以向前,也可以向后,即链表之间是相互连接的,我们即可从某一个节点开始获取到它后面的节点,也可以获取到它前面的节点。有两个指针的好处自然是移动元素更方便。
(2) 创建
双向链表的节点定义如下:
class DoubleListNode {public int data; //数据域public DoubleListNode next; //指向下一个结点public DoubleListNode prev; //指向上一个结点public DoubleNode(int data) {this.data = data;}
}
其遍历与单向链表非常相似便不赘述。
(3) 插入
(3.1) 首尾插入
在首部和尾部插入比较容易且类似:
// 首部插入
public void insertFirst(int data) {DoubleNode newDoubleNode = new DoubleNode(data);if (first == null) {last = newDoubleNode;} else {// 将第一个结点的prev指向newNodefirst.prev = newDoubleNode; // 将还未插入节点结点的next指向第一个节点newDoubleNode.next = first;}//将新结点赋给first成为第一个结点first = newDoubleNode;
}
// 尾部插入
public void insertLast(int data) {DoubleNode newDoubleNode = new DoubleNode(data);if (first == null) {first = newDoubleNode; } else {// 将最后一个结点的next指向newNodelast.next = newDoubleNode; // 将还未插入节点结点的prev指向最后一个节点newDoubleNode.prev = last;}// 将新结点赋给last成为最后一个结点last = newDoubleNode;
}
示意图如下:
(3.2) 中间插入
由于双向链表的特性,中间插入节点方法不唯一,总之都需要找到要插入位置的前驱节点或者后继节点。
(4) 删除
(4.1) 首尾删除
在首部和尾部删除比较容易且类似:
//删除首元素
public void deleteFirst() {//若链表只有一个结点,删除后链表为空,将last指向nullif (first.next == null) { last = null;} else {//若链表有两个及以上的结点 ,因为是头部删除,则first.next将变成第一个结点,其previous将变成nullfirst.next.prev = null; //将first.next赋给firstfirst = first.next; }
}//从尾部删除结点
public DoubleNode deleteLast() {//如果链表只有一个结点,则删除以后为空表,last指向nullif (first.next == null) { first = null;} else {//将上一个结点的next域指向nulllast.prev.next = null; //上一个结点称为最后一个结点,last指向它last = last.prev; }
}
示意图如下:
(4.2) 中间删除
由于双向链表的特性,中间删除节点的方法也不唯一。
示意图如下:
五.经典问题分析
(1) 链表相交
力扣链接:链表相交
(1.1) Hash
我们可以先将任意一个链表节点全部加入到集合中,再遍历另一个链表比较该节点是否包含在集合中,如果包含则代表它就是第一个公共子节点,中止循环,否则表示两个链表没有公共子节点。
public Node getIntersectionNode(Node headA, Node headB) {HashSet<Node> nodes = new HashSet<>(); Node ans = null;// 1.将其中一个链表节点全部加入到集合while (headA != null) {nodes.add(headA);headA = headA.next;}// 2.遍历另一个链表while (headB != null) {// 3.判断该节点是否包含在集合中if (nodes.contains(headB)) {// 4.如果包含则代表该节点就是第一个公共子节点ans = headB;// 5.中止循环break;}headB = headB.next;}return ans;}
(1.2) 栈
如果两个链表有公共子节点,那么它的第一个公共子节点以后的节点都应该是相同的,我们可以先将两个链表的所有节点都分别压入栈中,再依次出栈进行比较,如果相等则继续出栈,一直找到最晚出栈的那一组,可以得知最后出栈的一对相等节点为第一个公共子节点。
public Node getIntersectionNode(Node headA, Node headB) {Node ans = null;Stack<Node> aS = new Stack<>();Stack<Node> bS = new Stack<>();// 1.将两个链表分别压入栈中while (headA != null) {aS.push(headA);headA = headA.next;}while (headB != null) {bS.push(headB);headB = headB.next;}// 2.依次出栈进行比较while (aS.size() > 0 && bS.size() > 0) {Node aN = aS.pop();Node bN = bS.pop();// 3.当碰到第一对不相等的节点时,可以得出上一对出栈的节点为第一个公共子节点if (!aN.equals(bN)) {break;}ans = aN;}return ans;}
(1.3) 双指针
由于第一个公共子节点可能距离两条链表的头节点不同距离,我们可以先计算出两个链表的长度差,令较长的链表先移动长度差距离,使得两个链表的起始同步遍历位置与第一个公共子节点距离相同,然后我们再同步遍历两条链表,比较各个节点,判断是否相等,当碰到第一个相等的子节点则表示第一个公共子节点。
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {if(pHead1==null || pHead2==null){return null;}// 1.创建操作新节点,避免丢失头节点地址ListNode cur1=pHead1;ListNode cur2=pHead2;int l1=0,l2=0;// 2.分别统计两个链表的长度while(cur1!=null){cur1=cur1.next;l1++;}while(cur2!=null){cur2=cur2.next;l2++;}// 3.计算两条链表的长度差int sub=l1>l2?l1-l2:l2-l1;// 重新获取头节点地址cur1=pHead1;cur2=pHead2;// 4.令较长的链表先走sub步if(l1>l2){int a=0;while(a<sub){cur1=cur1.next;a++;} }if(l1<l2){int a=0;while(a<sub){cur2=cur2.next;a++;} }// 5.同时遍历两个链表,当碰到第一个相等的子节点则表示第一个公共子节点while(cur2!=cur2){cur2=cur2.next;cur1=cur1.next;} return cur1;
}
(2) 回文链表
力扣链接:回文链表
(2.1) 集合+双指针
我们可以先将链表中的所有数据添加到集合中,再针对集合同时从首尾遍历比较数据是否相同。
public boolean isPalindrome(ListNode head) {// 1.全部添加到集合中ArrayList<Integer> list = new ArrayList<>();while (head != null) {list.add(head.val);head = head.next;}// 2.左右同时开始遍历比较是否相同int last = list.size() - 1;int start = 0;while (start <= last) {// 3.如果发现不同则代表不是回文数组if (!Objects.equals(list.get(start), list.get(last))) {return false;}start++;last--;}return true;}
(2.2) 栈
我们也可以先将链表中的所有数据添加到栈中,再次遍历链表同时进行出栈,比较数据是否相同。
public boolean isPalindrome(ListNode head) {ListNode temp = head;Stack<Integer> stack = new Stack();// 1.把链表节点的值存放到栈中while (temp != null) {stack.push(temp.val);temp = temp.next;}// 2.之后一边出栈,一遍比较while (head != null) {if (head.val != stack.pop()) {return false;}head = head.next;}return true;}
(3) 合并有序链表合集
(3.1) 合并两个有序链表
力扣链接:合并两个有序链表
题解:我们可以先创建一个新的链表用来保存结果,再逐个比较每个节点的数值,使用新的链表链接较小值节点,并令较小值节点链表指针移动,当有一方为null时,停止比较,令新链表链接上另一条链表。
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {// 创建一个虚拟头节点用于标识新链表的开始ListNode dummyNode = new ListNode(-1);// 重新创捷一个节点用于链接,避免新链表头节点指针丢失ListNode head = dummyNode;// 一直逐个进行比较,直到一条链表为空while (list1 != null && list2 != null) {// 连接上较小值节点,并使其链表指针后移一个if (list1.val > list2.val) {head.next = list2;list2 = list2.next;} else {head.next = list1;list1 = list1.next;}// 让新链表指针后移,随时准备连接新节点head = head.next;}// 至多一条链表为null,并且不为null的链表都为较大值,直接连接上即可head.next = list1 == null ? list2 : list1;// 虚拟节点仅用于链接,next才为真正有效的节点return dummyNode.next;}
(3.2) 合并k个有序链表
力扣链接:合并 K 个有序链表
目前我们只在此利用最暴力的解法,要求合并N个,我们是不是可以延伸上一题合并两条的解法,即将合并k条转换为两条两条合并逐渐合并?
public ListNode mergeKLists(ListNode[] lists) {ListNode dummyNode = null;// 拆分为两条两条开始合并for (ListNode list : lists) {dummyNode = mergeTwoLists(dummyNode, list);}return dummyNode;}public ListNode mergeTwoLists(ListNode list1, ListNode list2) {// 创建一个虚拟头节点用于标识新链表的开始ListNode dummyNode = new ListNode(-1);// 重新创捷一个节点用于链接,避免新链表头节点指针丢失ListNode head = dummyNode;// 一直逐个进行比较,直到一条链表为空while (list1 != null && list2 != null) {// 连接上较小值节点,并使其链表指针后移一个if (list1.val > list2.val) {head.next = list2;list2 = list2.next;} else {head.next = list1;list1 = list1.next;}// 让新链表指针后移,随时准备连接新节点head = head.next;}// 至多一条链表为null,并且不为null的链表都为较大值,直接连接上即可head.next = list1 == null ? list2 : list1;// 虚拟节点仅用于链接,next才为真正有效的节点return dummyNode.next;}
(4) 双指针链表应用
(4.1) 链表的中间结点
力扣链接:链表的中间结点
我们可以暴力的求出单链表的长度,即可轻松找到位于中间的节点。
这个问题用经典的快慢指针也可以轻松搞定,用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间。当链表长度为奇数时,快指针刚好移动到末尾节点,慢指针移动到中间节点;当链表长度为偶数时,快指针刚好移动到null节点,慢指针移动到中间的第二个节点节点;由于fast != null && fast.next != null
条件,相当于链表始终为一个奇数链表进行移动。
public ListNode middleNode(ListNode head) {ListNode fast = head;ListNode slow = head;// 当快指针移动到末尾时,慢指针恰好移动到中间while (fast != null && fast.next != null) {// 定义快指针走两格fast = fast.next.next;// 定义慢指针走一格slow = slow.next;}return slow;}
(4.2) 返回倒数第 k 个节点
力扣链接: 返回倒数第 k 个节点
与上一题类似,我们也可以使用快慢指针,不同的是我们需要先将fast 向后遍历到第k+1个节点, slow仍然指向链表的第一个节点,此时指针fast 与slow 二者之间刚好间隔 k 个节点。之后两个指针同步向后走,当 fast 走到链表的尾部空节点时,slow 指针刚好指向链表的倒数第k个节点。
public int kthToLast(ListNode head, int k) {ListNode slow = head;ListNode fast = head;// 先令快指针移动k步while (k-- > 0) {fast = fast.next;}// 当快指针移动到null时,慢指针刚好移动到倒数第k个节点while (fast != null) {// 再令快慢指针同时移动fast = fast.next;slow = slow.next;}return slow.val;}
(4.3) 旋转链表
力扣链接: 旋转链表
仔细观察可以发现旋转链表其实就是将最后n个元素移动到了链表前面,因此我们可以利用与上一题寻找倒数第k个节点类似的方法,寻找到第k+1个节点(为什么不是第k个呢?因为我们需要从第k+1个开始链接操作)一波寻找过后,刚好快指针指向链表末节点,而慢指针指向第k+1个节点,此时就可以非常方便的开始链接操作了。
public ListNode rotateRight(ListNode head, int k) {if (head == null || k == 0) {return null;}// 计算出链表的长度int num = 0;ListNode node = head;while (node != null) {num++;node = node.next;}// 定义快慢指针ListNode slow = head;ListNode fast = head;// 取模剔除无效循环k = k % num;while (k-- > 0) {fast = fast.next;}// 找到倒数第k+1个节点while (fast.next != null) {// 最后指向最后一个节点fast = fast.next;// 最后指向倒数第k+1个节点slow = slow.next;}// 开始进行链接操作// 令最后一个节点指向头节点fast.next = head;// 令头节点等于倒数第k个节点head = slow.next;// 令倒数第k+1个节点指向nullslow.next = null;// 返回新的头节点return head;}
(4.4) 是否为环形链表
力扣链接: 环形链表
我们可以用HashSet依次遍历记录每个节点同时判断是否以及出现过,如果出现过就代表为环,否则不是环,借此轻松解决。我们也可以用双指针的思想来解决,类似于两个速度不一样的人在操场上跑步,在跑了N圈之后,速度快的人一定会超圈速度慢的人。
public boolean hasCycle(ListNode head) {// 定义快慢指针ListNode slow = head, fast = head;// 如果为null说明是普通链表while (fast != null && fast.next != null) {// 如果不为null,快慢指针以不同速度行进slow = slow.next;fast = fast.next.next;// 两个指针相遇代表一定存在环if (slow == fast){return true;}}return false;}
(5) 删除链表元素
(5.1) 移除链表元素
力扣链接:移除链表元素
本题移除链表元素可能会碰到三种情况,即要删除的元素在链表的头部,中部,尾部,而删除头部与删除中部及尾部的处理方法不一致,如果我们单独处理将非常的繁琐,这时我们可以引入一个虚拟头节点指向头节点,这样我们是不是就可以剔除掉要删除的元素出现的头部的情况?接下来我们相当于只需要处理要删除的元素出现在链表的中部以及尾部,而这两种操作非常类似,我们仅需要找到要删除元素的前驱节点即可轻松完成。
public ListNode removeElements(ListNode head, int val) {// 定义虚拟头节点指向头节点ListNode dummyNode = new ListNode();dummyNode.next = head;// 定义指针记录当前位置用于比较以及存储前驱节点ListNode cur = dummyNode.next, pre = dummyNode;while (cur != null) {// 如果当前位置为val,则通过前驱节点删除// 当前位置后移,前驱节点记录指针不移动if (cur.val == val) {pre.next = cur.next;cur = cur.next;continue;}// 如果当前位置不为val,当前位置以及前驱位置指针同时移动cur = cur.next;pre = pre.next;}// 由于虚拟头节点仅用于排除头节点情况,并不存储数据,因此我们需要返回其nextreturn dummyNode.next;}
(5.2) 删除链表的倒数第 N 个结点
力扣链接: 删除链表的倒数第 N 个结点
这题与 返回倒数第 k 个节点 类似,不过我们要删除第k个节点则必须找到第k个节点的前驱节点才行,而移除元素可能会碰到三种情况,即要删除的元素在链表的头部,中部,尾部,而删除头部与删除中部及尾部的处理方法不一致,如果我们单独处理将非常的繁琐,这时我们同样可以引入一个虚拟头节点指向头节点,这样我们是不是就可以剔除掉要删除的元素出现的头部的情况?
public ListNode removeNthFromEnd(ListNode head, int n) {// 引入虚拟头节点使操作一致ListNode dummyNode = new ListNode();dummyNode.next = head;ListNode fast = dummyNode, slow = dummyNode;// 先让快指针走n步 while (n-- > 0) {fast = fast.next;}// 找到第 k + 1 个节点while (fast.next != null) {slow = slow.next;fast = fast.next;}// 进行移除操作slow.next = slow.next.next;return dummyNode.next;}
(5.3) 删除排序链表中的重复元素
力扣链接: 删除排序链表中的重复元素
由于给定的链表是排好序的,因此可以推出重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。具体地,我们从指针 cur 指向链表的头节点,随后开始对链表进行遍历。如果当前 cur 与cur.next 对应的元素相同,那么我们就将cur.next 从链表中移除;否则说明链表中已经不存在其它与cur 对应的元素相同的节点,因此可以将 cur 指向 cur.next。当遍历完整个链表之后,我们返回链表的头节点即可。
public ListNode deleteDuplicates(ListNode head) {// 为null直接返回if(head == null){return null;}ListNode temp = head;// 遍历到最后一个节点即可while (temp.next != null) {// 如果和下一位相等,则指向下下位,因为我们并不能确保当前元素和下下是否相等,// 还需要做一次判断,所以不能移动if (temp.val == temp.next.val) {temp.next = temp.next.next;} else {// 如果和下一位不相等,则移动到下一位temp = temp.next;}}return head;}
相关文章:

【算法通关村】链表基础经典问题解析
【算法通关村】链表基础&经典问题解析 一.什么是链表 链表是一种通过指针将多个节点串联在一起的线性结构,每一个节点(结点)都由两部分组成,一个是数据域(用来存储数据),一个是指针域&…...

【华为OD题库-056】矩阵元素的边界值-java
题目 给定一个N * M矩阵,请先找出M个该矩阵中每列元素的最大值,然后输出这M个值中的最小值 补充说明: N和M的取值范围均为: [0,100] 示例1: 输入: [[1,2],[3,4]] 输出: 3 说明: 第一列元素为:1和3,最大值为3 第二列元素为: 2和4,最…...

zabbix_sender——向zabbix交互的sdk
zabbix给我们提供了win32的交互方法。地址为src\zabbix_sender\win32\zabbix_sender.c zabbix_sender_send_values 函数声明为: int zabbix_sender_send_values(const char *address, unsigned short port, const char *source,const zabbix_sender_value_t *values...

JDBC概述(什么是JDBC?JDBC的原理、Mysql和Sql Server入门JDBC操作)
Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍JDBC概述(什么是JDBC?JDBC的原理、Mysql和Sql Server入门JDBC操作)简单知识以及部分理论知识 🍉欢迎点赞 👍 收藏 ⭐留言评论 📝私信必回哟😁 &am…...

【android开发-06】android中textview,button和edittext控件的用法介绍
1,TextView控件使用代码参考用例 在Android中,我们通常使用XML来定义布局和设置视图属性。以下是一个TextView的XML布局设置示例: 1.1在res/layout目录下的activity_main.xml文件中定义一个TextView: <TextView android:id…...

【JMeter】BeanShell了解基础知识
1. BeanShell是什么? 完全符合java语法的免费,可嵌入式的脚本语言 2.BeanShell用法 操作变量,使用vars内置对象 String 自定义变量名 vars.get("变量名") 从jmeter中获取变量值并定义一个变量接收vars.put(…...

Unity | 渡鸦避难所-0 | 创建 URP 项目并导入商店资源
0 前言 知识点零零碎碎,没有目标,所以,一起做游戏吧 各位老师如果有什么指点、批评、漫骂、想法、建议、疑惑等,欢迎留言,一起学习 1 创建 3D(URP)项目 在 Unity Hub 中点击新项目ÿ…...

SQL Server数据库部署
数据库简介 使用数据库的必要性 使用数据库可以高效且条理分明地存储数据,使人们能够更加迅速、方便地管理数据。数据库 具有以下特点。 》可以结构化存储大量的数据信息,方便用户进行有效的检索和访问。 》 可以有效地保持数据信息的一致性,…...

YOLOv8界面-目标检测+语义分割+追踪+姿态识别(姿态估计)+界面DeepSort/ByteTrack-PyQt-GUI
YOLOv8-DeepSort/ByteTrack-PyQt-GUI:全面解决方案,涵盖目标检测、跟踪和人体姿态估计 YOLOv8-DeepSort/ByteTrack-PyQt-GUI是一个多功能图形用户界面,旨在充分发挥YOLOv8在目标检测/跟踪和人体姿态估计/跟踪方面的能力,与图像、…...

MiniDumpWriteDump函数生成dmp文件
MiniDumpWriteDump函数生成dmp文件 一:概述二: CreateDump.h三:CreateDump.cpp四:main测试五:winDbg分析 一:概述 v2008及以上版本都可以用。 包含CreateDump.h,CreateDump.cpp文件,…...

【Qt开发流程】之事件系统1:事件系统描述及事件发生流程
Qt的事件系统 在Qt中,事件是对象,派生自抽象的QEvent类,它表示应用程序内部发生的事情或作为应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理,但它们与小部件特别相关。以下描述了在典型应用程序中…...

初始数据结构(加深对旋转的理解)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/rotate-array/submissions/ 与字…...

Android 13 - Media框架(18)- CodecBase
从这一节开始我们会回到上层来看ACodec的实现,在这之前我们会先了解ACodec的基类CodecBase。CodecBase.h 中除了声明有自身接口外,还定义有内部类 CodecCallback、BufferCallback,以及另一个基类 BufferChannelBase,接下来我们会一…...

关于微信公众号授权的几件事
背景 项目需要使用微信公众号发消息,然后就来接入这个微信授权啦,微信公众号发消息前提是还需要用户先关注公众号~ 微信授权是有点恶心的,真的真的需要先配置好环境,开发的话目前是可以使用测试号申请公众号使用测试号的appid~ …...

Docker监控Weave Scope的安装和使用
1.本地安装Weave Scope 1)创建文件夹。 mkdir /usr/local/bin/scope 2)从本地上传文件。 rz scope.bin以资源形式已上传到文章开篇。 3)修改scope.bin文件为可执行文件。 chmod 755 /usr/local/bin/scope/scope.bin 4)执行sco…...

为自己创建的游戏编程源码申请软件著作权详细流程(免费分享模板)
以为我这篇文章制作的游戏申请软件著作权为例 Ren‘py 视觉小说 交互式故事游戏制作过程学习笔记(Windows下实现)(多结局游戏)-CSDN博客 一、网站注册 申请软著时,所有的著作权人都需要在中国版权保护中心官网注册账号,并进行实名认证后,才…...

代币化:2024年的金融浪潮预示着什么?
自“TradFi”领袖到加密专家,各方预测代币化机会高达数十万亿。虽然已有引人注目的用例,但与未来几年可能在链上转移的大量数字化资产相比,这些仅是冰山一角。 代币化何时会变为洪流?什么阻碍了其发展? 今年10月&…...

[学习记录]Node event loop 总结流程图
文章目录 文章来源根据内容输出的流程图待处理遗留的问题参考 文章来源 详解JavaScript中的Event Loop(事件循环)机制 根据内容输出的流程图 待处理 这里从polling阶段开始 好像有些问题 遗留的问题 为什么“在I/O事件的回调中,setImmediate…...

【LeetCode热题100】【双指针】移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nums [0] 输出…...

Mybatis 分页查询的三种实现
Mybatis 分页查询 1. 直接在 sql 中使用 limit2. 使用 RowBounds3. 使用 Mybatis 提供的拦截器机制3.1 创建一个自定义拦截器类实现 Interceptor3.2 创建分页查询函数 与 sql3.3 编写拦截逻辑3.4 注册 PageInterceptor 到 Mybatis 拦截器链中3.5 测试 准备一个分页查询类 Data…...

各类声音数据集大合集—乐器、车辆、鸟鸣、蜜蜂声音、歌曲、喇叭、人类声音不同等类型的声音数据集
最近收集了一大波关于各类声音的数据集,包含乐器、车辆、鸟鸣、蜜蜂声音、歌曲、喇叭、人类声音不同等类型的声音数据集,废话不多说,给大家逐一介绍!! 1、吉他和弦大调、小调数据集 吉他和弦大调、小调数据集&#x…...

java设计模式学习之【原型模式】
文章目录 引言原型模式简介定义与用途实现方式UML 使用场景优势与劣势原型模式在spring中的应用员工记录示例代码地址 引言 原型模式是一种创建型设计模式,它允许对象能够复制自身,以此来创建一个新的对象。这种模式在需要重复地创建相似对象时非常有用…...

链表数组插入排序
InsertSort 插入排序算法,比如打扑克牌的算法时,按照从左到右,找到对应的位置插入排序 最重要的是位置移动 找到对应位置值 #include "iostream" #include "bits/stdc.h"using namespace std;void sort(vector<in…...

MyBatis的创建,简单易懂的一篇blog
文章目录 一、MyBatis是什么二、操作流程三.配置resource总结 一、MyBatis是什么 MyBatis 是⼀款优秀的持久层框架,它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注…...

MOS管的静电击穿问题
MOS管输入电阻很高,为什么一遇到静电就不行了? 静电击穿:由于静电的积累导致电压超过了原本MOS的绝缘能力,导致电流突然增大的现象。 MOS管基础知识了解: G极(gate)—栅极,不用说比较好认 S极(source)—源…...

在线 SQL 模拟器SQL Fiddle使用简介
在线 SQL 模拟器SQL Fiddle使用简介 本文可作为“SQL语言与SQL在线实验工具的使用” https://blog.csdn.net/cnds123/article/details/115038700 一文的补充。 有时候,我们想去验证 SQL语句,却缺少数据库环境,那该怎么办呢? 这…...

仿京东淘宝商品列表筛选组件:实现一个高效的侧边栏弹框筛选功能
仿京东淘宝商品列表筛选组件:实现一个高效的侧边栏弹框筛选功能 一、引言 随着电子商务的快速发展,用户体验成为了竞争的关键因素。在众多的电商网站中,如京东和淘宝,商品列表筛选功能为用户提供了便捷的途径来找到心仪的商品。本…...

软件工程 - 第8章 面向对象建模 - 4 - 物理体系结构建模
构件图 构件图概述 构件图描述了软件的各种构件和它们之间的依赖关系。 构件图的作用 在构件图中,系统中的每个物理构件都使用构件符号来表示,通常,构件图看起来像是构件图标的集合,这些图标代表系统中的物理部件,…...

【智能家居】二、添加火灾检测模块(烟雾报警功能点)
可燃气体传感器 MQ-2 和 蜂鸣器 代码段 controlDevice.h(设备控制)smokeAlarm.c(烟雾报警器)buzzer.c(蜂鸣器)mainPro.c(主函数)运行结果 可燃气体传感器 MQ-2 和 蜂鸣器 代码段 …...

history和hash两种路由模式原理,和优缺点
Hash Hash 模式是在 URL 中使用井号(#)来作为路由的模式。在 Hash 模式下,即使页面刷新,浏览器仍然只会请求页面的初始 HTML 文件,所有的路由变化都会在 URL 前面添加 “#/” 符号。 原理 在 Hash 模式下,路…...