Python之路【第二十四篇】Python算法排序一

什麼是算法

一、什麼是算法
python

算法(algorithm):就是定義良好的計算過程,他取一個或一組的值爲輸入,併產生出一個或一組值做爲輸出。簡單來講算法就是一系列的計算步驟,用來將輸入數據轉化成輸出結果。算法

mark:咱們能夠把全部的算法想象爲一本「菜譜」,特定的算法好比菜譜中的的一道「老醋花生米」的製做流程,只要按照菜譜的要求製做老醋花生米,那麼誰均可以作出一道好吃的老醋花生米。so,這個作菜的步驟就能夠理解爲:「解決問題的步驟」
數組

二、算法的意義app

假設計算機無限快,而且計算機存儲容器是免費的,咱們還須要各類亂七八糟的算法嗎?若是計算機無限快,那麼對於某一個問題來講,任何一個均可以解決他的正確方法均可以的!dom

固然,計算機能夠作到很快,可是不能作到無限快,存儲也能夠很便宜可是不能作到免費。函數

那麼問題就來了效率:解決同一個問題的各類不一樣算法的效率經常相差很是大,這種效率上的差距的影響每每比硬件和軟件方面的差距還要大。性能

三、如何選擇算法測試

第一首先要保證算法的正確性優化

一個算法對其每個輸入的實例,都能輸出正確的結果並中止,則稱它是正確的,咱們說一個正確的算法解決了給定的計算問題。不正確的算法對於某些輸入來講,可能根本不會中止,或者中止時給出的不是預期的結果。然而,與人們對不正確算法的見解想反,若是這些算法的錯誤率能夠獲得控制的話,它們有時候也是有用的。可是通常而言,咱們仍是僅關注正確的算法!ui

第二分析算法的時間複雜度

算法的時間複雜度反映了程序執行時間隨輸入規模增加而增加的量級,在很大程度上能很好反映出算法的好壞。

時間複雜度

一、什麼是時間複雜度

一個算法花費的時間與算法中語句的執行次數成正比例,哪一個算法中語句執行次數多,它花費時間就多。一個算法中的語句執行次數稱爲語句頻度或時間頻度。記爲T(n)
通常狀況下,算法中基本操做重複執行的次數是問題規模n的某個函數,用T(n)表示,如有某個輔助函數f(n),使得當n趨近於無窮大時,T(n)/f(n)的極限值爲不等於零的常數,則稱f(n)是T(n)的同數量級函數。記做T(n)=O(f(n)),稱O(f(n)) 爲算法的漸進時間複雜度,簡稱時間複雜度。

二、時間複雜度的計算方法

一個算法執行所耗費的時間,從理論上是不能算出來的,必須上機運行測試才能知道。但咱們不可能也沒有必要對每一個算法都上機測試由於該方法有兩個缺陷:

  • 想要對設計的算法的運行性能進行測評,必須先依據算法編寫相應的程序並實際運行。
  • 所得時間的統計計算依賴於計算機的硬件、軟件等環境因素,有時候容易掩蓋算法的自己優點。

因此只需知道哪一個算法花費的時間多,哪一個算法花費的時間少就能夠了。而且一個算法花費的時間與算法中語句的執行次數成正比例,哪一個算法中語句執行次數多,它花費時間就多。

 

通常狀況下,算法的基本操做重複執行的次數是模塊n的某一個函數f(n),所以,算法的時間複雜度記作:T(n)=O(f(n))。隨着模塊n的增大,算法執行的時間的增加率和f(n)的增加率成正比,因此f(n)越小,算法的時間複雜度越低,算法的效率越高。 

 在計算時間複雜度的時候,先找出算法的基本操做,而後根據相應的各語句肯定它的執行次數,再找出T(n)的同數量級(它的同數量級有如下:1,Log2n ,n ,nLog2n ,n的平方,n的三次方,2的n次方,n!),找出後,f(n)=該數量級,若T(n)/f(n)求極限可獲得一常數c,則時間複雜度T(n)=O(f(n))。

三、常見的時間複雜度

常見的算法時間複雜度由小到大依次爲:

Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)<…<Ο(2n)<Ο(n!)

 求解算法的時間複雜度的具體步驟:

  • 找出算法中的基本語句,算法中執行最多的那條語句是基本語句,一般是最內層循環的循環體。
  • 計算基本語句的執行次數的量級,保證最高次冪正確便可查看他的增加率。
  • 用大O幾號表示算法的時間性能

 若是算法中包含鑲套的循環,則基本語句一般是最內層的循環體,若是算法中包並列的循環,則將並列的循環時間複雜度相加,例如:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'

n = 100

