用python实现基本数据结构【03/4】

说明

        如果需要用到这些知识却没有掌握,则会让人感到沮丧,也可能导致面试被拒。无论是花几天时间“突击”,还是利用零碎的时间持续学习,在数据结构上下点功夫都是值得的。那么Python 中有哪些数据结构呢?列表、字典、集合,还有……栈?Python 有栈吗?本系列文章将给出详细拼图。
 

9章:Advanced Linked Lists

之前曾经介绍过单链表,一个链表节点只有data和next字段,本章介绍高级的链表。

Doubly Linked List,双链表,每个节点多了个prev指向前一个节点。双链表可以用来编写文本编辑器的buffer。

class DListNode:def __init__(self, data):self.data = dataself.prev = Noneself.next = Nonedef revTraversa(tail):curNode = tailwhile cruNode is not None:print(curNode.data)curNode = curNode.prevdef search_sorted_doubly_linked_list(head, tail, probe, target):""" probing technique探查法,改进直接遍历,不过最坏时间复杂度仍是O(n)searching a sorted doubly linked list using the probing techniqueArgs:head (DListNode obj)tail (DListNode obj)probe (DListNode or None)target (DListNode.data): data to search"""if head is None:    # make sure list is not emptyreturn Falseif probe is None:    # if probe is null, initialize it to first nodeprobe = head# if the target comes before the probe node, we traverse backward, otherwise# traverse forwardif target < probe.data:while probe is not None and target <= probe.data:if target == probe.dta:return Trueelse:probe = probe.prevelse:while probe is not None and target >= probe.data:if target == probe.data:return Trueelse:probe = probe.nextreturn Falsedef insert_node_into_ordered_doubly_linekd_list(value):""" 最好画个图看,链表操作很容易绕晕,注意赋值顺序"""newnode = DListNode(value)if head is None:    # empty listhead = newnodetail = headelif value < head.data:    # insert before headnewnode.next = headhead.prev = newnodehead = newnodeelif value > tail.data:    # insert after tailnewnode.prev = tailtail.next = newnodetail = newnodeelse:    # insert into middlenode = headwhile node is not None and node.data < value:node = node.nextnewnode.next = nodenewnode.prev = node.prevnode.prev.next = newnodenode.prev = newnode

循环链表

def travrseCircularList(listRef):curNode = listRefdone = listRef is Nonewhile not None:curNode = curNode.nextprint(curNode.data)done = curNode is listRef   # 回到遍历起始点def searchCircularList(listRef, target):curNode = listRefdone = listRef is Nonewhile not done:curNode = curNode.nextif curNode.data == target:return Trueelse:done = curNode is listRef or curNode.data > targetreturn Falsedef add_newnode_into_ordered_circular_linked_list(listRef, value):""" 插入并维持顺序1.插入空链表;2.插入头部;3.插入尾部;4.按顺序插入中间"""newnode = ListNode(value)if listRef is None:    # empty listlistRef = newnodenewnode.next = newnodeelif value < listRef.next.data:    # insert in frontnewnode.next = listRef.nextlistRef.next = newnodeelif value > listRef.data:    # insert in backnewnode.next = listRef.nextlistRef.next = newnodelistRef = newnodeelse:    # insert in the middlepreNode = NonecurNode = listRefdone = listRef is Nonewhile not done:preNode = curNodepreNode = curNode.nextdone = curNode is listRef or curNode.data > valuenewnode.next = curNodepreNode.next = newnode

利用循环双端链表我们可以实现一个经典的缓存失效算法,lru:

