Python算法基礎

1、簡介

定義和特徵

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

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

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

設計要求

算法設計的要求: shell

  • 肯定性: 指的是算法至少應該有輸入,輸出和加工處理無歧義性,能正確反映問題的需求,可以獲得問題的正確答案。肯定性大致分爲四個層次:

    1.算法程序無語法錯誤;數組

    2.算法程序對於合法的輸入產生知足要求的輸出; app

    3.對於非法輸入可以產生知足規格的說明;dom

    4.算法程序對於故意刁難的測試輸入都有知足要求的輸出結果。 函數

  • 可讀性: 程序便於閱讀,理解交流。 
  • 健壯性: 當輸入數據不合法時,算法也能做出相關處理,而不是產生異常,崩潰或者莫名其妙的結果。 
  • 時間效率高和存儲量低。

算法效率的度量方法

  過後統計方法:主要是經過設計好的測試程序和數據,利用計算機計時器對不一樣算法編制的程序的運行時間進行比較,從而肯定算法效率的高低,但這種方法有很大缺陷,通常不予採納。性能

  事前分析估算方法:在計算機程序編制前,依據統計方法對算法進行估算。測試

  一個用高級語言編寫的程序在計算機上運行時所消耗的時間取決於如下因素:優化

  1. 算法採用的策略,方法;(算法好壞的根本)
  2. 編譯產生的代碼質量;(由軟件來支持)
  3. 問題的輸入規模;(由數據決定)
  4. 機器執行指令的速度。(看硬件的性能)

 

算法時間複雜度

  定義:在進行算法分析時,語句總的執行次數T(n)是關於問題規模n的函數,進而分析T(n)隨n的變化狀況並肯定T(n)的數量級。算法的時間複雜度,也就是算法的時間量度,記做:T(n}=0(f(n))。它表示隨問題規模n的增大,算法執行時間的埔長率和 f(n)的埔長率相同,稱做算法的漸近時間複雜度,簡稱爲時間複雜度。其中f( n)是問題規橫n的某個函數。

根據定義,求解算法的時間複雜度的具體步驟是:

  ⑴ 找出算法中的基本語句;

  算法中執行次數最多的那條語句就是基本語句,一般是最內層循環的循環體。
  ⑵ 計算基本語句的執行次數的數量級;
  只需計算基本語句執行次數的數量級,這就意味着只要保證基本語句執行次數的函數中的最高次冪正確便可,能夠忽略全部低次冪和最高次冪的係數。這樣可以簡化算法分析,而且使注意力集中在最重要的一點上:增加率。
  ⑶ 用大Ο記號表示算法的時間性能。
  將基本語句執行次數的數量級放入大Ο記號中。

 

如何推導大o階呢?下面是基本的推導方法:

  1.用常數1取代運行時間中的全部加法常數。
  2.在修改後的運行次數函數中,只保留最髙階項。
  3.若是最高階項存在且不是1,則去除與這個項相乘的常數。

簡單的說,就是保留求出次數的最高次冪,而且把係數去掉。  如T(n)=n2+n+1 =O(n2)

一些例子

######複雜度O(1)
print("this is wd")


######複雜度O(n)
for i in range(n):
    print(i)


######複雜度O(n2)
for i in range(n):
    for j in range(n):
        print(j)


######複雜度O(n3)
for i in range(n):
    for j in range(n):
        for k in range(n):
            print('wd')



######複雜度O(log2n)
while n > 1:
    print(n)
    n = n // 2

常見的複雜度按效率排序:O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(2nlogn)<O(n2)

 

空間複雜度 

  空間複雜度(Space Complexity)是對一個算法在運行過程當中臨時佔用存儲空間大小的量度。一個算法在計算機存儲器上所佔用的存儲空間,包括存儲算法自己所佔用的存儲空間,算法的輸入輸出數據所佔用的存儲空間和算法在運行過程當中臨時佔用的存儲空間這三個方面。算法的輸入輸出數據所佔用的存儲空間是由要解決的問題決定的,是經過參數表由調用函數傳遞而來的,它不隨本算法的不一樣而改變。存儲算法自己所佔用的存儲空間與算法書寫的長短成正比,要壓縮這方面的存儲空間,就必須編寫出較短的算法。算法在運行過程當中臨時佔用的存儲空間隨算法的不一樣而異,有的算法只須要佔用少許的臨時工做單元,並且不隨問題規模的大小而改變,這種算法是節省存儲的算法;有的算法須要佔用的臨時工做單元數與解決問題的規模n有關,它隨着n的增大而增大,當n較大時,將佔用較多的存儲單元。

