數據結構和算法是什麼?html
算法啊,就是獨立存在的一種解決問題的一個思想node
算法的五大特性:python
引入:首先,咱們先來看一道題算法
注意:這裏的^2表明 平方 數據結構
# 三重循環 for a in range(0, 1001): for b in range(0, 1001): for c in range(0, 1001): if a**2 + b**2 == c**2 and a+b+c == 1000: print("a, b, c: %d, %d, %d" % (a, b, c))
而後,咱們調用time模塊進行一個簡單的計時app
import time start_time = time.time() 最容易想到的一種方法 end_time = time.time() print("執行時間:",end_time-start_time)
因而呢,你就會驚奇的發現竟然總共執行了200多秒.......數據結構和算法
咱們優化一下代碼ide
import time start_time = time.time() # 注意是兩重循環 for a in range(0, 1001): for b in range(0, 1001-a): c = 1000 - a - b if a**2 + b**2 == c**2: print("a, b, c: %d, %d, %d" % (a, b, c)) end_time = time.time() print("執行時間:",end_time-start_time)
而後看一下結果........函數
僅僅只執行了0.x秒,形成這個的緣由僅僅只是我去掉了一層的循環性能
兩種算法,爲什麼會有如此之大的差距呢?
1.執行時間反應算法的效率:實現算法程序的執行時間能夠反應出算法的效率,即算法的優劣
2.但靠時間比較算法的優劣比不必定可靠:硬件的基礎一樣會影響執行的時間
時間複雜度與"大O記法"
大O記法:對於單調的整數函數f,若是存在一個整數函數g和實常數c>0,使 得對於充分大的n總有f(n)<=c*g(n),就說函數g是f的一個漸近函數(忽略常 數),記爲f(n)=O(g(n))。也就是說,在趨向無窮的極限意義下,函數f的增加 速度受到函數g的約束,亦即函數f與函數g的特徵類似。
時間複雜度:假設存在函數g,使得算法A處理規模爲n的問題示例所用時間爲 T(n)=O(g(n)),則稱O(g(n))爲算法A的漸近時間複雜度,簡稱時間複雜度, 記爲T(n)
對大O算法的理解
對於算法進行特別具體的細緻分析雖然很好,但在實踐中的實際價值有限。對於
算法的時間性質和空間性質,最重要的是其數量級和趨勢,這些是分析算法效率
的主要部分。而計量算法基本操做數量的規模函數中那些常量因子能夠忽略不
計。例如,能夠認爲3n2和100n2屬於同一個量級,若是兩個算法處理一樣規
模實例的代價分別爲這兩個函數,就認爲它們的效率「差很少」,都爲n2級。
最壞時間的複雜度
咱們主要關注算法的最壞狀況,即最壞時間複雜度
時間複雜度的幾條基本計算規則
1.
for a in range(0, 1001): for b in range(0, 1001): for c in range(0, 1001): if a**2 + b**2 == c**2 and a+b+c == 1000: print("a, b, c: %d, %d, %d" % (a, b, c))
三次循環,他的時間複雜度爲:T(n) = O(n*n*n) = O(n3)
2.
for a in range(0, 1001): for b in range(0, 1001-a): c = 1000 - a - b if a**2 + b**2 == c**2: print("a, b, c: %d, %d, %d" % (a, b, c))
兩次循環,他的時間複雜度爲:T(n) = O(n*n*(1+1)) = O(n*n) = O(n2)
因而可知啊,咱們嘗試的第二種算法要比第一種算法的時間複雜度要好得多
常見的時間複雜度
注意:常常將log2n(以2爲底的對數)簡寫成logn
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
Python內置類型性能分析
timeit模塊能夠用來測試一小段Python代碼的執行速度
def t1(): l = [] for i in range(1000): l = l + [i] def t2(): l = [] for i in range(1000): l.append(i) def t3(): l = [i for i in range(1000)] def t4(): l = list(range(1000)) from timeit import Timer test1 = Timer("t1()","from __main__ import t1") print('result1:',test1.timeit(number=100),'seconds') test2 = Timer("t2()","from __main__ import t2") print('result2:',test2.timeit(number=100),'seconds') test3 = Timer("t3()","from __main__ import t3") print('result3:',test3.timeit(number=100),'seconds') test4 = Timer("t4()","from __main__ import t4") print('result4:',test4.timeit(number=100),'seconds')
因爲時間的問題,在這裏我每一個只測試了100次,number=100
即便次數很少們也能明顯的看出速度的差距。
from timeit import Timer x = list(range(1000000)) pop_test1 = Timer("x.pop(0)","from __main__ import x") print("POP Test1:",pop_test1.timeit(number=1000)) x = list(range(1000000)) pop_test2 = Timer("x.pop()","from __main__ import x") print("POP Test2:",pop_test2.timeit(number=1000))
此時結果:
結果很是的明顯:pop最後一個元素的效率遠遠高於pop第一個元素
數據結構
問題:咱們若是用Python中的類型來保存一個班的學生信息呢?若是想要快速的經過學生姓名獲取到其餘的信息呢?
實際上當咱們在思考這個問題的時候,咱們已經用到了數據結構。列表和字典均可以存儲一個班的學生信息,可是想要在列表
中獲取一名同窗的信息時,就要遍歷這個列表,其時間複雜度爲O(n),而使用字典存儲時,可將學生姓名做爲字典的鍵,學
生信息做爲值,進而查詢時不須要遍歷即可快速獲取到學生信息,其時間複雜度爲O(1)。
咱們爲了解決問題,須要將數據保存下來,而後根據數據的存儲方式來設計算法實現進行處理,那麼數據的存儲方式不一樣就
會致使須要不一樣的算法進行處理。咱們但願算法解決問題的效率越快越好,因而咱們就須要考慮數據究竟如何保存的問
題,這就是數據結構。
在上面的問題中咱們能夠選擇Python中的列表或字典來存儲學生信息。列表和字典就是Python內建幫咱們封裝好的兩種
數據結構。
數據是一個抽象的概念,將其進行分類後獲得程序設計語言中的基本類型。如:int,float,char等。
數據元素之間不是獨立的,存在這特定的關係,這些關係即是結構。
數據結構指數據對象中數據元素之間的關係。
Python中給咱們提供了不少現成的數據結構類型,這些系統本身定義好的,不須要咱們本身去定義的數據結構叫作Python的內置數據結構,好比:列表、元組、字典。而有些數據組織方式,Python系統類並無直接的定義,須要咱們本身去定義實現這些數據則組織方式,這些數據組織方式稱之爲Python拓展數據結構,好比棧、隊列等
數據結構只是靜態的描述了數據元素之間的關係。
高效的程序須要在數據結構的基礎上設計和選擇算法
程序 = 數據結構 + 算法
總結:算法是爲了解決實際問題而設計的,數據結構是算法須要處理的問題的載體
抽象數據類型(ADT)的含義是指一個數學模型以及定義在此數據模型上的一組操做。即把數據類型和數據類型上的運算捆在一塊兒,進行封裝。引入抽象數據類型的目的是把數據類型表示和數據類型上運算的實現與這些數據類型和運算在程序中的引用隔開,使它們互相獨立。
最經常使用的數據運算:
順序表
在程序中,常常須要將一組(一般是同爲某個類型的)數據元素做爲總體管理和使用,須要建立這樣的元素組,用變量去記錄他們,傳進傳出函數等。一組數據中包含的元素個數可能發生變化(能夠增長或者刪除元素)
對於這種需求,最簡單的解決方案即是將這樣一組元素當作一個序列,用元素在序列中的位置和順序,表示時間應用中的某種有意義的信息,或者表示數據之間的某種關係。
這樣一組序列元素的組織形式,咱們能夠將其抽象爲線性表。一個線性表是某類元素的一個集合,還記錄着元素之間的一種順序關係。
線性表是最基本的數據結構之一,在實際程序中應用的很是普遍,它還常常被用做更復雜的數據結構的實現基礎。
根據線性表的實際存儲方式,分爲兩種實現模型:
Python中的list和tuple兩種類型均採用了順序表的實現技術,既有順序表的全部性質。
tuple是不可變類型,即一個不變的順序表,所以不支持改變其內部狀態的任何操做,而其餘方面,則與list類似
Python標準類型list就是一種元素個數可變的線性表,能夠加入和刪除元素,並在各類操做中維持已有的順序(即保序),並且還具備如下的行爲特徵:
在Python的官方實現中,list就是一種採用分離式技術實現的動態順序表。這就是爲啥用list.append(x) (或list.insert(len(list),x),即尾部插入)比在指定位置插入元素的效率高的緣由。
在Python的官方中,list的實現採用了以下的策略:在創建空表(或者很小的表)時,系統分配一塊能容納8個元素的存儲
區;在執行插入操做(insert或者append)時,若是元素存儲區滿了就換一塊4倍大的存儲區。但若是此時的表已經很大
了(閾值爲50000),則改變策略,採用加一倍的方法。引入這種改策略的方式,是爲了不出現過多的空閒的存儲位置。
鏈表
順序表的構建須要預先知道數據大小來申請連續的存儲空間,而在進行擴充時又須要進行數據的搬遷,因此使用起來並非很
靈活。
鏈表結構能夠充分利用計算機內存空間,實現靈活的內存動態管理。
鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,可是不像順序表同樣連續存儲數據,而是在每個節點(數據存儲單元)裏存放下一個節點的位置信息(即地址)。
單向鏈表也叫作單鏈表,是鏈表中最簡單的一種形式,它每一個節點包含兩個域,一個信息域(元素域)和一個鏈表域。這個鏈表指向鏈表種的下一個節點,而最後一個節點則指向了一個空值。
節點的實現(一個例子):
class SingleNode(object): '''單鏈表的節點''' def __init__(self,item): #_item用於存放數據文件 self.item = item #_next是下一個節點的標識 self._next = None
單鏈表的操做:
單鏈表的實現(一個例子):
class SingleLinkList(object): '''單鏈表''' def __init__(self): self._head = None def is_empty(self): '''判斷鏈表是否爲空''' return self._head == None def length(self): '''鏈表長度''' cur = self._head #初始時指向頭節點 count = 0 #尾節點指向None,當未達到尾部時 while cur != None: count += 1 cur = cur.next return count def travel(self): '''遍歷鏈表''' cur = self._head while cur != None: print(cur.item) cur = cur.next print('')
頭部添加元素的方法:
def add(self,item): '''頭部添加元素''' #先建立一個保存item值的節點 node = SingleNode(item) #將新節點的連接域next指向頭節點,即_head指向的位置 node.next = self._head # 將鏈表的頭_head指向新節點 self._head = node
尾部添加元素的方法:
def append(self,item): '''尾部添加元素''' node = SingleNode(item) #首先判斷鏈表是否爲空,如果空鏈表,則將_head指向新的節點 if self.is_empty(): self._head = node #如果不爲空,則找到尾部,將尾部的next指向新的節點 else: cur = self._head while cur.next != None: cur = cur.next cur.next = node
指定位置添加元素
def insert(self,pos,item): '''指定位置添加元素''' #若指定位置pos爲第一個元素以前,則執行頭部插入 if pos <= 0: self.add(item) #若指定位置超過鏈表尾部,則執行尾部插入 elif pos > (self.length()-1): self.append(item) #找到指定的位置 else: node = SingleNode(item) count = 0 #pre用來指向指定位置pos的前一個位置pos-1,初始從頭節點開始移動到指定位置 pre = self._head while count < (pos-1): count += 1 pre = pre.next #先將新節點node的next指向插入位置的節點 node.next = pre.next #將插入位置的前一個節點的next指向新節點 pre.next = node
刪除節點
def remove(self,item): """刪除節點""" cur = self._head pre = None while cur != None: # 找到了指定元素 if cur.item == item: # 若是第一個就是刪除的節點 if not pre: # 將頭指針指向頭節點的後一個節點 self._head = cur.next else: # 將刪除位置前一個節點的next指向刪除位置的後一個節點 pre.next = cur.next break else: # 繼續按鏈表後移節點 pre = cur cur = cur.next
查找節點是否存在
def search(self,item): """鏈表查找節點是否存在,並返回True或者False""" cur = self._head while cur != None: if cur.item == item: return True cur = cur.next return False
鏈表失去了順序表隨機讀取的優勢,同時鏈表因爲增長了節點的指針域,空間開銷比較大,但對於存儲空間的使用要相對靈活。
鏈表與順序表的各類操做複雜度以下:
注意雖然表面看起來複雜度都是 O(n),可是鏈表和順序表在插入和刪除時進行的是徹底不一樣的操做。鏈表的主要耗時操做是
遍歷查找,刪除和插入操做自己的複雜度是O(1)。順序表查找很快,主要耗時的操做是拷貝覆蓋。由於除了目標元素在尾部
的特殊狀況,順序表進行插入和刪除時須要對操做點以後的全部元素進行先後移位操做,只能經過拷貝和覆蓋的方法進行。
單列表的一個變形是單向循環鏈表,鏈表中最後一個節點的next域再也不爲None,而是執行鏈表的頭節點
操做:
實現:
#單項循環列表 class Node(object): """節點""" def __init__(self, item): self.item = item self.next = None class SinCycLinkedlist(object): """單向循環鏈表""" def __init__(self): self._head = None def is_empty(self): """判斷鏈表是否爲空""" return self._head == None def length(self): """返回鏈表的長度""" # 若是鏈表爲空,返回長度0 if self.is_empty(): return 0 count = 1 cur = self._head while cur.next != self._head: count += 1 cur = cur.next return count def travel(self): """遍歷鏈表""" if self.is_empty(): return cur = self._head print(cur.item) while cur.next != self._head: cur = cur.next print(cur.item) print("") def add(self, item): """頭部添加節點""" node = Node(item) if self.is_empty(): self._head = node node.next = self._head else: #添加的節點指向_head node.next = self._head # 移到鏈表尾部,將尾部節點的next指向node cur = self._head while cur.next != self._head: cur = cur.next cur.next = node #_head指向添加node的 self._head = node def append(self, item): """尾部添加節點""" node = Node(item) if self.is_empty(): self._head = node node.next = self._head else: # 移到鏈表尾部 cur = self._head while cur.next != self._head: cur = cur.next # 將尾節點指向node cur.next = node # 將node指向頭節點_head node.next = self._head def insert(self, pos, item): """在指定位置添加節點""" if pos <= 0: self.add(item) elif pos > (self.length()-1): self.append(item) else: node = Node(item) cur = self._head count = 0 # 移動到指定位置的前一個位置 while count < (pos-1): count += 1 cur = cur.next node.next = cur.next cur.next = node def remove(self, item): """刪除一個節點""" # 若鏈表爲空,則直接返回 if self.is_empty(): return # 將cur指向頭節點 cur = self._head pre = None # 若頭節點的元素就是要查找的元素item if cur.item == item: # 若是鏈表不止一個節點 if cur.next != self._head: # 先找到尾節點,將尾節點的next指向第二個節點 while cur.next != self._head: cur = cur.next # cur指向了尾節點 cur.next = self._head.next self._head = self._head.next else: # 鏈表只有一個節點 self._head = None else: pre = self._head # 第一個節點不是要刪除的 while cur.next != self._head: # 找到了要刪除的元素 if cur.item == item: # 刪除 pre.next = cur.next return else: pre = cur cur = cur.next # cur 指向尾節點 if cur.item == item: # 尾部刪除 pre.next = cur.next def search(self, item): """查找節點是否存在""" if self.is_empty(): return False cur = self._head if cur.item == item: return True while cur.next != self._head: cur = cur.next if cur.item == item: return True return False
測試:
ll = SinCycLinkedlist() ll.add(1) ll.add(2) ll.append(3) ll.insert(2, 4) ll.insert(4, 5) ll.insert(0, 6) print("length:",ll.length()) ll.travel() print(ll.search(3)) print(ll.search(7)) ll.remove(1) print("length:",ll.length()) ll.travel()
結果:
雙向鏈表又稱爲「雙面列表」。
每個節點都有連個連接:一個指向前一個節點,當此節點成爲第一個節點時,指向空值;而另外一個指向下一個節點,當此節點爲最後一個節點時,指向空值。
操做:
#雙向鏈表 class Node(object): """雙向鏈表節點""" def __init__(self, item): self.item = item self.next = None self.prev = None class DLinkList(object): """雙向鏈表""" def __init__(self): self._head = None def is_empty(self): """判斷鏈表是否爲空""" return self._head == None def length(self): """返回鏈表的長度""" cur = self._head count = 0 while cur != None: count += 1 cur = cur.next return count def travel(self): """遍歷鏈表""" cur = self._head while cur != None: print(cur.item) cur = cur.next print("") def add(self, item): """頭部插入元素""" node = Node(item) if self.is_empty(): # 若是是空鏈表,將_head指向node self._head = node else: # 將node的next指向_head的頭節點 node.next = self._head # 將_head的頭節點的prev指向node self._head.prev = node # 將_head 指向node self._head = node def append(self, item): """尾部插入元素""" node = Node(item) if self.is_empty(): # 若是是空鏈表,將_head指向node self._head = node else: # 移動到鏈表尾部 cur = self._head while cur.next != None: cur = cur.next # 將尾節點cur的next指向node cur.next = node # 將node的prev指向cur node.prev = cur def search(self, item): """查找元素是否存在""" cur = self._head while cur != None: if cur.item == item: return True cur = cur.next return False def insert(self, pos, item): """在指定位置添加節點""" if pos <= 0: self.add(item) elif pos > (self.length() - 1): self.append(item) else: node = Node(item) cur = self._head count = 0 # 移動到指定位置的前一個位置 while count < (pos - 1): count += 1 cur = cur.next # 將node的prev指向cur node.prev = cur # 將node的next指向cur的下一個節點 node.next = cur.next # 將cur的下一個節點的prev指向node cur.next.prev = node # 將cur的next指向node cur.next = node def remove(self, item): """刪除元素""" if self.is_empty(): return else: cur = self._head if cur.item == item: # 若是首節點的元素便是要刪除的元素 if cur.next == None: # 若是鏈表只有這一個節點 self._head = None else: # 將第二個節點的prev設置爲None cur.next.prev = None # 將_head指向第二個節點 self._head = cur.next return while cur != None: if cur.item == item: # 將cur的前一個節點的next指向cur的後一個節點 cur.prev.next = cur.next # 將cur的後一個節點的prev指向cur的前一個節點 cur.next.prev = cur.prev break cur = cur.next
操做:
ll = DLinkList() ll.add(1) ll.add(2) ll.append(3) ll.insert(2, 4) ll.insert(4, 5) ll.insert(0, 6) print("length:",ll.length()) ll.travel() print(ll.search(3)) print(ll.search(4)) ll.remove(1) print("length:",ll.length()) ll.travel()
結果:
棧,有些地方稱爲堆棧,是一種容器,能夠存入數據元素、訪問元素、刪除元素、他的特色在於只能容許容器的一端top,進行加入數據push和輸出數據pop。沒有了位置的概念,保證任什麼時候候能夠訪問、刪除的元素都是此前最後存入的那個元素,肯定了一種默認的訪問順序。
因爲棧數據結構只容許在一端進行操做,於是按照後進先出的原理運做
棧的操做:
class Stack(object): """棧""" def __init__(self): self.items = [] def is_empty(self): """判斷是否爲空""" return self.items == [] def push(self, item): """加入元素""" self.items.append(item) def pop(self): """彈出元素""" return self.items.pop() def peek(self): """返回棧頂元素""" return self.items[len(self.items)-1] def size(self): """返回棧的大小""" return len(self.items) if __name__ == "__main__": stack = Stack() stack.push("hello") stack.push("world") stack.push("nullnull") print(stack.size()) print(stack.peek()) print(stack.pop()) print(stack.pop()) print(stack.pop())
隊列(queue)是隻容許在一端進行插入操做,而在另外一端進行刪除操做的線性表。
隊列是一種先進先出的線性表
操做:
實現:
class Queue(object): """隊列""" def __init__(self): self.items = [] def is_empty(self): return self.items == [] def enqueue(self, item): """進隊列""" self.items.insert(0,item) def dequeue(self): """出隊列""" return self.items.pop() def size(self): """返回大小""" return len(self.items) if __name__ == "__main__": q = Queue() q.enqueue("hello") q.enqueue("world") q.enqueue("nullnull") print(q.size()) print(q.dequeue()) print(q.dequeue()) print(q.dequeue())
是一種具備隊列和棧的性質的數據結構。
雙端隊列中的元素能夠從兩端彈出,其限定插入和刪除操做在表的兩端進行。雙端隊列能夠在隊列任意一端入隊和 出對。
操做:
實現:
class Deque(object): """雙端隊列""" def __init__(self): self.items = [] def is_empty(self): """判斷隊列是否爲空""" return self.items == [] def add_front(self, item): """在隊頭添加元素""" self.items.insert(0,item) def add_rear(self, item): """在隊尾添加元素""" self.items.append(item) def remove_front(self): """從隊頭刪除元素""" return self.items.pop(0) def remove_rear(self): """從隊尾刪除元素""" return self.items.pop() def size(self): """返回隊列大小""" return len(self.items) if __name__ == "__main__": deque = Deque() deque.add_front(1) deque.add_front(2) deque.add_rear(3) deque.add_rear(4) print(deque.size()) print(deque.remove_front()) print(deque.remove_front()) print(deque.remove_rear()) print(deque.remove_rear())