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來進行猜測。首先計算出結構的人可獲得一定獎金。而某人首先計算成功的概率與其擁有的計算量成正比,所以其獲得的獎金的期望值與其擁有的計算量成正比。