Python数据结构与算法篇(四)-- 链表的实现
实现线性表的另一种常用方式就是基于链接结构,用链接关系显式表示元素之间的顺序关联。基于链接技术实现的线性表称为链接表或者链表。
采用链接方式实现线性表的基本想法如下:
- 把表中的元素分别存储在一批独立的存储块(称为表的结点)里。
- 保证从组成表结构中的任一个结点可找到与其相关的下一个结点。
- 在前一结点里用链接的方式显式地记录与下一结点之间的关联。
这样,只要能找到组成一个表结构的第一个结点,就能顺序找到属于这个表的其他结点。从这些结点里可以看到这个表中的所有元素。
链接技术是一类非常灵活的数据组织技术,实现链表有多种不同的方式。下面首先讨论最简单的单链表,其中在每个表结点里记录着存储下一个表元素的结点的标识(引用/链接)。后面将介绍另外一些结构的链表,它们各有所长,支持不同的需要。在下面的讨论中,将把“存储着下一个表元素的结点”简称为“下一结点”。
1 单链表
单向链接表(下面将简称为单链表或者链表)的结点是一个二元组,形式如下图a所示,其表元素域 elem 保存着作为表元素的数据项(或者数据项的关联信息),链接域 next 里保存同一个表里的下一个结点的标识。

在最常见形式的单链表里,与表里的n个元素对应的n个结点通过链接形成一条结点链,如上图b所示。从引用表中首结点的变量(上图b中变量p)可以找到这个表的首结点,从表中任一结点可以找到保存着该表下一个元素的结点(表中下一结点),这样,从p出发就能找到这个表里的任一个结点。
要想掌握一个单链表,就需要(也只需要)掌握这个表的首结点,从它出发可以找到这个表里的第一个元素(即在这个表结点里保存的数据,保存在它的 elem域中),还可以找到这个表里的下一结点(有关信息保存在这个结点的 next 域中)。按照同样的方式继续下去,就可以找到表里的所有数据元素。
也就是说,为了掌握一个表,只需要用一个变量保存着这个表的首结点的引用(标识或称为链接)。今后将把这样的变量称为表头变量或表头指针。
小结一下:
- —个单链表由一些具体的表结点构成。
- 每个结点是一个对象,有自己的标识,下面也常称其为该结点的链接。
- 结点之间通过结点链接建立起单向的顺序联系。
为了表示一个链表的结束,只需给表的最后结点(表尾结点)的链接域设置一个不会作为结点对象标识的值(称为空链接),在 Python 里自然可以用系统常量 None 表示这种情况,在上图里用接地符号“丄”表示链表结束,下面将一直这样表示。
通过判断一个(域或变量的)值是否为空链接,可知是否已到链表的结束。在顺序扫描表结点时,应该用这种方法确定操作是否完成。如果一个表头指针的值是空链接,就说明“它所引用的链表已经结束”,这是没有元素就已结束,说明该表是空表。
在实现链表上的算法时,并不需要关心在某个具体的表里各结点的具体链接值是什么(虽然保存在表结构里的值都是具体的),只需要关心链表的逻辑结构。对链表的操作也只需要根据链表的逻辑结构考虑和实现。
为方便下面的讨论,现在定义个简单的表结点类:
class ListNode:def __init__(self, elem=0, next=None):self.elem = elem self.next = next
1.1 基本操作
创建空链表: 只需要把相应的表头变量设置为空链接,在Python语言中将其设置为None,在其他语言里也有惯用值,例如有的语言里用0作为这个特殊值。
删除链表: 应丢弃这个链表里的所有结点。这个操作的实现与具体的语言环境有关。在一些语言(如C语言)里,需要通过明确的操作释放一个个结点所用的存储。在Python语言中这个操作很简单,只需简单地将表指针赋值为None,就抛弃了链表原有的所有结点。Python解释器的存储管理系统会自动回收不用的存储。
判断表是否为空: 将表头变量的值与空链接比较。在Python语言中,就是检查相应变量的值是否为None.
判断表是否满: 一般而言链表不会满,除非程序用完了所有可用的存储空间。
加入元素
现在考虑给单链表加入元素的操作,同样有插入位置问题,可以做首端插入、尾端插人或者定位插人。不同位置的操作复杂度可能不同。
首先应该注意,在链表里加入新元素时,并不需要移动已有的数据,只需为新元素安排一个新结点,然后根据操作要求,把新结点连在表中的正确位置。也就是说,插入新元素的操作是通过修改链接,接入新结点,从而改变表结构的方式实现的。
表首端插入: 首端插入元素要求把新数据元素插入表中,作为表的第一个元素,这是最简单的情况。这一操作需要通过三步完成:
- 创建一个新结点并存入数据(下图a表示要向表头变量 head 的链表加入新首元素13,为它创建了新结点,变量q指着该结点。这是实际插入前的状态)。

