1.概念
算法:一個計算過程(函數),或者說是解決問題的方法可以理解成一個算法
時間複雜度:用來估算算法運行時間的一個式子(單位)。一般來說,時間複雜度高的算法比複雜度低的算法慢
空間複雜度:用來估算算法佔用內存的一個式子
1.1 常見時間複雜度按照效率排序
O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)
一般來說
循環減半的過程,時間複雜度通常是O(logn)
幾次循環,時間複雜度就是n的幾次方
1.2 案例
""" 一段有n個臺階組成的樓梯,小明從樓梯的最底層向最高處前進,它可以選擇一次邁一級臺階或者一次邁兩級臺階。問:他有多少種不同的走法? """
思路:
該題其實也是一個斐波那契數列,f(n) = f(n-1)+f(n-2)
def steps(n): if n <= 2: return n else: return steps(n-1) + steps(n-2) print(steps(10))
1.3 二分排序法(時間複雜度O(logn))
這裏針對的是一個有序列表,一般是在算法中輸入該列表中某個數,然後輸出這個數在列表中的位置,即索引
示意圖:
代碼:
def bin_search(key, lis): low = 0 # 首 high = len(lis) - 1 # 尾 while low < high: mid = (low + high) // 2 if lis[mid] == key: # 命中 return mid elif lis[mid] > key: # key在左邊,說明key的範圍在low到mid-1之間 high = mid - 1 elif lis[mid] < key: # key在右邊,說明key的範圍在high+1到high之間 low = mid + 1
利用遞歸實現二分查找:
def bin_search_2(key, lis, low, high): if low <= high: mid = (low + high) // 2 if lis[mid] == key: return mid elif lis[mid] > key: # 在左邊,說明key在low和mid-1之間 return bin_search_2(key,lis,low, mid-1) else: # 在右邊,說明key在mid+1和high之間 return bin_search_2(key,lis,mid+1,high) else: return
對於一個二維列表實現的二分查找:
li = [ [1, 3, 5, 7], [9,10,14,17], [18,22,25,30] ]
代碼:
def binary_search_2d(li, val): left = 0 m = len(li) n = len(li[0]) right = m * n - 1 while left <= right: # 候選區非空 mid = (left + right) // 2 i = mid // n # 取整 j = mid % n # 取餘 if li[i][j] == val: return i, j elif li[i][j] < val: left = mid + 1 # 更新候選區 else: # > right = mid - 1 # 更新候選區 return None print(binary_search_2d(li, 22))
2. 排序入門篇
2.1 冒泡排序(時間複雜度O(n**2))
針對的是列表中的兩個相鄰的數,如果前邊比後面的大,則交換這兩個數,如此持續進行
代碼關鍵點在於幾趟以及無序區
示例代碼:
import random def bubble_sort(li): for i in range(len(li)-1): # i表示第i趟 for j in range(len(li)-i-1): # j表示箭頭的下標 if li[j] > li[j+1]: li[j], li[j+1] = li[j+1], li[j] print(li) li = list(range(10)) random.shuffle(li) bubble_sort(li)
以上代碼忽視了極端情況,從運行效率上來講,我們還可以在做優化,排除已經是有序的列表
import random def bubble_sort_2(li): for i in range(len(li)-1): # i表示第i趟 exchange = False for j in range(len(li)-i-1): # j表示箭頭的下標 if li[j] > li[j+1]: # 如果前面比後面大 li[j], li[j+1] = li[j+1], li[j] exchange = True if not exchange: return print(li) li = list(range(10)) random.shuffle(li) bubble_sort_2(li)
2.2 選擇排序(時間複雜度O(n**2))
一趟遍歷記錄最小的數,放到第一個位置;
再一趟遍歷記錄剩餘列表中最小的數,繼續放置;
關鍵點在於無序區以及最小數的位置
代碼:
import random def select_sort(li): for i in range(len(li)-1): # i表示趟數 min_pos = i # 最小值默認爲無序區第一個數 i for j in range(i+1, len(li)): if li[j] < li[min_pos]: min_pos = j li[i], li[min_pos] = li[min_pos], li[i] li = list(range(100)) random.shuffle(li) select_sort(li) print(li)
2.3 插入排序(時間複雜度O(n**2))
列表被分爲有序區和無序區兩個部分。最初有序區只有一個元素。
每次從無序區選擇一個元素,插入到有序區的位置,直到無序區變空。(類似我們玩紙牌)
代碼關鍵點在於我們摸到的牌,以及手裏的牌
代碼:
import random def insert_sort(li): for i in range(1, len(li)): # i表示摸到牌的位置,也表示趟 j = i - 1 # j是當前用來比較的牌 tmp = li[i] # 摸到的牌 while j >= 0 and li[j] > tmp: # 摸到的牌和他前面位置(前面位置必須大於等於0)的牌作比較 li[j+1] = li[j] # 前面的牌比手裏的大,把它往後移 j -= 1 # 再去看前面那張牌 li[j+1] = tmp # 用來比較的牌比摸到的牌小,直接放到j+1的位置 li = list(range(100, -1, -1)) random.shuffle(li) insert_sort(li) print(li)
3. 排序升級篇
3.1 快排(時間複雜度O(n*log(n))) --->算法裏面簡單,也是必須要掌握的一個
取一個元素p(第一個元素),使元素p歸位;
列表被p分成兩部分,左邊都比p小,右邊都比p大;
遞歸完成排序。
該算法的關鍵點是先對數組進行整理,然後再做遞歸操作
代碼第一部分
def quick_sort(lis, left, right): if left < right: mid = partition(lis, left, right) # 返回中間這個值 quick_sort(lis, left, mid - 1) # 對左邊的進行排序 quick_sort(lis, mid + 1, right) # 對右邊的進行排序
但是怎麼對它做p歸位處理呢?這裏我們先取出第一個數,即5,5的空位需要從右邊往左找一個比5小的數填充,即2
2放在了5的空位上,現在需要再從左邊取一個比5大的數放在2的空位上,即7
7放在了2的空位上,現在需要再從右邊取一個比5小的數放在7的空位上,即1
---
代碼第二部分(做歸位處理)
def partition(lis, left, right): tmp = lis[left] # 先取出最左邊這個數,存起來 while left < right: # 從右邊找比tmp小的數 while left < right and lis[right] >= tmp: right -= 1 # 如果右邊的數總比取出的數大,則往左前進一位繼續找直到找見爲止 lis[left] = lis[right] # 找見比tmp小的數,此時插入到左邊空出的那個位置 while left < right and lis[left] <= tmp: # 從左邊找比tmp大的數 left += 1 # 如果左邊的數總比取出的數小,則往右前進一位繼續找直到找見爲止 lis[right] = lis[left] # 找見比tmp大的數,此時插入到右邊空出的那個位置 lis[left] = tmp # 再把中間這個值tmp寫回來 return left # 返回中間這個值,left,right最後重合在一起了
lis = [5, 7, 4, 6, 3, 1, 2, 9, 8] partition(lis, 0, len(lis) - 1) print(lis) # [2, 1, 4, 3, 5, 6, 7, 9, 8] # 可以看出4歸位
以上是我們的基本思想,但是如果不從最左邊取這個tmp,而是從中任意取一個數作爲我們的tmp值呢?
代碼:
import random def partition(li, left, right): i = random.randint(left, right) # 從中取一個隨機數i li[left], li[i] = li[i], li[left] # 把它調換位置放在最左邊 tmp = li[left] while left < right: while left < right and li[right] >= tmp: right -= 1 li[left] = li[right] while left < right and li[left] <= tmp: left += 1 li[right] = li[left] li[left] = tmp return left def quick_sort(lis, left, right): if left < right: mid = partition(lis, left, right) # 返回中間這個值 quick_sort(lis, left, mid - 1) # 對左邊的進行排序 quick_sort(lis, mid + 1, right) # 對右邊的進行排序 lis = list(range(10000)) random.shuffle(lis) quick_sort(lis, 0, len(lis) - 1) print(lis)
3.2 堆排(最複雜的一個,利用二叉樹)
3.2.1 二叉樹
3.3 歸併排序