python拓展3 經常使用算法

知識內容:html

1.遞歸複習python

2.算法基礎概念算法

3.查找與排序api

 

 

參考資料:數組

http://python3-cookbook.readthedocs.io/zh_CN/latest/index.htmlapp

http://www.cnblogs.com/alex3714/articles/5474411.htmldom

關於時間複雜度:http://www.cnblogs.com/alex3714/articles/5910253.html函數

關於遞歸:http://www.cnblogs.com/alex3714/articles/8955091.html優化

 

 

 

1、遞歸複習ui

1.什麼是遞歸:函數內部本身調用本身

 

 

2.遞歸的特色

  • 必須有一個明確的結束條件
  • 每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減小
  • 遞歸效率不高,遞歸層次過多會致使棧溢出

 

 

3.看函數說結果

 1 def func1(x):
 2     print(x)
 3     func1(x-1)
 4 func1(5)
 5 # 一直打印到限制次數(無出口)
 6 
 7 def func2(x):
 8     if x > 0:
 9         print(x)
10         func2(x+1)
11 func2(5)
12 # 一直打印到限制次數(無出口)
13 
14 def func3(x):
15     if x > 0:
16         print(x)
17         func3(x-1)
18 func3(5) 
19 # 從5打印到1
20 
21 def func4(x):
22     if x > 0:
23         func4(x-1)
24         print(x)
25 func4(5)
26 # 從1打印到5

 

 

4.經典遞歸

(1)漢諾塔問題

解決思路:

假設有n個盤子:

  • 1.把n-1個圓盤從A通過C移動到B
  • 2.把第n個圓盤從A移動到C
  • 3.把n-1個小圓盤從B通過A移動到C

1:

2:

3:

 代碼:

 1 def hanoi(a, b, c, n):
 2     if n == 1:
 3         print(a, "->", c)       # 將n-1個盤子從a通過c移動到b
 4     else:
 5         hanoi(a, c, b, n-1)     # 將剩餘的最後一個盤子從a移動到c
 6         print(a, "->", c)
 7         hanoi(b, a, c, n-1)     # 將n-1個盤子從b通過a移動到c
 8 
 9 
10 hanoi('柱子a', '柱子b', '柱子c', 4)

總結:漢諾塔移動次數的遞推式:h(x)=2h(x-1)+1

 

(2)字符串逆序輸出

1 def rvs(s):
2     if s == "":
3         return s
4     else:
5         return rvs(s[1:]) + s[0]
6 
7 
8 s = rvs("Hello, Python")
9 print(s)

 

 

5.尾遞歸

定義:當遞歸調用是整個函數體中最後執行的語句且返回值不屬於表達式的一部分時,這個遞歸調用就是尾遞歸。尾遞歸函數的特色是在迴歸過程當中不用作任何操做,這個特性很重要,由於大多數現代的編譯器會利用這種特色自動生成優化的代碼。

原理:當編譯器檢測到一個函數調用是尾遞歸的時候,它就覆蓋當前的活動記錄而不是在棧中去建立一個新的。編譯器能夠作到這點,由於遞歸調用是當前活躍期內最後一條待執行的語句,因而當這個調用返回時棧幀中並無其餘事情可作,所以也就沒有保存棧幀的必要了。經過覆蓋當前的棧幀而不是在其之上從新添加一個,這樣所使用的棧空間就大大縮減了,這使得實際的運行效率會變得更高。

 

尾遞歸實例

1 def calc(n):
2     print(n - 1)
3     if n > -50:
4         return calc(n-1)

 

 

 

2、算法基礎概念

1.什麼是算法

算法就是一個計算過程,解決問題的方法

算法(Algorithm)是指解題方案的準確而完整的描述,是一系列解決問題的清晰指令,算法表明着用系統的方法描述解決問題的策略機制。也就是說,可以對必定規範的輸入,在有限時間內得到所要求的輸出。若是一個算法有缺陷,或不適合於某個問題,執行這個算法將不會解決這個問題。不一樣的算法可能用不一樣的時間、空間或效率來完成一樣的任務。一個算法的優劣能夠用空間複雜度與時間複雜度來衡量

