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


什么是算法

1、什么是算法

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

mark:我們可以把所有的算法想象為一本“菜譜”,特定的算法比如菜譜中的的一道“老醋花生米”的制作流程,只要按照菜譜的要求制作老醋花生米,那么誰都可以做出一道好吃的老醋花生米。so,這個做菜的步驟就可以理解為:“解決問題的步驟”

2、算法的意義

假設計算機無限快,並且計算機存儲容器是免費的,我們還需要各種亂七八糟的算法嗎?如果計算機無限快,那么對於某一個問題來說,任何一個都可以解決他的正確方法都可以的!

當然,計算機可以做到很快,但是不能做到無限快,存儲也可以很便宜但是不能做到免費。

那么問題就來了效率:解決同一個問題的各種不同算法的效率常常相差非常大,這種效率上的差距的影響往往比硬件和軟件方面的差距還要大。

3、如何選擇算法

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

一個算法對其每一個輸入的實例,都能輸出正確的結果並停止,則稱它是正確的,我們說一個正確的算法解決了給定的計算問題。不正確的算法對於某些輸入來說,可能根本不會停止,或者停止時給出的不是預期的結果。然而,與人們對不正確算法的看法想反,如果這些算法的錯誤率可以得到控制的話,它們有時候也是有用的。但是一般而言,我們還是僅關注正確的算法!

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

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

時間復雜度

1、什么是時間復雜度

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

2、時間復雜度的計算方法

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

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

所以只需知道哪個算法花費的時間多,哪個算法花費的時間少就可以了。並且一個算法花費的時間與算法中語句的執行次數成正比例,哪個算法中語句執行次數多,它花費時間就多。

 

一般情況下,算法的基本操作重復執行的次數是模塊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))。

3、常見的時間復雜度

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

Ο(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我懂對於沒有算法基礎的同學,看起算法來也很頭疼,但是這個是基礎和重點,不會算法的開發不是一個合格的開發並且包括語言記得基礎也是需要好好整理的!加油吧~~  咱們在一起看下時間復雜度的詳細說明吧

常見的時間復雜度示例

1、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)

2、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)

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

3、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)

4、Ο(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)

5、常用的算法的時間復雜度和空間復雜度

排序法 平均時間 最差情況 穩定度 額外空間 備注
冒泡排序 Ο(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是基數(個十百)

 

 

 

 

 

 

 

 

 

 

 

排序實例

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

1、冒泡排序

需求:

請按照從小到大對列表,進行排序==》:[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)

2、選擇排序

需求:

請按照從小到大對列表,進行排序==》:[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)

3、插入排序

需求

請按照從小到大對列表,進行排序==》:[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)
'''

4、快速排序

設要排序的數組是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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM