数据结构与算法_渡劫5_链表

  • 入门级书籍:《大话数据结构》(第二章 算法 & 第三章 线性表)的学习笔记
  • 以下所有代码均已上传至github,传送门:github.com/Lebhoryi/Algorithms
  • 复制的代码前面只有三个空格,玄学问题,具体的规范代码看github

一、概念

  • 算法:算法就是解决问题的技巧和方式。

      官方(@.@):解决特定问题求解步骤的描述,在计算机中表现为指令的优先序列,并且每条指令表示一个或多个操作。
    
  • 算法的特性:输入输出、有穷性、确定性、可行性

  • 算法的要求: 正确性、可读性、健壮性、时间效率和存储量低

  • 算法效率的度量方法:

    • 事后统计方法(不靠谱)

    • 事前统计方法(推荐)

  • 算法时间复杂度

    • 最好坏情况

    • 平均情况

  • 算法空间复杂度

二、线性表

1. 定义

定义:0个或者多个数据元素的序列。

  • 快捷判断:假如有两个及以上的数据元素,则第一个元素无前驱,最后一个元素无后继

  • 特殊情况:复杂线性表中,一个元素可以有多个数据项组成。例如班级里的人是数据元素,每个人的各科成绩是数据项。

学号 姓名 语文 数学
1 张三 78 98
2 李四 87 90
3 王五 90 99

2. 线性表的抽象数据类型

  • 抽象数据类型(重温一下概念):将数据类型和相关操作捆绑在一起

  • 线性表的操作:

    1. 创建和初始化

    2. 查找数据

    3. 插入数据

    4. 删除数据


撸代码时间

# coding=utf-8
'''
@ Summary: 有线性表A和B,实现AUB
@ Update:  

@ file:    2.实现交集操作.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-24 下午3:18
'''

def union(la, lb):
   for i in lb:
       if i not in la:
           la.append(i)
   return la

if __name__ == "__main__":
   la = [1, 2, 3]
   lb = [2, 3, 4]
   result = union(la, lb)
   print(result)

3. 线性表的顺序存储结构的骚操作

  • 获得元素
# coding=utf-8
'''
@ Summary: 从线性表中获得元素
@ Update:  

@ file:    3.获得元素.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-24 下午3:33
'''

def get_elem(i, l):
   if not l:
       return None
   return l[i-1]

if __name__ == "__main__":
   l = [1, 2, 3]
   i = 1
   result = get_elem(i, l)
   print(result)

  • 插入和删除操作

print(l.insert(i,n)) 的输出是None,因此分开写

# coding=utf-8
'''
@ Summary: 在线性表中插入和删除一个元素
@ Update:  

@ file:    2-3.线性表-插入操作.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-24 下午3:39
'''

def list_insert(l, i, new_elem):
   if not l or i < 0 or i > len(l):
       return None
   if len(l) <= max_lenth:
       l.insert(i, new_elem)
   return l

'''
python list 删除的常用三种方法:

   1. del list[3]
   2. list.pop(index)  # 删除索引指向的元素
   3. list.remove(elem)  # 删除指定的元素
'''

def list_del(l, i):
   if not l or i < 0 or i > len(l):
       return None
   if len(l) <= max_lenth:
       l.pop(i-1)
   return l

if __name__ == "__main__":
   l = [1, 2, 3]
   i, new_elem = 1, 5
   global max_lenth
   max_lenth = 7
   result = list_insert(l, i, new_elem)
   print(result)
   result2 = list_del(l, i)
   print(result2)


结论:线性表的顺序存储结构,在读数据时,不管是哪个位置,时间复杂度都是O(1);而插入或删除时,时间复杂度都是O(n)。

4. 顺序表的链式存储结构

  • 定义:用一组任意的存储单元存储线性表的数据元素

  • 结点:数据元素,包含数据域和指针域两部分

    • 头结点:单链表第一个结点之前,设定一个头结点,顺序是第0位。该数据域为空或者存储链表的长度,指针域指向第一个结点的数据域

    • 头指针:指向链表的第一个结点,假若有头结点,则指向头结点,链表可以没有头结点,但是一定要有头指针