for i in range(n):
    print(i)


for i in range(n): ##每循i裏的一個元素,for循環內部嵌套的for循環就整個循環一次
    for q in range(n):
        print(q)

第一個for循環的時間複雜度爲Ο(n),第二個for循環的時間複雜度爲Ο(n2),則整個算法的時間複雜度爲Ο(n+n2)=Ο(n2)。

Ο(1)表示基本語句的執行次數是一個常數,通常來講,只要算法中不存在循環語句,其時間複雜度就是Ο(1)。

其中Ο(log2n)、Ο(n)、 Ο(nlog2n)、Ο(n2)和Ο(n3)稱爲多項式時間,而Ο(2n)和Ο(n!)稱爲指數時間,計算機科學家廣泛認爲前者(即多項式時間複雜度的算法)是有效算法,把這類問題稱爲P(Polynomial,多項式)類問題,而把後者(即指數時間複雜度的算法)稱爲NP(Non-Deterministic Polynomial, 非肯定多項式)問題在選擇算法的時候,優先選擇前者!

 

OK我懂對於沒有算法基礎的同窗,看起算法來也很頭疼,可是這個是基礎和重點,不會算法的開發不是一個合格的開發而且包括語言記得基礎也是須要好好整理的!加油吧~~  我們在一塊兒看下時間複雜度的詳細說明吧

常見的時間複雜度示例

一、O(1)

#O(1)

n = 100 
sum = (1+n) * n/2 #執行一次
sum_1 = (n/2) - 10 #執行一次
sum_2 = n*4 - 10 + 8 /2 #執行一次

這個算法的運行次數函數是f(n)=3。根據咱們推導大O階的方法,第一步就是把常數項3改成1。在保留最高階項時發現,它根本沒有最高階項,因此這個算法的時間複雜度爲O(1)。

而且:若是算法的執行時間不隨着問題規模n的增加而增長,及時算法中有上千條語句,其執行的時間也不過是一個較大的常數。此類算法的時間複雜度記做O(1)

二、O(n2)

n = 100 

for i in range(n): #執行了n次
    for q in range(n): #執行了n2
        print(q) #執行了n2

解:T(n)=2n2+n+1 =O(n2)

通常狀況下,對進循環語句只需考慮循環體中語句的執行次數,忽略該語句中步長加一、終值判別、控制轉移等成分當有若干個循環語句時,算法的時間複雜度是由嵌套層數最多的循環語句中最內層語句的頻度f(n)決定的。  

三、O(n)   

#O(n)

n =100 
a = 0 #執行一次
b = 1#執行一次
for i in range(n): #執行n次
    s = a +b #執行n-1次
    b =a #執行n-1次
    a =s #執行n-1次

解:T(n)=2+n+3(n-1)=4n-1=O(n)

四、Ο(n3)

#O(n3)
n = 100
for i in range(n):#執行了n次
    for q in range(n):#執行了n^2
        for e in range(n):#執行了n^3
            print(e)#執行了n^3

簡單點來去最大值是:Ο(n3)

五、經常使用的算法的時間複雜度和空間複雜度

排序法 平均時間 最差狀況 穩定度 額外空間 備註
冒泡排序 Ο(n2) Ο(n2) 穩定 O(1) n小時較好
交換排序 Ο(n2) Ο(n2) 不穩定 O(1) n小時較好
選擇排序 Ο(n2) Ο(n2) 不穩定 O(1) n小時較好
插入排序 Ο(n2) Ο(n2) 穩定 O(1) 大部分已排序時較好
快速排序 Ο(nlogn) Ο(n2) 不穩定 Ο(nlogn) n較大時較好
希爾排序(SHELL) Ο(log2n) Ο(ns)  1<s<2
不穩定 O(1) s是所選分組
歸併排序 Ο(log2n) Ο(log2n) 穩定 O(1) n大時較好
堆排序 Ο(log2n) Ο(log2n) 不穩定 O(1) n大時較好
基數排序 Ο(logRB) Ο(logRB) 穩定 O(N)

B是真數(0-9)

R是基數(個十百)

 

 

 

 

 

 

 

 

 

 

 

排序實例

排序算法是在更復雜的算法中的是一個構建基礎,因此先看下經常使用的排序。

一、冒泡排序

需求:

請按照從小到大對列表,進行排序==》:[69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619] 

思路:相鄰兩個值進行比較,將較大的值放在右側,依次比較!

原理圖:

原理分析:

列表中有5個元素兩兩進行比較,若是左邊的值比右邊的值大,就用中間值進行循環替換!
既然這樣,咱們還能夠用一個循環把上面的循環進行在次循環,用表達式構造出內部循環!

