当前位置: 首页 > 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…...

【华为OD题库-012】模拟消息队列-Java

题目 让我们来模拟一个消息队列的运作&#xff0c;有一个发布者和若干消费者 &#xff0c;发布者会在给定的时刻向消息队列发送消息。>若此时消息队列有消费者订阅&#xff0c;这个消息会被发送到订阅的消费者中优先级最高(输入中消费者按优先级升序排列)的一个。>若此时…...

Android修行手册 - 阴影效果的几种实现以及一些特别注意点

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列点击跳转>ChatGPT和AIGC &#x1f449;关于作者 专…...

【星海出品】SDN neutron (五) openvswitch

1、ovs-vswitchd组件是交换机的主要模块&#xff0c;运行在用户态&#xff0c;其主要负责基本的转发逻辑、地址学习、外部物理端口绑定等。还可以运用OVS自带的ovs-ofctl工具采用openflow协议对交换机进行远程配置和管理。 2、ovsdb-server组件是存储OVS的网桥等配置、日志以及…...

springboot整合vue2实现简单的新增删除,整合ECharts实现图表渲染

先看效果图&#xff1a; 1.后端接口 // 查询所有商品信息 // CrossOrigin(origins "*")RequestMapping("/list1")ResponseBodypublic List<Goodsinfo> list1(){List<Goodsinfo> list goodsService.list();return list;}// 删除 // …...

<蓝桥杯软件赛>零基础备赛20周--第5周--杂题-2

报名明年4月蓝桥杯软件赛的同学们&#xff0c;如果你是大一零基础&#xff0c;目前懵懂中&#xff0c;不知该怎么办&#xff0c;可以看看本博客系列&#xff1a;备赛20周合集 20周的完整安排请点击&#xff1a;20周计划 每周发1个博客&#xff0c;共20周&#xff08;读者可以按…...

数据结构哈希表(散列)Hash,手写实现(图文推导)

目录 一、介绍 二、哈希数据结构 三、✍️实现哈希散列 1. 哈希碰撞&#x1f4a5; 2. 拉链寻址⛓️ 3. 开放寻址⏩ 4. 合并散列 一、介绍 哈希表&#xff0c;也被称为散列表&#xff0c;是一种重要的数据结构。它通过将关键字映射到一个表中的位置来直接访问记录&#…...

【嵌入式设计】Main Memory:SPM 便签存储器 | 缓存锁定 | 读取 DRAM 内存 | DREM 猝发(Brust)

目录 0x00 便签存储器&#xff08;Scratchpad memory&#xff09; 0x01 缓存锁定&#xff08;Cache lockdown&#xff09; 0x02 读取 DRAM 内存 0x03 DREM Banking 0x04 DRAM 猝发&#xff08;DRAM Burst&#xff09; 0x00 便签存储器&#xff08;Scratchpad memory&#…...

dameng数据库数据id decimal类型,精度丢失

问题处理 这一次也是精度丢失&#xff0c;但是问题呢还是不一样&#xff0c;这一次所有的id都被加一了&#xff0c;只有id字段被加一&#xff0c;还有的查询查出来封装成对象之后对象的id字段被减一了&#xff0c;数据库id字段使用的decimal&#xff08;20,6&#xff09;&…...

python图神经网络,注意力机制、Transformer模型、目标检测算法、强化学习等

近年来&#xff0c;伴随着以卷积神经网络&#xff08;CNN&#xff09;为代表的深度学习的快速发展&#xff0c;人工智能迈入了第三次发展浪潮&#xff0c;AI技术在各个领域中的应用越来越广泛 本文重点为&#xff1a;注意力机制、Transformer模型&#xff08;BERT、GPT-1/2/3/…...

安装包 amd,amd64, arm,arm64 都有什么区别

现在的安装包也不省心&#xff0c;有各种版本都不知道怎么选。 根据你安装的环境配置。 amd&#xff1a; 32位X86 amd64&#xff1a; 64位X86 arm&#xff1a; 32位ARM arm64&#xff1a; 64位ARM amd64是X86架构的CPU&#xff0c;64位版。amd64又叫X86_64。主流的桌面PC&am…...