1. 队列
队列(Queue)是一个数据集合,仅允许在列表的一端进行插入,另一端进行删除
进行插入的一端称为队尾(rear),插入动作称为进队或入队
进行删除的一端称为队头(front),删除动作称为出队
队列的性质:先进先出(First-in,First-out)
队列的实现方式——环形队列
环形队列:当队尾指针front==Maxsize-1时,再前进一个位置就自动到0。
- 队首指针前进1:front=(front+1)%Maxsize
- 队尾指针前进1:rear=(rear+1)%Maxsize
- 队空条件:rear=front
- 队满条件:(rear+1)%Maxsize=front
其中Maxsize表示对列的最大长度。
自己写代码完成环形队列
#队列的实现
class Queue:
def __init__(self,size=100):
self.queue=[0 for _ in range(size)]
self.rear=0#队尾指针
self.front=0#队首指针
def push(self,element):
if not self.is_filled():#如果队不满
self.rear=(self.rear+1)%self.size
self.queue[self.rear]=element
else:
raise IndexError("Queue is filled")
def pop(self):
if not self.is_empty():#如果队不空
self.front=(self.front+1)%self.size
return self.queue[self.front]
else:
raise IndexError("Queue is empty")
#判断对空
def is_empty(self):
return self.rear==self.front
#判断队满
def is_filled(self):
return (self.rear+1) %self.size==self.front
q=Queue(5)
for i in range(4):
q.push(i)
print(q.pop())
q.push(4)
双向队列
双向队列的两端都支持进队和出队操作
双向队列的基本操作:队首进队、队首出队、队尾进队、队尾出队
python队列内置模块
使用方法: from collections import deque
创建队列:queue=deque()
进队:append()
出队:popeft()
双向队列队首进队:appendleft()
双向队列队尾出队:pop()
2.链表
链表是由一系列节点组成的元素集合。每个节点包含两部分,数据域item和指向下一个节点的指针next。通过节点之间的相互连接,最终串联成一个链表。
手动创建链表
#链表
class Node:
def __init__(self,item):
self.item=item
self.next=None
a=Node(1)
b=Node(2)
c=Node(3)
a.next=b
b.next=c
print(a.next.item)
print(a.next.next.item)
头插法创建列表
class Node:
def __init__(self,item):
self.item=item
self.next=None
def create_linklist_head(li):
head=Node(li[0])
for element in li[1:]:
node=Node(element)#创建新节点
node.next=head
head=node
return head
#打印链表
def print_linklist(lk):
while lk:
print(lk.item,end=',')
lk=lk.next
lk=Node.create_linklist_head([1,2,3])
Node.print_linklist(lk)
尾插法创建列表
#创建链表,尾插法
class Node:
def __init__(self,item):
self.item=item
self.next=None
def create_linklist_tail(li):
head=Node(li[0])
tail=head
for element in li[1:]:
node=Node(element)#创建新节点
tail.next=node
tail=node
return head
def print_linklist(lk):
while lk:
print(lk.item,end=',')
lk=lk.next
lk=Node.create_linklist_tail([1,2,3,6,8])
Node.print_linklist(lk)
链表的插入和删除
链表节点的插入:
将4插入到1和2之间
p.next=curNode.next #先把4和2连起来
curNode.next=p #再把1和4连起来
时间复杂度为1
链表节点的删除
把4删除
p=curNode.next
curNode.next=curNode.next.next #先将1和2连接起来再删除4
del p#删除p,该操作可有可无
双链表
双链表的每个节点有两个指针:一个指向后一个节点,另一个指向前一个节点。
class Node(object):
def __init__(self,item=None):
self.item=item#存数据
self.next=None#指向下一个节点
self.prior=None#指向前一个节点
双链表节点的插入
p.next=curNode.next #2先和3连
curNode.next.prior=p #3再和2连
p.prior=curNode #2再和1连
curNode.next=p #1再和2连
双链表节点的删除
p=curNode.next #p变为3
curNode.next=p.next #1和3相连
p.next.prior=curNode #3的前向节点指向1
del p
**注意:**链表在插入和删除的操作上明显快于顺序表(数组/列表)
3.哈希表
哈希表是一个通过哈希函数来计算数据存储位置的数据结构,通常支持如下操作:
insert(key,value):插入键值对(key,value)
get(key):如果存在键为key的键值对则返回其value,否则烦恼会空值
delete(key):删除键为key的键值对
(1)直接寻址表
当关键字的全域U比较小时,直接寻址是一种简单而有效的方法。、
例如:
直接寻址技术的缺点:
- 当域U很大时,需要消耗大量内存,很不实际
- 如果域U很大而实际出现的key很少,则大量空间被浪费
- 无法处理关键字不是数字的情况
(2)哈希 - 直接寻址表:key为k的元素放到k位置上
- 改进直接寻址表:哈希(Hashing)
- 构建大小为m的寻址表T
- key为k的元素放到h(k)位置上
- h(k)是一个函数,将其域U映射到表T[0,1,…,m-1]
哈希表(Hash Table,又称为散列表),是一种线性表的存储结构。哈希表由一个直接寻址表和一个哈希函数组成。哈希函数h(k)将元素关键字k作为自变量,返回元素的存储下标。
哈希冲突:由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置上的情况,这种情况叫做哈希冲突。
解决哈希冲突——开放寻址法
开放寻址法:如果哈希函数返回的位置已经有值,则可以向后探查新的位置来存储这个值。 - 线性探查:如果位置i被占用,则探查i+1,i+2,…
- 二次探查:如果位置i被占用,则探查i+12,i-12,i+22,i-22,…
- 二度哈希:有n个哈希函数,当使用第1个哈希函数h1发生冲突时,则尝试使用h2,h3,…
解决哈希冲突——拉链法
拉链法:哈希表每个位置都连接一个链表,当冲突发生时,冲突的元素将被加到该位置链表的最后。
常见的哈希函数
除法哈希法:h(k)=k%m #m为哈希表的长度
乘法哈希法:h(k)=floor(m*(Akey%1)) #对1取模表示得到它的小数部分,floor为向下取整,A为参数
全域哈希法:h_a,b_(k)=((akey+b)% p) % m a,b=1,2,…,p-1
哈希表的实现
class LinkList:#创建一个链表
class Node:
def __init__(self,item=None):
self.item=item
self.next=None
class LinkListIterator:#定义一个迭代器
def __init__(self,node):
self.node=node
def __next__(self):
if self.node:
cur_node=self.node
self.node=cur_node.next
return cur_node.item
else:
raise StopIteration
def __iter__(self):
return self
def __init__(self,iterable=None):#传一个列表iterable进来
self.head=None
self.tail=None
if iterable:
self.extend(iterable)
def append(self,obj):
s=LinkList.Node(obj)
if not self.head:
self.head=s
self.tail=s
else:
self.tail.next=s
self.tail=s
def extend(self,iterable):
for obj in iterable:
self.append(obj)
def find(self,obj):
for n in self:
if n==obj:
return True
else:
return False
def __iter__(self):
return self.LinkListIterator(self.head)
def __repe__(self):#转换成字符串
return "<<"+",".join(map(str,self))+">>"
'''
lk=LinkList([1,2,3,4,5])
print(lk)
for element in lk:
print(element)
'''
#类似于集合的结构(不能有重复的元素)
class HashTable:
def __init__(self,size=101):
self.size=size
self.T=[LinkList() for i in range(self.size)]
def h(self,k):#哈希函数
return k%self.size
def find(self,k):
i=self.h(k)
return self.T[i].find(k)
def insert(self,k):
i=self.h(k)
if self.find(k):
print("Duplicated Insert.")
else:
self.T[i].append(k)
ht=HashTable()
ht.insert(0)
ht.insert(1)
ht.insert(101)
ht.insert(207)
ht.insert(3)
print(ht.find(201))
哈希表的应用——集合与字典
- 字典与集合都是通过哈希表来实现的。a={‘name’:‘Alex’,‘age’:18,‘gender’:‘Man’}
- 使用哈希表存储字典,通过哈希函数将字典的键映射为下标。假设h(‘name’)=3,h(‘age’)=1,h(‘gender’)=4,则哈希表存储为[None,18,None,‘Alex’,‘Man’]。
- 如果发生哈希冲突,则通过拉链法或开发寻址法解决。
哈希表的应用——md5算法
MD5(Message-Digest Algorithm 5)曾经是密码学中常用的哈希函数,可以把任意长度的数据映射为128位的哈希值,其曾经包含如下特征:
(1)同样的消息,其MD5值必定相同
(2)可以快速计算出任意给定消息的MD5值
(3)除非暴力的枚举所有可能的消息,否则不可能从哈希值反推出消息本身。
(4)两条消息之间即使只有微小的差别,其对应的MD5值也应该是完全不同、完全不相关的。
(5)不能在有意义的时间内人工的构造两个不同的消息使其具有相同的MD5值。
应用举例:文件的哈希值
算出文件的哈希值,若两个文件的哈希值相同,则可认为这两个文件是相同的。因此:
(1)用户可以利用它来验证下载的文件是否完整
(2)云存储服务商可以利用它来判断用户要上传的文件是否已经存在于服务器上,从而实现秒传的功能,同时避免存储过多相同的文件副本。
哈希表的应用——SHA2算法
历史上MD5和SHA-1曾经是使用最为广泛的cryptographic hash function,但随着密码学的发展,这两个哈希函数的安全性相继受到了各种挑战。因此现在安全性较重要的场合推荐使用SHA-2等新的更安全的哈希函数。 - SHA-2包含了一系列的哈希函数:SHA-224,SHA-384,SHA-512,SHA-512/224,SHA-512/256,其对应的哈希值长度分别为224,256,384 or 512位。
- SHA-2具有和MD5类似的性质。
应用举例:
例如,在比特币系统中,所有参与者需要共同解决如下问题:对于一个给定的字符串U,给定的目标哈希值H,需要计算出一个字符串V,使得U+V的哈希值与H的差小于一个给定值D。此时,只能通过暴力枚举V来进行猜测。首先计算出结构的人可获得一定奖金。而某人首先计算成功的概率与其拥有的计算量成正比,所以其获得的奖金的期望值与其拥有的计算量成正比。