python 常用算法学习(1)
算法就是為了解決某一個問題而采取的具體有效的操作步驟
算法的復雜度,表示代碼的運行效率,用一個大寫的O加括號來表示,比如O(1),O(n)
認為算法的復雜度是漸進的,即對于一個大小為n的輸入,如果他的運算時間為n3+5n+9,那么他的漸進時間復雜度是n3
遞歸
遞歸就是在函數中調用本身,大多數情況下,這會給計算機增加壓力,但是有時又很有用,比如下面的例子:
漢諾塔游戲
把A柱的盤子,移動到C柱上,最少需要移動幾次,大盤子只能在小盤子下面
遞歸實現:
def hanoi(x, a, b, c): # 所有的盤子從 a 移到 cif x > 0:hanoi(x-1, a, c, b) # step1:除了下面最大的,剩余的盤子 從 a 移到 bprint('%s->%s' % (a, c)) # step2:最大的盤子從 a 移到 chanoi(x-1, b, a, c) # step3: 把剩余的盤子 從 b 移到 chanoi(10, 'A', 'B', 'C')#計算次數def h(x):num = 1for i in range(x-1):num = 2*num +1print(num) h(10)?
用遞歸打印斐波那契數列
def fei(n):if n == 0:return 0elif n == 1:return 1else:return fei(n-1)+fei(n-2)?
你會發現,即使n只有幾十的時候,你的計算機內存使用量已經飆升了
其實,如果結合生成器,你會發現不管n有多大,都不會出現卡頓,但這是生成器的特性,本篇博客不重點介紹
# 結合生成器 def fei(n):pre,cur = 0,1while n >=0:yield pren -= 1pre,cur = cur,pre+curfor i in fei(400000):print(i)?
關于遞歸次數,Python中有個限制,可以通過sys模塊來修改
import sys sys.setrecursionlimit(1000000)?
?
查找
1.順序查找
這個沒的說,就是for循環唄,時間復雜度O(n)
def linear_search(data_set, value):for i in range(len(data_set)):if data_set[i] == value:return ireturn?
2.二分查找
時間復雜度O(logn)
就是一半一半的查找,看目標值在左邊一半還是右邊一半,然后替換左端點或者右端點,繼續判斷
非遞歸版本:
def binary_serach(li,val):low = 0high = len(li)-1while low <= high:mid = (low+high)//2if li[mid] == val:return midelif li[mid] > val:high = mid-1else:low = mid+1else:return None?
遞歸版本的二分查找
def bin_search_rec(data_set, value, low, high):if low < high:mid = (low + high) // 2if data_set[mid] == value:return midelif data_set[mid] > value:return bin_search_rec(data_set, value, low, mid - 1)else:return bin_search_rec(data_set, value, mid + 1, high)else:return None?
?
排序
速度慢的三個:
1.冒泡排序
原理就是,列表相鄰的兩個數,如果前邊的比后邊的小,那么交換順序,經過一次排序后,最大的數就到了列表最前面
代碼:
def bubble_sort(li):for j in range(len(li)-1):for i in range(1, len(li)):if li[i] > li[i-1]:li[i], li[i-1] = li[i-1], li[i]return li?
冒泡排序的最差情況,即每次都交互順序的情況,時間復雜度是O(n2)
存在一個最好情況就是列表本來就是排好序的,所以可以加一個優化,加一個標志位,如果沒有出現交換順序的情況,那就直接return?
# 優化版本的冒泡 def bubble_sort_opt(li):for j in range(len(li)-1):flag = Falsefor i in range(1, len(li)):if li[i] > li[i-1]:li[i], li[i-1] = li[i-1], li[i]flag = Trueif not flag:return lireturn li?
2.插入排序
原理:把列表分為有序區和無序區兩個部分。最初有序區只有一個元素。然后每次從無序區選擇一個元素,插入到有序區的位置,直到無序區變空。
def insert_sort(li):for i in range(1,len(li)):tmp = li[i]j = i - 1while j >= 0 and tmp < li[j]: # 找到一個合適的位置插進去li[j+1] = li[j]j -= 1li[j+1] = tmpreturn li?
時間復雜度是O(n2)
?
3.選擇排序
原理:遍歷列表一遍,拿到最小的值放到列表第一個位置,再找到剩余列表中最小的值,放到第二個位置。。。。
def select_sort(li):for i in range(len(li)-1):min_loc = i # 假設當前最小的值的索引就是ifor j in range(i+1,len(li)):if li[j] < li[min_loc]:min_loc = jif min_loc != i: # min_loc 值如果發生過交換,表示最小的值的下標不是i,而是min_locli[i],li[min_loc] = li[min_loc],li[i]return li?
時間復雜度是O(n2)
?
速度快的幾種排序:
4.快速排序(快排)
原理:讓指定的元素歸位,所謂歸位,就是放到他應該放的位置(左變的元素比他小,右邊的元素比他大),然后對每個元素歸位,就完成了排序
可以參考這個動圖來理解下面的代碼
代碼:
# 歸位函數 def partition(data, left, right): # 左右分別指向兩端的元素tmp = data[left] # 把左邊第一個元素賦值給tmp,此時left指向空while left < right: # 左右兩個指針不重合,就繼續while left < right and data[right] >= tmp: # right指向的元素大于tmp,則不交換right -= 1 # right 向左移動一位data[left] = data[right] # 如果right指向的元素小于tmp,就放到左邊現在為空的位置while left < right and data[left] <= tmp: # 如果left指向的元素小于tmp,則不交換left += 1 # left向右移動一位data[right] = data[left] # 如果left指向的元素大于tmp,就交換到右邊data[left] = tmp # 最后把最開始拿出來的那個值,放到左右重合的那個位置return left # 最后返回這個位置# 寫好歸位函數后,就可以遞歸調用這個函數,實現排序 def quick_sort(data, left, right):if left < right:mid = partition(data, left, right) # 找到指定元素的位置quick_sort(data, left, mid - 1) # 對左邊元素排序quick_sort(data, mid + 1, right) # 對右邊元素排序return data?
正常的情況,快排的復雜度是O(nlogn)
快排存在一個最壞情況,就是每次歸位,都不能把列表分成兩部分,此時復雜度就是O(n2)了,如果要避免設計成這種最壞情況,可以在取第一個數的時候不要取第一個了,而是取一個列表中的隨機數
?
5.歸并排序
原理:列表分成兩段有序,然后分解成每個元素后,再合并成一個有序列表,這種操作就叫做一次歸并
應用到排序就是,把列表分成一個元素一個元素的,一個元素當然是有序的,將有序列表一個一個合并,最終合并成一個有序的列表
?
圖示:
?
代碼:
def merge(li, left, mid, right):# 一次歸并過程,把從mid分開的兩個有序列表合并成一個有序列表i = leftj = mid + 1ltmp = []# 兩個列表的元素依次比較,按從大到小的順序放到一個臨時的空列表中while i <= mid and j <= right:if li[i] < li[j]:ltmp.append(li[i])i += 1else:ltmp.append(li[j])j += 1# 如果兩個列表并不是平均分的,就會存在有元素沒有加入到臨時列表的情況,所以再判斷一下while i<= mid:ltmp.append(li[i])i += 1while j <= right:ltmp.append(li[j])j += 1li[left:right+1] = ltmpreturn lidef _merge_sort(li, left, right):# 細分到一個列表中只有一個元素的情況,對每一次都調用merge函數變成有序的列表if left < right:mid = (left+right)//2_merge_sort(li, left, mid)_merge_sort(li, mid+1, right)merge(li, left, mid, right)return lidef merge_sort(li):return(_merge_sort(li, 0, len(li)-1))?
照例,時間復雜度是O(nlogn)
特殊的,歸并排序還有一個O(n)的空間復雜度
?
6.堆排序
把這個放到最后,是因為這個是最麻煩的,把最麻煩的放到最后,是一種對工作負責的表現
如果要說堆排序,首先得先把‘樹’搞明白
樹
樹是一種數據結構;
樹是由n個節點組成的集合; -->如果n為0,那這是一顆空樹,如果n>0,那么那存在1個節點作為樹的根節點,其他節點可以分為m個集合,每個集合本身又是一棵樹。
一些可能會用到的概念:
根節點:樹的第一個節點,沒有父節點的節點
葉子節點:不帶分叉的節點
樹的深度(高度):就是分了多少層
孩子節點、父節點:節點與節點之間的關系
圖示:
?
二叉樹
然后在樹的基礎上,有一個二叉樹,二叉樹就是每個節點最多有兩個子節點的樹結構,比如這個:
?
滿二叉樹:除了葉子節點,所有節點都有兩個孩子,并且所有葉子節點深度都一樣
完全二叉樹:是有滿二叉樹引申而來,假設二叉樹深度為k,那么除了第k層,之前的每一層的節點數都達到最大,即沒有空的位置,而且第k層的子節點也都集中在左子樹上(順序)
?
二叉樹的存儲方式
有鏈式存儲和順序存儲的方式(列表),本篇只討論順序存儲的方式
思考:
父節點和左孩子節點的編號下標有什么關系? 0-1 1-3 2-5 3-7 4-9? ? ? ? ?i? ---->? ?2i+1
父節點和右孩子節點的編號下標有什么關系? 0-2 1-4 2-6 3-8 4-10 i? ----->? 2i+2
?
再來了解下堆,堆說起來又麻煩了,我將在另一篇博客中單獨寫堆,棧等這些數據結構,本篇先討論與排序有關的東西
堆
堆是一類特殊的樹,要求父節點大于或小于所有的子節點
大根堆:一棵完全二叉樹,滿足任一節點都比其孩子節點大?? ,升序用大根堆
小根堆:一棵完全二叉樹,滿足任一節點都比其孩子節點小
?
?
堆的調整:當根節點的左右子樹都是堆時,可以通過一次向下的調整來將其變換成一個堆。
所謂一次向下調整,就是說把堆頂的值,向下找一個合適的位置,是一次一次的找,跟他交換位置的值,也要找到一個合適的位置
“瀏覽器寫的沒保存,丟失了,所以這塊不想再寫了。。。”
?
堆排序的過程
1.構造堆
2.得到堆頂元素,就是最大的元素
3.去掉堆頂,將堆的最后一個元素放到堆頂,此時可以通過一次調整重新使堆有序
4.堆頂元素為第二大元素
5.重復步驟3,直到堆為空
?
其中構造堆的過程:
?
?
挨個出數的過程:
代碼:
def sift(li, left, right): # left和right 表示了元素的范圍,是根節點到右節點的范圍,然后比較根和兩個孩子的大小,把大的放到堆頂# 和兩個孩子的大小沒關系,因為我們只需要拿堆頂的元素就行了# 構造堆i = left # 當作根節點j = 2 * i + 1 # 上面提到過的父節點與左子樹根節點的編號下標的關系tmp = li[left]while j <= right:if j+1 <= right and li[j] < li[j+1]: # 找到兩個孩子中比較大的那個j = j + 1if tmp < li[j]: # 如果孩子中比較大的那個比根節點大,就交換li[i] = li[j]i = j # 把交換了的那個節點當作根節點,循環上面的操作j = 2 * i + 1else: breakli[i] = tmp # 如果上面發生交換,現在的i就是最后一層符合條件(不用換)的根節點,def heap_sort(li):n = len(li)for i in range(n//2-1, -1, -1): # 建立堆 n//2-1 是為了拿到最后一個子樹的根節點的編號,然后往前走,最后走到根節點0//2 -1 = -1sift(li, i, n-1) # 固定的把最后一個值的位置當作right,因為right只是為了判斷遞歸不要超出當前樹,所以最后一個值可以滿足# 如果每遍歷一個樹,就找到它的右孩子,太麻煩了for i in range(n-1, -1, -1): # 挨個出數li[0], li[i] = li[i],li[0] # 把堆頂與最后一個數交換,為了節省空間,否則還可以新建一個列表,把堆頂(最大數)放到新列表中sift(li, 0, i-1) # 此時的列表,應該排除最后一個已經排好序的,放置最大值的位置,所以i-1?
時間復雜度也是O(nlogn)
來擴展一下,如果要取一個列表的top10,就是取列表的前十大的數,怎么做?
可以用堆來實現,取堆的前十個數,構造成一個小根堆,然后依次遍歷列表后面的數,如果比堆頂小,則忽略,如果比堆頂大,則將堆頂替換成改元素,然后進行一次向下調整,最終這個小根堆就是top10
其實Python自帶一個heapq模塊,就是幫我們對堆進行操作的
heapq模塊
隊列中的每個元素都有優先級,優先級最高的元素優先得到服務(操作),這就是優先隊列,而優先隊列通常用堆來實現
如果用heapq模塊來實現堆排序,就簡單多了:
import heapq def heapq_sort(li):h = []for value in li:heapq.heappush(h,value)return [heapq.heappop(h) for i in range(len(h))]而想取top10 ,直接一個方法就行了
heapq.nlargest(10,li)?
這三種速度快的排序方式就說完了,其中,快排是速度最快的,即使這樣,也不如Python自帶的sort快
再來介紹兩種排序,希爾排序和計數排序
7.希爾排序
希爾排序是一種分組插入排序的算法
思路:
首先取一個整數d1=n/2,將元素分為d1個組,每組相鄰量元素之間距離為d1,在各組內進行直接插入排序;
取第二個整數d2=d1/2,重復上述分組排序過程,直到di=1,即所有元素在同一組內進行直接插入排序。
希爾排序每趟并不使某些元素有序,而是使整體數據越來越接近有序;最后一趟排序使得所有數據有序。
?
圖示:
?
代碼:
def shell_sort(li):gap = int(len(li)//2) # 初始把列表分成 gap個組,但是每組最多就兩個元素,第一組可能有三個元素while gap >0:for i in range(gap,len(li)):tmp = li[i]j = i - gapwhile j>0 and tmp<li[j]: # 對每一組的每一個數,都和他前面的那個數比較,小的在前面li[j+gap] = li[j]j -= gapli[j+gap] = tmpgap = int(gap//2) # Python3中地板除也是float類型return li? 通過diamante也能看出來,其實希爾排序和插入排序是非常相像的,插入排序就可以看做是固定間隔為1的希爾排序,希爾排序就是把插入排序分了個組,同一個組內,相鄰兩個數之間不是相差1,而是相差gap
時間復雜度:O((1+t)n),其中t是個大于0小于1的數,取決于gap的取法,當gap=len(li)//2的時候,t大約等于0.3
?
8.計數排序
需求:有一個列表,列表中的數都在0到100之間(整數),列表長度大約是100萬,設計算法在O(n)時間復雜度內將列表進行排序
分析:列表長度很大,但是數據量很少,會有大量的重復數據。可以考慮對這100個數進行排序
代碼:
def count_sort(li):count = [0 for i in range(101)] # 根據原題,0-100的整數for i in li:count[i] += 1i = 0for num,m in enumerate(count): # enumerate函數將一個可遍歷的數據對象(如列表、元組或字符串)組合為一個索引序列,同時列出數據和數據下標,一般用在 for 循環當中。for j in range(m):li[i] = numi += 1?
?此文摘抄來自? ? http://www.cnblogs.com/zhang-can/p/8011002.html? ? 抄過來的目的是為了學習,并不是炫耀什么的,不喜勿噴,謝謝
轉載于:https://www.cnblogs.com/wj-1314/p/8021989.html
總結
以上是生活随笔為你收集整理的python 常用算法学习(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET创建WebService服务简单
- 下一篇: win7本地连接删除了怎么恢复 如何恢复