python之算法

python之算法

這個合格程序員仍是須要熟練掌握一些算法的,今天主要介紹一些排序算法

遞歸是算法中一個比較核心的概念,有三個特色,

  • 1 調用自身  

  • 2 具備結束條件  

  • 3 代碼規模逐漸減小

如下四個函數只有兩個爲遞歸python

這裏須要注意一些 fun3和fun4輸出的結果是不同的程序員

fun3:5,4,3,2,1算法

fun4:1,2,3,4,5數據結構

也只有3和4是遞歸app

這裏介紹一個漢諾塔的問題:

def  hanoi(n , A , B , C):
    # n 個盤子從A通過B 到C
    if n >0:
        hanoi(n-1,A , C ,B)
        print("%s --->%S"%(A , C))
        hanoi(n-1 , B , A ,C )

hanoi(4 , A , B ,C)

 

兩個概念:時間複雜度和空間複雜度

時間複雜度:用於體現算法執行時間的快慢,用O表示。通常經常使用的有:幾回循環就爲O(n幾回方)  循環減半的O(logn)dom

空間複雜度:用來評估算法內存佔用大小的一個式子,一般狀況下會選擇使用空間換時間函數

e.g 列表查找:從列表中查找指定元素

    輸入:列表、待查找元素優化

    輸出:元素下標或未查找到元素 ui

    version 1 順序查找:從列表中的第一個元素開始,順序進行搜索,直到找到爲止,複雜度爲O(n)spa

       version 2 二分查找:從有序列表中,經過待查值與中間值比較,以減半的方式進行查找,複雜度爲O(logn)

    代碼以下:     

list = [1,2,3,4,5,6,7,8,9]
element = 7
def ord_sear(list,element):
    for i in range(0,len(list)):
        if list[i] == element:
            print('list[{0}]={1}'.format(i,element))
            return i
    else:
        print('not found')

def bin_sear(list,element):
    low = 0
    high = len(list)-1
    while low<=high:
        mid = (low+high)//2
        if element == list[mid]:
            print('list[{0}]={1}'.format(mid,element))
            return mid
        elif element > list[mid]:
            low =mid +1
        else:
            high =mid -1
    return None


i = bin_sear(list,element)
j = ord_sear(list,element)

  二分查找雖然在時間複雜度上優於順序查找,可是有比較苛刻的條件,即列表必須爲有序的。下面將介紹列表排序:

首先介紹的是最簡單的三種排序方式:(low B三人組)

  • 1 冒泡排序

  • 2 選擇排序

  • 3 插入排序

寫一個計算時間的裝飾器

import time

def cal_time(func):
    def wrapper(*args , **kwargs):
        t1 = time.time()
        result = func(*args , **kwargs)
        t2 = time.time()
        print("%s running time : %s secs " %(func.__name__ , t2-t1))
        return result
    return wrapper

 

冒泡排序:時間複雜度:   O(n²)

(思路):首先,列表每倆個相鄰的數,若是前面的比後面的大,那麼交換這倆個數.......

from cal_time import cal_time

@cal_time
def bubble_sort(li):
    for i in range(0 , len(li)-1):
        # i 表示第i趟 有序區有i個數
        for j in range(0 , len(li)-i-1):
            if li[j] > li[j+1]:
                li[j],li[j+1] = li[j+1] , li[j]

import random
li = list(range(1000))
random.shuffle(li)
bubble_sort(li)
print(li)

 

小優化一下

若是冒泡排序中執行一趟而沒有交換,則列表已是有序狀態,能夠直接結束

def bubble_sort2(li):
    for i in range(0 , len(li)-1):
        # i 表示第i趟 有序區有i個數
        exchange = False
        for j in range(0 , len(li)-i-1):
            if li[j] > li[j+1]:
                li[j],li[j+1] = li[j+1] , li[j]
                exchange = True
        if not exchange:
            return

 

選擇排序:時間複雜度:   O(n²)

一趟遍歷記錄最小的數,放到第一個位置

再一趟遍歷剩下列表中最小的數,繼續放置:

from cal_time import cal_time

@cal_time
def select_sort(li):
    for i in range(len(li)-1):
        # 第i趟 , 有序區li[0:i] 無序區li[i : n]
        min_loc = i
        for j in range(i+1, len(li)):
            if li[min_loc] > li[j]:
                min_loc = j
        li[min_loc], li[i] = li[i], li[min_loc]