# -*- coding: utf-8 -*-class Node(object):def __init__(self, prev=None, next=None, key=None, value=None):self.prev, self.next, self.key, self.value = prev, next, key, valueclass CircularDoubleLinkedList(object):def __init__(self):node = Node()node.prev, node.next = node, nodeself.rootnode = nodedef headnode(self):return self.rootnode.nextdef tailnode(self):return self.rootnode.prevdef remove(self, node):if node is self.rootnode:returnelse:node.prev.next = node.nextnode.next.prev = node.prevdef append(self, node):tailnode = self.tailnode()tailnode.next = nodenode.next = self.rootnodeself.rootnode.prev = nodeclass LRUCache(object):def __init__(self, maxsize=16):self.maxsize = maxsizeself.cache = {}self.access = CircularDoubleLinkedList()self.isfull = len(self.cache) >= self.maxsizedef __call__(self, func):def wrapper(n):cachenode = self.cache.get(n)if cachenode is not None:  # hitself.access.remove(cachenode)self.access.append(cachenode)return cachenode.valueelse:  # missvalue = func(n)if not self.isfull:tailnode = self.access.tailnode()newnode = Node(tailnode, self.access.rootnode, n, value)self.access.append(newnode)self.cache[n] = newnodeself.isfull = len(self.cache) >= self.maxsizereturn valueelse:  # fulllru_node = self.access.headnode()del self.cache[lru_node.key]self.access.remove(lru_node)tailnode = self.access.tailnode()newnode = Node(tailnode, self.access.rootnode, n, value)self.access.append(newnode)self.cache[n] = newnodereturn valuereturn wrapper@LRUCache()
def fib(n):if n <= 2:return 1else:return fib(n - 1) + fib(n - 2)for i in range(1, 35):print(fib(i))

10章:Recursion

Recursion is a process for solving problems by subdividing a larger problem into smaller cases of the problem itself and then solving the smaller, more trivial parts.

递归函数:调用自己的函数

# 递归函数:调用自己的函数,看一个最简单的递归函数,倒序打印一个数
def printRev(n):if n > 0:print(n)printRev(n-1)printRev(3)    # 从10输出到1# 稍微改一下,print放在最后就得到了正序打印的函数
def printInOrder(n):if n > 0:printInOrder(n-1)print(n)    # 之所以最小的先打印是因为函数一直递归到n==1时候的最深栈,此时不再# 递归,开始执行print语句,这时候n==1,之后每跳出一层栈,打印更大的值printInOrder(3)    # 正序输出

Properties of Recursion: 使用stack解决的问题都能用递归解决

  • A recursive solution must contain a base case; 递归出口,代表最小子问题(n == 0退出打印)
  • A recursive solution must contain a recursive case; 可以分解的子问题
  • A recursive solution must make progress toward the base case. 递减n使得n像递归出口靠近

Tail Recursion: occurs when a function includes a single recursive call as the last statement of the function. In this case, a stack is not needed to store values to te used upon the return of the recursive call and thus a solution can be implemented using a iterative loop instead.

# Recursive Binary Searchdef recBinarySearch(target, theSeq, first, last):# 你可以写写单元测试来验证这个函数的正确性if first > last:    # 递归出口1return Falseelse:mid = (first + last) // 2if theSeq[mid] == target:return True    # 递归出口2elif theSeq[mid] > target:return recBinarySearch(target, theSeq, first, mid - 1)else:return recBinarySearch(target, theSeq, mid + 1, last)

11章:Hash Tables

基于比较的搜索(线性搜索,有序数组的二分搜索)最好的时间复杂度只能达到O(logn),利用hash可以实现O(1)查找,python内置dict的实现方式就是hash,你会发现dict的key必须要是实现了 __hash__ 和 __eq__ 方法的。

Hashing: hashing is the process of mapping a search a key to a limited range of array indeices with the goal of providing direct access to the keys.