一個算法應該具備如下七個重要的特徵:

  • 有窮性(Finiteness):算法的有窮性是指算法必須能在執行有限個步驟以後終止
  • 確切性(Definiteness):算法的每一步驟必須有確切的定義
  • 輸入項(Input):一個算法有0個或多個輸入,以刻畫運算對象的初始狀況,所謂0個輸     入是指算法自己定出了初始條件
  • 輸出項(Output):一個算法有一個或多個輸出,以反映對輸入數據加工後的結果。沒       有輸出的算法是毫無心義的
  • 可行性(Effectiveness):算法中執行的任何計算步驟都是能夠被分解爲基本的可執行       的操做步,即每一個計算步均可以在有限時間內完成(也稱之爲有效性)
  • 高效性(High efficiency):執行速度快,佔用資源少
  • 健壯性(Robustness):對數據響應正確

 

 

2.時間複雜度及空間複雜度

(1)時間複雜度

 1 print("hello, world")
 2 
 3 for i in range(n):
 4     print("hello, world")
 5 
 6 for i in range(n):
 7     for j in range(n):
 8         print("hello, world")
 9 
10 for i in range(n):
11     for j in range(n):
12         for k in range(n):
13             print("hello, world")    

問以上代碼的運行時間誰最短?用什麼方法來提現代碼(算法)的運行快慢呢?答案就是用時間複雜度來衡量

 

常見算法的時間複雜度(由小到大排列):O(1)  O(logn)  O(n)  O(nlogn)  O(n^2) O(n^2logn)  O(n^3)

實例:

 1 print('hello world')
 2 print('hello python')   # O(1)    大O,能夠認爲它的含義是「order of」(大約是)
 3 
 4 n= 64
 5 while n>1:
 6     print(n)     # O(logn)  # n=64是輸出依次爲: 64 32 16 8 4 2 
 7     n = n//2
 8 
 9 for i in range(n):
10     print(i)      # O(n)
11 
12 for i in range(n):
13     for j in range(n):
14         print('hello world')   # O(n^2)
15 
16 for i in range(n):
17     for j in range(n):
18         for k in range(n):
19             print('hello world')   # O(n^3)

注:切片的複雜度是O(n) ,由於切的時候是賦值

 

總結:

  • 時間複雜度是用來估計算法運行時間的一個式子(單位)
  • 通常來講,時間複雜度高的算法比算法時間複雜度低的算法慢
  • 循環減半的過程就是O(logn),幾回循環就是n的幾回方的複雜度

 

(2)空間複雜度

空間複雜度是用來評估算法內存佔用大小的一個式子,常見的空間複雜度:O(1)  O(n)  O(n^2)

空間換時間:計算機的資源很充足,能夠用空間的消耗來換取必定的時間

 

 

3、查找與排序

1.經常使用查找

(1)列表查找

列表查找:從列表中查找指定元素

輸入:列表、待查找的元素,輸出:元素下標或未查找到元素

列表查找的方法:順序查找和二分查找

  • 順序查找:從列表第一個元素開始,順序進行搜索直到找到爲止
  • 二分查找:從有序列表的後續區開始查找,經過對查找的值和候選區中間的值進行比較,使候選區減半(二分查找的列表必須有序!)

以上兩種查找的代碼以下:

# 順序查找 時間複雜 O(n)
def linear_search(find, data_list):
    for i in range(len(data_list)):
        if data_list[i] == find:
            return i
    return -1


# 二分查找 時間複雜 O(logn)
def binary_search(find, data_list):
    low = 0
    high = len(data_list)
    while low <= high:
        mid = (low + high) // 2
        # 找到find
        if data_list[mid] == find:
            return mid
        # find在左半邊
        elif data_list[mid] > find:
            high = mid - 1
        # find在右半邊
        else:
            low = mid + 1
    # 未找到find返回-1
    return -1

 

