算法和數據結構

算法:一個計算過程,解決問題的辦法python

遞歸

遞歸的兩個必須條件算法

  1. 遞推(調用自身)
  2. 回溯(結束條件)

來看幾個例子:數據結構

eg1:該函數不是遞歸,沒有結束條件app

def func1(n):
    print(n)
    func1(n - 1)

eg2:該函數亦不是遞歸,雖有條件,但條件是無窮的dom

def func2(n):
    if n > 0:
        print(n)
        func2(n + 1)

eg3:該函數亦是遞歸,知足遞歸的兩個條件ide

def func3(n):
    if n > 0:
        print(n)
        func3(n - 1)

eg4:該函數亦是遞歸,知足遞歸的兩個條件函數

def func4(n):
    if n > 0:
        func4(n - 1)
        print(n)

那麼,eg3 和 eg4的輸出結果是同樣的嗎?若是不同,爲何?優化

解釋:ui

由於,func3執行的時候,print是在調用自身以前,因此當n是5傳入函數時,會先打印5,再調用自身,這時候n是4,,依次循環遞歸。。。。到最後n=0,因此回溯時沒有任何輸出,因此func3 輸出的結果是:5,4,3,2,1;而func4執行時,print是在調用自身以後,當n是5傳入函數執行時,此時的n已經被減了1變成了4,再依次循環遞歸。。。。,再回溯的時候func4纔有輸出,的結果是:1,2,3,4,5spa

遞歸的簡單使用,給出一個列表:[1, [2, [3, [4, [5, [6, [7, ]]]]]]],需求:拿到列表中的元素(純數字)

# coding=utf-8
li = [1, [2, [3, [4, [5, [6, [7, ]]]]]]]


def tell(li):
    for item in li:
        if type(item) is list:
            tell(item)
        else:
            print(item)


tell(li)

列表查找

順序查找:最經常使用的就是for循環,挨個對比查找,效率極低!

二分查找:把一個列表一分爲二(切片),判斷要找的數大於仍是小於中間值,大於則在右側查找,小於在左側查找。每次都是將列表一切爲二進行查找!

原始的二分法(切片),時間複雜度爲O(n)

def find(find_num, ll):
    print(ll)
    if len(ll) == 0:
        print('not find')
        return
    mid_index = len(ll) // 2
    if find_num > ll[mid_index]:
        ll = ll[mid_index + 1:]
        find(find_num, ll)
    elif find_num < ll[mid_index]:
        ll = ll[:mid_index]
        find(find_num, ll)
    else:
        print('find', ll[mid_index])


l = [1, 3, 5, 8, 12, 34, 45, 56, 67, 78, 89, 123, 234, 345, 456, 566, 789]
find(566, l)

改進後的二分法(不用切片),時間複雜度爲O(logn)

def num_search(num_list, num):
    start = 0
    end = len(num_list) - 1
    while start <= end:
        mid = (end + start) // 2
        if num_list[mid] == num:
            return mid
        elif num_list[mid] < num:
            start = mid + 1
        else:
            end = mid - 1
    return


num_l = [i for i in range(1000)]
print(num_search(num_l, 666))

排序

冒泡排序

思路:比較列表相鄰的兩個數,若是前邊的大於後邊的,就交換這兩個數。。。,(升序)

代碼實現,時間複雜度:O(n*n)

import random


def sort_list(list1):
    for i in range(len(list1) - 1):
        for j in range(len(list1) - i - 1):
            if list1[j] > list1[j + 1]:
                list1[j], list1[j + 1] = list1[j + 1], list1[j]
    print('遍歷次數: {}'.format(i))
    return list1


data = list(range(1000))
random.shuffle(data)
print(sort_list(data))

上面的代碼雖然實現了冒泡排序,可是有個效率優化的問題,假設一個極端的例子,一個列表:[1,2,3,4,5,6,7,8,9],按照上述的冒泡排序方法,程序會循環8次結束,可是在這個過程當中,列表中的數字位置並未發生改變,那怎麼解決這個問題呢?加一個變量來判斷一次循環後數字的位置是否有改變,有就繼續循環,沒有就結束循環

import random