-
把原链表首结点的链接存入新结点的链接域 next,这一操作将原表的一串结点链接在刚创建的新结点之后。
-
修改表头变量,使之指向新结点,这个操作使新结点实际成为表头变量所指的结点,即表的首结点(上图b表示设置链接的这两步操作完成后的状态,新结点已成为 head 所指链表的首结点,13成为这个表的首元素。注意,示意图中链接指针的长度和形状都不表示任何意义,只有图中的链接指向关系有意义)。
注意,即使在插入前head指向的是空表,上面三步也能正确完成工作。这个插人只是一次安排新存储和几次赋值,操作具有常量时间复杂度。
示例代码段:
q = ListNode(13)
q.next = head.next
head = q
一般情况的元素插入: 要想在单链表里的某位置插入一个新结点,必须先找到该位置之前的那个结点,因为新结点需要插入在它的后面,需要修改它的next 域。如何找到这个结点的问题将在后面讨论,先看已经找到了这个结点之后怎样插入元素。
设变量pre已指向要插入元素位置的前一结点,操作也分为三步:
- 创建一个新结点并存入数据(下图a是实际插入前的状态)。
- 把 pre 所指结点 next 域的值存入新结点的链接域 next,这个操作将原表在 pre 所指结点之后的一段链接到新结点之后。
- 修改 pre 的 next 域,使之指向新结点,这个操作把新结点链入被操作的表,整个操作完成后的状态如下图b所示。

注意,即使在插入前 pre 所指结点是表中最后一个结点,上述操作也能将新结点正确接入,作为新的表尾结点。
q = ListNode(13)
q.next = pre.next
pre.next = q
删除元素
删除链表中元素,也可通过调整表结构删除表中结点的方式完成。这里也区分两种情况:删除表头结点的操作可以直接完成,删除其他结点也需要掌握其前一结点。
删除表首元素: 删除表中第一个元素对应于删除表的第一个结点,为此只需修改表头指针,令其指向表中第二个结点。丢弃不用的结点将被Python解释器自动回收。
head = head.next
一般情况的元素删除:一般情况删除须先找到要删元素所在结点的前一结点,设用变量pre指向,然后修改pre的next域,使之指向被删结点的下一结点。
pre.next = pre.next.next
显然,这两个操作都要求被删结点存在。

