一、单向链表
1、链表的提出
(1)链表和顺序表的区别???
顺序表的特点:存储空间必须连续,而且一旦不够的情况下就涉及到动态的改变数据区。
思考:有没有一种数据结构,能够在进行扩充的时候,原有的数据完全不用变,你多一个,我就增加一个。====>链表
【简单的指向】===>链表的操作:
【所以:链表的实现方式,把数据分成两部分:数据区+链接区】
(2)链表和顺序表统称为:线性表
-
顺序表:将元素顺序地存放在一块连续的存储区里,元素间的顺序关系由它们的存储顺序自然表示。
-
链表:将元素存放在通过链表构造起来的一系列存储块中。
2、单链表的ADT模型
单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
- 表元素域elem用来存放具体的数据。
- 链接域next用来存放下一个节点的位置(python中的标识)
- 变量p指向链表的头节点(首节点)的位置,从p出发能找到表中的任意节点。
3、单链表与顺序表的对比
链表失去了顺序表随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大,但对存储空间的使用要相对灵活。
链表与顺序表的各种操作复杂度如下所示:
操作 | 链表 | 顺序表 |
访问元素 | o(n) | o(1) |
在头部插入/删除 | o(1) | o(n) |
在尾部插入/删除 | o(n) | o(1) |
在中间插入/删除 | o(n) | o(n) |
注:
虽然表面看起来复杂度都是 O(n),但是链表和顺序表在插入和删除时进行的是完全不同的操作。
链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是O(1)。
顺序表查找很快,主要耗时的操作是拷贝覆盖。因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前后移位操作,只能通过拷贝和覆盖的方法进行。
分析:
(1)原有顺序表访问元素,在尾部插入/删除的时间复杂度都是O(1),但是换成链表时,时间复杂度是O(n),为什么还要用链表呢?
答:假设要存储一个巨大的数据,而操作系统没有一块连续的内存给你分配,这时候可以使用链表。
因为链表可以把整个内存中所有分散的、只要可用的,通过链表进行串起来,但是顺序表达不到这样的效果。
(2)顺序表的优点:存取元素时,可以通过O(1)的方式一次性定位。
缺点:顺序表的空间必须是连续的,如果一旦动态的改变,整个存储区都得改变。而且若是存储大数据时,没有相应的存储空间,则达不到要求。
(3)链表的特点:对于分散的、离散的内存空间可以达到充分的利用。但是利用的同时,额外的开销也是大的。且利用链表存取元素时的时间复杂度是O(n)。
重点:(4)在中间插入/删除,链表和顺序表的时间复杂度都是O(n),但是其中的n所代表的重复的操作是不一样的
对于链表来说,n花费在遍历上;对于顺序表,n花费在数据搬迁上。
4、单链表:节点的实现+单链表中的操作
【操作:is_empty()链表是否为空;length()链表长度;travel()遍历整个链表;add(item)链表头部添加元素;append(item)链表尾部添加元素;insert(pos,item)指定位置添加元素;remove(item)删除节点;search(item)查找节点】
代码如下:
class Node(object): #链表中的节点 def __init__(self,elem): self.elem=elem #元素区 self.next=None #刚创建的节点,默认是在链表之外,所以它的指向是Null class SingleLinkList(object): #单向链表 def __init__(self,node=None): #定义的节点默认是None self.__head=node #初始化的头结点 指向 定义的node节点 def is_empty(self): '''链表是否为空''' return self.__head==None #链表的头节点指向None,则返回True,为空;否则不为空! def length(self): '''链表长度''' cur = self.__head # cur游标,用来移动遍历节点 count = 0 # 记录数量 while cur != None: count += 1 cur = cur.next # 每次往后移一个位置 return count def travel(self): '''遍历整个链表''' cur=self.__head #cur游标,用来移动遍历节点 while cur!=None: print(cur.elem,end=' ') cur=cur.next #每次往后移一个位置 print("") #相当于多次调用travel时换行 def add(self,item): '''链表头部添加元素''' node=Node(item) node.next=self.__head self.__head=node def append(self,item): '''链表尾部添加元素''' node=Node(item) #将要添加的元素转化为节点 if self.is_empty(): #如果链表为空,则将头结点指向该节点 self.__head=node else: cur=self.__head while cur.next!=None: cur=cur.next cur.next=node def insert(self,pos,item): '''指定位置添加元素 pos 从0开始''' if pos<=0: #为头插法 self.add(item) elif pos>self.length()-1: #尾插法 self.append(item) else: node=Node(item) pre=self.__head count=0 while count<(pos-1): pre=pre.next count+=1 #跳出while循环时,count=pos-1,此时pre指向pos-1位置,在插入节点位置的前一个 node.next=pre.next pre.next=node def remove(self,item): '''删除节点''' cur=self.__head pre=None while cur!=None: if cur.elem==item: #找到这个节点 #先判断此节点是否是头结点 if cur==self.__head: #头结点 self.__head=cur.next else: #不是头结点 pre.next=cur.next break else: pre=cur #先移动pre游标 cur=cur.next #再移动cur游标 def search(self,item): '''查找节点是否存在''' cur=self.__head while cur!=None: if cur.elem==item: return True else: cur=cur.next return False if __name__ == '__main__': l1=SingleLinkList() print(l1.is_empty()) #True print(l1.length()) #0 l1.append(1) print(l1.is_empty()) #False print(l1.length()) #1 l1.append(2) l1.append(3) l1.append(4) l1.append(5) l1.add(8) l1.travel() # 8 1 2 3 4 5 l1.insert(-1,9) l1.travel() #9 8 1 2 3 4 5 l1.insert(2,100) l1.travel() #9 8 100 1 2 3 4 5 l1.insert(10,200) l1.travel() #9 8 100 1 2 3 4 5 200 l1.remove(100) l1.travel() #9 8 1 2 3 4 5 200 l1.remove(9) l1.travel() #8 1 2 3 4 5 200 l1.remove(200) l1.travel() #8 1 2 3 4 5
二、单向循环链表
单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点。
单向循环链表:节点的实现+单链表中的操作
【单向循环链表的操作:is_empty()链表是否为空;length()链表长度;travel()遍历整个链表;add(item)链表头部添加元素;append(item)链表尾部添加元素;insert(pos,item)指定位置添加元素;remove(item)删除节点;search(item)查找节点】
class Node(object): #节点的构造 def __init__(self,elem): self.elem=elem self.next=None class SingleCycleLinkList(object): #单向循环链表 def __init__(self,node=None): #定义的节点默认是None #【与单项链表不同】 self.__head=node #初始化的头结点 指向 定义的node节点 if node: #如果传入不为None node.next=node #设置循环 def is_empty(self): '''链表是否为空''' return self.__head==None #链表的头节点指向None,怎返回True,为空;否则不为空! def length(self): #【与单项链表不同】 '''链表长度''' if self.is_empty(): #如果链表时空链表,返回0 return 0 #链表不为空 cur = self.__head # cur游标,用来移动遍历节点 count = 1 # 记录数量 while cur.next!=self.__head: count += 1 cur = cur.next # 每次往后移一个位置 return count def travel(self): '''遍历整个链表''' cur=self.__head #cur游标,用来移动遍历节点 while cur.next!=self.__head: print(cur.elem,end=' ') cur=cur.next #每次往后移一个位置 print("") #相当于多次调用travel时换行 def add(self,item): #【与单项链表不同】 (需要找到尾节点,然后尾节点指向头结点) 【先找到尾节点,然后插入头结点,最后尾节点指向头结点】 '''链表头部添加元素''' node=Node(item) if self.is_empty(): #若是空链表 self.__head=node node.next=node else: #不是空链表 cur=self.__head while cur.next!=self.__head: cur=cur.next #退出循环时,cur指向尾节点 node.next=self.__head self.__head=node cur.next=self.__head def append(self,item): #【与单项链表不同】 '''链表尾部添加元素''' node=Node(item) #将要添加的元素转化为节点 if self.is_empty(): #如果链表为空 self.__head=node node.next=node else: cur=self.__head while cur.next!=self.__head: cur=cur.next node.next=self.__head cur.next=node def insert(self,pos,item): #【与单项链表相同,不修改】 '''指定位置添加元素 pos 从0开始''' if pos<=0: #为头插法 self.add(item) elif pos>self.length()-1: #尾插法 self.append(item) else: node=Node(item) pre=self.__head count=0 while count<(pos-1): pre=pre.next count+=1 #跳出while循环时,count=pos-1,此时pre指向pos-1位置,在插入节点位置的前一个 node.next=pre.next pre.next=node def remove(self,item): #【与单项链表相同,小修改】 '''删除节点''' cur=self.__head pre=None while cur.next!=self.__head: if cur.elem==item: #找到这个节点 #先判断此节点是否是头结点 if cur==self.__head: #头结点,然后找尾节点 rear=self.__head while rear.next!=self.__head: rear=rear.next self.__head=cur.next rear.next=self.__head else: #中间节点 pre.next=cur.next return '' else: pre=cur #先移动pre游标 cur=cur.next #再移动cur游标 #退出循环,cur指向尾节点 if cur.elem==item: if cur==self.__head: #链表只有一个节点 self.__head=Node else: pre.next=cur.next def search(self,item): #【与单项链表大致相同,小修改】 '''查找节点是否存在''' if self.is_empty(): #如果是空链表,就直接返回 False return False cur=self.__head while cur.next!=self.__head: if cur.elem==item: return True else: cur=cur.next #退出循环时,cur指向尾节点 【需要判断尾节点是否指向item】 if cur.elem==item: return True return False if __name__ == '__main__': l1=SingleCycleLinkList() print(l1.is_empty()) #0 print(l1.length()) #True l1.append(1) print(l1.is_empty()) #False print(l1.length()) #1 l1.append(2) l1.append(3) l1.append(4) l1.append(5) l1.add(8) l1.travel() # 8 1 2 3 4 5 l1.insert(-1,9) l1.travel() #9 8 1 2 3 4 5 l1.insert(2,100) l1.travel() #9 8 100 1 2 3 4 5 l1.insert(10,200) l1.travel() #9 8 100 1 2 3 4 5 200 l1.remove(100) l1.travel() #9 8 1 2 3 4 5 200 l1.remove(9) l1.travel() #8 1 2 3 4 5 200 l1.remove(200) l1.travel() #8 1 2 3 4 5
三、双向链表
每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;
而另一个指向下一个节点,当此节点为最后一个节点是,指向空值。
双向链表:节点的实现+单链表中的操作
【单向循环链表的操作:is_empty()链表是否为空;length()链表长度;travel()遍历整个链表;add(item)链表头部添加元素;append(item)链表尾部添加元素;insert(pos,item)指定位置添加元素;remove(item)删除节点;search(item)查找节点】
class Node(object): #节点 def __init__(self,elem): self.elem=elem #元素区 self.next=None #刚创建的节点,默认是在链表之外,所以它的后继节点为Null self.prev=None #刚创建的节点,默认是在链表之外,所以它的前驱节点为Null class DoubleLinkList(object): #双向链表 def __init__(self,node=None): #定义的节点默认是None 【与单链表相同】 self.__head=node #初始化的头结点 指向 定义的node节点 def is_empty(self): #【与单链表相同】 '''链表是否为空''' return self.__head==None #链表的头节点指向None,则返回True,为空;否则不为空! def length(self): #【与单链表相同】 '''链表长度''' cur = self.__head # cur游标,用来移动遍历节点 count = 0 # 记录数量 while cur != None: count += 1 cur = cur.next # 每次往后移一个位置 return count def travel(self): #【与单链表相同】 '''遍历整个链表''' cur=self.__head #cur游标,用来移动遍历节点 while cur!=None: print(cur.elem,end=' ') cur=cur.next #每次往后移一个位置 print("") #相当于多次调用travel时换行 def add(self,item): #【与单链表不同,只需要加入一行】 '''链表头部添加元素''' node=Node(item) node.next=self.__head self.__head=node node.next.prev=node #增加一行 def append(self,item): #【与单链表不同,只需要加入一行】 '''链表尾部添加元素''' node=Node(item) #将要添加的元素转化为节点 if self.is_empty(): #如果链表为空,则将头结点指向该节点 self.__head=node else: cur=self.__head while cur.next!=None: cur=cur.next cur.next=node node.prev=cur # 增加一行 def insert(self,pos,item): #【与单链表不同】 '''指定位置添加元素 pos 从0开始''' if pos<=0: #为头插法 self.add(item) elif pos>self.length()-1: #尾插法 self.append(item) else: cur=self.__head node=Node(item) count=0 while count<(pos-1): cur=cur.next count+=1 #跳出while循环时,cur指向pos位置 node.next=cur node.prev=cur.prev cur.prev.next=node cur.prev=node def remove(self,item): #【与单链表不同】 '''删除节点''' cur=self.__head while cur!=None: if cur.elem==item: #找到这个节点 #先判断此节点是否是头结点 if cur==self.__head: #头结点 self.__head=cur.next if cur.next: #判断链表是否只有一个节点 cur.next.prev=None else: #不是头结点 cur.prev.next=cur.next if cur.next: cur.next.prev=cur.prev break else: cur=cur.next #移动cur游标 def search(self,item): #【与单链表相同】 '''查找节点是否存在''' cur=self.__head while cur!=None: if cur.elem==item: return True else: cur=cur.next return False if __name__ == '__main__': l1=DoubleLinkList() print(l1.is_empty()) #True print(l1.length()) #0 l1.append(1) print(l1.is_empty()) #False print(l1.length()) #1 l1.append(2) l1.append(3) l1.append(4) l1.append(5) l1.add(8) l1.travel() # 8 1 2 3 4 5 l1.insert(-1,9) l1.travel() #9 8 1 2 3 4 5 l1.insert(2,100) l1.travel() #9 8 100 1 2 3 4 5 l1.insert(10,200) l1.travel() #9 8 100 1 2 3 4 5 200 l1.remove(100) l1.travel() #9 8 1 2 3 4 5 200 l1.remove(9) l1.travel() #8 1 2 3 4 5 200 l1.remove(200) l1.travel() #8 1 2 3 4 5