文章目录
- 入门级书籍:《大话数据结构》(第二章 算法 & 第三章 线性表)的学习笔记
- 以下所有代码均已上传至github,传送门:github.com/Lebhoryi/Algorithms
- 复制的代码前面只有三个空格,玄学问题,具体的规范代码看github
一、概念
-
算法:算法就是解决问题的技巧和方式。
官方(@.@):解决特定问题求解步骤的描述,在计算机中表现为指令的优先序列,并且每条指令表示一个或多个操作。
-
算法的特性:输入输出、有穷性、确定性、可行性
-
算法的要求: 正确性、可读性、健壮性、时间效率和存储量低
-
算法效率的度量方法:
-
事后统计方法(不靠谱)
-
事前统计方法(推荐)
-
-
算法时间复杂度
-
最好坏情况
-
平均情况
-
-
算法空间复杂度
二、线性表
1. 定义
定义:0个或者多个数据元素的序列。
-
快捷判断:假如有两个及以上的数据元素,则第一个元素无前驱,最后一个元素无后继
-
特殊情况:复杂线性表中,一个元素可以有多个数据项组成。例如班级里的人是数据元素,每个人的各科成绩是数据项。
学号 | 姓名 | 语文 | 数学 |
---|---|---|---|
1 | 张三 | 78 | 98 |
2 | 李四 | 87 | 90 |
3 | 王五 | 90 | 99 |
2. 线性表的抽象数据类型
-
抽象数据类型(重温一下概念):将数据类型和相关操作捆绑在一起
-
线性表的操作:
-
创建和初始化
-
查找数据
-
插入数据
-
删除数据
-
撸代码时间
# 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)]
对于执行较慢的程序,可以通过消耗更多的内存(空间换时间)来
进行优化;而消耗过多内存的程序,可以通过消耗更多的时间(时间换空间)来降低内存的消耗。