如果在其他编程语言里删除结点,还可能要自己释放存储。Python的自动存储管理机制能自动处理这方面的问题,使编程工作更简单,也保证了安全性。
扫描、定位和遍历
在一般情况下插入和删除元素,都要求找到被删结点的前一结点。另外,程序里也可能需要定位链表中的元素、修改元素、逐个处理其中元素等。这些操作都需要检查链表的内容,实际上是检查表中的一些(或全部)结点。
由于单链表只有一个方向的链接,开始情况下只有表头变量在掌握中,所以对表内容的一切检查都只能从表头变量开始,沿着表中链接逐步进行。这种操作过程称为链表的扫描,这种过程的基本操作模式是:
p = head
while p is not None and 还需继续的其他条件:对p所指结点里的数据做所需操作p = p.next
根据Python语言的规定,这里的 p is not None
可以简单地只写一个 p
。
循环的继续(或结束)条件、循环中的操作由具体问题决定。循环中使用的辅助变量p称为扫描指针。注意,每个扫描循环必须用一个扫描指针作为控制变量,每步迭代前必须检查其值是否为None,保证随后操作的合法性。这与连续表的越界检查类似。
上面表扫描模式是最一般的链表操作模式,下面介绍几个常用操作的实现。
按下标定位: 按 Python 惯例,链表首结点的元素应看作下标0,其他元素依次排列。确定第i个元素所在结点的操作称为按下标定位,可以参考表扫描模式写出:
p = head
while p is not NOne and i > 0:i -= 1p = p.next
假设循环前变量i已有所需的值,循环结束时可能出现两种情况:或者扫描完表中所有结点还没有找到第i个结点,或者p所指结点就是所需。通过检查 p 值是否为None可以区分这两种情况。显然,如果现在需要删除第k个结点,可以先将i设置为k-1,循环后检查i是0且p.next不是None就可以执行删除了。
按元素定位: 假设需要在链表里找到满足谓词pred的元素。同样可以参考上面的表扫描模式,写出的检索循环如下:
p = head
while p is not None and not pred(p.elem):p = p.next
循环结束时或者p是None;或者 pred(p.elem)
是 True,找到了所需元素。
完整的扫描称为遍历,这样做通常是需要对表中每个元素做一些事情,例如:
p = head
while p is not None:print(p.elem)p = p.next
这个循环依次输出表中各元素。只以条件 p is not None
控制循环,就能完成一遍完整的遍历。同样模式可用于做其他工作。
链表操作的复杂度
总结一下链表操作的时间复杂度。
-
创建空表:O(1)O(1)O(1)。
-
删除表:在Python里是O(1)O(1)O(1)。当然,Python 解释器做存储管理也需要时间。
-
判断空表:O(1)O(1)O(1)。
-
加入元素(都需要加一个T(分配)的时间)
- 首端加入元素:O(1)O(1)O(1)。
- 尾端加入元素:O(n)O(n)O(n)、因为需要找到表的最后结点。
- 定位加人元素:O(n)O(n)O(n),平均情况和最坏情况。
-
删除元素:
- 首端删除元素:O(1)O(1)O(1)。
- 尾端删除元素:O(n)O(n)O(n)。
- 定位删除元素:O(n)O(n)O(n),平均情况和最坏情况。
- 其他册除:通常需要扫描整个表或其一部分O(n)O(n)O(n)。
扫描、定位或遍历操作都需要检查一批表结点,其复杂度受到表结点数的约束,都是 O(n)O(n)O(n) 操作。其他在工作中有此类行为的操作也至少具有 O(n)O(n)O(n) 时间复杂度。
求表的长度
在使用链表时,经常需要求表的长度,为此可以定义一个函数:
p, n = head, 0
while p is not None:n += 1p = p.next
return n
这个函数采用表扫描模式,遍历表中所有结点完成计数。显然,这个求表长度的算法所用时间与表结点个数成正比,具有 O(n)O(n)O(n) 时间复杂度。
实现方式的变化
以求表的长度为例,如果程序经常需要调用上面函数,O(n)O(n)O(n) 复杂度就可能成为效率问题。如果表很长,执行该函数就可能造成可察觉的停顿。解决这个问题的一种方法是改造单链表的实现结构,增加一个表长度记录。显然,这个记录不属于任何表元素,是有关表的整体的信息。表示这件事的恰当方法是定义一种链表对象,把表的长度和表中的结点链表都作为这个表对象的数据成分,如下图所示。