如當一個算法的空間複雜度爲一個常量,即不隨被處理數據量n的大小而改變時,可表示爲O(1);當一個算法的空間複雜度與以2爲底的n的對數成正比時,可表示爲0(log2n);當一個算法的空間複雜度與n成線性比例關係時,可表示爲0(n).若形參爲數組,則只須要爲它分配一個存儲由實參傳送來的一個地址指針的空間,即一個機器字長空間;若形參爲引用方式,則也只須要爲其分配存儲一個地址的空間,用它來存儲對應實參變量的地址,以便由系統自動引用實參變量。 

 

2、python中的常見算法

冒泡排序

效率:O(n2)

原理:

  1. 比較相鄰的元素,若是第一個比第二個大,就交換他們兩個;
  2. 對每一對相鄰元素作一樣的工做,從開始第一對到結尾的最後一對。作完之後,最後的元素會是最大的數,這裏能夠理解爲走了一趟;
  3. 針對全部的元素重複以上的步驟,除了最後一個;
  4. 持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較,最後數列就是從大到小一次排列;

demo:

def bubble_sort(data):
    """
    冒泡排序
    :param data: 
    :return: 
    """
    for i in range(len(data)-1):  # 趟數
        for j in range(len(data)-i-1):  # 遍歷數據,依次交換
            if data[j]>data[j+1]:  # 當較大數在前面
                data[j],data[j+1]=data[j+1],data[j] #交換兩個數的位置

if __name__=='__main__':
    import random
    data_list=list(range(30))
    random.shuffle(data_list)
    print("pre:",data_list)
    bubble_sort(data_list)
    print("after:",data_list)
#結果:
#pre: [22, 11, 19, 16, 12, 18, 20, 28, 27, 4, 21, 10, 9, 7, 1, 6, 5, 29, 8, 0, 17, 26, 13, 14, 15, 24, 25, 23, 3, 2]
#after: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]

優化版本:當某一趟走完之後發現並無進行數據交換,那麼此時的數列已經排列好了,沒有必要在進行下去。例如:極端狀況下,數列原本已經排序好的,咱們只須要走一趟便可完成排序。

def bubble_sort(data):
    """
    冒泡排序優化版
    :param data: 
    :return: 
    """
    for i in range(len(data)-1):  # 趟數
        exchange=False   # 交換標誌
        for j in range(len(data)-i-1):  # 遍歷數據,依次交換
            if data[j]>data[j+1]:  # 當較大數在前面
                data[j],data[j+1]=data[j+1],data[j]  # 交換兩個數的位置
                exchange = True  # 改變標誌
        if not exchange: # 若是某一趟沒有進行交換,表明排序完成
            break
    return i  # 返回次數的趟數

if __name__=='__main__':
    data_list=list(range(30))
    print("pre:",data_list)
    num =bubble_sort(data_list)
    print("after:",data_list,'趟數:',num+1)
#結果:
#pre: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
#after: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29] 趟數: 1

選擇排序

效率:O(n2)

原理:

  1. 每一次從待排序的列表中選出一個元素,並將其與其餘數依次比較,若列表中的某個數比選中的數小,則交換位置,把全部數比較完畢,則會選出最小的數,將其放在最左邊(這一過程稱爲一趟);
  2. 重複以上步驟,直到所有待排序的數據元素排完;

demo:

def select_sort(data):
    """
    選擇排序
    :param data: 待排序的數據列表
    :return: 
    """
    for i in range(len(data)-1):  #趟數
        min_index=i  # 記錄i趟開始最小的數的索引,咱們從最左邊開始
        for j in range(i+1,len(data)): # 每一次趟須要循環的次數
            if data[j] < data[min_index]:  # 當數列中的某一個數比開始的數要小時候,更新最小值索引位置
                min_index=j
        data[i],data[min_index]=data[min_index],data[i]  # 一趟走完,交換最小值的位置,第一趟最小



if __name__=='__main__':
    import random
    data_list=list(range(30))
    random.shuffle(data_list)  # 打亂列表數據
    print("pre:",data_list)
    select_sort(data_list)
    print("after:",data_list)
#結果:
#pre: [20, 11, 22, 0, 18, 21, 14, 19, 7, 23, 27, 29, 24, 4, 17, 15, 5, 10, 26, 13, 25, 1, 8, 16, 3, 9, 2, 28, 12, 6]
#after: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]

 

插入排序

效率:O(n2)

原理:

  1. 以從小到大排序爲例,元素0爲第一個元素,插入排序是從元素1開始,儘量插到前面。
  2. 插入時分插入位置和試探位置,元素i的初始插入位置爲i,試探位置爲i-1,在插入元素i時,依次與i-1,i-2······元素比較,若是被試探位置的元素比插入元素大,那麼被試探元素後移一位,元素i插入位置前移1位,直到被試探元素小於插入元素或者插入元素位於第一位。
  3. 重複上述步驟,最後完成排序