(2)查找練習

如今有一個學員信息列表(按id增序排列),格式爲:

1 [
2     {"id": 1001, "name": "張三", "age": 20},
3     {"id": 1002, "name": "woz", "age": 22},
4     {"id": 1003, "name": "alex", "age": 23},
5     {"id": 1004, "name": "hf", "age": 26},
6     {"id": 1005, "name": "kk", "age": 27},
7 ]

如今要求修改二分查找代碼,輸入學生id,輸出該學生在該列表下的下標並輸出完整的學生信息

實現代碼以下:

 1 import random
 2 stu_info = []       # 存儲學生信息的列表
 3 
 4 # 隨機生成n個數據
 5 def random_list(n):
 6     ids = list(range(1001, 1001+n))
 7     n1 = ["", "", "", "", "", ""]
 8     n2 = ["", "", "",  "", ""]
 9     n3 = ['', '', ""]
10     for i in range(n):
11         stu_age = random.randint(20, 30)
12         stu_id = ids[i]
13         stu_name = random.choice(n1)+random.choice(n2)+random.choice(n3)
14         stu_info.append({"id": stu_id, "age": stu_age, "name":stu_name})
15 
16 # 二分查找
17 def bin_search(data_set, find):
18     low = 0
19     high = len(data_set) - 1
20     while low <= high:
21         mid = (low+high)//2
22         if data_set[mid]["id"] == find:
23             return mid
24         elif data_set[mid]["id"] < find:
25             low = mid + 1
26         else:
27             high = mid - 1
28     return -1
29 
30 # 搜索信息
31 def search_info(info, find):
32     res = bin_search(info, find)
33     if res == -1:
34         print("沒有找到")
35     else:
36         print(info[res])
37 
38 
39 random_list(15)
40 print("如下是全部學生的id信息: ")
41 # print(stu_info)
42 for item in stu_info:
43     print(item["id"], end=" ")
44 sid = int(input("\n請輸入你想要查找的學生的id: ").strip())
45 search_info(stu_info, sid)

 

 

2.經常使用排序

經常使用的排序有如下幾種:

(1)排序:將無序序列變爲有序序列

輸入:無序序列,輸出:有序序列

 

(2)應用場景

  • 各類榜單
  • 各類表格
  • 給二分查找用
  • 給其餘算法用

 

(3)冒泡排序、選擇排序、插入排序(必須背下來)

算法關鍵點:無序區和有序區

 

冒泡排序

思路:首先,列表每兩個相鄰的數比較大小,若是前邊的比後邊的大那麼這兩個數就互換位置,另外冒泡排序的排序趟數爲n-1

 1 # 冒泡排序:
 2 import random
 3 
 4 def bubble_sort(s):
 5     for i in range(len(s)-1):
 6         for j in range(len(s)-i-1):
 7             if s[j] > s[j+1]:
 8                 s[j], s[j+1] = s[j+1], s[j]
 9 
10 data = list(range(100))
11 random.shuffle(data)            # 打亂列表中的數
12 print(data)
13 bubble_sort(data)               # 冒泡排序
14 print(data) 
 1 # 冒泡排序優化:   若是冒泡排序中執行一趟而沒有發生交互,則列表已是有序狀態,能夠直接結束算法
 2 import random
 3 
 4 def bubble_sort(s):
 5     for i in range(len(s)-1):
 6         exchange = False
 7         for j in range(len(s)-i-1):
 8             if s[j] > s[j+1]:
 9                 s[j], s[j+1] = s[j+1], s[j]
10                 exchange = True
11         if not exchange:
12             break
13 
14 data = list(range(100))
15 random.shuffle(data)            # 打亂列表中的數
16 print(data)
17 bubble_sort(data)               # 冒泡排序
18 print(data)

 

