第三章 链表(一)
1.顺序表与链表的区别:顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活。链表结构可以充分利用计算机零散的内存空间,实现灵活的内存动态管理。链表比较适合具有大量数据,内存空间不充裕的场合。扩充时原有数据及其物理地址均未发生变化,新添加的数据只对原尾节点的next区进行操作,所以这种数据结构可以随新元素边到来边申请存储空间,所以构建时无需像顺序表一样考虑申请列表的容量大小。
2.链表的定义与结构:链表是一种常见的基础数据结构,是一种线性表,它是数据存储单元与下一个节点的位置信息的集合体。链表的实现方式示意图如下:
3.若上图中所绘制的链有方向性,则称之为单链表SingleLinkList。如上图所示,它的每个节点包含两个域,一个信息域(元素域elem)和一个链接域(next:存放下一个节点的位置)。这个链接指向链表中的下一个节点,而最后一个节点(尾结点)的链接域则指向一个空值。上图中变量p指向链表的头节点(首节点)的物理地址,从p出发通过链能找到表中的任意节点。要进行链表的实现首先要考虑链表中数据的存储结构——节点,和对节点进行的相应操作。故链表是将相关数据与对数据进行的相关操作封装到一起的一种数据结构,这再次体现了面向对象的思想(可以用'类'实现)。
4.python中实现变量交换的本质:
可以通过上图了解到,变量名绑定的内存块中存储的是对象的地址,并不是存储的该对象,在a,b=b,a(右侧采用元组(20,10)的形式)中:等号右侧的元素代表该内存链接的数据,=意味着将右侧数据的地址赋值给左侧对应变量名,使其存储在变量名绑定的内存空间中。
5.节点的实现(创建类时只需要设定形式参数为数据,另外要考虑到只建一个节点的情况,此时存在的第一个节点中只有数据,链接区应指空None):
class SingleNode(object): #单链表的节点
def __init__(self,elem):
self.elem=elem #elem存放数据
self.next=None #next存放下一节点的链接
6.单链表中对节点的相关操作:is_empty() 链表是否为空;length() 链表长度;travel() 遍历整个链表;add(item) 链表头部添加元素;append(item) 链表尾部添加元素;insert(pos, item) 指定位置添加元素;remove(item) 删除节点;search(item) 查找节点是否存在。将这些相关操作封装到类对象SingleLinkList中。所以在运用链表时,一定要注意有两个相关的类(节点类与链表类)。(删除操作尽量保证每个表方法的一致性:1.判断是否空链表;2.(内嵌)判断是否只有一个节点;3.(内嵌的内嵌)如果找到要删除的元素,判断该元素位置为头、尾、还是中间。)链表的翻转:可以通过新建链表,然后在从头遍历旧链表的同时,不断在新链表的头部插入元素。
7.单链表的实现:(十分重要)
# -*- coding:utf-8 -*-
class Node():
def __init__(self,elem):
self.elem=elem
self.next=None
class SingleLinkList():
def __init__(self,node=None):
self.__head=node
def is_empty(self):
return self.__head==None
def length(self):
cur=self.__head
num=0
while cur!=None:
cur=cur.next
num+=1
return num
def travel(self):
cur=self.__head
while cur!=None:
print(cur.elem,end='')
cur=cur.next
print()
def add(self,elem):
node=Node(elem)
node.next=self.__head
self.__head=node
def append(self,elem):
node=Node(elem)
if self.is_empty()==True:
self.__head = node
else:
cur=self.__head
while cur.next!=None:
cur=cur.next
cur.next=node
def insert(self,pos,elem):
if pos<=0:
return self.add()
elif pos>self.length()-1:
return self.append()
else:
node = Node(elem)
num=0
cur=self.__head
while num<pos-1:
cur=cur.next
num+=1
node.next=cur.next
cur.next=node
def search(self,elem):
cur=self.__head
while cur!=None:
if cur.elem==elem:
return True
cur=cur.next
return False
def remove(self, elem):
cur = self.__head
pre=None
if self.__head!=None:
if self.__head.next!=None:
while cur!= None:
if cur.elem==elem:
if pre==None:
self.__head=self.__head.next
elif cur.next==None:
pre.next=None
else:
pre.next=cur.next
break
pre=cur
cur=cur.next
else:
if self.__head.elem==elem:
self.__head=None
if __name__=='__main__':#模块中独立运行主程序的语句
ll = SingleLinkList()
ll.add(1)
ll.add(2)
ll.append(3)
ll.insert(2, 4)
print("length:", ll.length())
ll.travel()
print(ll.search(3))
print(ll.search(5))
ll.remove(2)
print("length:", ll.length())
ll.travel()
ll.remove(1)
print("length:", ll.length())
ll.travel()
ll.remove(3)
print("length:", ll.length())
ll.travel()
ll.remove(4)
print("length:", ll.length())
ll.travel()
单链表的翻转:
def reverse0(list):#外部函数
list0=SingleLinkList()
#list0.add(list._SingleLinkList__head.elem)
cur=list._SingleLinkList__head
while cur!=None:
list0.add(cur.elem)
cur=cur.next
return list0
8.链表与顺序表的对比:链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。
同样操作对应的时间复杂度为:
链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是O(1)。顺序表查找很快,主要耗时的操作是拷贝覆盖。因为除了目标元素在尾部(对应最优时间复杂度)的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。