线性表就是数据排成像一条线一样的结构.每个现行表上的数据最多只有前和后两个方向.常见的线性表结构:数组,链表、队列、栈等
是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。连续的内存空间和相同类型的数据(随机访问的前提)
顺序表分为两部分:表的元素集合和表的元素的整体情况。list是分离式结构
数组怎么根据下标随机访问的:通过寻址公式:a[i]_address = base_address + i * data_type_size
优点:
可以随时存取表中的元素
缺点:
删除,插入数据需要移动大量元素
线性表的链式存储,通过任意的存储单元存储线性表中的数据元素,对于每个节点出来存放元素自身的信息之外,还存放指向后继的指针。
不需要地址连续的存储单元。
插入和删除不需要移动元素,只需修改指针,但不能随意存取,要找到某个特定节点,必须从表头开始遍历,依次查找。但单链表只能从头开始遍历。
两个指针prior和next,分别指向前驱节点和后继节点。
不需要从头开始遍历,可以从表中任意一个节点开始遍历。
提高尾部加入元素的效率。
缺点:双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。
优点:虽然两个指针比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。那相比单链表,双向链表适合解决哪种问题呢?
从结构上来看,双向链表可以支持O(1)时间复杂度的情况下找到前驱结点,正是这样的特点,也使双向链表在某些情况下的插入、删除,查找操作都要比单链表简单、高效。
最后一个节点不是指向NULL而是头节点,从而形成一个环。可以从表中任意一个元素开始遍历整个链表。
适用于存储有循环特点的数据,比如约瑟夫问题。
1.插入、删除和随机访问的时间复杂度
插入、删除操作:
在进行数组的插入、删除操作时,为了保持内存数据的连续性,需要做大量的数据搬移,所以时间复杂度是O(n)。而在链表中插入或者删除一个数据,我们并不需要为了保持内存的连续性而搬移结点,因为链表的存储空间本身就不是连续的,只需要考虑相邻结点的指针改变,所以对应的时间复杂度是O(1)。
随机访问:
链表要想随机访问第k个元素,就没有数组那么高效了。因为链表中的数据并非连续存储的,所以无法像数组那样,根据首地址和下标,通过寻址公式就能直接计算出对应的内存地址,而是需要根据指针一个结点一个结点地依次遍历,直到找到相应的结点。链表随机访问需要O(n)的时间复杂度。
结论:
数组:插入、删除的时间复杂度是O(n),随机访问的时间复杂度是O(1)。 链表:插入、删除的时间复杂度是O(1),随机访问的时间复杂端是O(n)。
2.数组
优点:
数组简单易用,在实现上使用连续的内存空间,可以借助CPU的缓冲机制预读数组中的数据,所以访问效率更高,
缺点 大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它,导致“内存不足(out of memory)”。如果声明的数组过小,则可能出现不够用的情况。这时只能再申请一个更大的内存空间,把原数组拷贝进去,非常费时
3.链表
优点:
链表本身没有大小的限制,天然地支持动态扩容,我觉得这也是它与数组最大的区别。
缺点: 1)内存空间消耗更大,因为需要额外的空间存储指针信息。内存消耗会翻倍 2)对链表进行频繁的插入和删除操作,会导致频繁的内存申请和释放,容易造成内存碎片,如果是Java语言,还可能会造成频繁的GC(自动垃圾回收器)操作。
4.如何选择?
如果代码对内存的使用非常苛刻,那数组就更适合。 链表更适合插入、删除操作频繁的场景,查询的时间复杂度较高。
重点考虑 链表代码的边界问题:
如果链表为空时,代码是否能正常工作?
如果链表只包含一个结点时,代码是否能正常工作?
如果链表只包含两个结点时,代码是否能正常工作?
代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
单向链表的python实现
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 # cur游标,用来移动遍历节点
count = 0 # count记录数量
while cur:
count += 1
cur = cur.next
return count
def travel(self):
'''遍历'''
cur = self.__head # cur游标,用来移动遍历节点
while cur:
print(cur.elem, end=' ') #不换行
cur = cur.next
print("")
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 #cur和头节点指向同一个节点
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 # 让pre等于head指向的节点不是等于head
count = 0
while count < (pos-1):
count += 1
pre = pre.next # 循环结束后,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 #删除节点,将删除位置前一节点的next指向删除位置的后一节点
break
else:
pre = cur #考虑pre初始为None,后面的循环时可以等价为 pre = pre.next
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()
ll.append(2)
ll.append(3)
ll.add(8)
ll.add(67)
ll.travel()
ll.remove(8)
ll.travel()
ll.insert(3,5)
ll.travel()
ll.remove(3)
ll.travel()
print(ll.search(8))
2.单向循环链表的python实现
class Node():
def __init__(self,elem):
'''节点'''
self.elem = elem
self.next = None
class SingleCycleLinkList():
'''单向循环链表'''
def __init__(self, node = None):
self.__head = node #私有属性加__
if node:
node.next = node
def is_empty(self):
'''判空'''
return self.__head == None
def length(self):
'''链表长度'''
if self.is_empty():
return 0
cur = self.__head # cur游标,用来移动遍历节点
count = 1 # count记录数量
while cur.next != self.__head:
count += 1
cur = cur.next
return count
def travel(self):
'''遍历'''
if self.is_empty():
return
cur = self.__head # cur游标,用来移动遍历节点
while cur.next != self.__head:
print(cur.elem, end=' ') #不换行
cur = cur.next
#退出循环,cur指向尾节点,但尾节点的元素未打印
print(cur.elem)
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 #cur和头节点指向同一个节点
while cur.next != self.__head:
cur = cur.next
node.next = self.__head
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 #让pre等于head指向的节点不是等于head
count = 0
while count < (pos-1):
count += 1
pre = pre.next #循环结束后,pre指向pos-1位置
node.next = pre.next
pre.next = node
def remove(self,item):
'''删除节点'''
if self.is_empty():
return
cur = self.__head
pre = None
while cur.next != self.__head:
if cur.elem == item:
#先判断是否是头节点
#头节点
if cur == self.__head:
# self.__head = cur.next
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
cur = cur.next
#退出循环,cur指向尾节点
if cur.elem == item:
if cur == self.__head:
self.__head = None
else:
pre.next = self.__head
def search(self,item):
'''查找节点是否存在'''
if self.is_empty():
return False
cur = self.__head
while cur.next != self.__head:
if cur.elem == item:
return True
else:
cur = cur.next
if cur.elem == item:
return True
return False
'''测试'''
if __name__ == "__main__":
ll = SingleCycleLinkList()
ll.append(2)
ll.append(3)
ll.add(8)
ll.add(67)
ll.travel()
ll.remove(8)
ll.travel()
ll.insert(1, 5)
ll.travel()
ll.remove(3)
ll.travel()
print(ll.search(2))
3.双向链表的python实现
class Node(object):
def __init__(self,item):
"""节点"""
self.elem = item
self.next = None
self.prev = None
class DoubleLinkList(object):
"""双链表"""
def __init__(self, node=None):
self.__head = node # 私有属性加__
def is_empty(self):
'''判空'''
return self.__head is None
def length(self):
'''链表长度'''
cur = self.__head # cur游标,用来移动遍历节点
count = 0 # count记录数量
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("")
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 # cur和头节点指向同一个节点
while cur.next != None:
cur = cur.next
cur.next = node
node.prev = cur
def insert(self, pos, item):
'''指定位置添加元素'''
if pos <= 0:
self.add(item)
elif pos > (self.length() - 1):
self.append(item)
else:
node = Node(item)
cur = self.__head # 让pre等于head指向的节点不是等于head
count = 0
while count < pos :
count += 1
cur = cur.next # 循环结束后,pre指向pos-1位置
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
def search(self, item):
'''查找节点是否存在'''
cur = self.__head
while cur != None:
if cur.elem == item:
return True
else:
cur = cur.next
return False
参考:
数据结构与算法之美--王争
王道考研算法与数据结构
数据结构与算法:python语言描述--裘宗燕