图中变星p指向表对象,这个对象的一个数据域记录表中元素个数(图中的20表示这个表当时有20个结点),另一个域引用着该表的结点链。采用了这种表示方式,求表长度的操作就可以简单返回元素计数域的值。但另一方面,这种表的每个变动操作都需要维护计数值。从整体看有得有失。这种调整消除了一个线性时间操作,可能在一些应用中很有意义。
1.2 单链表类的实现
自定义异常
为能合理地处理一些链表操作中遇到的错误状态(例如,方法执行时遇到了无法操作的错误参数),首先为链表类定义一个新的异常类:
class LinkedListUnderflow(ValueError):pass
这里把LinkedListUnderflow定义为标准异常类valueError的子类,准备在空表访问元索等场合抛出这个异常。在这些情况下抛出 valueError 也没问题,但定义了自己的异常类,就可以写专门的异常处理器,在一些情况下可能有用。
LList类的定义,初始化函数和简单操作
现在基于结点类 ListNode 定义一个单链表对象的类,在这种表对象里只有一个引用链接结点的_head域,初始化为None表示建立的是一个空表。判断表空的操作检查_head;在表头插入数据的操作是prepend,它把包含新元素的结点链接在最前面;操作 pop 删除表头结点并返回这个结点里的数据:
class LList:def __init__(self):self._head = Nonedef is_empty(self):return self._head is Nonedef prepend(self, elem):self._head = ListNode(elem, self._head)def pop(self):if self._head is None: # 无结点,引发异常raise LinkedListUnorderflow("in pop")e = self._head.elemself._head = self._head.nextreturn e
这里把 LList 对象的 _head
域作为对象的内部表示,不希望外部使用。上面定义里的几个操作都很简单,只有 pop 操作需要检查对象的状态,表中无元素时引发异常。
后端操作
在链表的最后插入元素,必须先找到链表的最后一个结点。其实现首先是一个扫描循环,找到相应结点后把包含新元素的结点插入在其后。下面是定义:
def append(self, elem):if self._head is None:self._head = ListNone(elem)return p = self._headwhile p.next is not None:p = p.nextp.next = ListNode(elem)
这里需要区分两种情况:如果原表为空,引用新结点的就应该是表对象的_head域,否则就是已有的最后结点的next域。两种情况下需要修改的数据域不一样。许多链表变动操作都会遇到这个问题,只有表首端插入/删除可以统一处理。
现在考虑删除表中最后元素的操作,也就是要删除最后的结点。前面说过,要从单链表中删除一个结点,就必须找到它的前一结点。在尾端删除操作里,扫描循环应该找到表中倒数第二个结点,也就是找到p.next.next
为None的p。下面定义的pop_last
函数不仅删去表中最后元素,还把它返回(与pop统一)。
在开始一般性扫描之前,需要处理两个特殊情况:如果表空没有可返回的元素时应该引发异常。表中只有一个元素的情况需要特殊处理,因为这时应该修改表头指针。一般情况是先通过循环找到位置,取出最后结点的数据后将其删除:
def pop_last(self):if self._head is None: # 空表raise LinkeListUnderflow("in pop_last")p = self._headif p.next is None: # 表中只有一个元素e = p.elemself._head = Nonereturn ewhile p.next.next is not None: # 直到p.next是最后结点p = p.nexte = p.next.elemp.next = Nonereturn e
LList类的下一个方法是找到满足给定条件的表元素。这个方法有一个参数,调用时通过参数提供一个判断谓词,该方法返回第一个满足谓词的表元素。显然,这个操作也需要采用前面的基本扫描模式。定义如下:
def find(self, pred):p = self._headwhile p is not None:if pred(p.elem):return p.elemp = p.next
最后一个方法非常简单,但实际中可能很有用。在开发一个表类的过程中,人们会经常想看看被操作的表的当时情况:
def print_all(self):p = self._headwhile p is not None:print(p.elem, end='')if p.next is not None:print(', ', end='')p = p.nextprint('')
2 循环单链表
单链表的另一常见变形是循环单链表(简称循环链表),其中最后一个结点的next域不用None,而是指向表的第一个结点,如下图a所示。但如果仔细考虑,就会发现在链表对象里记录表尾结点更合适(如下图b),这样可以同时支持 O(1)O(1)O(1) 时间的表头/表尾插入和 O(1)O(1)O(1) 时间的表头删除。当然,由于循环链表里的结点连成一个圈,哪个结点算是表头或表尾,主要是概念问题,从表的内部形态上无法区分。
循环单链表操作与普通单链表的差异就在于扫描循环的结束控制。易见,一些不变操作的实现也要修改,如 printall。