選擇排序

思路: 一趟遍歷完記錄最小的數,放到第一個位置;在一趟遍歷記錄剩餘列表中的最小的數,繼續放置,那麼怎麼選最小的數?

每次假設最開始的爲最小的數,而後從左至右掃描序列,記下最小值的位置。另外選擇排序和冒泡排序同樣也是n-1趟

 1 # 選擇排序:
 2 import random
 3 
 4 def select_sort(s):
 5     for i in range(len(s)-1):
 6         min_locate = i                    # 每次的開始以第一個爲最小值
 7         for j in range(i+1, len(s)):
 8             if s[j] < s[min_locate]:      # 兩數比較,若是另一個數比最小值小,說明這個數爲最小值
 9                 min_locate = j              
10         s[i], s[min_locate] = s[min_locate], s[i]
11 
12 data = list(range(100))
13 random.shuffle(data)            # 打亂列表中的數
14 print(data)
15 select_sort(data)               # 選擇排序
16 print(data)

 

插入排序

思路:元素被分爲有序區和無序區兩部分。最初有序區只有一個元素。每次從無序區中選擇一個元素,插入到有序區的位置,直到無序區變空

 1 # 插入排序:
 2 import random
 3 
 4 def insert_sort(s):
 5     for i in range(1, len(s)):  # i 表示無序區的第一個數
 6         tmp = s[i]              # 要插入有序區的數
 7         j = i - 1               # 指向有序區最後一個位置
 8         while s[j] > tmp and j >= 0:
 9             # 循環終止條件 li[j]<=tmp or j==-1
10             s[j + 1] = s[j]     # 向後移動
11             j -= 1
12         s[j + 1] = tmp
13 
14 data = list(range(100))
15 random.shuffle(data)            # 打亂列表中的數據
16 print(data)
17 insert_sort(data)               # 插入排序
18 print(data)

注:冒泡排序、選擇排序、插入排序的時間複雜度均爲O(n^2)

 

(4)快速排序、堆排序、歸併排序(排序NB三人組)

快速排序

 1 # encoding: utf-8
 2 # __author__ = "wyb"
 3 # date: 2018/9/26
 4 
 5 
 6 def quick_sort(data, left, right):
 7     if left < right:
 8         mid = partition(data, left, right)
 9         quick_sort(data, left, mid-1)
10         quick_sort(data, mid+1, right)
11 
12 
13 def partition(data, left, right):
14     tmp = data[left]
15     while left < right:
16         while left < right and data[right] >= tmp:
17             right -= 1
18         data[left] = data[right]
19         while left < right and data[left] <= tmp:
20             left += 1
21         data[right] = data[left]
22     data[left] = tmp
23     return left
24 
25 
26 data = list(range(10))
27 quick_sort(data, 0, len(data)-1)
28 print(data)

注:快速排序通常時間複雜度爲O(nlogn),最好複雜度爲O(nlogn),最壞複雜度爲O(n*n)

 

堆排序

 

 1 關於堆排序:
 2 堆排序實質上是利用了二叉樹 
 3 實質上在工程中二叉樹利用數組結構存儲,抽象存儲原理以下:
 4 二叉樹能夠轉換成數組: 節點i的左右孩子分別是2*i+1和2*i+2 節點i的父節點是(i-1)/2
 5         
 6 大根堆和小根堆:
 7     大根堆: 樹的任何一個子樹的最大值是這顆子樹的頂部
 8     小根堆: 樹的任何一個子樹的最小值是這顆子樹的頂部
 9     