三、单链表的相关操作

1. 单链表的元素查找

# coding=utf-8
'''
@ Summary: 整表生成和删除
@ Update:  

@ file:    2-7.线性表-链表生成和删除.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-25 下午10:12
'''


class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):

   @staticmethod
   def c_link(vals):
       if not vals:
           return None

       # 链表可以没有头结点但是不能没有头指针,下面这行看不懂的去看下头结点

       head = Node(len(vals))  # 创建头节点
       move = head
       for val in vals:
           tmp = Node(val)
           # 头插法,新元素永远插在第一个节点,头结点之后
           # move2 = head.next
           # head.next = tmp
           # tmp.next = move2
           # 尾插法,新元素永远插在最后一个节点
           move.next = tmp
           move = tmp
       return head, len(vals)

   @staticmethod
   def p_link(link):
       if not link:
           return None
       link = link.next
       while link:
           print(link.val, end=" ")
           link = link.next
       print()

   @staticmethod
   def d_link(link):
       if not link:
           return None
       link.next = None
       return link


if __name__ == "__main__":
   link_a = [1, 2, 1, 4, 3]
   link_a, l_link = Link.c_link(link_a)
   Link.p_link(link_a)
   # Link.p_link(Link.d_link(link_a))

2. 单链表的元素插入


# coding=utf-8
'''
@ Summary: 单链表的元素插入
@ Update:  

@ file:    2-5.线性表-链表元素插入.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-25 下午6:45
'''

class ListNode(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):

   @staticmethod
   def c_link(vals):
       # creat link
       if not vals:
           return None
       head = ListNode(len(vals))
       move = head
       for val in vals:
           tmp = ListNode(val)
           move.next = tmp
           move = tmp
       return head.next

   @staticmethod
   def p_link(ln):
       # print link
       if not ln:
           return None
       while ln:
           print(ln.val, end=" ")
           ln = ln.next
       print()

   @staticmethod
   def l_link(link):
       if not link:
           return None
       count = 0
       while link:
           count += 1
           link = link.next
       return count


def insert_elem(ln, node, s, lenth_link):
   """单链表中插入元素

   :param ln: node
   :param a: node
   :param s: int
   :return: node
   """
   if not ln or s < 0 or s > lenth_link:
       return None
   cur = ln
   count = 1
   while count < s:
       cur = cur.next
       count += 1
   node = ListNode(node)
   node.next = cur.next
   cur.next = node
   return ln


if __name__ == "__main__":
   link_a = [1, 2, 3, 4]
   node_b, s = 5, 2  # 在第二个节点的位置插入5

   link_a = Link.c_link(link_a)
   lenth_link = Link.l_link(link_a)
   Link.p_link(link_a)

   link_b = insert_elem(link_a, node_b, s, lenth_link)
   Link.p_link(link_b)

3. 单链表的元素删除


# coding=utf-8
'''
@ Summary: 单链表中删除元素
@ Update:  

@ file:    2-6.线性表-链表元素删除.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-25 下午8:59
'''

class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):
   @staticmethod
   def create_link(vals):
       if not vals:
           return None
       head = Node(len(vals))
       move = head
       for val in vals:
           tmp = Node(val)
           move.next = tmp
           move = tmp
       return head.next

   @staticmethod
   def p_link(link):
       if not link:
           return None
       while link:
           print(link.val, end=" ")
           link = link.next
       print()

   @staticmethod
   def l_link(link):
       if not link:
           return None
       count = 0
       while link:
           count += 1
           link = link.next
       return count


def del_link(ln, i, lenth_link):
   if not ln or i < 0 or i > lenth_link:
       return None
   cur = ln
   count = 1
   # 找打要删除的节点的上一个节点,pre.next = pre.next.next
   while count < i-1:
       cur = cur.next
       count += 1
   # print(cur.val)
   cur.next = cur.next.next
   return ln


if __name__ == "__main__":
   link_a = [1, 3, 4, 5, 2]
   link_a = Link.create_link(link_a)
   lenth_link = Link.l_link(link_a)
   # Link.p_link(link_a)
   i = 3  # 删除第二个节点
   link_a = del_link(link_a, i, lenth_link)
   Link.p_link(link_a)