demo:

def insert_sort(data):
    """
    插入排序
    :param data: 待排序的數據列表
    :return: 
    """
    for i in range(1, len(data)): # 無序區域數據
        tmp = data[i] # 第i次插入的基準數
        for j in range(i, -1, -1):
            if tmp < data[j - 1]:  # j爲當前位置,試探j-1位置
                data[j] = data[j - 1]  #  移動當前位置
            else:  # 位置肯定爲j
                break
        data[j] = tmp  # 將當前位置數還原

if __name__=='__main__':
    import random
    data_list=list(range(30))
    random.shuffle(data_list)  # 打亂列表數據
    print("pre:",data_list)
    insert_sort(data_list)
    print("after:",data_list)
#結果:
#pre: [7, 17, 10, 16, 23, 24, 13, 11, 2, 5, 15, 29, 27, 18, 4, 19, 1, 9, 3, 21, 0, 14, 12, 25, 22, 28, 20, 6, 26, 8]
#after: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]

快速排序

效率:平均O(nlogn)

原理:

  1. 從數列中隨機挑選出一個數做爲基數;
  2. 從新排列數列,使得比基數小的元素在左邊,比基數大元素在右邊,相等的元素放左邊或者右邊均可以,最後使得該基數在處於數列中間位置,這個稱爲分區操做;
  3. 遞歸上述操做,完成排序,以下如;

      

demo:

#!/usr/bin/env python3
#_*_ coding:utf-8 _*_
#Author:wd

def quick_sort(data,left,right):
    """
    快速排序
    :param data: 待排序的數據列表
    :param left: 基準數左邊元素的索引
    :param right: 基準數右邊元素的索引
    :return: 
    """
    if left < right:
        mid = partition(data,left,right)  # 分區操做,mid表明基數所在的索引
        quick_sort(data,left,mid-1)   # 對基準數前面進行排序
        quick_sort(data,mid+1,right)  # 對基準數後面進行排序


def partition(data,left,right):
    tmp=data[left]  # 隨機選擇的基準數,從最左邊開始選
    while left < right:
        while left < right and data[right] >= tmp:  # 右邊的數比基準數大
            right-=1  # 保留該數,而後索引指針往左移動
        data[left]=data[right]   # 不然此時右邊數比基數小,則將該數放到基準位置
        while left < right and data[left] <= tmp: # 右邊的數比基準數小
            left+=1  # 此時保持該數位置不動,索引指針往前移動
        data[right]=data[left]  # 不然此時左邊的數比基數大,則將該數放到右邊
    data[left] = tmp  # 最後將基準數量放回中間
    return left  # 返回基準數位置

if __name__=='__main__':
    data_list=[1,3,21,6,50,33,34,58,66]
    quick_sort(data_list,0,len(data_list)-1)
    print(data_list)
###結果:[1, 3, 6, 21, 33, 34, 50, 58, 66]

堆排序

堆定義:本質是一個徹底二叉樹,若是根節點的值是全部節點的最小值稱爲小根堆,若是根節點的值是全部節點的最大值,稱爲大根堆。

效率:O(nlogn)

原理:

  1. 將待排序數據列表創建成堆結構(創建堆);
  2. 經過上浮(shift_up)或下沉(shift_down)等操做獲得堆頂元素爲最大元素(已大根堆爲例);
  3. 去掉堆頂元素,將最後的一個元素放到堆頂,從新調整堆,再次使得堆頂元素爲最大元素(相比第一次爲第二大元素);
  4. 重複3操做,直到堆爲空,最後完成排序;

      

demo:

def sift(data, low, high):
    """
    調整堆函數
    :param data: 帶排序的數據列表
    :param low: 值較小的節點的位置,能夠理解爲是根節點
    :param high:值較大的節點的位置 
    :return: 
    """
    i = low
    j = 2 * i  # 父節點i所對應的左孩子
    tmp = data[i]  # 最較小節點的值
    while j <= high:
        if j < high and data[j] < data[j + 1]:  # 若是右孩子比左孩子大則把j指向右節點
            j += 1  # 指向右節點
        if tmp < data[j]:  # 若是此時位置較小的節點值比該節點值小,則將該節點上浮最爲新的父節點,並調整該節點雙親
            data[i] = data[j]
            i = j  # 調整該節點的雙親的位置
            j = 2 * i
        else:
            break  # 不然表明本次調整已經完成,而且節點i已經無值
    data[i] = tmp  # 最後將被調整節點的值放到i節點上(空出的位置)


