在进行存储数据时,你请求计算机提供存储空间,计算机会给你一个存储地址。当有很多数据需要存储的时候,可以采用两种形式——数组和链表,那么这两种方式又有什么区别呢?
(一) 数组与链表
某大学教室的每排座椅11个,从左到右排号,为0-10号(类似内存空间),某宿舍6名女生来上自习,从0号开始依次入座,她们都是连续的挨在一起的。一段时间后,某女同学的男朋友来了,非得和她坐在一起,浴室这个女生之后其他女生就依次往后移动一下,空出一个位置让该男生坐下了。这对情侣太腻歪,其他女生不乐意了,于是纷纷把男朋友叫来上自习,然后就像之前一样,不断的给来的男生挪位置,很不巧,最后来的一位男生没位置了,他们就商量去更大的教室,那里的每排座椅较多。他们心里开始埋怨了,真麻烦。这就是数组的形式了。
数组的存储就是和上述的情形是一样的,添加一个新元素,其余的元素需要移动,空间不足时需要移到其他的内存中去。也许可以预留空间,但这显然不是好办法,浪费内存。
后来这6名女生再去小教室上自习的时候,如果有男朋友来的话,女生就会说:自己找教室去,然后告诉我你的教室号。这样方便多了,该女生只需要记住男朋友的教室号,就能找到男朋友所在了。这就是链表的形式了。
链表中的元素可以存放在内存的任何地方,链表的每个元素都存存储了下一个元素的地址,从而使一系列随机的内存地址串在一起。
先来看看比较简单的单向列表
(二) 单向链表结构
首先来看看单向列表的基本结构:1,,2,3,4表示的是链表的节点,节点1表示的是链表的头部,4节点是链表的尾部,其指针存放的是null.。
每一个节点是由数据域和指针域组成,如下图所示:
通过数据可以找到节点,通过指针域可以找到下一个节点。通过节点可获得节点的数据和指针信息。
明白了基本的结构,下面通过编程来实现单向列表结构。
(三)、编程实现
在代码实现之前,需要确定单向列表需要实现哪些功能。
功能
- is_empty() 链表是否为空
- length() 链表的长度
- scan() 遍历链表的节点
- add(item) 头部添加节点
- append(item) 在尾部添加节点
- insert(position,item) 中间插入节点
- remove(item) 移除指定节点
- searchbyvalue(item) 通过元素查找节点是否存在
- searchbyindex(index) 通过索引查找节点的元素
- reverse() 反转单向链表
编程实现
(1) 创建节点的类
添加节点之前,需要创建节点,可以采用类的形式来实现该功能。节点包括数据和指针。创建的时候,指针是空的。
class Node:
def __init__(self,data):
self.data=data
self.next=None
(2) 创建单向链表
先创建单向链表的类,后续一步一步的实现链表的功能。
class SingleLinkedlist(object):
"""单向链表"""
def __init__(self,node=None):
#如果创建的时候具有节点,那个链表头就是该节点,如果不具有节点,那链表头就是None
self.__head=node
后面的函数都是包含在上述的单向链表的类中的。
(2.1) 链表是否为空
如果链表的头元素不为None时,则链表不为空,否则为空
def is_empty(self):
"""链表是否为空"""
return self.__head==None
(2.2) 链表的长度
这里采用的利用函数来返回链表的长度。在设计的时候,也可以将length设计成链表的属性,在增加,删除元素的时候,进行length的更新。以属性的形式更加合理。
def length(self):
"""链表长度"""
count=0
"""遍历元素"""
curnode=self.__head
while curnode: #当前节点为None时,遍历完毕
count+=1
curnode=curnode.next
return count
(2.3)链表的遍历
当前节点为None时,链表遍历完毕
def scan(self):
"""遍历列表"""
curNode = self.__head # 当前节点
while curNode:
print(curNode.data, end=' ')
curNode = curNode.next
print()
(2.4)链表的头部添加
def add(self,item)
node=Node(item)
node.next=self.__head
self.__head=node
当决定将新节点node从头部插入链表的时候,此时的self.__head代表的就是新链表的第二个节点了。因此node.next=self.__head就表示将第二个节点存入上一个节点的指针中,然后将链表的头部进行更新:self.__head=node
(2.5)链表的尾部插入
def append(self, item):
"""尾部插入"""
node = Node(item) # 创建新节点
lastnode = None # 保存最后一个节点
if self.is_empty(): # 链表为空
node.next = None # 新插入的节点为第一个节点
self.__head = node
else: # 链表非空
"""遍历找到最后一个节点,节点的next属性为None时为最后一个节点"""
cur = self.__head
while cur.next: # 此处的循环条件如果使用cur的话,在尾部添加的时候会出错
cur = cur.next
lastnode = cur
lastnode.next = node
当要从尾部插入节点时,需要遍历节点,查找到最后一个节点,然后进行相应的操作。
在这里踩了一个坑:下面的代码节选自上一段代码,不同的是,循环条件是cur。
while cur:
cur = cur.next
lastnode = cur
lastnode.next = node
试想一下,如果采用的是cur作为循环条件时,当遍历到最后一个节点时,节点不为空,循环条件满足,继续进行下循环,cur = cur.next,该语句使得cur变为了None,lastnode也变成了None。退出循环后,执行后面的 lastnode.next = node 就会报错,显示NoneType没有next属性。
如前面的代码,采用cur.next作为条件则不会。因为,cur.next为None时,说明遍历完成了,退出循环后,cur指向的就是最后一个节点。
(2.6) 插入节点到指定位置
def inset(self, position, item):
"""插入节点到指定位置"""
if position <= 0:
# 如果插入位置在0或者以前,那么可以当做头插法来进行插入
self.add(item)
elif position > self.length() - 1:
self.append(item)
else:
targetNode = self.__head
count = 0
while count < position - 1:
count += 1
targetNode = targetNode.next
# 退出循环后,targetNode指向的是position-1处的节点
node = Node(item)
node.next = targetNode.next
targetNode.next = node
(2.7)移除指定节点
删除节点,即是断开指定节点与其前后节点的联系,然后将指定位置节点的前一个节点与后一个节点接在一起。在查找节点时,需要记录指定节点的前一个节点,便于将断裂的链表续接起来。
需要判断指定位置的节点是否是链表的头部,如果是的话,需要将链表的头部后移一位。
def remove(self, item):
targetNode = self.__head
pretarget = None # 目标节点的前一个节点
while targetNode:
if targetNode.data == item:
# 判断是否是头节点
if targetNode == self.__head:
self.__head = targetNode.next # 将头结点后移一位
else:
pretarget.next = targetNode.next
break
else:
pretarget = targetNode
targetNode = pretarget.next
(2.8) 通过值查找节点是否存在
该函数返回的是bool值。
def searchbyValue(self, item):
targetNode = self.__head
while targetNode:
if targetNode.data == item:
return True
else:
targetNode = targetNode.next
return False
(2.9) 通过索引查找节点元素
节点的索引从0开始。返回的是节点的元素。
def searchbyIndex(self,index):
if index<=0:
return self.__head.data
elif index>=self.length()-1:
curnode=self.__head
while curnode.next:
curnode=curnode.next
return curnode.data
else:
curnode=self.__head
while index:
curnode=curnode.next
index-=1
return curnode.data
以上各函数的代码是在记录这篇博客的时候,边写边用手打的,主要是为了练习手写代码,后面将写博客之前在pycharm中调试过的代码附上,这部分代码测试没问题的。
(2.10) 反转单向链表
先上代码。这是我根据对链表的理解,写出的反转链表的代码,不知道是否简便,先记录下来再说。
def reverse(self):
"""curNode:表示当前节点的指针"""
"""nextNode:表示curNode下一个节点的指针"""
"""tempNode:存储nextNode的下一个节点的指针,在nextNode往后移动时,赋给nextNode"""
if self.__head==None: #链表空,直接返回
return
else:
curNode=self.__head
nextNode=curNode.next
curNode.next = None #头部变尾部,next属性为None
tempNode=None #临时存储节点
while nextNode:
temp=nextNode #临时存储节点
tempNode=nextNode.next
if not tempNode: #如果下一个节点为None,说明遍历已经快完成,此时只有两个节点了。
nextNode.next=curNode
self.__head=nextNode
break
else:
nextNode.next = curNode
curNode = temp
nextNode = tempNode
self.scan()
(四)完成的代码
class Node:
def __init__(self, data):
self.data = data
self.next = None # 初始设置下一节点为空
class SingleLinkList(object):
"""单链表"""
def __init__(self, node=None):
self.__head = node
def is_empty(self):
"""链表是否为空"""
return self.__head == None
def length(self):
"""链表的长度"""
count = 0
# 遍历的节点
curnode = self.__head
while curnode:
count += 1
curnode = curnode.next
return count
def scan(self):
"""遍历列表"""
curNode = self.__head # 当前节点
while curNode:
print(curNode.data, end=' ')
curNode = curNode.next
print()
def add(self, item):
"""从头插入"""
node = Node(item) # 创建节点
node.next = self.__head # 新节点指向下一个节点(在头部插入节点时,新节点的指针其实指向的就是原来链表的头部)
self.__head = node # 更新头结点到最新插入的节点上
def append(self, item):
"""尾部插入"""
node = Node(item) # 创建新节点
lastnode = None # 保存最后一个节点
if self.is_empty(): # 链表为空
node.next = None # 新插入的节点为第一个节点
self.__head = node
else: # 链表非空
"""遍历找到最后一个节点,节点的next属性为None时为最后一个节点"""
cur = self.__head
while cur.next: # 此处的循环条件如果使用cur的话,在尾部添加的时候会出错
cur = cur.next
lastnode = cur
lastnode.next = node
def inset(self, position, item):
"""插入节点到指定位置"""
if position <= 0:
# 如果插入位置在0或者以前,那么可以当做头插法来进行插入
self.add(item)
elif position > self.length() - 1:
self.append(item)
else:
targetNode = self.__head
count = 0
while count < position - 1:
count += 1
targetNode = targetNode.next
# 退出循环后,targetNode指向的是position-1处的节点
node = Node(item)
node.next = targetNode.next
targetNode.next = node
def remove(self, item):
targetNode = self.__head
pretarget = None # 目标节点的前一个节点
while targetNode:
if targetNode.data == item:
# 判断是否是头节点
if targetNode == self.__head:
self.__head = targetNode.next # 将头结点后移一位
else:
pretarget.next = targetNode.next
break
else:
pretarget = targetNode
targetNode = pretarget.next
def searchbyValue(self, item):
targetNode = self.__head
while targetNode:
if targetNode.data == item:
return True
else:
targetNode = targetNode.next
return False
def searchbyIndex(self,index):
if index<=0:
return self.__head.data
elif index>=self.length()-1:
curnode=self.__head
while curnode.next:
curnode=curnode.next
return curnode.data
else:
curnode=self.__head
while index:
curnode=curnode.next
index-=1
return curnode.data
def reverse(self):
if self.__head==None:
return
else:
curNode=self.__head
nextNode=curNode.next
curNode.next = None
tempNode=None
while nextNode:
#temp=nextNode
tempNode=nextNode.next
if not tempNode:
nextNode.next=curNode
self.__head=nextNode
break
else:
nextNode.next = curNode
curNode = nextNode
nextNode = tempNode
self.scan()
sl = SingleLinkList()
sl.append(2)
sl.add(1)
sl.append(3)
sl.inset(4, 4)
sl.append(5)
sl.scan() #链表遍历
sl.reverse() #链表反转
从上图的结果可以看出,链表反转正确。
上述内容如有不妥之处,敬请指正,谢谢!