当前位置: 首页 > news >正文

python数据结构与算法-03_链表

链式结构

上一节讲到了支持随机访问的线性结构,这次我们开始讲链式结构, 视频里我会说下这两种结构的区别,然后讲解最常见的单链表和双链表。
之前在专栏文章那些年,我们一起跪过的算法题[视频]里实现过一个 lru_cache,
使用到的就是循环双端链表,如果感觉这篇文章有点难理解,我们这里将会循序渐进地来实现。
后边讲到哈希表的冲突解决方式的时候,我们会再次提到链表。

上一节我们分析了 list 的各种操作是如何实现的,如果你还有印象的话,list
在头部进行插入是个相当耗时的操作(需要把后边的元素一个一个挪个位置)。假如你需要频繁在数组两头增删,list 就不太合适。
今天我们介绍的链式结构将摆脱这个缺陷,当然了链式结构本身也有缺陷,比如你不能像数组一样随机根据下标访问,你想查找一个元素只能老老实实从头遍历。
所以嘛,学习和了解数据结构的原理和实现你才能准确地选择到底什么时候该用什么数据结构,而不是瞎选导致代码性能很差。

单链表

和线性结构不同,链式结构内存不连续的,而是一个个串起来的,这个时候就需要每个链接表的节点保存一个指向下一个节点的指针。
这里可不要混淆了列表和链表(它们的中文发音类似,但是列表 list 底层其实还是线性结构,链表才是真的通过指针关联的链式结构)。
看到指针你也不用怕,这里我们用的 python,你只需要一个简单赋值操作就能实现,不用担心 c 语言里复杂的指针。

先来定义一个链接表的节点,刚才说到有一个指针保存下一个节点的位置,我们叫它 next, 当然还需要一个 value 属性保存值

class Node(object):def __init__(self, value, next=None):self.value = valueself.next = next

然后就是我们的单链表 LinkedList ADT:

class LinkedList(object):""" 链接表 ADT[root] -> [node0] -> [node1] -> [node2]"""

实现我们会在视频中用画图来模拟并且手动代码实现,代码里我们会标识每个步骤的时间复杂度。这里请高度集中精力,
虽然链表的思想很简单,但是想要正确写对链表的操作代码可不容易,稍不留神就可能丢失一些步骤。
这里我们还是会用简单的单测来验证代码是否按照预期工作。

来看下时间复杂度:

链表操作平均时间复杂度
linked_list.append(value)O(1)
linked_list.appendleft(value)O(1)
linked_list.find(value)O(n)
linked_list.remove(value)O(n)

双链表

上边我们亲自实现了一个单链表,但是能看到很明显的问题,单链表虽然 append 是 O(1),但是它的 find 和 remove 都是 O(n)的,
因为删除你也需要先查找,而单链表查找只有一个方式就是从头找到尾,中间找到才退出。
这里我之前提到过如果要实现一个 lru 缓存(访问时间最久的踢出),我们需要在一个链表里能高效的删除元素,
并把它追加到访问表的最后一个位置,这个时候单链表就满足不了了,
因为缓存在 dict 里查找的时间是 O(1),你更新访问顺序就 O(n)了,缓存就没了优势。

这里就要使用到双链表了,相比单链表来说,每个节点既保存了指向下一个节点的指针,同时还保存了上一个节点的指针。

class Node(object):# 如果节点很多,我们可以用 __slots__ 来节省内存,把属性保存在一个 tuple 而不是 dict 里# 感兴趣可以自行搜索  python  __slots____slots__ = ('value', 'prev', 'next')def __init__(self, value=None, prev=None, next=None):self.value, self.prev, self.next = value, prev, next

对, 就多了 prev,有啥优势嘛?

  • 看似我们反过来遍历双链表了。反过来从哪里开始呢?我们只要让 root 的 prev 指向 tail 节点,不就串起来了吗?
  • 直接删除节点,当然如果给的是一个值,我们还是需要查找这个值在哪个节点? - 但是如果给了一个节点,我们把它拿掉,直接让它的前后节点互相指过去不就行了?哇欧,删除就是 O(1) 了,两步操作就行啦

