一,C实现链表
上一篇博文写到的顺序表是线性表的顺序存储结构,也就是说在内存中存储单元地址是连续的。线性表还有一种链式存储结构,也就链表,链表由节点所构成,节点内含一个指向下一个节点的指针,节点依次链接成为链表。因此,链表这种数据结构通常在物理内存上是不连续的。C语言中对节点的定义如下:
typedef struct Node
{
ElemType data; //数据域
struct Node *Next; //指针域
}Node;
typedef struct Node *LinkList;
这里使用到了个人觉得是C语言最精髓的东西——指针,对于大神来时,指针就是神器,对于像我这样的菜菜,就是一脸懵。
二,Python的链表
C语言中实现链表使用到指针,但是在Python中并没有指针,那要怎么实现链表?
C语言实现基本的链表时节点中指针唯一的作用就是指向,指向下个节点的地址,不涉及指针运算,你可以把这里的指针当作一个变量,存储着下个节点的地址,这种方式在Python中称为“引用”,变量中记录数据的地址。Python里所有的变量其实都是对象的引用(连基本类型都是如此),而这个引用,说白了就是个指向实例的指针。所以Python是可以实现基本的数据结构的。
1,单链表
单向链表也叫单链表,是表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
class LNode(object):
def __init__(self, item, next_=None):
self.item = item
self.next = next_
class SingleLinkList(object):
"""
初始化单链表
"""
def __init__(self):
self.head = None
def is_empty(self):
"""链表是否为空"""
return self.head is None
def length(self):
"""获取链表长促"""
cur = self.head
count = 0
while cur:
count += 1
cur = cur.next
return count
def travel(self):
cur = self.head
while cur:
print(cur.item, end='')
if cur.next:
print(',', end='')
cur = cur.next
print('')
def append_front(self, item):
"""头部插入"""
self.head = LNode(item, self.head)
def append_rear(self, item):
"""尾部插入"""
node = LNode(item)
if self.is_empty():
self.head = node
else:
cur = self.head
while cur.next:
cur = cur.next
cur.next = node
def insert(self, item, pos):
"""在第pos个位置插入元素"""
if pos <= 1:
self.append_front(item)
elif pos > self.length():
self.append_rear(item)
else:
node = LNode(item)
cur = self.head
cur_pos = 1
while cur.next and cur_pos < pos - 1:
cur = cur.next
cur_pos += 1
node.next = cur.next
cur.next = node
def getitem(self, pos):
"""获取第pos个位置的元素"""
if pos < 1 or pos > self.length():
print('pos 超出范围')
return None
else:
cur = self.head
cur_pos = 1
while cur.next and cur_pos < pos:
cur = cur.next
cur_pos += 1
return cur.item
def pop(self):
"""移除头部元素"""
if self.is_empty():
print('SingleLinkList is empty')
else:
e = self.head.item
self.head = self.head.next
return e
def remove(self, pos):
"""删除第pos个位置元素"""
if pos < 1 or pos > self.length():
print('pos 超出范围')
elif pos == 1:
self.head = self.head.next
else:
cur = self.head
cur_pos = 1
while cur and cur_pos < pos - 1:
cur = cur.next
cur_pos += 1
cur.next = cur.next.next
链表相对顺序表最大的缺点在于,如果想获取到第n个元素,需要从前面n-1个元素开始遍历。
这里想到一个算法题,如何在不知道元素个数的情况下,获取单链表的中间元素?
最简单的方法,先遍历一次链表,获得长度n,再从第一个遍历到n/2个元素,时间复杂度时间复杂度O(3N/2)
另一种算法就是使用快慢指针,让A指针每次走两步,B指针每次走一步,当A指针到末尾时,B指针则到达中间位置。时间复杂度时间复杂度O(N/2)。
if __name__ == '__main__':
SingleLinkList = SingleLinkList()
for i in range(1, 101): # 随机插入100个元素
SingleLinkList.append_rear(random.randint(1, 100))
SingleLinkList.travel()
a = SingleLinkList.head # a 每次走两步
b = SingleLinkList.head # b 每次走一步
while a.next:
if a.next.next:
a = a.next.next
b = b.next
else:
a = a.next
print(b.item)
2,静态链表
静态链表,也是线性存储结构的一种,它兼顾了顺序表和链表的优点于一身,可以看做是顺序表和链表的升级版。使用静态链表存储数据,数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整形变量(称为"游标",和指针功能类似)维持(和链表类似)。
具体实现原理可以参考:http://data.biancheng.net/view/163.html
对于静态链表有以下几点需要注意:
1,数组的第一个位置和最后一个位置做特殊处理,并不存放数据。
2,将未使用到数组位置称为备用链表。
3,数组第一个位置,即下标为0的元素对应的cur,存放备用链表第一个节点的下标,数组的最后一位置,即下标为MAXSIZE-1的元素对应的cur,存放第一个有值元素的下标,相当于链表的头结点。
class LNode(object):
def __init__(self, cur, item=None):
self.item = item
self.cur = cur
# 最后一个下标的cur 存储第一个非空元素节点的下标,
# 第一个下标的cur 存储第一个空元素节点的下标
class StaticLinkList(object):
def __init__(self):
"""初始化,将每个节点的cur,存储下一个节点的下标"""
self.MAXSIZE = 100
self.array = list()
for i in range(0, self.MAXSIZE):
self.array.append(LNode(i + 1))
self.array[self.MAXSIZE - 1] = LNode(0)
def is_empty(self):
return self.array[self.MAXSIZE - 1].cur == 0
def length(self):
k = self.array[self.MAXSIZE - 1].cur
count = 0
while self.array[k].item:
count += 1
k = self.array[k].cur
return count
def travel(self):
k = self.array[self.MAXSIZE - 1].cur
n = self.array[0].cur
print('第一个非空节点下标为', k)
print('第一个空节点下标为', n)
while self.array[k].item:
print('index=', k, 'item=', self.array[k].item, 'cur=', self.array[k].cur)
k = self.array[k].cur
def malloc_node(self):
"""从备用链表中申请一个节点"""
i = self.array[0].cur
self.array[0].cur = self.array[i].cur # 下一个备用节点
return i
def insert(self, item, pos):
k = self.MAXSIZE - 1
if pos < 1 or pos > self.length() + 1:
print('插入位置非法')
return None
if self.length() + 1 > self.MAXSIZE - 2:
print('数组空间已满')
return None
i = self.malloc_node()
if i:
self.array[i].item = item
for j in range(1, pos):
k = self.array[k].cur
self.array[i].cur = self.array[k].cur
self.array[k].cur = i
def remove(self, pos):
if pos < 1 or pos > self.length():
print('删除位置非法')
return None
k = self.MAXSIZE - 1
for i in range(1, pos):
k = self.array[k].cur
j = self.array[k].cur # 需要删除元素的下标
self.array[k].cur = self.array[j].cur # 指向删除元素的下一个元素
# 清除pos的数据,将pos 加入备用链表中
self.array[j].item = None
self.array[j].cur = self.array[0].cur
self.array[0].cur = j
3,单向循环链表
单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头结点。
class LNode(object):
def __init__(self, item, next_=None):
self.item = item
self.next = next_
class SCLinkList(object):
def __init__(self):
"""初始化"""
self.head = None
# self.head.next = self.head
def is_empty(self):
return self.head is None
def length(self):
if self.is_empty():
print('链表为空')
return 0
else:
cur = self.head
count = 1
while cur.next != self.head:
cur = cur.next
count += 1
return count
def travel(self):
"""遍历链表"""
if self.is_empty():
print('链表为空')
return
else:
cur = self.head
while cur.next != self.head:
print(cur.item, end=',')
cur = cur.next
print(cur.item)
def insert(self, val, pos):
node = LNode(val)
if self.is_empty():
self.head = node
node.next = self.head
else:
if pos == 1:
cur = self.head
while cur.next != self.head:
cur = cur.next
node.next = self.head
cur.next = node
self.head = node
else:
cur = self.head
cur_pos = 1
while cur.next != self.head and cur_pos < pos - 1:
cur = cur.next
cur_pos += 1
node.next = cur.next
cur.next = node
def remove(self, pos):
if self.is_empty():
print('链表为空')
return
else:
if pos == 1:
cur = self.head
pre = self.head
while cur.next != self.head:
cur = cur.next
self.head = pre.next
cur.next = pre.next
else:
cur = self.head
cur_pos = 1
while cur.next != self.head and cur_pos < pos - 1:
cur = cur.next
cur_pos += 1
cur.next = cur.next.next
与单向循环链表相关的一个算法问题题就是约瑟夫问题,具体由来可以直接百度。
简单来说,就是按照如下规则去排除人:
所有人围成一圈
顺时针报数从1报到q,每次报到q的人将被排除掉
然后从被排除掉的下一个人重新从1报数,继续报到q,再清除,直到剩余一人。
解决思路很多,但是个人觉得使用单向循环链表是最契合这个规则的。
def joseph(self, n, m):
"""
单循环链表实现约瑟夫环
n : 成还人数
m : 报数间隔
"""
for i in range(1, n + 1):
self.insert(i, i)
cur = self.head
while cur != cur.next:
for i in range(1, m - 1):
cur = cur.next
print(cur.next.item, end='->')
cur.next = cur.next.next
cur = cur.next
n -= 1
print(cur.item)
if __name__ == '__main__':
SCLinkList = SCLinkList()
SCLinkList.joseph(10, 12) # 2->5->9->6->4->8->7->3->1->10
SCLinkList.joseph(10, 8) # 8->6->5->7->10->3->2->9->4->1
4,双向循环链表
之前接触到的链表都只有一个指针,指向直接后继,整个链表只能单方向从表头访问到表尾,这种结构的链表统称为 “单向链表”或“单链表”。
如果算法中需要频繁地找某结点的前趋结点,单链表的解决方式是遍历整个链表,增加算法的时间复杂度,影响整体效率。
为了快速便捷地解决这类问题,在单向链表的基础上,给各个结点额外配备一个指针变量,用于指向每个结点的直接前趋元素。这样的链表被称为“双向链表”或者“双链表”。
双向链表中的结点有两个指针域,一个指向直接前趋,一个指向直接后继。当第一个节点,也就头结点的前趋指向最后一个节点,最后一个节点的后继指向头结点,那么就构成双向循环链表。
class DLNode(object):
def __init__(self, item, prior_=None, next_=None):
self.item = item
self.prior = prior_
self.next = next_
class DCLinkList(object):
def __init__(self):
self.head = None
def is_empty(self):
return self.head is None
def length(self):
if self.is_empty():
print('链表为空')
return 0
else:
cur = self.head
count = 1
while cur.next != self.head:
cur = cur.next
count += 1
return count
def travel(self):
"""遍历链表"""
if self.is_empty():
print('链表为空')
return
else:
cur = self.head
while cur.next != self.head:
print(cur.item, end=',')
cur = cur.next
print(cur.item)
def insert(self, val, pos):
node = DLNode(val)
if self.is_empty():
self.head = node
node.next = self.head
node.prior = self.head
else:
if pos == 1:
cur = self.head
node.next = cur
node.prior = cur.prior
cur.prior.next = node
cur.prior = node
self.head = node
else:
cur = self.head
cur_pos = 1
while cur.next != self.head and cur_pos < pos - 1:
cur = cur.next
cur_pos += 1
node.next = cur.next
node.prior = cur
cur.next.prior = node
cur.next = node
def remove(self, pos):
if self.is_empty():
print('链表为空')
return
else:
if pos == 1:
cur = self.head
cur.prior.next = cur.next
cur.next.prior = cur.prior
self.head = cur.next
else:
cur = self.head
cur_pos = 1
while cur.next != self.head and cur_pos < pos:
cur = cur.next
cur_pos += 1
cur.prior.next = cur.next
cur.next.prior = cur.prior
举个栗子,能帮助理解双向链表,现有一个序列S,给定一个整数N,当N>0,则将序列向左循环移动N位,得到新序列S1,当N<0,则将序列向右循环移动N位,得到新序列S2。
序列S
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
当N=3时,输出
D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
当N=-3时,输出
X Y Z A B C D E F G H I J K L M N O P Q R S T U V W
def caesars(self, n):
for i in range(0, 26):
val = chr(ord('A') + i)
pos = i + 1
self.insert(val, pos)
if n > 0:
while n:
self.head = self.head.next
n -= 1
if n < 0:
while n:
self.head = self.head.prior
n += 1
cur = self.head
for j in range(0, 26):
print(cur.item, end=' ')
cur = cur.next
参考
(小甲鱼)数据结构和算法