hash方法有个hash函数用来给key计算一个hash值,作为数组下标,放到该下标对应的槽中。当不同key根据hash函数计算得到的下标相同时,就出现了冲突。解决冲突有很多方式,比如让每个槽成为链表,每次冲突以后放到该槽链表的尾部,但是查询时间就会退化,不再是O(1)。还有一种探查方式,当key的槽冲突时候,就会根据一种计算方式去寻找下一个空的槽存放,探查方式有线性探查,二次方探查法等,cpython解释器使用的是二次方探查法。还有一个问题就是当python使用的槽数量大于预分配的2/3时候,会重新分配内存并拷贝以前的数据,所以有时候dict的add操作代价还是比较高的,牺牲空间但是可以始终保证O(1)的查询效率。如果有大量的数据,建议还是使用bloomfilter或者redis提供的HyperLogLog。

如果你感兴趣,可以看看这篇文章,介绍c解释器如何实现的python dict对象:Python dictionary implementation。我们使用Python来实现一个类似的hash结构。

import ctypesclass Array:  # 第二章曾经定义过的ADT,这里当做HashMap的槽数组使用def __init__(self, size):assert size > 0, 'array size must be > 0'self._size = sizePyArrayType = ctypes.py_object * sizeself._elements = PyArrayType()self.clear(None)def __len__(self):return self._sizedef __getitem__(self, index):assert index >= 0 and index < len(self), 'out of range'return self._elements[index]def __setitem__(self, index, value):assert index >= 0 and index < len(self), 'out of range'self._elements[index] = valuedef clear(self, value):""" 设置每个元素为value """for i in range(len(self)):self._elements[i] = valuedef __iter__(self):return _ArrayIterator(self._elements)class _ArrayIterator:def __init__(self, items):self._items = itemsself._idx = 0def __iter__(self):return selfdef __next__(self):if self._idx < len(self._items):val = self._items[self._idx]self._idx += 1return valelse:raise StopIterationclass HashMap:""" HashMap ADT实现,类似于python内置的dict一个槽有三种状态:1.从未使用 HashMap.UNUSED。此槽没有被使用和冲突过,查找时只要找到UNUSEd就不用再继续探查了2.使用过但是remove了,此时是 HashMap.EMPTY,该探查点后边的元素扔可能是有key3.槽正在使用 _MapEntry节点"""class _MapEntry:    # 槽里存储的数据def __init__(self, key, value):self.key = keyself.value = valueUNUSED = None    # 没被使用过的槽,作为该类变量的一个单例,下边都是is 判断EMPTY = _MapEntry(None, None)     # 使用过但是被删除的槽def __init__(self):self._table = Array(7)    # 初始化7个槽self._count = 0# 超过2/3空间被使用就重新分配,load factor = 2/3self._maxCount = len(self._table) - len(self._table) // 3def __len__(self):return self._countdef __contains__(self, key):slot = self._findSlot(key, False)return slot is not Nonedef add(self, key, value):if key in self:    # 覆盖原有valueslot = self._findSlot(key, False)self._table[slot].value = valuereturn Falseelse:slot = self._findSlot(key, True)self._table[slot] = HashMap._MapEntry(key, value)self._count += 1if self._count == self._maxCount:    # 超过2/3使用就rehashself._rehash()return Truedef valueOf(self, key):slot = self._findSlot(key, False)assert slot is not None, 'Invalid map key'return self._table[slot].valuedef remove(self, key):""" remove操作把槽置为EMPTY"""assert key in self, 'Key error %s' % keyslot = self._findSlot(key, forInsert=False)value = self._table[slot].valueself._count -= 1self._table[slot] = HashMap.EMPTYreturn valuedef __iter__(self):return _HashMapIteraotr(self._table)def _slot_can_insert(self, slot):return (self._table[slot] is HashMap.EMPTY orself._table[slot] is HashMap.UNUSED)def _findSlot(self, key, forInsert=False):""" 注意原书有错误,代码根本不能运行,这里我自己改写的Args:forInsert (bool): if the search is for an insertionReturns:slot or None"""slot = self._hash1(key)step = self._hash2(key)_len = len(self._table)if not forInsert:    # 查找是否存在keywhile self._table[slot] is not HashMap.UNUSED:# 如果一个槽是UNUSED,直接跳出if self._table[slot] is HashMap.EMPTY:slot = (slot + step) % _lencontinueelif self._table[slot].key == key:return slotslot = (slot + step) % _lenreturn Noneelse:    # 为了插入keywhile not self._slot_can_insert(slot):    # 循环直到找到一个可以插入的槽slot = (slot + step) % _lenreturn slotdef _rehash(self):    # 当前使用槽数量大于2/3时候重新创建新的tableorigTable = self._tablenewSize = len(self._table) * 2 + 1    # 原来的2*n+1倍self._table = Array(newSize)self._count = 0self._maxCount = newSize - newSize // 3# 将原来的key value添加到新的tablefor entry in origTable:if entry is not HashMap.UNUSED and entry is not HashMap.EMPTY:slot = self._findSlot(entry.key, True)self._table[slot] = entryself._count += 1def _hash1(self, key):""" 计算key的hash值"""return abs(hash(key)) % len(self._table)def _hash2(self, key):""" key冲突时候用来计算新槽的位置"""return 1 + abs(hash(key)) % (len(self._table)-2)class _HashMapIteraotr:def __init__(self, array):self._array = arrayself._idx = 0def __iter__(self):return selfdef __next__(self):if self._idx < len(self._array):if self._array[self._idx] is not None and self._array[self._idx].key is not None:key = self._array[self._idx].keyself._idx += 1return keyelse:self._idx += 1else:raise StopIterationdef print_h(h):for idx, i in enumerate(h):print(idx, i)print('\n')def test_HashMap():""" 一些简单的单元测试,不过测试用例覆盖不是很全面 """h = HashMap()assert len(h) == 0h.add('a', 'a')assert h.valueOf('a') == 'a'assert len(h) == 1a_v = h.remove('a')assert a_v == 'a'assert len(h) == 0h.add('a', 'a')h.add('b', 'b')assert len(h) == 2assert h.valueOf('b') == 'b'b_v = h.remove('b')assert b_v == 'b'assert len(h) == 1h.remove('a')assert len(h) == 0n = 10for i in range(n):h.add(str(i), i)assert len(h) == nprint_h(h)for i in range(n):assert str(i) in hfor i in range(n):h.remove(str(i))assert len(h) == 0