好,废话不多说,我们在视频里介绍怎么实现一个双链表 ADT。你可以直接在本项目的 docs/03_链表/double_link_list.py 找到代码。
最后让我们看下它的时间复杂度:(这里 CircularDoubleLinkedList 取大写字母缩写为 cdll)

循环双端链表操作平均时间复杂度
cdll.append(value)O(1)
cdll.appendleft(value)O(1)
cdll.remove(node),注意这里参数是 nodeO(1)
cdll.headnode()O(1)
cdll.tailnode()O(1)

源码

double_link_list.py

# -*- coding: utf-8 -*-class Node(object):__slots__ = ('value', 'prev', 'next')   # save memorydef __init__(self, value=None, prev=None, next=None):self.value, self.prev, self.next = value, prev, nextclass CircularDoubleLinkedList(object):"""循环双端链表 ADT多了个循环其实就是把 root 的 prev 指向 tail 节点,串起来"""def __init__(self, maxsize=None):self.maxsize = maxsizenode = Node()node.next, node.prev = node, nodeself.root = nodeself.length = 0def __len__(self):return self.lengthdef headnode(self):return self.root.nextdef tailnode(self):return self.root.prevdef append(self, value):    # O(1), 你发现一般不用 for 循环的就是 O(1),有限个步骤if self.maxsize is not None and len(self) >= self.maxsize:raise Exception('LinkedList is Full')node = Node(value=value)tailnode = self.tailnode() or self.roottailnode.next = nodenode.prev = tailnodenode.next = self.rootself.root.prev = nodeself.length += 1def appendleft(self, value):if self.maxsize is not None and len(self) >= self.maxsize:raise Exception('LinkedList is Full')node = Node(value=value)if self.root.next is self.root:   # emptynode.next = self.rootnode.prev = self.rootself.root.next = nodeself.root.prev = nodeelse:node.prev = self.rootheadnode = self.root.nextnode.next = headnodeheadnode.prev = nodeself.root.next = nodeself.length += 1def remove(self, node):      # O(1),传入node 而不是 value 我们就能实现 O(1) 删除"""remove:param node  # 在 lru_cache 里实际上根据key 保存了整个node:"""if node is self.root:returnelse:    #node.prev.next = node.nextnode.next.prev = node.prevself.length -= 1return nodedef iter_node(self):if self.root.next is self.root:returncurnode = self.root.nextwhile curnode.next is not self.root:yield curnodecurnode = curnode.nextyield curnodedef __iter__(self):for node in self.iter_node():yield node.valuedef iter_node_reverse(self):"""相比单链表独有的反序遍历"""if self.root.prev is self.root:returncurnode = self.root.prevwhile curnode.prev is not self.root:yield curnodecurnode = curnode.prevyield curnodedef test_double_link_list():dll = CircularDoubleLinkedList()assert len(dll) == 0dll.append(0)dll.append(1)dll.append(2)assert list(dll) == [0, 1, 2]assert [node.value for node in dll.iter_node()] == [0, 1, 2]assert [node.value for node in dll.iter_node_reverse()] == [2, 1, 0]headnode = dll.headnode()assert headnode.value == 0dll.remove(headnode)assert len(dll) == 2assert [node.value for node in dll.iter_node()] == [1, 2]dll.appendleft(0)assert [node.value for node in dll.iter_node()] == [0, 1, 2]if __name__ == '__main__':test_double_link_list()

linked_list.py

