一、链表的引入
顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活。
链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。
链表的结构
链表中的每一个节点都包括数据域和链接域两部分。数据域中存放真实的数据,链接域存放下一个数据的地址。
Python中变量标识的本质
a = 10 a本身是一个地址,该地址指向10
当a,b = b,a其实相当于修改了a,b 两个变量的指向,将a指向了20,将b指向了20.
二、单链表的引入
2.1 节点的实现
链表中的每一个数据都由数据区和链接域两部分组成,在Python或者其他语言中没有一个数据类型可以同时储存这两个数据,因此我们可以定义节点这样一个类,而节点中的数据和链接区可以想象成该类的两个属性。
由于链表中的链接域存储的是下一个数据的地址,但在内存中并没有地址这一个明确的数据,因此我们可以将链接域的值设置为下一个节点。
在定义一个新的节点时,我们没有办法确定该节点的下一个节点,因此我们可以先将其设置为空,当有下一个节点时,在重新对其赋值。
class SingleNode(object):
"""单链表的节点"""
def __init__(self,item,):
#item存放的数据区域
self.item = item
#next存放下一个节点的标识
self.next = None
2.2 单链表的操作
is_empty() 链表是否为空
length() 链表长度
travel() 遍历整个链表
append(item) 链表尾部添加元素
add(item) 链表头部添加元素
insert(pos, item) 指定位置添加元素
search(item) 查找节点是否存在
remove(item) 删除节点
2.3 单链表的操作实现
2.3.1 判断单链表是否为空
如果单链表为空,则其首节点为空(self.__head = None);如果单链表不为空,则其首节点不为空(self.__head = node)。
class SingleNode(object):
"""单链表的节点"""
def __init__(self,item):
self.item = item
self.next = None
class SingleLinkList(object):
def __init__(self,node = None):
# 向参数传递默认值,如果没有向该参数传递实参,则该参数等于默认值
# 否则,等于实参值
self.__head = node
def is_empty(self):
"""单链表是否为空"""
# if self.__head == None:
# return True
# else:
# return False
return self.__head == None
node = SingleNode(100)
s = SingleLinkList(node)
print(s.is_empty())
2.3.2 单链表的长度
定义一个游标cur,让cur从头指针head出发直到遍历到为指针None
定义一个计数变量count,cur移动一次,count就加1
class SingleNode(object):
"""单链表的节点"""
def __init__(self,item):
self.item = item
self.next = None
class SingleLinkList(object):
def __init__(self,node = None):
# 向参数传递默认值,如果没有向该参数传递实参,则该参数等于默认值
# 否则,等于实参值
self.__head = node
def length(self):
"""链表长度"""
count = 0
cur = self.__head
while cur != None:
count += 1
cur = cur.next
return count
node = SingleNode(100)
s = SingleLinkList(node)
print(s.length())
2.3.3 遍历单链表
遍历链表和求链表长度的思想基本一致,唯一不同的就是我们不需要计数变量但每循环一次,我们就需要打印该节点的数据
class SingleNode(object):
"""单链表的节点"""
def __init__(self,item):
self.item = item
self.next = None
class SingleLinkList(object):
def __init__(self,node = None):
# 向参数传递默认值,如果没有向该参数传递实参,则该参数等于默认值
# 否则,等于实参值
self.__head = node
def travel(self) :
"""遍历整个链表"""
cur = self.__head
while cur != None:
#打印节点中的数据
print(cur.item,end = ' ')
cur = cur.next
node = SingleNode(100)
s = SingleLinkList(node)
s.travel()
2.3.4 向链表尾部添加元素
向链表尾部添加节点时,需要进行两个操作:
1.创建一个新的节点 node = SingleNode(item)
2.遍历到最后一个节点,令其的next属性等于新的节点
cur.next == node
注意,当链表为空时,self.__head = None,而None类型没有next属性
class SingleNode(object):
"""单链表的节点"""
def __init__(self,item):
self.item = item
self.next = None
class SingleLinkList(object):
def __init__(self,node = None):
# 向参数传递默认值,如果没有向该参数传递实参,则该参数等于默认值
# 否则,等于实参值
self.__head = node
def append(self,item):
"""链表尾部添加元素"""
node = SingleNode(item)
#空链表的情况
if self.is_empty():
self.__head = node
else:
cur = self.__head
#遍历找到最后一个节点
while cur.next != None:
#添加新的节点
cur = cur.next
cur.next = node
s = SingleLinkList()
s.append(200)
s.append(220)
s.append(890)
s.travel()
2.3.5 链表头部添加元素
向链表头部添加节点,需要进行一下三步操作:
1.创建一个新的节点 node = SingleNode(item)
2.令原先的首指针等于新节点的下一个节点标识
node.next = self.__head
3.令新节点等于首指针 self.__head = node
class SingleNode(object):
"""单链表的节点"""
def __init__(self,item):
self.item = item
self.next = None
class SingleLinkList(object):
def __init__(self,node = None):
# 向参数传递默认值,如果没有向该参数传递实参,则该参数等于默认值
# 否则,等于实参值
self.__head = node
def add(self,item):
"""链表头部添加元素"""
node = SingleNode(item)
node.next = self.__head
self.__head = node
s = SingleLinkList()
s.append(200)
s.append(220)
s.append(890)
s.add(67)
s.travel()
2.3.6 insert(pos, item) 指定位置添加元素
在指定位置插入节点需要进行以下三步操作
1.通过循环找到指定插入位置的前一个节点
count < pos-1
2.将当前节点下一个节点赋值给新节点的下一个节点标识
node.next = pre.next
3.将新节点赋值给当前节点的下一个节点标识
pre.next = node
需要考虑特殊两种情况,头部插入add()和尾部插入append()
class SingleNode(object):
"""单链表的节点"""
def __init__(self,item):
self.item = item
self.next = None
class SingleLinkList(object):
def __init__(self,node = None):
# 向参数传递默认值,如果没有向该参数传递实参,则该参数等于默认值
# 否则,等于实参值
self.__head = node
def insert(self,pos,item):
node = SingleNode(item)
count = 0
pre = self.__head
#考虑特殊情况:
#头部插入
if pos < 0:
self.add(item)
#尾部插入
elif pos > self.length():
self.append(item)
#指定位置插入
#通过循环找到指定位置的前一个位置
else:
while count < (pos -1):
count += 1
pre = pre.next
node.next = pre.next
pre.next = node
s = SingleLinkList()
s.append(200)
s.append(220)
s.append(890)
s.insert(2,67)
s.travel()
注意:节点位置从0开始。
2.3.7 查找节点是否存在
遍历链表,如果当前节点的item值等于所查找的item值,则返回True,如果不是则游标后移。如果遍历完整个链表仍没有找到,则返回False。
class SingleNode(object):
"""单链表的节点"""
def __init__(self,item):
self.item = item
self.next = None
class SingleLinkList(object):
def __init__(self,node = None):
# 向参数传递默认值,如果没有向该参数传递实参,则该参数等于默认值
# 否则,等于实参值
self.__head = node
def search(self,item):
cur = self.__head
while cur != None:
if cur.item == item:
return True
else:
cur = cur.next
return False
s = SingleLinkList()
s.append(200)
s.append(220)
s.append(890)
s.insert(2,67)
s.travel()
print(s.search(200))
2.3.8 删除节点
删除指定节点,具体操作如下
1.遍历链表,判断cur.item == item
2.如果相等,则令pre.next= cur.next
3.不相等则指针继续后移 pre= cur cur= cur.next
注意特殊情况,如果删除的是头节点则令self.__head = cur.next
class SingleNode(object):
"""单链表的节点"""
def __init__(self,item):
self.item = item
self.next = None
class SingleLinkList(object):
def __init__(self,node = None):
# 向参数传递默认值,如果没有向该参数传递实参,则该参数等于默认值
# 否则,等于实参值
self.__head = node
def remove(self,item):
pre = None
cur = self.__head
while cur != None:
#找到指定的节点
if cur.item == item:
#当删除的是头节点时
if cur == self.__head:
self.__head = cur.next
else:
pre.next = cur.next
break
#如果不加break,则cur 一直是当前的值,不会移动,进入了死循环
else:
# 没有找到继续执行
pre = cur
cur = cur.next
s = SingleLinkList()
s.append(200)
s.append(220)
s.append(890)
s.insert(2,67)
s.travel()
print()
s.remove(200)
s.travel()
2.4 链表与顺序表的对比
操作 | 顺序表复杂度 | 链表复杂度 |
---|---|---|
访问元素 | O(1) | O(n) |
在头部插入或删除 | O(n) | O(1) |
在尾部插入或删除 | O(1) | O(n) |
在中间插入或删除 | O(n) | O(n) |
写链表的代码建议
1.理解指针或引用的含义(pre、cur)
2.警惕指针丢失(先处理后一个节点,在处理前一个节点)
3.重点留意边界条件处理(空链表、头部插入或删除,尾部插入或删除)
4.举例画图,辅助思考
如何实现LRU缓存淘汰算法?
1.缓存是高数据读取技术,常见的包括CPU缓存、数据库缓存、浏览器缓存等。缓存大小有限,当缓存满时,就需要删除缓存中的部分数据。
2.缓存淘汰的三种常见策略:先进先出(FIFO First In,First Out)、最少使用策略(LFU Least FrequentlyUsed)、最近最少使用策略(LRU Least Recently Used)
3.我们可以这样来操作:
当有一个新的数据被访问时,我们从链表头开始顺序遍历链表。
a.如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除,然后再插入到链表的头部。
b.如果此数据没有在缓存链表中,又可以分为两种情况:
如果此时缓存未满,则将此结点直接插入到链表的头部;
如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。
越靠近链表尾部的结点是越早之前访问的