12章: Advanced Sorting

第5章介绍了基本的排序算法,本章介绍高级排序算法。

归并排序(mergesort): 分治法

def merge_sorted_list(listA, listB):""" 归并两个有序数组,O(max(m, n)) ,m和n是数组长度"""print('merge left right list', listA, listB, end='')new_list = list()a = b = 0while a < len(listA) and b < len(listB):if listA[a] < listB[b]:new_list.append(listA[a])a += 1else:new_list.append(listB[b])b += 1while a < len(listA):new_list.append(listA[a])a += 1while b < len(listB):new_list.append(listB[b])b += 1print(' ->', new_list)return new_listdef mergesort(theList):""" O(nlogn),log层调用,每层n次操作mergesort: divided and conquer 分治1. 把原数组分解成越来越小的子数组2. 合并子数组来创建一个有序数组"""print(theList)    # 我把关键步骤打出来了,你可以运行下看看整个过程if len(theList) <= 1:    # 递归出口return theListelse:mid = len(theList) // 2# 递归分解左右两边数组left_half = mergesort(theList[:mid])right_half = mergesort(theList[mid:])# 合并两边的有序子数组newList = merge_sorted_list(left_half, right_half)return newList""" 这是我调用一次打出来的排序过程
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
[10, 9, 8, 7, 6]
[10, 9]
[10]
[9]
merge left right list [10] [9] -> [9, 10]
[8, 7, 6]
[8]
[7, 6]
[7]
[6]
merge left right list [7] [6] -> [6, 7]
merge left right list [8] [6, 7] -> [6, 7, 8]
merge left right list [9, 10] [6, 7, 8] -> [6, 7, 8, 9, 10]
[5, 4, 3, 2, 1]
[5, 4]
[5]
[4]
merge left right list [5] [4] -> [4, 5]
[3, 2, 1]
[3]
[2, 1]
[2]
[1]
merge left right list [2] [1] -> [1, 2]
merge left right list [3] [1, 2] -> [1, 2, 3]
merge left right list [4, 5] [1, 2, 3] -> [1, 2, 3, 4, 5]
"""

快速排序

def quicksort(theSeq, first, last):    # average: O(nlog(n))"""quicksort :也是分而治之,但是和归并排序不同的是,采用选定主元(pivot)而不是从中间进行数组划分1. 第一步选定pivot用来划分数组,pivot左边元素都比它小,右边元素都大于等于它2. 对划分的左右两边数组递归,直到递归出口(数组元素数目小于2)3. 对pivot和左右划分的数组合并成一个有序数组"""if first < last:pos = partitionSeq(theSeq, first, last)# 对划分的子数组递归操作quicksort(theSeq, first, pos - 1)quicksort(theSeq, pos + 1, last)def partitionSeq(theSeq, first, last):""" 快排中的划分操作,把比pivot小的挪到左边,比pivot大的挪到右边"""pivot = theSeq[first]print('before partitionSeq', theSeq)left = first + 1right = lastwhile True:# 找到第一个比pivot大的while left <= right and theSeq[left] < pivot:left += 1# 从右边开始找到比pivot小的while right >= left and theSeq[right] >= pivot:right -= 1if right < left:breakelse:theSeq[left], theSeq[right] = theSeq[right], theSeq[left]# 把pivot放到合适的位置theSeq[first], theSeq[right] = theSeq[right], theSeq[first]print('after partitionSeq {}: {}\t'.format(theSeq, pivot))return right    # 返回pivot的位置def test_partitionSeq():l = [0,1,2,3,4]assert partitionSeq(l, 0, len(l)-1) == 0l = [4,3,2,1,0]assert partitionSeq(l, 0, len(l)-1) == 4l = [2,3,0,1,4]assert partitionSeq(l, 0, len(l)-1) == 2test_partitionSeq()def test_quicksort():def _is_sorted(seq):for i in range(len(seq)-1):if seq[i] > seq[i+1]:return Falsereturn Truefrom random import randintfor i in range(100):_len = randint(1, 100)to_sort = []for i in range(_len):to_sort.append(randint(0, 100))quicksort(to_sort, 0, len(to_sort)-1)    # 注意这里用了原地排序,直接更改了数组print(to_sort)assert _is_sorted(to_sort)test_quicksort()

利用快排中的partitionSeq操作,我们还能实现另一个算法,nth_element,快速查找一个无序数组中的第k大元素

def nth_element(seq, beg, end, k):if beg == end:return seq[beg]pivot_index = partitionSeq(seq, beg, end)if pivot_index == k:return seq[k]elif pivot_index > k:return nth_element(seq, beg, pivot_index-1, k)else:return nth_element(seq, pivot_index+1, end, k)def test_nth_element():from random import shufflen = 10l = list(range(n))shuffle(l)print(l)for i in range(len(l)):assert nth_element(l, 0, len(l)-1, i) == itest_nth_element()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/131346.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JDK8新特性--函数式接口--(Consumer的概念理解,模拟练习,企业实战)全流程彻底搞懂

背景&#xff0c;起因是因为在项目开发过程中&#xff0c;发现了一处代码的写法觉得很新奇看不懂&#xff0c;了解后发现是用到了函数式接口的知识。特此学习记录&#xff0c;整体过程梳理在本文。如果你不满足就会写个CURD&#xff0c;业务代码只会new来new去&#xff0c;代码…

软件工程评级B-,有大量调剂名额。北京联合大学考情分析

北京联合大学(B-) 考研难度&#xff08;☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、院校概况、23专业目录、23复试详情、各专业考情分析、各科目考情分析。 正文1239字&#xff0c;预计阅读&#xff1a;3分钟 2023考情概况 北京…

从零开始-与大语言模型对话学技术-gradio篇(4)

前言 本文介绍「星火杯」认知大模型场景创新赛中的落选项目- AI命理分析系统&#xff0c;属于个人娱乐练手。总结提炼了往期文章精华并发掘出新的知识。 包括本地部署版本和Web在线版本&#xff0c;两种打包方式基于 半自动化使用.bat手动打包迁移python项目 如何把 Gradio …

Java——》synchronized互斥性

推荐链接&#xff1a; 总结——》【Java】 总结——》【Mysql】 总结——》【Redis】 总结——》【Kafka】 总结——》【Spring】 总结——》【SpringBoot】 总结——》【MyBatis、MyBatis-Plus】 总结——》【Linux】 总结——》【MongoD…

你应该知道的几个国产化平台-行云管家

近年来我国国产化加速发展&#xff0c;国产化平台也越来越多。但还有很多小伙伴不知道有哪些&#xff0c;这里就给大家汇总几个&#xff0c;大家应该知道的国产化平台。 你应该知道的几个国产化平台 1、cpu&#xff1a;龙芯、飞腾、鲲鹏、兆芯、海光、申威 2、浏览器&#xf…

Vercel的下一件大事:AI SDK和开发人员加速器

Vercel CEO Guillermo Rauch说&#xff0c;构建AI应用程序是开发人员注册Vercel的第二大原因。Ergo&#xff1a;Vercel AI SDK和加速器。 在 2020 年代&#xff0c;很少有公司比流行的 React 框架 Next.js 的管理者 Vercel 对前端开发人员生态系统产生更大的影响。当我在 2020…

骨传导耳机怎么听到声音?骨传导耳机是否会对听力造成损害?

其实骨传导耳机让我们听到的的传声原理很简单&#xff0c;而且骨传导现象很常见&#xff0c;简单的来说&#xff0c;就是像我们平时吃薯片或者挠头发&#xff0c;无论声音再小&#xff0c;自己也能听见&#xff0c;这就是骨传导的现象&#xff0c;也是为啥骨传导耳机不需要入耳…

GitHubGiteeGitlab极狐(JihuLab)同时生成并配置SSH公私钥详细过程

GitHub-微软-github.com Gitee-开源中国- gitee.com Gitlab-乌克兰GitLab 公司-gitlab.com 极狐(JihuLab)-中国代理商运营的Gitlab -gitlab.cn或者jihulab.com 使用SSH公钥可以让你在你的电脑和GitHub等平台通讯的时候使用更安全的连接&#xff08;Git的Remote要使用SSH地址&a…

计算机竞赛 大数据疫情分析及可视化系统

文章目录 0 前言2 开发简介3 数据集4 实现技术4.1 系统架构4.2 开发环境4.3 疫情地图4.3.1 填充图(Choropleth maps)4.3.2 气泡图 4.4 全国疫情实时追踪4.6 其他页面 5 关键代码最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 大数据疫…

vue应用全局音乐(自动播放)

这里写自定义目录标题 1.从同事哪里白嫖过来的&#xff0c;主要是jq写的&#xff0c;需要单独引入jq cdn 2.打开index.html 将代码放到里面 <!DOCTYPE html> <html><head><meta charset"utf-8" /><metaname"viewport"content…

软件设计——面向对象的七大原则

前言 软件设计模式和设计原则是成为一个软件架构师的基本功&#xff0c;较好的理解这些基础知识无疑是十分重要的。在这篇文章中荔枝将会比较详细梳理一下面向对象的七大原则&#xff0c;大家可以先看看这部分内容再去学习设计模式会比较好哈哈哈哈~~~ 在软件开发中&#xff0c…

Linux RPM JDK升级

以JDK1.8升级JDK17为例 上传jdk17安装包到linux服务器 检查jdk版本 rpm -qa|grep jdk 删除查询到的jdk rpm -e --nodeps jdk1.8-1.8.0_201-fcs.x86_64 删除完毕后安装新的jdk rpm -ivh jdk-17_linux-x64_bin.rpm 检查jdk版本 java -version

深入探讨基于python的SGBM参数影响效果

什么是SGBM SGBM&#xff08;Semi-Global Block Matching&#xff09;是一种用于计算双目视觉中视差&#xff08;disparity&#xff09;的半全局匹配算法&#xff0c;在OpenCV中的实现为semi-global block matching&#xff08;SGBM&#xff09;。它是基于全局匹配算法和局部匹…

Matlab 2016安装MinGW-w64-4.9.2

Matlab 2016安装MinGW-w64-4.9.2 项目需求&#xff1a;需要将matlab中的.m文件编译为cpp文件 .dll .h .lib。 我相信大家在对matlab2016安装MinGW-w64出现了各种各样的问题。如&#xff1a;4.9.2安装失败&#xff1b;安装了其他版本但是matlab检测不到&#xff0c;或者其他各种…

Matlab之DICOM(数字图像和通信医学)格式图像数据读取函数dicomread

一、DICOM是什么&#xff1f; DICOM是数字图像和通信医学格式的图像数据&#xff0c;在MATLAB中&#xff0c;可以使用dicomread函数读取DICOM格式的图像数据。 二、dicomread函数 使用方法如下&#xff1a; imageData dicomread(filename);其中&#xff0c;filename表示DI…

pdfjs解决ie浏览器预览pdf问题

pdfjs是一个js库&#xff0c;可以将pdf文件用canvas重新绘制&#xff0c;从而无需借助pdf读取插件就可以直接预览。 目前chrome内核的浏览器已内置pdf读取插件&#xff0c;但ie浏览器还没有。而我们最近在做的一个项目使用对象是医院&#xff0c;使用的浏览器竟然还是ie。所以我…

基础秘钥、公钥、地址的熟悉指南

1. 地址 0基础漫画式阅读&#xff1a;https://www.cnblogs.com/charlesblc/p/6130433.html 清晰详细的地址生成解释&#xff1a;比特币&#xff1a;账户私钥、公钥、地址的生成 - kumata - 博客园 (cnblogs.com) 对原理更详细解释&#xff1a;区块链技术核心篇之二&#xff…

在微信小程序上怎么实现多门店管理功能

微信小程序已经成为连接线上与线下的重要工具&#xff0c;尤其对于拥有多家门店的企业来说&#xff0c;通过微信小程序可以实现多门店管理&#xff0c;提高管理效率和用户体验。下面&#xff0c;我将为大家详细介绍如何在微信小程序上实现多门店管理功能。 一、确定多门店管理功…

【网络教程】记一次使用Docker手动搭建BT宝塔面板的全过程(包含问题解决如:宝塔面板无法开启防火墙,ssh,nginx等)

文章目录 准备安装安装宝塔面板开启ssh和修改ssh的密码导出镜像问题解决宝塔面板无法开启防火墙无法启动ssh设置密码nginx安装失败设置开机启动相关服务准备 演示的系统环境:Ubuntu 22.04.3 LTS更新安装/升级docker到最新版本升级docker相关命令如下# 更新软件包列表并自动升级…

没有软件怎么管理固定资产

在当今数字化的世界中&#xff0c;我们已经习惯了使用各种软件来管理我们的日常生活和工作。然而&#xff0c;当我们面临一个看似简单的问题——如何管理固定资产时&#xff0c;我们可能会感到困惑。那么&#xff0c;如果没有软件&#xff0c;我们该如何进行资产管理呢&#xf…