代碼實現:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'
import random

maopao_list = [13, 22, 6, 99, 11]
'''
原理分析:
列表中有5個元素兩兩進行比較,若是左邊的值比右邊的值大,就用中間值進行循環替換!
既然這樣,咱們還能夠用一個循環把上面的循環進行在次循環,用表達式構造出內部循環!
'''

def handler(array):
    for i in range(len(array)):
        for j in range(len(array)-1-i):
            '''
            這裏爲何要減1,咱們看下若是裏面有5個元素咱們須要循環幾回?最後一個值和誰對比呢?對吧!因此須要減1
            這裏爲何減i?,這個i是循環的下標,若是咱們循環了一次以後最後一隻值已是最大的了還有必要再進行一次對比嗎?沒有必要~
            '''
            print('left:%d' % array[j],'right:%d' % array[j+1])
            if array[j] > array[j+1]:
                tmp = array[j]
                array[j] = array[j+1]
                array[j+1] = tmp



if __name__ == '__main__':
    handler(maopao_list)
    print(maopao_list)

時間複雜度說明看下他的代碼複雜度會隨着N的增大而成指數型增加,而且根據判斷他時間複雜度爲Ο(n2)

二、選擇排序

需求:

請按照從小到大對列表,進行排序==》:[69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619] 

思路:

第一次,從列表最左邊開始元素爲array[0],往右循環,從右邊元素中找到小於array[0]的元素進行交換,直到右邊循環完以後。

第二次,左邊第一個元素如今是最小的了,就從array[1],和剩下的array[1:-1]內進行對比,依次進行對比!

對比:

他和冒泡排序的區別就是,冒泡排序是相鄰的兩兩作對比,可是選擇排序是左側的「對比元素」和右側的列表內值作對比!

原理圖:

代碼實現:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'


xuanze_list = [13, 22, 6, 99, 11]

print(range(len(xuanze_list)))

def handler(array):
    for i in range(len(array)):
        '''
        循環整個列表
        '''
        for j in range(i,len(array)):
            '''
            這裏的小循環裏,循環也是整個列表可是他的起始值是i,當這一個小循環完了以後最前面的確定是已經排序好的
            第二次的時候這個值是循環的第幾回的值好比第二次是1,那麼循環的起始值就是array[1]
            '''
            if array[i] > array[j]:
                temp = array[i]
                array[i] = array[j]
                array[j] = temp
        # print(array)


if __name__ == '__main__':
    handler(xuanze_list)
    print(xuanze_list)

選擇排序代碼優化:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'

import random
import time


def handler(array):
    for i in range(len(array)):
        smallest_index = i  #假設默認第一個值最小
        for j in range(i,len(array)):
            if array[smallest_index] > array[j]:
                smallest_index = j  #若是找到更小的,記錄更小元素的下標
        '''
        小的循環結束後在交換,這樣整個小循環就以前的選擇排序來講,少了不少的替換過程,就只替換了一次!提高了速度
        '''
        tmp = array[i]
        array[i] = array[smallest_index]
        array[smallest_index] = tmp


if __name__ == '__main__':
    array = []
    old_time = time.time()
    for i in range(50000):
        array.append(random.randrange(1000000))
    handler(array)
    print(array)
    print('Cost time is :',time.time() - old_time)

三、插入排序

需求

請按照從小到大對列表,進行排序==》:[69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619] 

思路:

一個列表默認分爲左側爲排序好的,咱們拿第一個元素舉例,他左邊的全是排序好的,他右側是沒有排序好的,若是右側的元素小於左側排序好的列表的元素就把他插入到合適的位置

原理圖:

 

代碼實現:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
__author__ = 'luotianshuai'


import random
import time
chaoru_list = [69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619]

def handler(array):
    for i in range(1,len(array)):
        position = i #剛開始往左邊走的第一個位置
        current_val = array[i] #先把當前值存下來
        while position > 0 and current_val < array[position -1]:
            '''
            這裏爲何用while循環,我們在判斷左邊的值得時候知道他有多少個值嗎?不知道,因此用while循環
            何時停下來呢?當左邊沒有值得時候,或者當他大於左邊的值得時候!
            '''
            array[position] = array[position - 1] #若是whille條件成立把當前的值替換爲他上一個值
            '''
            好比一個列表:
            [3,2,4,1]
            如今循環到 1了,他前面的元素已經循環完了
            [2,3,4] 1

            首先咱們記錄下當前這個position的值 = 1
            [2,3,4,4] 這樣,就出一個位置了
            在對比前面的3,1比3小
            [2,3,3,4] 在替換一下他們的值
             在對比2
            [2,2,3,4]
            最後while不執行了在進行替換'array[position] = current_val  #把值替換'
            '''
            position -= 1
        #當上面的條件都不成立的時候{左邊沒有值/左邊的值不比本身的值小}
        array[position] = current_val  #把值替換