这种表对象只需一个数据域 _rear,它在逻辑上始终引用着表的尾结点。前端加入结点,就是在尾结点和首结点之间加人新的首结点,尾结点引用不变。通过尾结点引用很容易实现这个操作。另一方面,尾端加入结点也是在原尾结点之后(与首结点之间)插人新结点,只是插入后要把它作为新的尾结点,因此需要更新尾结点引用。这两个操作都要考虑空表插入的特殊情况。对于输出表元素的操作,关键在于循环结束的控制。下面实现中比较扫描指针与表头结点的标识,到达了表头结点就结束。前端弹出操作也很容易实现,后端弹出操作需要通过一个扫描循环确定位置。
下面循环单链表类定义只实现了几个典型方法,供参考:
class LCList: # 循环单链表类def __init__(self):self._rear = Nonedef is_empty(self):return self._rear is Nonedef prepend(self, elem): # 前端插入p = LNode(elem)if self._rear is None:p.next = p # 建立一个结点的环self._rear = pelse:p.next = self._rear.nextself.rear.next = pdef append(self, elem): # 尾插入self.prepend(elem)self._rear = self._rear.nextdef pop(self): # 前端弹出if self._rear is None:raise LinkedListUnderflow("in pop of CLList")p = self._rear.nextif self._rear is p:self._rear = Noneelse:self._rear.next = p.nextreturn p.elemdef printall(self): # 输出元素if self.is_empty():returnp = self._rear.nextwhile True:print(p.elem)if p is self._rear:breakp = p.next
3 双链表
单链表只有一个方向的链接,只能做一个方向的扫描和逐步操作。即使增加了尾结点引用,也只能支持 O(1)O(1)O(1) 时间的首端元素加入/删除和尾端加入。如果希望两端插入和删除操作都能高效完成,就必须修改结点(从而也是链表)的基本设计,加入另一方向的链接。这样就得到了双向链接表,简称双链表。有了结点之间的双向链接,不仅能支持两端的高效操作,一般结点操作也会更加方便。当然,这样做也需要付出代价:每个结点都需要增加一个链接域,增加的空间开销与结点数成正比,是 O(n)O(n)O(n)。如果每个表结点里的数据规模比较大,新增加的开销可能就显得不太重要了。
为了支持首尾两端的高效操作,双链表应该采用下图所示的结构,包含一个尾结点引用域。易见,从双链表中任一结点出发,可以直接找到其前后的相邻结点(都是 O(1)O(1)O(1) 操作)。而对单链表而言,只能方便地找到下一个结点,要找前一结点,就必须从表头开始逐一检查(通过一次扫描)。

可以直接找到当前结点的前后结点,使得双链表的许多操作都很容易地进行。下面假定结点的下一结点引用域是next,前一结点引用域是prev。
结点操作
先考虑结点删除。实际上,只要掌握着双链表里一个结点,就可以把它从表中取下,并把其余结点正确链接好。下图说明了这个操作。示例代码是:

p.prev.next = p.next
p.next.prev = p.prev
这两个语句使p所指结点从表中退出,其余结点保持顺序和链接。如果要考虑前后可能无结点的情况,只需增加适当的条件判断。
在任一结点的前后加入结点的操作也很容易局部完成,只需掌握确定加入位置的这个结点。易见,加入一个结点需要做四次引用赋值。
相关文章:

Python数据结构与算法篇(四)-- 链表的实现
实现线性表的另一种常用方式就是基于链接结构,用链接关系显式表示元素之间的顺序关联。基于链接技术实现的线性表称为链接表或者链表。 采用链接方式实现线性表的基本想法如下: 把表中的元素分别存储在一批独立的存储块(称为表的结点)里。保…...

【java基础】循环语句、中断控制语句
文章目录循环while循环for循环for each循环中断控制语句breakcontinue带标签的break(相当于goto)循环 在java中有3种循环,分别是while循环,for循环,for each循环 while循环 while循环的形式是 while(condition) statement int i 5;while …...

万字长文带你实战 Elasticsearch 搜索
ES 高级实战 前言 上篇我们讲到了 Elasticsearch 全文检索的原理《别只会搜日志了,求你懂点原理吧》,通过在本地搭建一套 ES 服务,以多个案例来分析了 ES 的原理以及基础使用。这次我们来讲下 Spring Boot 中如何整合 ES,以及如何在 Spring Cloud 微服务项目中使用 ES 来…...

Web网页测试全流程解析论Web自动化测试
1、功能测试 web网页测试中的功能测试,主要测试网页中的所有链接、数据库连接、用于在网页中提交或获取用户信息的表单、Cookie 测试等。 (1)查看所有链接: 测试从所有页面到被测特定域的传出链接。 测试所有内部链接。 测试链…...

初识Python——“Python”
各位CSDN的uu们你们好呀,今天进入到了我们的新专栏噢,Python是小雅兰的专业课,那么现在,就让我们进入Python的世界吧 计算机基础概念 什么是计算机? 什么是编程? 编程语言有哪些? Python背景知…...

LocalDateTime使用
开发中常常需要用到时间,随着jdk的发展,对于时间的操作已经摒弃了之前的Date等方法,而是采用了LocalDateTime方法,因为LocalDateTime是线程安全的。 下面我们来介绍一下LocalDateTime的使用。 时间转换 将字符串转换为时间格式…...

设计模式学习笔记 - 外观模式
设计模式学习笔记 - 外观模式一、影院管理问题二、传统方式解决影院管理问题三、外观模式介绍1、基本介绍2、原理类图四、外观模式解决影院管理问题五、外观模式在MyBatis框架应用的源码分析六、外观模式的注意事项和细节一、影院管理问题 组建一个家庭影院:DVD 播放…...

如何写出一份优秀的简历和求职信?
写一份优秀的简历和求职信是成功求职的重要一步。 01、简历 突出重点信息:把最重要的信息放在简历的前面,例如您的工作经验和教育背景等。 使用简明扼要的语言:在简历中使用简短的句子和简明扼要的语言,让招聘者能够快速了解您的…...

OpenGL超级宝典学习笔记:原子计数器
前言 本篇在讲什么 本篇为蓝宝书学习笔记 原子计数器 本篇适合什么 适合初学Open的小白 本篇需要什么 对C语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重实践,轻理论&#…...

深圳/东莞/惠州师资比较强的CPDA数据分析认证
深圳/东莞/惠州师资比较强的CPDA数据分析认证培训机构 CPDA数据分析师认证是中国大数据领域有一定权威度的中高端人才认证,它不仅是中国较早大数据专业技术人才认证、更是中国大数据时代先行者,具有广泛的社会认知度和权威性。 无论是地方政府引进人才、…...

LeetCodeHOT100热题02
写在前面 主要是题目太多,所以和前面的分开来记录。有很多思路的图都来源于力扣的题解,如侵权会及时删除。不过代码都是个人实现的,所以有一些值得记录的理解。之前的算法系列参看: 剑指offer算法题01剑指offer算法题02 七、动…...

虹科Dimetix激光测距仪在锯切系统中的应用
HK-Dimetix激光测距仪——锯切系统应用 许多生产设施,例如金属服务中心,使用切割锯将每个客户的订单切割成一定长度。定长切割过程通常涉及卷尺和慢跑锯的传送带。但更简单的替代方法是使用虹科Dimetix非接触式激光距离传感器。 为了切断大长度的棒材&…...

FreeRTOS入门(02):任务基础使用与说明
文章目录目的创建任务任务调度任务控制延时函数任务句柄获取与修改任务优先级删除任务挂起与恢复任务强制任务离开阻塞状态空闲任务总结目的 任务(Task)是FreeRTOS中供用户使用的最核心的功能,本文将介绍任务创建与使用相关的基础内容。 本…...

ESP通过乐为物联控制灯,微信发送数值,ESP上传传感器数据
暂时放个程序 //ME->>{“method”: “update”,“gatewayNo”: “01”,“userkey”: “2b64c489d5f94237bcf2e23151bb7d01”}&^! //Ser->>{“f”:“message”,“p1”:“ok”}&^! //ME->>{“method”: “upload”,“data”:[{“Name”:“A1C”,“Val…...

Linux:共享内存api使用
代码: #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <arpa/inet.h> #include <sys/un.h> #include <sys/ipc.h…...