10 堆排序過程:
11     堆排序實際上就是利用堆結構的排序
12     創建一個大根堆(複雜度爲O(n)) -> 使用heapInsert插入數 
13     而後把大根堆第一個數和最後一個數替換 拿出這個元素把堆的size-1 而後使用heapify對堆進行調整
14         
15 去掉堆頂的最大數或最小數: 
16     把堆頂(第一個元素)和最後一個元素交換,拿出這個元素而後把堆的size-1,而後對堆頂進行hapify調整
17     
18 優先級隊列結構其實就是堆結構

 

 1 def sift(data, low, high):
 2     i = low
 3     j = 2 * i + 1
 4     tmp = data[i]
 5     while j <= high:    # 沒到子樹的最下邊
 6         if j + 1 <= high and data[j] < data[j+1]:   # 若是有右孩子且比左孩子大
 7             j += 1  # j指向右孩子
 8         if data[j] > tmp:   # 孩子比最高領導大
 9             data[i] = data[j]   # 孩子填到父親的空位上
10             i = j               # 孩子成爲新父親
11             j = 2 * i +1        # 新孩子
12         else:
13             break
14     data[i] = tmp           # 最高領導放到父親位置
15 
16 def heap_sort(data):
17     n = len(data)
18     for i in range(n // 2 - 1, -1, -1):
19         sift(data, i, n - 1)
20     #堆建好了
21     for i in range(n-1, -1, -1):            # i指向堆的最後
22         data[0], data[i] = data[i], data[0] # 領導退休,刁民上位
23         sift(data, 0, i - 1)                # 調整出新領導

 

歸併排序

 1 def merge(a, b):
 2     c = []
 3     h = j = 0
 4     while j < len(a) and h < len(b):
 5         if a[j] < b[h]:
 6             c.append(a[j])
 7             j += 1
 8         else:
 9             c.append(b[h])
10             h += 1
11 
12     if j == len(a):
13         for i in b[h:]:
14             c.append(i)
15     else:
16         for i in a[j:]:
17             c.append(i)
18 
19     return c
20 
21 
22 def merge_sort(lists):
23     if len(lists) <= 1:
24         return lists
25     middle = len(lists)/2
26     left = merge_sort(lists[:middle])
27     right = merge_sort(lists[middle:])
28     return merge(left, right)

 

(5)沒什麼人用的排序(基數排序、希爾排序、桶排序)

這三種都是比較少用到的算法(瞭解便可)

  • 桶排序: 時間複雜度O(N) 額外空間複雜度O(N)
  • 計數排序: 時間複雜度O(N) 額外空間複雜度O(N)
  • 基數排序: 時間複雜度O(N) 額外空間複雜度O(N)
  • 上述三種都是非基於比較的排序,與被排序的樣本數據實際狀況頗有關係,在實際中並不經常使用
  • 另外以上三種都是穩定的排序
1 桶: 至關於一個容器,能夠是數組中的某個位置也能夠是雙向鏈表也能夠是一個隊列也能夠是一個堆
2 把相應東西放入相應桶內 而後再從低位置的桶依次到高位置 依次把東西倒出來
3     
4 eg: 數組arr長度爲60 數據值爲0到60 使用桶排序: 生成一個數組temp長度爲61 依次遍歷arr獲得arr[i]
5     將temp對應的數組值++ temp[arr[i]] = temp[arr[i]] + 1 遍歷完了以後遍歷數組temp輸出結果
6     每一個下標對應幾個值就把下標值輸出幾遍
7     實際上這個實例是計數排序 實質上是桶排序的一種實現
8     
9 基數排序將計數排序進行了改進,用10個桶進行排序 分別針對個位、十位、百位

 

(6)排序算法的穩定性

追求算法穩定性的意義: 第一次排序和第二次排序在某些值相等的狀況下保持原來的排序順序

  • 冒泡排序: 能夠實現穩定(相等的狀況下後一個繼續日後走)
  • 插入排序: 能夠實現穩定(相等就不往前插入)
  • 選擇排序: 作不到穩定
  • 歸併排序: 能夠實現穩定(merge相等的時候就先移動左邊的)
  • 快速排序: 作不到穩定(partition時作不到)
  • 堆排序: 作不到穩定
相關文章
相關標籤/搜索