if __name__ == '__main__':
    handler(chaoru_list)
    print(chaoru_list)

'''
    array = []#[69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619]
    old_time = time.time()
    for i in range(50000):
        array.append(random.randrange(1000000))
    handler(array)
    print(array)
    print('Cost time is :',time.time() - old_time)
'''

四、快速排序

設要排序的數組是A[0]……A[N-1],首先任意選取一個數據(一般選用數組的第一個數)做爲關鍵數據,而後將全部比它小的數都放到它前面,全部比它大的數都放到它後面,這個過程稱爲一趟快速排序。值得注意的是,快速排序不是一種穩定的排序算法,也就是說,多個相同的值的相對位置也許會在算法結束時產生變更.他的時間複雜度是:O(nlogn) ~Ο(n2)

排序示例:

假設用戶輸入了以下數組:

建立變量i=0(指向第一個數據)[i所在位置紅色小旗子], j=5(指向最後一個數據)[j所在位置藍色小旗子], k=6(賦值爲第一個數據的值)。

咱們要把全部比k小的數移動到k的左面,因此咱們能夠開始尋找比6小的數,從j開始,從右往左找,不斷遞減變量j的值,咱們找到第一個下標3的數據比6小,因而把數據3移到下標0的位置,把下標0的數據6移到下標3,完成第一次比較:

i=0 j=3 k=6

接着,開始第二次比較,此次要變成找比k大的了,並且要從前日後找了。遞加變量i,發現下標2的數據是第一個比k大的,因而用下標2的數據7和j指向的下標3的數據的6作交換,數據狀態變成下表:

 i=2 j=3 k=6

稱上面兩次比較爲一個循環。
接着,再遞減變量j,不斷重複進行上面的循環比較。
在本例中,咱們進行一次循環,就發現i和j「碰頭」了:他們都指向了下標2。因而,第一遍比較結束。獲得結果以下,凡是k(=6)左邊的數都比它小,凡是k右邊的數都比它大:

若是i和j沒有碰頭的話,就遞加i找大的,尚未,就再遞減j找小的,如此反覆,不斷循環。注意判斷和尋找是同時進行的。

而後,對k兩邊的數據,再分組分別進行上述的過程,直到不能再分組爲止。
注意:第一遍快速排序不會直接獲得最終結果,只會把比k大和比k小的數分到k的兩邊。爲了獲得最後結果,須要再次對下標2兩邊的數組分別執行此步驟,而後再分解數組,直到數組不能再分解爲止(只有一個數據),才能獲得正確結果。

代碼實現:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:luotianshuai
import random
import time

def quick_sort(array,start,end):
    if start >= end:
        return
    k = array[start]
    left_flag = start
    right_flag = end
    while left_flag < right_flag:
        '''
        left_flag = start 默認爲0
        right_flag = end 默認爲傳來的列表總長度
        當left_flag 小與right_flag的時候成立,說明左右兩邊的小旗子尚未碰頭(爲相同的值)
        '''
        #右邊旗子
        while left_flag < right_flag and array[right_flag] > k:#表明要繼續往左一移動小旗子
            right_flag -= 1
        '''
        若是上面的循環中止說明找到右邊比左邊的值小的數了,須要進行替換
        '''
        tmp = array[left_flag]
        array[left_flag] = array[right_flag]
        array[right_flag] = tmp

        #左邊旗子
        while left_flag < right_flag and array[left_flag] <= k:
            #若是沒有找到比當前的值大的,left_flag 就+=1
            left_flag += 1
        '''
        若是上面的循環中止說明找到當前段左邊比右邊大的值,進行替換
        '''
        tmp = array[left_flag]
        array[left_flag] = array[right_flag]
        array[right_flag] = tmp

    #進行遞歸把問題分半
    quick_sort(array,start,left_flag-1)
    quick_sort(array,left_flag+1,end)

if __name__ == '__main__':
    array = []  # [69, 471, 106, 66, 149, 983, 160, 57, 792, 489, 764, 589, 909, 535, 972, 188, 866, 56, 243, 619]
    start_time = time.time()
    for i in range(50000):
        array.append(random.randrange(1000000))
    quick_sort(array,0,len(array)-1)
    end_time = time.time()
    print(array)
    print(start_time,end_time)
    cost_time = end_time - start_time
    print('Cost time is :%d' % cost_time)
相關文章
相關標籤/搜索