android9.0 java静态库操作JNI实例 动态注册
一、java层 源码 目录:/Demo/java/com/android/simpleini/SimpleJNI.java package com.example.simplejni;import android.app.Activity; import android.os.Bundle; import android.widget.TextView;public class SimpleJNI {private IpoManagerService(Context …...

自定义复杂图片水印
我的社交能力还不如5岁儿童和狗。 文章目录前言一、主要工具类总结前言 之前写过一些简单的图片压缩和图片加水印:JAVA实现图片质量压缩和加水印 本次主要是针对图片加水印进行一个升级,图片水印自定义,自适应大小。 来,先看几…...

文章读后感——《人间清醒,内容为王》
观后感 人间清醒,内容为王 - 技术er究竟该如何写博客?1024上海嘉年华之敖丙演讲观后感。 致敬愿意带领后生的前辈——哈哥 哈哥,《人间清醒,内容为王》这篇,我的鱼获, 是里面传递出来写技术博客的思维与想法…...

51单片机入门 - 驱动多位数码管
我使用的是普中51单片机开发板A2套件(2022),驱动数码管可能需要参考电路原理图。开发环境的搭建教程在本专栏的 51单片机开发环境搭建 - VS Code 从编写到烧录 有过介绍。 关于我的软硬件环境信息: Windows 10STC89C52RCSDCC &am…...

Java进击框架:Spring(一)
Java进击框架:Spring(一)前言创建Spring项目Spring IoC容器和Beans介绍Bean的概述Spring IoC配置元数据实例化Bean依赖注入循环依赖详细配置生命周期回调Bean定义继承基于注解的容器配置Component和进一步的原型注解自动检测类和注册Bean定义…...

Java笔记(18)
目录 什么是mvc? 什么是SpringMVC Spring MVC的特点: 原理: 什么是Thymeleaf? thymeleaf依赖包:...

【免费教程】地下水环境监测技术规范HJ/T164-2020解读使用教程
地下水环境监测技术规范依据《中华人民共和国环境保护法》第十一条“国务院环境保护行政主管部门建立监测制度、制订监测规范”和《中华人民共和国水污染防治法》的要求,积极开展地下水环境监测,掌握地下水环境质量,保护地下水水质࿰…...

Html 代码学习
场景:在页面中插入音频 代码 常见属性: src 音频的路径 controls 显示播放的控件 autoplay 自动播放 loop 循环播放 场景:在页面中插入视频 代码 常见属性: src 路径 controls 显示播放的控件 autoplay 自动播放 要配合muted 例如 autoplay muted loop 循环播放 链接 /…...

如何通过IP找到地址?
在我们印象中,我们都知道可以通过 IP 地址找到某个人。但当我们细想一下,我们会发现其实 IP 地址与地理位置并不是直接相关的。那我们到底是如何通过 IP 地址找到地址的呢?答案是:通过自治系统(Autonomous System&…...

业务单据堆积如山?如何提升会计做账效率?
某集团以“创建现代能源体系、提高人民生活品质”为使命,形成了贯通下游分销、中游贸易储运、上游生产的清洁能源产业链和涵盖健康、文化、旅游、置业的生命健康产品链。目前,某集团在全国21个省,为超过2681万个家庭用户、21万家企业提供能源…...

华为OD机试题,用 Java 解【VLAN 资源池】问题
最近更新的博客 华为OD机试 - 猴子爬山 | 机试题算法思路 【2023】华为OD机试 - 分糖果(Java) | 机试题算法思路 【2023】华为OD机试 - 非严格递增连续数字序列 | 机试题算法思路 【2023】华为OD机试 - 消消乐游戏(Java) | 机试题算法思路 【2023】华为OD机试 - 组成最大数…...

面试加分项:JVM 锁优化和逃逸分析详解
1 锁优化JVM 在加锁的过程中,会采用自旋、自适应、锁消除、锁粗化等优化手段来提升代码执行效率。1.1 自旋锁和自适应自旋现在大多的处理器都是多核处理器 ,如果在多核心处理器,有让两个或者以上的线程并行执行,我们可以让一个等待…...

C++继承、构造函数和析构函数
构造函数 与 析构函数 构造函数代表一个对象的生成,主要作用是初始化类的成员变量,可以被重载 如果没有显式构造函数,则实例化对象时,系统会自动生成一个无参的构造函数 构造函数的名称与类名相同 析构函数代表的是一个对象的销…...

Python如何实现异步并发之async(1)
前言 本文是该专栏的第14篇,后面会持续分享python的各种干货知识,值得关注。 在python中使用async方式,实现异步并发,而本文笔者提到的代码案例仅支持python3.7及以上版本,这主要在于不同的版本之间都更新了异步的使用方法,这点暂时不详述了。 而所谓的异步,通常就是程…...

震撼!阿里首次开源 Java 10万字题库,Github仅一天星标就超60K
程序员面试 现在是程序员找工作、跳槽最重要的月份。随着行业的发展程序员面试也越来越难,面试中都是7分的能力,再加上3分的技巧; 对于应聘者,重中之重的就是简历,面试前一定要将最拿手和最能吸引面试官的技能在简历…...