线性表
线性表:由零个或多个数据元素组成的有限序列。
元素之间有先后关系,若有多个元素,第一个无前驱最后一个无后继,其他元素都只有一个前驱和后继。(一对一关系)
数据类型:一组性质相同的值的集合及定义在此集合上的一些操作的总称。
抽象数据类型:一个数学模型及定义在该模型上的一组操作。(对已有数据类型进行抽象,仅取决于逻辑特性)
ADT 抽象数据类型名 Data 数据元素之间逻辑关系的定义 Operation 操作 endADT
线性表的操作:初始化、判空、清空、返回值、定位值、插入、删除、统计表长。(增删改查)
例如,求集合 A 和 B 的并集:遍历 B 中每个元素,判断当前元素是否存在 A 中,若不存在则插入 A 中即可。用到的基本操作有:ListLength, GetElem, LocateElem, ListInsert
线性的两种物理存储结构:顺序存储结构(顺序表)和链式存储结构(链表)。
顺序表
存储空间起始位置,线性表最大容量Maxsize、线性表当前长度Length。一整块空间存储。查询O(1)、插入O(n)、删除O(n),适合元素个数稳定,不经常插入删除、更多的是存取数据的应用。
地址计算从1开始,假设 ElemType 占用c个字节,LOC(ai+1) = LOC(ai) + c = LOC(a1) + (i-1)*c
优点:无需为数据元素之间的逻辑关系增加额外的存储空间;快速存取
缺点:插入删除时间复杂度高;线性表长变化较大时难以确定表容量;容易造成存储空间的“碎片”
链表
一组任意的存储单元,可以在内存中未被占用的任意位置。一个 Node 由数据域和指针域构成。头指针指向头节点,头节点的数据域可空也可存链表长度,头节点的指针指向后继节点。
查询:工作指针后移O(n)、插入O(1)、删除O(1),适合插入删除频繁的情况。
单链表整表新建:
头插法:新节点的 next 指向头节点的后继;再让头节点的 next 指向新节点
尾插法:rear 始终后移,指向尾节点;rear 的 next 指向新节点,新节点的next指向null
单链表整表删除:节点p和q,第一个节点赋给p,下一个节点赋给q,循环执行释放p和将q赋给p的操作。
静态链表:数组代替指针描述单链表,游标实现法,静态模拟动态,了解一下思想就行。
结构体包括数据和游标。数组第一个和最后一个位置不存数据,未使用的数组称为备用链表。
数组第一个元素的游标存放备用链表第一个节点下标,数组最后一个元素的游标指向第一个有数据的元素下标,每个位置的游标都指向下一个有数据的下标位置,最后一个数据的游标是0。备用链表也通过游标链起来。
如何模拟动态链表插入和删除? 除了数据存储和释放,逻辑关系通过更新游标实现。
删除操作类似,空闲出来的位置要回收到备用链表头插。
快速找到未知长度的单链表的中间节点:
1. 遍历单链表确定长度L,再从头出发遍历到 L/2。
2.快慢双指针,都指向头节点,快指针移动速度是慢指针两倍,当快指针指向尾节点的时候,慢指针指向中间节点。
循环链表:尾节点指向头节点,整个单链表构成环。判断队尾条件变为是否指向头节点, rear == rear.next
约瑟夫问题:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式:41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是一开始要站在什么地方才能避免自杀?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
1 class ListNode: 2 def __init__(self, val): 3 self.val = val 4 self.next = None 5 6 def createCycleLink(n): 7 if n <= 0: 8 return None 9 if n == 1: 10 return ListNode(1) # 如果只有一个数,只有头节点 11 12 head = ListNode(1) # 头节点,value从1开始计数 13 p = head 14 for i in range(2, n+1): 15 p.next = ListNode(i) # 先赋val再链进去 16 p = p.next 17 p.next = head # 最后rear链到head,构成环 18 return head 19 20 def Josephus(n, k): 21 """约瑟夫问题,共n个人,每k个就自杀""" 22 if k == 1: 23 print('Survive:', n) # 如果每一个就自杀,只剩最后一个 24 return 25 head = createCycleLink(n) # 创建循环链表 26 p = head # cursor 27 count = 0 # 记录自杀人数 28 while True: 29 for i in range(k-2): # 从1数到k-1都没事:cursor要移k-1-1次,然后cursur.next就是要自杀的节点 30 p = p.next 31 print('Kill:', p.next.val) 32 count += 1 33 34 p.next = p.next.next # 删除cursor.next节点 35 p = p.next # 从下一个节点开始从1数到k 36 37 if n-count < k: # 如果剩下不够k个,不用报数自杀了;如果要求只剩一个,跳出条件为链表中只剩一个节点 38 break 39 print('Survive:') 40 for i in range(k-1): 41 print(p.val) 42 p = p.next 43 return
判断单链表中是否有环:
1. 遍历,哈希表保存节点,O(1) 的搜索判断新节点是否存在于哈希表中,存在即有环,且为环入口;
2.快慢双指针,相遇即有环。再有两个指针从头节点和相遇位置各自向后移,相遇位置即为环入口。
魔术师发牌问题:一位魔术师掏出一叠扑克牌,魔术师取出其中13张黑桃,洗好后,把牌面朝下。
说:“我不看牌,只数一数就能知道每张牌是什么?”魔术师口中念一,将第一张牌翻过来看正好是A;
魔术师将黑桃A放到桌上,继续数手里的余牌,
第二次数1,2,将第一张牌放到这叠牌的下面,将第二张牌翻开,正好是黑桃2,也把它放在桌子上。
第三次数1,2,3,前面二张牌放到这叠牌的下面,取出第三张牌,正好是黑桃3,这样依次将13张牌翻出,全部都准确无误。
求解:魔术师手中牌的原始顺序是什么样子的?
1 class ListNode: 2 def __init__(self, val): 3 self.val = val 4 self.next = None 5 6 def createCycleLink(n): 7 """初始化一个值均为0的循环链表""" 8 if n <= 0: 9 return None 10 if n == 1: 11 return ListNode(0) # 如果只有一个数,只有头节点 12 13 head = ListNode(0) # 头节点 14 p = head 15 for i in range(1, n): 16 p.next = ListNode(0) # 先赋val再链进去 17 p = p.next 18 p.next = head # 最后rear链到head,构成环 19 return head 20 21 def Magician(): 22 phead = createCycleLink(13) 23 24 p = phead 25 p.val = 1 26 count = 2 27 28 while True: 29 j = 0 30 while j < count: 31 p = p.next 32 if p.val != 0: # 遇到已经翻过的牌就跳过,不需要计数 33 continue 34 j += 1 # 否则计数,一共要数count张没有翻过的牌 35 36 if p.val == 0: 37 p.val = count 38 count += 1 39 40 if count == 14: 41 break 42 43 p = phead 44 res = [] 45 while True: 46 res.append(p.val) 47 if p.next == phead: 48 break 49 p = p.next 50 51 return res
拉丁方阵问题:拉丁方阵是一种n×n的方阵,方阵中恰有n种不同的元素,每种元素恰有n个,而且每种元素在一行和一列中恰好出现一次。
著名数学家和物理学家欧拉使用拉丁字母来作为拉丁方阵里元素的符号,拉丁方阵因此而得名。
比如:
1 2 3
2 3 1
3 1 2
问题:怎样构造N阶拉丁方阵?
1 # 利用循环链表,每一行的起点都往后移一次 2 class ListNode(object): 3 def __init__(self, val): 4 self.val = val 5 self.next = None 6 7 def createData(n): 8 if n <= 0: 9 return None 10 if n == 1: 11 return ListNode(1) 12 else: 13 head = ListNode(1) 14 p = head 15 for i in range(2, n+1): 16 p.next = ListNode(i) 17 p = p.next 18 p.next = head 19 return head 20 21 def Latin(n): 22 if n <= 0: 23 return None 24 array = [] 25 head = createData(n) 26 for i in range(1, n+1): 27 tmp = [] 28 p = head 29 for j in range(i-1): # 找一下每一行的起点 30 p = p.next 31 start = p 32 while True: # 遍历完整个环 33 tmp.append(p.val) 34 if p.next == start: 35 break 36 p = p.next 37 array.append(tmp) 38 return array
双向链表:一个指针指向前驱节点,一个指针指向后继节点。空间换时间
双向链表节点的插入和删除操作,注意顺序。
s.prior, s.next = p.prior, p
s.prior.next, p.prior = s, s
p.prior.next, p.next.prior = p.next, p.prior
free(p)