import random
li = list(range(10000))
random.shuffle(li)
select_sort(li)
print(li)

 

 

插入排序 時間複雜度:   O(n²)

列表被分爲有序區和無序區倆個部分,最初的有序區只有一個元素.

每次無序區選擇一個元素,插入到有序區的位置,直到無序區變空

from cal_time import cal_time

@cal_time
def insert_sort(li):
    for i in range(1 , len(li)):
        # i 既表達趟數, 又表達摸到排的下標
        j  = i - 1
        tmp = li[i]
        while j >=0 and li[j] > tmp:
            li[j+1] = li[j]
            j = j -1
        li[j + 1] = tmp

import random
li = list(range(10000))
random.shuffle(li)
insert_sort(li)
print(li)

 

牛逼三人組

  • 快速排序
  • 堆排序
  • 歸併排序

快速排序 

快速排序思路

  • 取一個元素p(第一個元素),使得元素p歸位
  • 列表被p分紅倆部分,左邊都比p小, 右邊都比p大
  • 遞歸完成排序
from cal_time import cal_time

def _quick_sort(li , left , right):
    if left < right:
        mid = partition(li , left , right)
        _quick_sort(li, left, mid-1)
        _quick_sort(li, mid+1 , right)

@cal_time
def quick_sort(li):
    _quick_sort(li , 0 , len(li)-1)


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


import random
li = list(range(10000))
random.shuffle(li)
quick_sort(li)
print(li)

 

堆排序

樹:

樹是一種數據結構(好比目錄),樹是一種能夠遞歸的數據結構,相關的概念有根節點、葉子節點,樹的深度(高度),樹的度(最多的節點),孩子節點/父節點,子樹等。

在樹中最特殊的就是二叉樹(度不超過2的樹),二叉樹又分爲滿二叉樹和徹底二叉樹,見下圖:

 

 

 知道了樹就能夠說說堆了,堆分爲大根堆和小根堆,分別的定義爲:一棵徹底二叉樹,知足任一節點都比其孩子節點大或者小。

堆排序過程:

  • 創建堆
  • 獲得堆頂元素,爲最大元素
  • 去掉堆頂,將堆最後一個元素放到堆頂,此時能夠經過一次調整從新使堆變的有序
  • 堆頂元素爲第二大元素
  • 重複步驟3,直到堆變空
from cal_time import cal_time


def sift(li, low, high):
    tmp = li[low]# 原省長
    i = low
    j = 2 * i + 1
    while j <= high: #第二種退出條件: j > high
        if j < high and li[j+1] > li[j]: # 若是左孩子存在大於右孩子
            j += 1
        if tmp < li[j]:
            li[i] = li[j]
            i = j
            j = 2 * i + 1
        else: # 第一種退出條件:li[j] <=tmp
            li[i] = tmp
            break
    li[i] = tmp

@cal_time
def heap_sort(li):
    n = len(li)
    #1 建堆
    for i in range(n // 2 - 1, -1, -1):
        sift(li , 0 , i-1)
    # 2 挨個出數
    for i in range(n-1, -1, -1): #i  表示此時堆的high的位置
        li[0], li[i] = li[i], li[0] # 退休 + 旗子
        sift(li , 0 , i-1)

import random
li = list(range(10000))
random.shuffle(li)
heap_sort(li)
print(li)

 

內置模塊

 

 歸併排序:

假設列表中能夠被分紅兩個有序的子列表,如何將這兩個子列表合成爲一個有序的列表成爲歸併

原理

 

from cal_time import cal_time


# 一次歸併
def merge(li , low , mid , high):
    i = low
    j = mid + 1
    ltmp = []
    while i <= mid and j <= high:
        if li[i] <= li[j]:
            ltmp.append(li[i])
            i += 1
        else:
            ltmp.append(li[j])
            j += 1
    while i <= mid:
        ltmp.append(li[i])
        i += 1
    while j <= high:
        j += 1
    li[low:high+1] = ltmp

@cal_time
def merge_sort(li, low , high):
    if low > high:
        mid = (low + high) // 2
        merge_sort(li , low , mid)
        merge_sort(li,mid+1 , high)
        merge(li ,low , mid, high)

 

快排,堆排,歸併的總結:

  • 時間複雜度都是O(nlogn)
  • 快排<歸併<堆排(通常狀況)
  • 快排的缺點:極端狀況效率較低,可到O(n^2),歸併則是須要額外的開銷,堆排則在排序算法中相對較慢

相關文章
相關標籤/搜索