def sort_list(list1):
    for i in range(len(list1) - 1):
        change = 0
        for j in range(len(list1) - i - 1):
            if list1[j] > list1[j + 1]:
                list1[j], list1[j + 1] = list1[j + 1], list1[j]
                change = 1
        if change == 0:
            break
    print('遍歷次數: {}'.format(i))
    return list1


data = list(range(1000))
random.shuffle(data)
print(sort_list(data))
改進後

選擇排序

思路:循環列表,找到最小的值放到列表第一位,在遍歷一次剩餘數中的最小值,繼續日後放。。。。

代碼實現,時間複雜度:O(n*n)

def select_sort(li):
    for i in range(len(li) - 1):
        min_num = i
        for j in range(i + 1, len(li)):
            if li[j] < li[min_num]:
                min_num = j
        li[i], li[min_num] = li[min_num], li[i]


data = list(range(1000))
random.shuffle(data)
select_sort(data)
print(data)

插入排序

思路:列表分爲無序區和有序區,最初的有序區只有一個值,每次從無序區選擇一個值,插入到有序區的位置,直到無序區爲空!

代碼實現,時間複雜度:O(n*n)

def insert_sort(li):
    for i in range(1, len(li)):
        tmp = li[i]
        j = i - 1
        while j >= 0 and li[j] > tmp:
            li[j + 1] = li[j]
            j = j - 1
        li[j + 1] = tmp


data = list(range(1000))
random.shuffle(data)
insert_sort(data)
print(data)

快排

思路:一個列表先取一個元素x(第一個元素),而後使元素x歸位,此時列表被元素x分爲左右兩半,左邊都會比元素x小,右邊的都會比元素x大,最後用遞歸完成排序!

代碼實現

def quick_sort(data, left, right):
    if left < right:
        mid = partition(data, left, right)
        quick_sort(data, left, mid - 1)
        quick_sort(data, mid + 1, right)


def partition(data, left, right):
    # 用變量保存第一個元素
    tmp = data[left]
    # 左右碰不到時循環
    while left < right:
        # 找到左邊比右邊小的數,大於tmp的數放在右邊不動
        while left < right and data[right] >= tmp:
            right -= 1
        # 將right放在左邊的left空位上
        data[left] = data[right]
        # 找到左邊比右邊小的數,小於tmp的數放在左邊不動
        while left < right and data[left] <= tmp:
            left += 1
        # 將left放在左邊的right空位上
        data[right] = data[left]
    # 左右碰到時
    data[left] = tmp
    return left


data = list(range(10000))
random.shuffle(data)
quick_sort(data, 0, len(data) - 1)
print(data)

堆排序

前言

一、樹與二叉樹

數是一種能夠遞歸定義的數據結構,是由N個節點組成的集合

  • 若是N=0,就是一顆空樹
  • 若是N>0,那就存在1個節點做爲數的根節點,其餘節點能夠分爲M個集合,每一個集合本身又是一棵樹

二、兩種特殊的二叉樹

  • 滿二叉樹:最後一層的節點都是滿的(a)
  • 徹底二叉樹:滿二叉樹只去掉最後一層的後面幾個節點(b)

三、二叉樹的存儲方式

  • 鏈式存儲
  • 順序存儲(列表)

 順序存儲就是從上往下依次按順序存入列表,以下圖:

那麼,這樣存儲後父節點與子節點的編號下標(i)有什麼關係?

  • 父節點與左子節點的編號下標關係:2i + 1
  • 父節點與右子節點的編號下標關係:2i + 2

堆排序

一、堆分爲大根堆和小根堆

  • 大根堆:一顆徹底二叉樹,知足任一節點都要比其子節點大
  • 小根堆:一顆徹底二叉樹,知足任一節點都要比其子節點小

二、堆排序的過程

  1. 創建堆
  2. 獲得堆頂元素,爲最大元素
  3. 去掉對頂,將堆最後一個元素放在堆頂,此時可經過一次調整從新使堆變得有序
  4. 堆頂元素爲第二大元素,重複步驟3,直到堆變空

三、代碼實現

# coding:utf-8
import random


# 調整
def sift(arr, start, end):
    i = start
    j = 2 * i + 1
    tmp = arr[i]
    while j <= end:  # 孩子在堆裏
        # 升序排序
        if j + 1 <= end and arr[j] < arr[j + 1]:  # 若是存在右孩子且大於左邊的孩子
            j += 1  # j指向右孩子
        if arr[j] > tmp:  # 子比父大
            arr[i] = arr[j]  # 子放在父的空位上
            i = j  # 子成爲新的父
            j = 2 * i + 1  # 新孩子
        else:
            break

    arr[i] = tmp


# 堆排序
def heap_sort(arr):
    length = len(arr)
    for i in range(length // 2 - 1, -1, -1):  # 循環每一個小堆,作一次sift
        sift(arr, i, length - 1)
    # 堆建好以後,挨個出數
    for i in range(length - 1, -1, -1):  # i 指向堆的最後
        arr[0], arr[i] = arr[i], arr[0]  # 將調換下來的父節點數放在最後一位
        sift(arr, 0, i - 1)
    return arr


arr = list(range(555))
random.shuffle(arr)
print(heap_sort(arr))

歸併排序

假設有一個列表,分爲有序的兩段,怎麼讓它合併爲一個有序的列表

先實現一次歸併,從左往右依次取左右兩邊的第一個元素,依次比較大小,將小的添加到新的列表中,而後繼續取值比較。。。

def merge(arr, start, mid, end):
    '''
    :param arr: 一個兩段有序的列表
    :param start: 左段有序列表的最小元素
    :param mid: 左段有序列表的最後一個元素
    :param end: 右段有序列表的最小元素
    '''
    tmp = []
    i = start
    j = mid + 1
    while i <= mid and j <= end:  # 兩邊都有數
        if arr[i] < arr[j]:  # 左邊小時將左邊的元素append到tmp
            tmp.append(arr[i])
            i += 1
        else:
            tmp.append(arr[j])  # 右邊小時將右邊的元素append到tmp
            j += 1
    while i <= mid:
        tmp.append(arr[i])
        i += 1
    while j <= end:
        tmp.append(arr[j])
        j += 1
    arr[start:end + 1] = tmp

歸併的使用

分解:用遞歸將一個列表越分越小,直到分紅一個元素,一個元素是有序的

合併:用歸併將兩個有序列表合併

def merge(arr, start, mid, end):
    '''
    :param arr: 一個兩段有序的列表
    :param start: 左段有序列表的最小元素
    :param mid: 左段有序列表的最後一個元素
    :param end: 右段有序列表的最小元素
    '''
    tmp = []
    i = start
    j = mid + 1
    while i <= mid and j <= end:  # 兩邊都有數
        if arr[i] < arr[j]:  # 左邊小時將左邊的元素append到tmp
            tmp.append(arr[i])
            i += 1
        else:
            tmp.append(arr[j])  # 右邊小時將右邊的元素append到tmp
            j += 1
    while i <= mid:
        tmp.append(arr[i])
        i += 1
    while j <= end:
        tmp.append(arr[j])
        j += 1
    arr[start:end + 1] = tmp


def merge_sort(arr, start, end):
    if start < end:
        mid = (start + end) // 2
        # 分解
        merge_sort(arr, start, mid)
        merge_sort(arr, mid + 1, end)
        # 合併
        merge(arr, start, mid, end)
    return arr


arr = list(range(555))
random.shuffle(arr)
print(merge_sort(arr, 0, len(arr) - 1))

計數排序

條件:假設有一個長度爲10萬的列表,其中的元素都在0-100之間,將該列表內的元素進行排序

思路:先定義一個0-100的列表(下標),讓每一個下標對應的數所有等於0,而後循環須要排序的列表,將循環出現的數對應到定義好下標的列表中進行個數統計

代碼實現

def count_sort(arr, max_num):
    count = [0 for i in range(max_num + 1)]
    for num in arr:
        count[num] += 1
    i = 0
    for num, m in enumerate(count):
        for j in range(m):
            arr[i] = num
            i += 1
    return arr


data = [random.randint(0, 100) for i in range(100)]
print('before: ', data)
print('after: ', count_sort(data, 100))
相關文章
相關標籤/搜索