4. 单链表的整表删除和生成

# coding=utf-8
'''
@ Summary: 整表生成和删除
@ Update:  

@ file:    2-7.线性表-链表生成和删除.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-25 下午10:12
'''


class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):

   @staticmethod
   def c_link(vals):
       if not vals:
           return None
       head = Node(len(vals))
       move = head
       for val in vals:
           tmp = Node(val)
           # 头插法,新元素永远插在第一个节点,头结点之后
           # move2 = head.next
           # head.next = tmp
           # tmp.next = move2
           # 尾插法,新元素永远插在最后一个节点
           move.next = tmp
           move = tmp
       return head, len(vals)

   @staticmethod
   def p_link(link):
       if not link:
           return None
       link = link.next
       while link:
           print(link.val, end=" ")
           link = link.next
       print()

   @staticmethod
   def d_link(link):
       if not link:
           return None
       link.next = None
       return link


if __name__ == "__main__":
   link_a = [1, 2, 1, 4, 3]
   link_a, l_link = Link.c_link(link_a)
   Link.p_link(link_a)
   # Link.p_link(Link.d_link(link_a))

5. 单链表和顺序存储结构的优缺点

四、静态链表

1. 定义

用数组来代替指针来描述单链表,让数组的元素都是由两个数据域组成,data和cur,其中数据域data用来存放数据元素,数据域cur相当於单链表中的next指针,存放该元素的后继在数组中的下标,把这种用数组描述的链表叫做静态链表,这种描述方法还有起名叫游标实现法。

在c中和python、java中是不一样的,c中的数组需要指定数组长度,存储空间是固定的,不能动态的更改,否则会溢出,而在python或者java中不需要指定,系统自检溢出,系统可以动态的增加空间。

2. 现状

单链表使用的多,对于静态链表掌握程度理解即可


Q:(腾讯面试题)快速找到未知长度单链表的中间节点

提示:走一步和走两步


# coding=utf-8
'''
@ Summary: 快速找到未知长度单链表的中间节点
@ Update:  

@ file:    2-9.寻找链表中间节点.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-26 下午6:49
'''

class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

class Link(object):

   @staticmethod
   def c_link(vals):
       if not vals:
           return None
       head = Node(len(vals))
       move = head
       for val in vals:
           tmp = Node(val)
           move.next = tmp
           move = tmp
       return head.next

   @staticmethod
   def p_link(link):
       if not link:
           return None
       while link:
           yield link.val
           link = link.next
       print()

def find_mid_node(link):
   cur = link
   cur2 = link.next
   while cur2 and cur2.next:
       cur = cur.next
       cur2 = cur2.next.next
   return cur.val


if __name__ == "__main__":
   list_a = [1, 4, 2, 5, 3, 2, 2]
   link_a = Link.c_link(list_a)
   for val in Link.p_link(link_a):
       print(val, end=" ")
   result = find_mid_node(link_a)
   print(result)

四、循环链表

1. 定义

将单链表中终端节点的指针从空指针改为指向头结点,形成一个环,头尾相连,称循环链表

2. 循环链表和单链表的差异

循环链表和单链表的主要差异在于循环的判断条件上,单链表是判断p->next是否为空,循环链表是判断p->next不等于头结点,则循环未结束。

3. 循环链表的有点

在单链表中采用头结点时,可以用O(1)的时间访问第一个结点,用O(n)的时间访问到最后一个结点;采用指向终端结点的尾指针来表示循环链表时可以用O(1)的时间由链表指针访问到最后一个结点


以下的代码都是尾指针示例


# coding=utf-8
'''
@ Summary: 循环链表及操作
@ Update:  

@ file:    2-10.线性表-循环链表.py
@ version: 1.0.0

@ Author:  [email protected]
@ Date:    19-9-26 下午7:45
'''

class Node(object):
   def __init__(self, val):
       self.val = val
       self.next = None

   def __repr__(self):   # 这个函数将内容‘友好’地显示出来,否则会显示对象的内存地址
       return str(self.val)