# -*- coding: utf-8 -*-class Node(object):def __init__(self, value=None, next=None):   # 这里我们 root 节点默认都是 None,所以都给了默认值self.value = valueself.next = nextdef __str__(self):"""方便你打出来调试,复杂的代码可能需要断点调试"""return '<Node: value: {}, next={}>'.format(self.value, self.next)__repr__ = __str__class LinkedList(object):""" 链接表 ADT[root] -> [node0] -> [node1] -> [node2]"""def __init__(self, maxsize=None):""":param maxsize: int or None, 如果是 None,无限扩充"""self.maxsize = maxsizeself.root = Node()     # 默认 root 节点指向 Noneself.tailnode = Noneself.length = 0def __len__(self):return self.lengthdef append(self, value):    # O(1)if self.maxsize is not None and len(self) >= self.maxsize:raise Exception('LinkedList is Full')node = Node(value)    # 构造节点tailnode = self.tailnodeif tailnode is None:    # 还没有 append 过,length = 0, 追加到 root 后self.root.next = nodeelse:     # 否则追加到最后一个节点的后边,并更新最后一个节点是 append 的节点tailnode.next = nodeself.tailnode = nodeself.length += 1def appendleft(self, value):if self.maxsize is not None and len(self) >= self.maxsize:raise Exception('LinkedList is Full')node = Node(value)if self.tailnode is None:  # 如果原链表为空,插入第一个元素需要设置 tailnodeself.tailnode = nodeheadnode = self.root.nextself.root.next = nodenode.next = headnodeself.length += 1def __iter__(self):for node in self.iter_node():yield node.valuedef iter_node(self):"""遍历 从 head 节点到 tail 节点"""curnode = self.root.nextwhile curnode is not self.tailnode:    # 从第一个节点开始遍历yield curnodecurnode = curnode.next    # 移动到下一个节点if curnode is not None:yield curnodedef remove(self, value):    # O(n)""" 删除包含值的一个节点,将其前一个节点的 next 指向被查询节点的下一个即可:param value:"""prevnode = self.root    #for curnode in self.iter_node():if curnode.value == value:prevnode.next = curnode.nextif curnode is self.tailnode:  # NOTE: 注意更新 tailnodeif prevnode is self.root:self.tailnode = Noneelse:self.tailnode = prevnodedel curnodeself.length -= 1return 1  # 表明删除成功else:prevnode = curnodereturn -1  # 表明删除失败def find(self, value):    # O(n)""" 查找一个节点,返回序号,从 0 开始:param value:"""index = 0for node in self.iter_node():   # 我们定义了 __iter__,这里就可以用 for 遍历它了if node.value == value:return indexindex += 1return -1    # 没找到def popleft(self):    # O(1)""" 删除第一个链表节点"""if self.root.next is None:raise Exception('pop from empty LinkedList')headnode = self.root.nextself.root.next = headnode.nextself.length -= 1value = headnode.valueif self.tailnode is headnode:   # 勘误:增加单节点删除 tailnode 处理self.tailnode = Nonedel headnodereturn valuedef clear(self):for node in self.iter_node():del nodeself.root.next = Noneself.length = 0self.tailnode = Nonedef reverse(self):"""反转链表"""curnode = self.root.nextself.tailnode = curnode  # 记得更新 tailnode,多了这个属性处理起来经常忘记prevnode = Nonewhile curnode:nextnode = curnode.nextcurnode.next = prevnodeif nextnode is None:self.root.next = curnodeprevnode = curnodecurnode = nextnodedef test_linked_list():ll = LinkedList()ll.append(0)ll.append(1)ll.append(2)ll.append(3)assert len(ll) == 4assert ll.find(2) == 2assert ll.find(-1) == -1assert ll.remove(0) == 1assert ll.remove(10) == -1assert ll.remove(2) == 1assert len(ll) == 2assert list(ll) == [1, 3]assert ll.find(0) == -1ll.appendleft(0)assert list(ll) == [0, 1, 3]assert len(ll) == 3headvalue = ll.popleft()assert headvalue == 0assert len(ll) == 2assert list(ll) == [1, 3]assert ll.popleft() == 1assert list(ll) == [3]ll.popleft()assert len(ll) == 0assert ll.tailnode is Nonell.clear()assert len(ll) == 0assert list(ll) == []def test_linked_list_remove():ll = LinkedList()ll.append(3)ll.append(4)ll.append(5)ll.append(6)ll.append(7)ll.remove(7)print(list(ll))def test_single_node():# https://github.com/PegasusWang/python_data_structures_and_algorithms/pull/21ll = LinkedList()ll.append(0)ll.remove(0)ll.appendleft(1)assert list(ll) == [1]def test_linked_list_reverse():ll = LinkedList()n = 10for i in range(n):ll.append(i)ll.reverse()assert list(ll) == list(reversed(range(n)))def test_linked_list_append():ll = LinkedList()ll.appendleft(1)ll.append(2)assert list(ll) == [1, 2]if __name__ == '__main__':test_single_node()test_linked_list()test_linked_list_append()test_linked_list_reverse()

