1、链表的提出 :
1、试想当我们的顺序表去存储的时候增加数据都需要重新申请空间!有没有一种结构可以让我们在进行扩充的时候,原有的数据不用改变!多一个数据就增加一个呢!?这样的数据就使用到了链表。
链表的定义:
链表(linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(就是地址)。下图就是一个链表
链表和我们的顺序表有一个统称 : 他们都叫做线性表! 这些都是一维的线性关系,一条线!但是当我们扩展到二维的时候,这个时候就可以引申出“树”的概念了。
下图是链表的一个实现方式:
2、单向链表的ADT模型:
单向链表:也就是单链表,是链表中最简单的一种形式,每个节点包含两个域!就像上图的数据区(elem区)和链接区(next区)就是一个节点,! 链接指向链表中的下一个节点,而最后的一个节点的链接域则指向一个空值。 上图就是一个简单的单向链表。
3、Python中变量标识的本质 :
我们在python中如何交换一个变量呢!?a = 10 b = 20 a ,b = b ,a ,这样子就把他给交换出来了。首先当我们定义a = 10 的时候,我们python中的存储方式是:a 是一个单独的内存空间,还有一块内存的空间中存储的是值10 , 然后将讲a 指定到b上面 ,通同样的b= 20 是一样的道理的, 最后在a ,b = b,a的时候,就是双方交换了一下索引。看下图
,这样就交换过来了。本质上是改变了两者的地址导向。但是在其他语言中像是java或者是C中的定义 int a = 10 ,的时候!本质上是直接就将这个保存了数值10的空间位置的内存直接命名为a , 值可以修改! 但是变量名为a就不能再指向任何东西!只能是一个整形 。
那么 我们在定义我们的链表的时候是怎么样的本质呢!?就是我们定义一个节点类,单后类中有两个元素!分别代表的是链表的数据区(elem)和链接区(下一个节点的位置Next),这样理解我们就能写我们的节点类了。他是跟我们交换变量一样的道理!那个下一个节点的位置指向的是下一个节点的地址值。
4、单链表及节点的定义代码:
#coding=utf8
#定义节点
class Node(object):
def __init__(self,elem):
self.elem = elem
self.next = None
#定义一个链表
class SingLeLinkList(object):
"""单链表"""
#初始化一个属性,指向单链表的头部! 就是第一个节点的额头信息
def __init__(self,node = None):
self.__head = node #私有的加上 一个下划线。
def is_empty(self):
"""判断链表是否为空"""
return self.__head == None
pass
def length(self):
"""返回链表的长度"""
#cur是游标, 用来移动遍历节点
cur = self.__head
#count记录数量
count = 0
while cur != None:
count += 1
cur = cur.next
return count
def travel(self):
"""遍历整个列表"""
cur = self.__head
while cur != Node:
print(cur.elem)
cur = cur.next
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):
"""指定位置添加元素"""
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):
count += 1
pre = pre.next
node.next = pre.next
pre.next = node
def remove(self,item):
"""删除节点"""
"""
我们要使用两个游标,将pre 和 cur 两个游标!
cur : 指向的是当前节点的位置
pre : 指的是当前节点的前一个节点的位置,
删除元素:原理是让想要删除节点的前一个节点的next指向当前节点的下一个节点的地址值。
"""
cur = self.__head
per = None
#定义移动过程
while cur != None:
if cur.elem == item:
#思考当我们的节点为头结点的时候
#先判断该节点是否为头结点
if cur == self.__head:
self.__head =cur.next
else:
per.next == cur.next
break
else:
per = cur
cur = cur.next
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__":
ll = SingLeLinkList()
print(ll.is_empty())
print(ll.length())
ll.append(1)
print(ll.is_empty())
print(ll.length())
5、单链表与顺序表的对比:
链表失去了顺序表随机读取的优点,同事链表由于增加了节点的指针域,空间开销较大,但对存储空间的使用相对灵活。下图是链表与顺序表的时间复杂度的对比:
你看上面的时间复杂度! 会发现链表的时间复杂度反而增加了!但是为什么我们还是要使用链表呢?! 当我们的内存中没有足够的连续的一段内存能够使你存储数据的时候,这个时候我们的链表就会派上用场了!
在我们分析链表与顺序表的优劣势的时候,可以从我们的内存利用率上面来说。
注意虽然表面看起来复杂度都是O(n) , 但是但是链表和顺序表在插入和删除时候进行的是完全不同的操作。
链表的主要耗时操作是遍历查找,删除和插入操作本身的复杂度是O(1) 。 顺序表查找很快,主要是耗时的操作是拷贝覆盖。因为除了目标元素在尾部的特殊情况,顺序表进行插入和删除时需要对操作点之后的所有元素进行前移位操作,只能通过拷贝和覆盖的方法进行。
6、单向循环链表:
单向循环链表就是单向链表的尾部的链接区不在指向None了! 而是指向的是我们的第一个节点的头部!这样就形成了循环!在操作的时候,总体的跟单向链表的差别不大!在条件判断上需要更加严谨!需要考虑各种情况!1、当链表为none的时候。2、当链表只有一个节点的时候。3、当链表操作最后一个节点的时候。4、当链表要操作第一个节点的时候。