class CLink(object):
   @ staticmethod
   def c_link(vals):
       # 创建循环链表
       if not vals:
           return None
       # 创建头结点
       head = Node(len(vals))
       move = head  # 给定一个变量,用做节点向前移动
       for i in range(len(vals)):
           tmp = Node(vals[i])
           if i == 0:  # 首节点循环
               move.next = tmp
               tmp.next = tmp
               move = tmp
           else:
               tmp.next = move.next
               move.next = tmp
               move = tmp
       # return head
       # 循环链表用尾节点比头结点友好,据说
       rear = head
       rear.next = move
       return rear

   @ staticmethod
   def p_link(link):
       # 打印循环链表
       if not link:
           return None
       cur = link.next.next  # cur为第一个节点,接着为当前节点
       while cur.next != link.next.next:
           # print(cur, link.next)
           yield cur.val
           cur = cur.next
       yield cur.val
       print()

   @ staticmethod
   def l_link(link):
       # lenth of link
       if not link:
           return None
       cur, count = link.next.next, 1
       # 判断条件是最后一个指向是否是第一个节点
       while cur.next != link.next.next:
           count += 1
           cur = cur.next
       return count

   @ staticmethod
   def add_node(link, node, s, l_link):
       if not link or not node or s < 0:
           return None
       s = s % l_link if s > l_link else s
       node = Node(node)
       cur = link.next.next
       if s == l_link:
           # cur指向第一个节点,最后一个节点指向cur
           node.next = cur
           link.next.next = node
       else:
           count = 1
           while count < s:
               # 找到要插入的cur位置
               cur = cur.next
               count += 1
           # cur指向下一个节点,当前节点指向cur
           node.next = cur.next
           cur.next = node
       return link

   @ staticmethod
   def del_node(link, s, l_link):
       if not link or s < 0:
           return None
           # 存在s>l_link的情况,需要对s做一步处理
       s = s % l_link if s > l_link else s
       cur = link.next.next
       count = 1
       while count < s:
           cur = cur.next
           count += 1
       # 当前cur指向下下一个节点,跳过下一个节点
       cur.next = cur.next.next
       return link

   @ staticmethod
   def find_node(link, s, l_link):
       if not link or s < 0:
           return None
       s = s % l_link if s > l_link else s
       cur = link.next.next
       count = 1
       while count < s:
           cur = cur.next
           count += 1
       return cur.val


if __name__ == "__main__":
   list_a = [1, 2, 1, 5, 3, 7]
   link_a = CLink.c_link(list_a)
   [print(node, end=" ") for node in CLink.p_link(link_a)]

   # 插入节点,在尾部插入节点的复杂度为O(1)
   lenth_link = CLink.l_link(link_a)
   node, s = 666, 10
   link_a = CLink.add_node(link_a, node, s, lenth_link)
   [print(node, end=" ") for node in CLink.p_link(link_a)]


   # delete node
   link_a = CLink.del_node(link_a, s, lenth_link)
   [print(node, end=" ") for node in CLink.p_link(link_a)]

   # find node
   node_a = CLink.find_node(link_a, s, lenth_link)
   print(node_a)


  • 手撕代码练习1-1:约瑟夫问题

  • 手撕代码练习1-2:强化版约瑟夫问题

  • 手撕代码练习2:链表相接

  • 手撕代码练习3:判断链表是否有环

  • 手撕代码练习4:魔术师发牌问题

  • 手撕代码练习5:拉丁方阵

上述练习题的代码已经上传至github,传送门☞:github.com/Lebhoryi/Algorithms

五、双向链表

1. 定义

在单链表的基础上,新增一个指向前驱节点的指针域

2. 循环链表

双向链表也可以是循环表:

3. 操作

  • 插入操作

  • 删除操作


双向链表保存有前后指针,因此插入和删除都很方便,时间效率提高但是空间存储变大,用空间换取时间

4. 代码练习time

  • 练习1(代码已经上传,地址同上):

一句话输出字母:


[chr(i) for i in range(65, 91)]


对于执行较慢的程序,可以通过消耗更多的内存(空间换时间)来

进行优化;而消耗过多内存的程序,可以通过消耗更多的时间(时间换空间)来降低内存的消耗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章