def heap_sort(data):
    """
    堆排序
    :param data: 待排序的數據列表
    :return: 
    """
    n = len(data)
    for i in range(n // 2 - 1, -1, -1):
        sift(data, i, n - 1)
    # 構建堆
    for i in range(n - 1, -1, -1):  # 調整過程,從最後一個元素開始交換
        data[0], data[i] = data[i], data[0]  # 交換
        sift(data, 0, i - 1)  # 開始調整


if __name__ == '__main__':
    import random
    data_list = [1, 3, 21, 6, 50, 33, 34, 58, 66]
    random.shuffle(data_list)  # 打亂列表數據
    print("pre:", data_list)
    heap_sort(data_list)
    print("after:", data_list)
#結果:
#pre: [66, 3, 58, 34, 1, 33, 21, 6, 50]
#after: [1, 3, 6, 21, 33, 34, 50, 58, 66]

歸併排序

效率:O(nlogn)

空間複雜度:O(n)

原理:

  1. 申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列;
  2. 設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置;
  3. 比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置;
  4. 重複步驟3直到某一指針達到序列尾;
  5. 將另外一序列剩下的全部元素直接複製到合併序列尾。

      

demo:

def merge(data, low, mid, high):
    """
    合併函數
    :param data: 數據列表
    :param low: 列表開頭位置
    :param mid: 分割中間位置
    :param high: 列表最後位置
    :return: 
    """
    i = low  # 第一個指針
    j = mid + 1  # 第二個指針
    tmp = []  # 臨時存放的列表
    while i <= mid and j <= high:  # 分割的列表當兩邊都有數才進行
        if data[i] < data[j]:
            tmp.append(data[i])
            i += 1  # 低的指針往右移動
        else:
            tmp.append(data[j])  # 右邊大,存右邊的數
            j += 1  # 同時指針右移動

    while i <= mid:  # 左邊分割有剩下
        tmp.append(data[i])
        i += 1
    while j <= high:  # 右邊有剩下
        tmp.append(data[j])
        j += 1
    data[low:high + 1] = tmp  # 最後將tmp中的數寫入到原來的列表中


def merge_sort(data, low, high):
    """
    歸併排序
    :param data: 待排序的數據列表
    :param low: 數據列表開始位置
    :param high: 數據列表結束位置
    :return: 
    """
    if low < high:  # 至少有兩個元素才進行
        mid = (low + high) // 2  # 分割
        merge_sort(data, low, mid)  # 遞歸分割上一部分
        merge_sort(data, mid + 1, high)  # 遞歸分割下一部分
        merge(data, low, mid, high)  # 合併


if __name__ == '__main__':
    import random

    data_list = [1, 3, 21, 6, 50, 33, 34, 58, 66]
    random.shuffle(data_list)  # 打亂列表數據
    print("pre:", data_list)
    merge_sort(data_list, 0, len(data_list) - 1)
    print("after:", data_list)

#結果:
#pre: [21, 3, 33, 58, 34, 66, 1, 6, 50]
#after: [1, 3, 6, 21, 33, 34, 50, 58, 66]

希爾排序

效率:與增量有關,O(n1+)其中<0£<1,如增量爲2k-1 複雜度爲O(n3/2)

原理:

  1. 先取一個小於n的整數d1做爲第一個增量,把文件的所有記錄分組。全部距離爲d1的倍數的記錄放在同一個組中。
  2. 先在各組內進行直接插入排序;
  3. 取第二個增量d2<d1重複上述的分組和排序,直至所取的增量  =1(  <  …<d2<d1),即全部記錄放在同一組中進行直接插入排序爲止。
def shell_sort(data):
    """
    希爾排序
    :param data:待排序的數據列表 
    :return: 
    """
    d1 = len(data) // 2  # 設置分割大小爲d1,
    while d1 > 0:
        for i in range(d1, len(data)):
            tmp = data[i]  # 當前分割元素位置
            j = i - d1  # 上一個分割元素位置
            while j >= 0 and tmp < data[j]:  # 上一個元素分割位置比當前分割位置要大,則須要調整位置
                data[j + d1] = data[j]  # 後移動當前分割元素位置
                j -= d1  # 往前移d1
            data[j + d1] = tmp
        d1 //= 2  # 繼續分割


if __name__ == '__main__':
    import random
    data_list = [1, 3, 21, 6, 50, 33, 34, 58, 66]
    random.shuffle(data_list)  # 打亂列表數據
    print("pre:", data_list)
    shell_sort(data_list)
    print("after:", data_list)
#結果:
#pre: [3, 66, 58, 34, 33, 50, 6, 21, 1]
#after: [1, 3, 6, 21, 33, 34, 50, 58, 66]
相關文章
相關標籤/搜索