lru_cache.py

"""
python3 only
LRU cache
"""
from collections import OrderedDict
from functools import wrapsdef fib(n):if n <= 1:  # 0 or 1return nreturn f(n - 1) + f(n - 2)  # 由于涉及到重复计算,这个递归函数在 n 大了以后会非常慢。 O(2^n)"""
下边就来写一个缓存装饰器来优化它。传统方法是用个数组记录之前计算过的值,但是这种方式不够 Pythonic
"""def cache(func):"""先引入一个简单的装饰器缓存,其实原理很简单,就是内部用一个字典缓存已经计算过的结果"""store = {}@wraps(func)def _(n):   # 这里函数没啥意义就随便用下划线命名了if n in store:return store[n]else:res = func(n)store[n] = resreturn resreturn _@cache
def f(n):if n <= 1:  # 0 or 1return nreturn f(n - 1) + f(n - 2)"""
问题来了,假如空间有限怎么办,我们不可能一直向缓存塞东西,当缓存达到一定个数之后,我们需要一种策略踢出一些元素,
用来给新的元素腾出空间。
一般缓存失效策略有
- LRU(Least-Recently-Used): 替换掉最近请求最少的对象,实际中使用最广。cpu缓存淘汰和虚拟内存效果好,web应用欠佳
- LFU(Least-Frequently-Used): 缓存污染问题(一个先前流行的缓存对象会在缓存中驻留很长时间)
- First in First out(FIFO)
- Random Cache: 随机选一个删除LRU 是常用的一个,比如 redis 就实现了这个策略,这里我们来模拟实现一个。
要想实现一个 LRU,我们需要一种方式能够记录访问的顺序,并且每次访问之后我们要把最新使用到的元素放到最后(表示最新访问)。
当容量满了以后,我们踢出最早访问的元素。假如用一个链表来表示的话:[1] -> [2] -> [3]假设最后边是最后访问的,当访问到一个元素以后,我们把它放到最后。当容量满了,我们踢出第一个元素就行了。
一开始的想法可能是用一个链表来记录访问顺序,但是单链表有个问题就是如果访问了中间一个元素,我们需要拿掉它并且放到链表尾部。
而单链表无法在O(1)的时间内删除一个节点(必须要先搜索到它),但是双端链表可以,因为一个节点记录了它的前后节点,
只需要把要删除的节点的前后节点链接起来就行了。
还有个问题是如何把删除后的节点放到链表尾部,如果是循环双端链表就可以啦,我们有个 root 节点链接了首位节点,
只需要让 root 的前一个指向这个被删除节点,然后让之前的最后一个节点也指向它就行了。使用了循环双端链表之后,我们的操作就都是 O(1) 的了。这也就是使用一个 dict 和一个 循环双端链表 实现LRU 的思路。
不过一般我们使用内置的 OrderedDict(原理和这个类似)就好了,要实现一个循环双端链表是一个不简单的事情,因为链表操作很容易出错。补充:其实 lru 有个缺点就是额外的链表比较占用空间,如果你感兴趣的话可以看看 redis 如何实现的 lru 算法
"""class LRUCache:def __init__(self, capacity=128):self.capacity = capacity# 借助 OrderedDict 我们可以快速实现一个 LRUCache,OrderedDict 内部其实也是使用循环双端链表实现的# OrderedDict 有两个重要的函数用来实现 LRU,一个是 move_to_end,一个是 popitem,请自己看文档self.od = OrderedDict()def get(self, key, default=None):val = self.od.get(key, default)  # 如果没有返回 default,保持 dict 语义self.od.move_to_end(key)   # 每次访问就把key 放到最后表示最新访问return valdef add_or_update(self, key, value):if key in self.od:  # updateself.od[key] = valueself.od.move_to_end(key)else:  # insertself.od[key] = valueif len(self.od) > self.capacity:  # fullself.od.popitem(last=False)def __call__(self, func):"""一个简单的 LRU 实现。有一些问题需要思考下:- 这里为了简化默认参数只有一个数字 n,假如可以传入多个参数,如何确定缓存的key 呢?- 这里实现没有考虑线程安全的问题,要如何才能实现线程安全的 LRU 呢?当然如果不是多线程环境下使用是不需要考虑的- 假如这里没有用内置的 dict,你能使用 redis 来实现这个 LRU 吗,如果使用了 redis,我们可以存储更多数据到服务器。而使用字典实际上是缓存了Python进程里(localCache)。- 这里只是实现了 lru 策略,你能同时实现一个超时 timeout 参数吗?比如像是memcache 实现的 lazy expiration 策略- LRU有个缺点就是,对于周期性的数据访问会导致命中率迅速下降,有一种优化是 LRU-K,访问了次数达到 k 次才会将数据放入缓存"""def _(n):if n in self.od:return self.get(n)else:val = func(n)self.add_or_update(n, val)return valreturn _@LRUCache(10)
def f_use_lru(n):if n <= 1:  # 0 or 1return nreturn f_use_lru(n - 1) + f_use_lru(n - 2)def test():import timebeg = time.time()for i in range(34):print(f(i))print(time.time() - beg)beg = time.time()for i in range(34):print(f_use_lru(i))print(time.time() - beg)# TODO 要怎么给 lru 写单测?if __name__ == '__main__':test()######################################### 使用双链表实现 LRUcache ####################################################
"""
一般面试中不会让我们直接用内置结构,所以这里提供一个自己实现的双链表+map lru 缓存。这也是力扣上的一道真题:
[146] LRU 缓存 https://leetcode-cn.com/problems/lru-cache/description/
"""class ListNode:def __init__(self, key=None, value=None):self.key = keyself.value = valueself.prev = self.next = Noneclass List:def __init__(self):"""循环双链表。注意增加了虚拟头尾结点 head,tail 方便处理"""self.head = ListNode()self.tail = ListNode()self.head.prev = self.head.next = self.tailself.tail.next = self.tail.prev = self.headdef delete_node(self, node):  # 删除指定节点node.prev.next = node.nextnode.next.prev = node.prevdef add_to_head(self, node):  # 指定节点添加到 self.head 后nextnode = self.head.nextnode.next = nextnodenode.prev = self.headself.head.next = nodenextnode.prev = nodeclass LRUCache(object):def __init__(self, capacity):"""思路:循环双链表 + 字典:type capacity: int"""self.map = dict()self.ll = List()self.capacity = capacitydef get(self, key):""":type key: int:rtype: int"""if key not in self.map:return -1node = self.map[key]self.ll.delete_node(node)self.ll.add_to_head(node)return node.valuedef put(self, key, value):""":type key: int:type value: int:rtype: None"""if key in self.map: # 更新不会改变元素个数,这里不用判断是否需要剔除node = self.map[key]node.value = value  # 修改结构体会也会修改 map 对应 value 的引用self.ll.delete_node(node)self.ll.add_to_head(node)else:if len(self.map) >= self.capacity:  # 直接用 len(self.map) ,不需要self.size 字段了tailnode = self.ll.tail.prevself.ll.delete_node(tailnode)del self.map[tailnode.key]node = ListNode(key, value)self.map[key] = nodeself.ll.add_to_head(node)

小问题:

  • 这里单链表我没有实现 insert 方法,你能自己尝试实现吗? insert(value, new_value),我想在某个值之前插入一个值。你同样需要先查找,所以这个步骤也不够高效。
  • 你能尝试自己实现个 lru cache 吗?需要使用到我们这里提到的循环双端链表
  • 借助内置的 collections.OrderedDict,它有两个方法 popitem 和 move_to_end,我们可以迅速实现一个 LRU cache。请你尝试用 OrderedDict 来实现。
  • python 内置库的哪些数据结构使用到了本章讲的链式结构?

相关阅读

那些年,我们一起跪过的算法题- Lru cache[视频]

勘误:

视频中 LinkedList.remove 方法讲解有遗漏, linked_list.py 文件已经修正,请读者注意。具体请参考 fix linked_list & add gitigonre。视频最后增加了一段勘误说明。

Leetcode

反转链表 reverse-linked-list

这里有一道关于 LRU 的练习题你可以尝试下。
LRU Cache

合并两个有序链表 merge-two-sorted-lists

相关文章:

python数据结构与算法-03_链表

链式结构 上一节讲到了支持随机访问的线性结构&#xff0c;这次我们开始讲链式结构, 视频里我会说下这两种结构的区别&#xff0c;然后讲解最常见的单链表和双链表。 之前在专栏文章那些年&#xff0c;我们一起跪过的算法题[视频]里实现过一个 lru_cache&#xff0c; 使用到的…...

Springboot-aop的使用

aop:面向切面编程&#xff0c;可以看作是面向对象的补充 举例 1.依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.1</version><relativePath/>…...

数列计算

题目描述 有一列数是 : 请找出这个数列的规律&#xff0c;编写程序计算并输出这个数列的第项&#xff0c;要求是分数形式&#xff0c;并计算这个数列的前项和 ( 结果四舍五入保留两位小数 ) 输入格式 第一行仅有一个正整数 &#xff08;) 。 输出格式 共有 行&#xff0c;第一…...

阿里云全球故障凸显“云集中”风险

阿里云12日发生的全球性故障再次将“云集中风险”推上风口浪尖。这一公有云史上罕见的事件不仅影响了数以万计的企业和服务&#xff0c;也引发了对云服务集中化趋势的深刻反思。 2023年11月12日17:44(GMT8)开始&#xff0c;阿里云基础设施发生严重故障&#xff0c;导致阿里巴巴…...

【2015年数据结构真题】

用单链表保存m个整数&#xff0c;结点的结构为 [data] [link]&#xff0c;且|data|<n(n为正整数)。现要求设计一个时问复杂度尽可能高效的算法&#xff0c;对于链表中 data 的绝对值相等的结点&#xff0c;仅保留第一次出现的结点而删除其余绝对值相等的结点。例如&#xff…...

vxe表格行拖拽

安装第三方插件 import Sortable from sortablejs 可以跟后端商议表格添加seq 顺序&#xff0c; 按照循序排序 secondInput 调用 修改接口api 然后重新获取数据 //在get 请求之后 使用nextTick 使用 const rowDrop () > {nextTick(() > {let xTable2 planDat…...

Linux之基本指令操作

1、whoami whoami&#xff1a;查看当前账号是谁 2、who who&#xff1a;查看当前我的系统当中有哪些用户&#xff0c;当前有哪些人登录了我的机器 3、 pwd pwd&#xff1a;查看我当前所处的目录&#xff0c;就好比Windows下的路径 4、ls ls&#xff1a;查看当前目录下的文件信…...

海康设备、LiveNVR等通过GB35114国密协议对接到LiveGBS GB28181/GB35114平台的详细操作说明

一、LiveNVR通过GB35114接入LiveGBS 1.1 开启LiveGBS 35114功能 信令服务livecms.ini配置文件中[sip]增加一行gm1 启动LiveCMS 1.2 生成设备端证书 我们用LiveNVR做为设备端向LiveGBS注册&#xff0c;这里先生成LiveNVR的设备证书&#xff0c;并将LiveNVR的设备证书给LiveGB…...

BUUCTF 面具下的flag 1

BUUCTF:https://buuoj.cn/challenges 题目描述&#xff1a; 下载附件&#xff0c;得到一张.jpg图片。 密文&#xff1a; 解题思路&#xff1a; 1、将图片放到Kali中&#xff0c;使用binwalk检测出隐藏zip包。 使用foremost提取zip压缩包到output目录下 解压zip压缩包&…...

ArcGIS实现矢量区域内所有要素的统计计算

1、任务需求&#xff1a;统计全球各国所有一级行政区相关属性的总和。 &#xff08;1&#xff09;有一个全球一级行政区的矢量图&#xff0c;包含以下属性&#xff08;洪灾相关属性 province.shp&#xff09; &#xff08;2&#xff09;需要按照国家来统计各个国家各属性的总值…...

3.4-初识Container

常用的docker container命令&#xff1a; 1、基于image创建docker container命令&#xff1a; docker run lvdapiaoliang/hello-docker 2、列举当前本地正在运行的container容器命令&#xff1a; docker container ls 3、列举当前本地所有的container容器命令(包括正在运行的和…...

壹基金爱泽瑞金 安全家园物料配送忙

11月9日到10日&#xff0c;瑞金赋能公益陆续收到壹基金、阿里巴巴公益爱心网友捐赠的社区志愿者救援队队伍物资&#xff0c;马不停蹄地把物资配送到河背街社区、金都社区和沙洲坝镇等项目点&#xff0c;扎实稳妥推进项目有序执行。 在这次物资配送中&#xff0c;志愿者冒雨前行…...

arcgis--二维建筑面的三维显示设置

1、打开ArcScene软件&#xff0c;导入数据&#xff0c;如下&#xff1a; 2、 对建筑面进行拉伸。双击建筑物面图层&#xff0c;打开属性表&#xff0c;选择【拉伸】选项卡&#xff0c;参数设置如下&#xff1a; 显示结果如下&#xff1a;...

Maven 插件统一修改聚合工程项目版本号

目录 引言直接修改 pom.xml 的版本号的问题Maven 插件修改版本号开源项目微服务商城项目前后端分离项目 引言 在Maven项目中&#xff0c;我们通常有两种常见的方式来修改版本号&#xff1a;直接在pom.xml文件中手动编辑和利用Maven插件进行版本号调整。 本文将比较这两种修改…...

主从复制和读写分离

MySQL 主从复制和读写分离&#xff1a; 主从复制&#xff1a;主MySQL上的数据&#xff0c;新增&#xff0c;修改库&#xff0c;表&#xff0c;表里的数据&#xff0c;都会同步到从MySQL上。 MySQL的主从复制的模式&#xff1a;&#xff08;面试题&#xff09; 1&#xff0c;异…...

Redis模块的高级使用方式

Redis 模块是Redis的高级功能&#xff0c;允许我们实现特定的自定义数据类型。本质上&#xff0c;模块是一个动态库&#xff0c;可以在启动时或根据命令按需加载到 Redis 中 MODULE LOAD 。模块可以用多种语言编写&#xff0c;包括 C 和 Rust。 我们自己使用 Redis 模块实现新…...

Failed to restart network.service: Unit network.service not found.

执行systemctl restart network命令&#xff0c;报错Failed to restart network.service: Unit network.service not found. 执行 yum install network-scripts命令 再次执行&#xff0c;正常...

wiki.js一个开源知识库系统

1 什么是wiki wiki.js是一个开源Wiki应用程序&#xff0c;官网介绍为&#xff1a; A modern, lightweight and powerful wiki app built on NodeJS 访问Github&#xff1a;github 访问Wike&#xff1a;js.wiki 省流总结 开源知识库平台&#xff0c;和语雀有一样的功能&…...

关于Java抽象类和接口的总结和一点个人的看法

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ ა 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如需转载还请通知˶⍤⃝˶个人主页&am…...

vue中ref的用法

vue中ref的用法 在项目中使用ref时有时候直接取值,有时候返回的却是一个数组,不知其中缘由,后查了一下ref用法,所以总结一下. 1.绑定在dom元素上时&#xff0c;用起来与id差不多&#xff0c;通过this.$refs来调用: <div id"passCarEchart" ref"passCarEch…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)

Aspose.PDF 限制绕过方案&#xff1a;Java 字节码技术实战分享&#xff08;仅供学习&#xff09; 一、Aspose.PDF 简介二、说明&#xff08;⚠️仅供学习与研究使用&#xff09;三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing

Muffin 论文 现有方法 CRADLE 和 LEMON&#xff0c;依赖模型推理阶段输出进行差分测试&#xff0c;但在训练阶段是不可行的&#xff0c;因为训练阶段直到最后才有固定输出&#xff0c;中间过程是不断变化的。API 库覆盖低&#xff0c;因为各个 API 都是在各种具体场景下使用。…...

在树莓派上添加音频输入设备的几种方法

在树莓派上添加音频输入设备可以通过以下步骤完成&#xff0c;具体方法取决于设备类型&#xff08;如USB麦克风、3.5mm接口麦克风或HDMI音频输入&#xff09;。以下是详细指南&#xff1a; 1. 连接音频输入设备 USB麦克风/声卡&#xff1a;直接插入树莓派的USB接口。3.5mm麦克…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...