Python數據結構和算法

1、算法入門

  程序設計 = 數據結構 + 算法node

算法時爲了解決實際問題而設計的,數據結構是算法須要處理的問題載體
數據結構只是靜態的描述呢數據元素之間的關係
高效的程序須要再數據結構的基礎上設計和選擇算法
複製代碼

1. 數據結構

1.1 數據結構存在的意義

若是用Python的類型來保存一個班學生的信息,並如何經過學生姓名快速獲取其信息呢?
複製代碼

Python中的列表和字典都可以來存儲學生信息。python

列表存儲:獲取一名學生的信息時,就要遍歷這個列表,其時間複雜度爲O(n);
字典存儲:能夠將學生姓名做爲字典的鍵,學生信息做爲值,查詢直接使用鍵獲取值,其時間複雜度爲O(1)
複製代碼

明顯的字典存儲的數據結構數據的處理效率更高,數據存儲方式(即數據結構)越優化,算法處理時效率越高。git

1.2 數據結構概念

  數據結構:數據元素相互之間存在的一種或多種特定關係的集合(數據結構指數據對象中數據元素之間的關係)。數據結構分爲邏輯結構和物理結構。github

邏輯結構:是指數據對象中數據元素之間的相互關係。

    (1)集合結構:數據元素除了同屬於一個集合外,沒有什麼其餘關係
    (2)線性結構:線性結構中的數據元素之間是一對一的關係
    (3)樹形結構:元素之間存在一種一對多的層次關係
    (4)圖形結構:元素是多對多的關係
    
物理結構:是指數據的邏輯結構在計算機中的存儲形式。
    
    (1)順序存儲:把數據元素存放在地址連續的存儲單元你,其數據間的邏輯關係和物理關係是一致的
    (2)鏈式存儲:把數據元素存放在任意的存儲單元裏,這組存儲單元能夠是連續的,也能夠是不連續的。鏈式存儲結構的數據元素存儲關係不能反映其邏輯關係,須要用一個指針存放數據元素的地址,這樣經過地址就能夠找到相關數據元素的位置。
複製代碼

Python的數據結構分兩種:算法

內置數據結構:列表、元組、字典等
擴展數據結構:Python系統裏面沒有直接定義,須要咱們本身取定義實現這些數據的組織方式,如棧、隊列等
複製代碼

1.3 抽象數據類型

抽象數據類型(Abstract Data Type):把數據類型和數據類型上的運算捆在一塊兒,進行封裝。編程

引入抽象數據類型目的是把數據類型的表示和數據類型上運算的實現與這些數據類型和運算在程序中引用隔開,使他們相互獨立。常見的物種數據運算:數據結構

插入
刪除
修改
查找
排序
複製代碼

2. 算法

  算法:是獨立存在(不依賴特定的編程語言)的一種解決問題的方法和思想。算法有五大特性:app

(1)輸入:算法有0個或多個輸入
(2)輸出:算法至少有一個或多個輸出
(3)有窮性:算法在有限的步驟以後會自動結束而不會無限循環,而且每個步驟能夠在可接受的時間內完成
(4)肯定性:算法中的每一步都有肯定的含義,不會出現二義性
(5)可行性:算法的每一步都是可以執行有限次數完成的
複製代碼

  程序在每臺機器執行的總時間不一樣,可是執行基本運算數量大體相同。編程語言

2.1 最壞時間複雜度

  時間複雜度:假設存在函數g,使得算法A處理規模爲n的問題所用時間爲T(n)=O(g(n)),則O(g(n))爲算法A的獎金時間複雜度,簡稱時間複雜度,記爲T(n)函數

最優時間複雜度:算法完成工做最少須要多少基本操做 --- 沒有什麼參考價值
* 最壞時間複雜度:算法完成工做作多須要多少基本操做 --- 基本操做都能完成
平均時間複雜度:算法完成工做平均須要多少基本操做
複製代碼

2.2 時間複雜度計算規則

1. 基本操做:只有常數項,認爲其時間複雜度爲O(1)
2. 順序結構:時間複雜度按加畫進行計算
3. 循環結構:時間複雜度按懲罰進行計算
4. 分支結構:時間複雜度取最大值
5. 判斷一個算法的效率時,只須要關注操做數量的最高次項,其餘次要項和參數項能夠忽略
6. 在沒有特殊說明時,咱們所分析的時間複雜度指的是 最壞時間複雜度
複製代碼

GitHub示例

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : 01_abc.py @Time : 2019/10/22 12:18 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : 若是 a + b +c = 1000 且 a**2 + b**2 = c**2(a, b, c均爲天然數),求解a, b, c的全部組合 """

import time

start_time = time.time()
print("程序開始執行時間: %s" % start_time)

# 算法一
# 時間複雜度:
# T(n) = O(n*n*n) = O(n**3)
for a in range(1001):
    for b in range(1001):
        for c in range(1001):
            if a + b + c == 1000 and a**2 + b**2 == c**2:
                print("a, b, c: %d, %d, %d" % (a, b, c))


# 算法二
for a in range(1001):
    for b in range(0, 1001):
        c = 1000 - a - b
        if a**2 + b**2 == c**2:
            print("a, b, c: %d, %d, %d" % (a, b, c))


# 算法三
# 時間複雜度:
# T(n) = O(n*n(1+1))) = O(n**2)
for a in range(1001):
    for b in range(0, 1001 - a):
        c = 1000 - a - b
        if a**2 + b**2 == c**2:
            print("a, b, c: %d, %d, %d" % (a, b, c))


end_time = time.time()
print("程序執行結束時間: %s" % end_time)

all_times = end_time - start_time
print("總耗時all_times: %s" % all_times)
print("執行完畢!")

複製代碼

2.3 常見時間複雜度

非正式術語 執行次函數 時間複雜度
常數階 12 O(1)
線性階 2n + 3 O(n)
平方階 3n**2+3n+2 O(n**2)
對數階 5log2n+20 O(logn)
nlogn階 2n+3nlog2n+19 O(nlogn)
立方階 6n**3 + 2n **2+3n+4 O(n**3)
指數階 2**n O(2**n)

所消耗的時間從小到大: O(1) < O(logn) < O(n) < O(nlogn) < O(n **2) < O(n **3) < O(2 **n) < O(n!) < O(n **n)

2.4 Python內置類型性能分析

  timeit模塊能夠用來測試一小段Python代碼的執行速度。class timeit.Timer(stmt='pass', setup='pass', timer=) Timer是測量小段代碼執行速度的類。

stmt參數是要測試的代碼語句(statment);
setup參數是運行代碼時須要的設置;
timer參數是一個定時器函數,與平臺有關。
timeit.Timer.timeit(number=1000000)
Timer類中測試語句執行速度的對象方法。number參數是測試代碼時的測試次數,默認爲1000000次。方法返回執行代碼的平均耗時,一個float類型的秒數。
複製代碼

GitHub示例

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : 202_timeit.py @Time : 2019/10/22 19:27 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """

# class timeit.Timer(stmt='pass', setup='pass', timer=<timer function>)
# Timer是測量小段代碼執行速度的類。
# stmt參數是要測試的代碼語句(statment);
# setup參數是運行代碼時須要的設置;
# timer參數是一個定時器函數,與平臺有關
# timeit.Timer.timeit(number=1000000)
# Timer類中測試語句執行速度的對象方法。number參數是測試代碼時的測試次數,默認爲1000000次。方法返回執行代碼的平均耗時,一個float類型的秒數。


def test1():
    l1 = []
    for i in range(1000):
        l1 = l1 + [i]

def test2():
    l2 = []
    for i in range(1000):
        l2.append(i)

def test3():
    l3 = [i for i in range(1000)]

def test4():
    l4 = list(range(1000))

from timeit import Timer

t1 = Timer("test1()", "from __main__ import test1")
print("concat ", t1.timeit(number=1000), "seconds")

t2 = Timer("test2()", "from __main__ import test2")
print("append ", t2.timeit(number=1000), "seconds")

t3 = Timer("test3()", "from __main__ import test3")
print("comprehension ", t3.timeit(number=1000), "seconds")

t4 = Timer("test4()", "from __main__ import test4")
print("list range ", t4.timeit(number=1000), "seconds")


# pop內置函數的測試
# pop最後一個元素的效率遠遠高於pop第一個元素
x = list(range(2000000))
pop_zero = Timer("x.pop(0)", "from __main__ import x")
print("pop_zero ", pop_zero.timeit(number=1000), "seconds")
x = list(range(2000000))
pop_end = Timer("x.pop()", "from __main__ import x")
print("pop_end ", pop_end.timeit(number=1000), "seconds")
複製代碼

2、順序表

  在程序中,將一組(一般同爲某個類型)數據元素做爲總體管理和使用,一般能夠將這組數據當作一個序列,用元素在序列裏的位置和順序表示實際應用中的某種有意義的信息或者表示數據之間的某種關係。

  這樣一列序列元素的組織形式,咱們能夠抽象爲線性表。一個線性表是某類元素的一個集合,還記錄着元素之間的一種順序關係。線性表是最基本的數據結構之一,它還常常被用做更復雜的數據結構的實現基礎。

順序表:將元素順序滴存放在一塊連續的存儲區裏,元素間的順序關係由他們的存儲順序天然表示。

1. 順序表的形式

順序表的形式的基本形式

  圖a)表示的是順序表的基本形式,數據元素自己連續存儲,每一個元素所佔的存儲單元大小固定相同,元素的下標是其邏輯地址。元素存儲的物理地址(實際內存地址)能夠經過存儲區的起始地址Loc (e0)加上邏輯地址(第i個元素)與存儲單元大小(c)的乘積計算而得,即:

Loc(ei) = Loc(e0) + c*i
複製代碼

  因此訪問指定元素時無需從頭遍歷,經過計算即可以得到對應地址,其時間複雜度爲:O(1)

  圖b)的元素外置的形式,將實際數據元素另行存儲,而順序表中各單元位置保存對應元素的地址信息(即連接)。元素的大小不統一,因爲每一個連接所需的存儲量相同,能夠計算出元素連接的存儲位置,然後順着連接找到實際存儲的數據元素。

2. 順序表的結構和實現

2.1 基本概念

順序表 = 元素存儲區的容量 + 當前表中已有元素個數

順序表的結構

順序表的兩種實現方式:

一體式結構

  一體式結構:存儲表信息的單元與元素存儲區以連續的方式安排在一塊存儲區裏,兩部分數據的總體造成一個完整的順序表對象。一體式結構總體性強,易於管理。可是因爲數據元素存儲區域是表對象的一部分,順序表建立後,元素存儲區就固定了。

分離式結構

  分離式結構:表對象裏只保存與整個表有關的信息(即容量和元素個數),實際數據元素存放在另外一個獨立的元素存儲區裏,經過連接與基本表對象關聯。

2.2 元素存儲區替換

  一體式結構: 因爲順序表信息區與數據區連續存儲在一塊兒,因此若想更換數據區,則只能總體搬遷,即整個順序表對象(指存儲順序表的結構信息的區域)改變了。

  分離式結構:只需將表信息區中的數據區連接地址更新便可,而該順序表對象不變。

2.3 元素存儲區擴充

採用分離式結構的順序表,若將數據區更換爲存儲空間更大的區域,則能夠在不改變表對象的前提下對其數據存儲區進行了擴充,全部使用這個表的地方都沒必要修改。

擴充策略:

(1)採用分離式結構的順序表,若將數據區更換爲存儲空間更大的區域,則能夠在不改變表對象的前提下對其數據存儲區進行了擴充,全部使用這個表的地方都沒必要修改。
     特色:節省空間,可是擴充操做頻繁,操做次數多。

(2)每次擴充容量加倍,如每次擴充增長一倍存儲空間。
    特色:減小了擴充操做的執行次數,但可能會浪費空間資源。以空間換時間,推薦的方式。
複製代碼

2.3 順序表的操做

增長元素

增長元素

a. 尾端加入元素,時間複雜度爲O(1)
b. 非保序的加入元素(不常見),時間複雜度爲O(1)
c. 保序的元素加入,時間複雜度爲O(n)
複製代碼

刪除元素

刪除元素

a. 刪除表尾元素,時間複雜度爲O(1)
b. 非保序的元素刪除(不常見),時間複雜度爲O(1)
c. 保序的元素刪除,時間複雜度爲O(n)
複製代碼

2.4 Python中順序表

Python中的list和tuple兩種類型採用了順序表的實現技術

tuple是不可變類型,即不變的順序表,所以不支持改變其內部狀態的任何操做。
list就是一種元素個數可變的線性表,能夠加入和刪除元素,並在各類操做中維持已有元素的順序(即保序)。
複製代碼

list順序表的特徵:

(1)基於下標(位置)的高效元素訪問和更新,時間複雜度應該是O(1),爲知足該特徵,應該採用順序表技術,表中元素保存在一塊連續的存儲區中。
(2)容許任意加入元素,並且在不斷加入元素的過程當中,表對象的標識(函數id獲得的值)不變,爲知足該特徵,就必須能更換元素存儲區,而且爲保證更換存儲區時list對象的標識id不變,只能採用分離式實現技術。
複製代碼

  list就是一種採用分離式技術實現的動態順序表。這就是爲何用list.append(x) (或 list.insert(len(list), x),即尾部插入)比在指定位置插入元素效率高的緣由。

  list實現採用了以下的策略:在創建空表(或者很小的表)時,系統分配一塊能容納8個元素的存儲區;在執行插入操做(insert或append)時,若是元素存儲區滿就換一塊4倍大的存儲區。但若是此時的表已經很大(目前的閥值爲50000),則改變策略,採用加一倍的方法。引入這種改變策略的方式,是爲了不出現過多空閒的存儲位置。

3、鏈表

  鏈表存在的意義:

順序表的構建須要預先知道數據大小來申請連續的存儲空間,而在進行擴充時又須要進行數據的搬遷,使用起來並非很靈活

鏈表結構能夠充分使用計算機內存空間,實現靈活的內存動態管理。它是一種線性表,在每一個數據節點(數據存儲單元)裏存放下一個節點的位置信息(即地址)。
複製代碼

鏈表結構

3.1 單向鏈表

  單向鏈表也叫單鏈表,是鏈表中最簡單的一種形式,它的每一個節點包含兩個域,一個信息域(元素域)和一個連接域。這個連接指向鏈表中的下一個節點,而最後一個節點的連接域則指向一個空值。

單向鏈表

(1)表元素域elem用來存放具體的數據。
(2)連接域next用來存放下一個節點的位置(python中的標識)
(3)變量p指向鏈表的頭節點(首節點)的位置,從p出發能找到表中的任意節點
複製代碼

單節點實現

class SingleNode(object):
    """單鏈表的結點"""
    def __init__(self,item):
        # _item存放數據元素
        self.item = item
        # _next是下一個節點的標識
        self.next = None
複製代碼

單鏈表的操做

is_empty() 鏈表是否爲空
length() 鏈表長度
travel() 遍歷整個鏈表
add(item) 鏈表頭部添加元素
append(item) 鏈表尾部添加元素
insert(pos, item) 指定位置添加元素
remove(item) 刪除節點
search(item) 查找節點是否存在
複製代碼

單鏈表的實現GitHub示例

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : 2031_singlelinkedList.py @Time : 2019/10/23 17:13 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """

# 節點的實現
class SingleNode(object):
    """單鏈表的節點"""
    def __init__(self, item):
        # item 存放數據元素
        self.item = item
        # next 是下一個節點的標識
        self.next = None


# 單鏈表的實現
class SingleLinkList(object):
    """單鏈表"""
    def __init__(self):
        self._head = None

    def is_empty(self):
        """判斷鏈表是否爲空"""
        return self._head is None

    def length(self):
        """鏈表長度"""
        cur = self._head
        count = 0
        # 尾節點指向None,當爲到達尾部時
        while cur is not None:
            count = count + 1
            # cur向後移動一個節點
            cur = cur.next
        return count

    def travel(self):
        """遍歷鏈表"""
        cur = self._head
        while cur is not None:
            print(cur.item)
            cur = cur.next
        print(" ")

    def add_item(self, item):
        """頭部添加元素"""
        # 建立一個保存item值的節點
        node = SingleNode(item)
        # 將新節點的連接域next指向頭節點,即_head指向的位置
        node.next = self._head
        # 將鏈表的頭_head指向新節點
        self._head = node

    def append_item(self, item):
        """尾部添加元素"""
        node = SingleNode(item)
        # 先判斷鏈表是否爲空,如果空鏈表,則將_head指向新節點
        if self.is_empty():
            self._head = node
        else:
            cur = self._head
            while cur.next is not None:
                cur = cur.next
            cur.next = node

    def insert_item(self, pos, item):
        """指定位置添加元素"""
        if pos <= 0:
            self.add_item(item)
        elif pos > (self.length() - 1):
            self.append_item(item)
        # 指定位置
        else:
            node = SingleNode(item)
            count = 0
            # pre用來指向指定位置pos的前一個位置pos-1,初始從頭節點開始移動到指定位置
            pre = self._head
            while count < (pos - 1):
                count += 1
                pre = pre.next
            # 先將新節點node的next指向插入位置的節點
            node.next = pre.next
            # 將插入位置的前一個節點的next指向新節點
            pre.next = node

    def remove_item(self, item):
        """刪除節點"""
        cur = self._head
        pre = None
        while cur is not None:
            if cur.item == item:
                # 若是第一個就是刪除節點
                if not pre:
                    self._head = cur.next
                else:
                    # 將刪除位置前一個節點的next指向刪除位置的最後一個節點
                    pre.next = cur.next
                break
            else:
                # 繼續按鏈表後移節點
                pre = cur
                cur = cur.next

    def search_item(self, item):
        """查找連接中元素是否存在"""
        cur = self._head
        while cur is not None:
            if cur.item == item:
                return True
            cur = cur.next
        return False


if __name__ == "__main__":
    sll = SingleLinkList()
    sll.add_item(11)
    sll.add_item(12)
    sll.add_item(13)
    sll.travel()
    sll.insert_item(1, 14)
    sll.travel()
    print(sll.search_item(12))
複製代碼

鏈表與順序表的對比

操做 鏈表 順序表
訪問元素 O(n) O(1)
在頭部插入/刪除 O(1) O(n)
在尾部插入/刪除 O(n) O(1)
在中間插入/刪除 O(n) O(n)
鏈表的主要耗時操做是遍歷查找,刪除和插入操做自己的複雜度是O(1)。
順序表查找很快,主要耗時的操做是拷貝覆蓋。
由於除了目標元素在尾部的特殊狀況,順序表進行插入和刪除時須要對操做點以後的全部元素進行先後移位操做,只能經過拷貝和覆蓋的方法進行。
複製代碼

3.2 單向循環鏈表

單向循環鏈表:單鏈表的一個變形是單向循環鏈表,鏈表中最後一個節點的next域再也不爲None,而是指向鏈表的頭節點。

GitHub示例

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : 2032_sincycLinkedList.py @Time : 2019/10/23 20:53 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """

class Node(object):
    """節點"""
    def __init__(self, item):
        self.item = item
        self.next = None


class SinCycLinkedList(object):
    """單向循環鏈表"""
    def __init__(self):
        self._head = None

    """判斷鏈表是否爲空"""
    def is_empty(self):
        return self._head is None

    """返回鏈表的長度"""
    def length(self):
        if self.is_empty():
            return 0
        count = 1
        cur = self._head
        while cur.next is not self._head:
            count += 1
            cur = cur.next
        return count

    """遍歷鏈表"""
    def travel(self):
        if self.is_empty():
            return
        cur = self._head
        print(cur.item)
        while cur.item != self._head:
            cur = cur.next
            print(cur.item)
        print(" ")

    """添加節點"""
    def add_item(self, item):
        node = Node(item)
        if self.is_empty():
            self._head = node
            node.next = self._head
        else:
            # 添加的節點時間指向_head
            node.next = self._head
            # 移到鏈表尾部,將尾部節點的next指向node
            cur = self._head
            while cur.next != self._head:
                cur = cur.next
            cur.next = node
            #_head指向添加node的
            self._head = node

    def append_item(self, item):
        """尾部添加節點"""
        node = Node(item)
        if self.is_empty():
            self._head = node
            node.next = self._head
        else:
            # 移到鏈表尾部
            cur = self._head
            while cur.next != self._head:
                cur = cur.next
            # 將尾節點指向node
            cur.next = node
            # 將node指向頭節點_head
            node.next = self._head

    def insert_item(self, pos, item):
        """在指定位置添加節點"""
        if pos <= 0:
            self.add(item)
        elif pos > (self.length()-1):
            self.append(item)
        else:
            node = Node(item)
            cur = self._head
            count = 0
            # 移動到指定位置的前一個位置
            while count < (pos-1):
                count += 1
                cur = cur.next
            node.next = cur.next
            cur.next = node

    def remove_item(self, item):
        """刪除一個節點"""
        # 若鏈表爲空,則直接返回
        if self.is_empty():
            return
        # 將cur指向頭節點
        cur = self._head
        pre = None
        # 若頭節點的元素就是要查找的元素item
        if cur.item == item:
            # 若是鏈表不止一個節點
            if cur.next != self._head:
                # 先找到尾節點,將尾節點的next指向第二個節點
                while cur.next != self._head:
                    cur = cur.next
                # cur指向了尾節點
                cur.next = self._head.next
                self._head = self._head.next
            else:
                # 鏈表只有一個節點
                self._head = None
        else:
            pre = self._head
            # 第一個節點不是要刪除的
            while cur.next != self._head:
                # 找到了要刪除的元素
                if cur.item == item:
                    # 刪除
                    pre.next = cur.next
                    return
                else:
                    pre = cur
                    cur = cur.next
            # cur 指向尾節點
            if cur.item == item:
                # 尾部刪除
                pre.next = cur.next

    def search_item(self, item):
        """查找節點是否存在"""
        if self.is_empty():
            return False
        cur = self._head
        if cur.item == item:
            return True
        while cur.next != self._head:
            cur = cur.next
            if cur.item == item:
                return True
        return False


if __name__ == "__main__":
    scll = SinCycLinkedList()
    print(scll.is_empty())
    print(scll.length())
複製代碼

3.3 雙向鏈表

雙向鏈表:每一個節點有兩個連接:一個指向前一個節點,當此節點爲第一個節點時,指向空值;而另外一個指向下一個節點,當此節點爲最後一個節點時,指向空值。

4、棧(LIFO)

  棧(stack):也稱爲堆棧,四一種容器,可存放、刪除、訪問元素。只容許在容器的一端進行數據運算。棧的數據遵循:後進先出(LIFO, Last in First Out)的原理。

棧的實現(既能夠用順序表實現,也能夠用鏈表實現)

棧的操做方法:
    Stack() 建立一個新的空棧
    push(item) 添加一個新的元素item到棧頂
    pop() 彈出棧頂元素
    peek() 返回棧頂元素
    is_empty() 判斷棧是否爲空
    size() 返回棧的元素個數
複製代碼

GitHub示例

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : 204_stack.py @Time : 2019/10/23 21:20 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """

"""棧"""
class Stack(object):
    def __init__(self):
        self.items = []

    """判斷棧是否爲空"""
    def is_empty(self):
        return self.items == []

    """加入元素"""
    def push_item(self, item):
        self.items.append(item)

    """彈出元素(刪除元素)"""
    def pop_item(self):
        self.items.pop()

    """返回棧頂元素"""
    def peek_item(self):
        return self.items[len(self.items)-1]

    def size_item(self):
        return len(self.items)


if __name__ == "__main__":
    s = Stack()
    print(s.is_empty())
    s.push_item("123")
    s.push_item("456")
    s.push_item("789")
    s.push_item("234")
    s.push_item("567")
    print(s.size_item())
    s.pop_item()
    print(s.peek_item())

複製代碼

5、隊列

  隊列(queue):是一種先進先出(First in First Out,FIFO)的線性表。容許插入的一端爲隊尾,容許刪除的一端爲對頭。隊列不容許在中間部位進行操做。

1. 隊列的實現

隊列的實現(既能夠用順序表實現,也能夠用鏈表實現)

隊列的操做:
    Queue() 建立一個空的隊列
    enqueue(item) 往隊列中添加一個item元素
    dequeue() 從隊列頭部刪除一個元素
    is_empty() 判斷一個隊列是否爲空
    size() 返回隊列的大小
複製代碼

GitHub示例

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : 2051_queue.py @Time : 2019/10/23 21:43 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """

"""隊列"""
class Queue(object):
    def __init__(self):
        self.items = []

    """判斷隊列是否爲空"""
    def is_empty(self):
        return self.items == []

    # 【["隊尾", ..., "對頭"]】
    """進隊列:只容許在隊尾插入數據"""
    def enqueue(self, item):
        self.items.insert(0, item)

    """出隊列:只容許在對頭刪除"""
    def dequeue(self):
        return self.items.pop()

    """返回隊列的大小"""
    def size(self):
        return len(self.items)


if __name__ == "__main__":
    q = Queue()
    print(q.is_empty())
    q.enqueue("123")
    q.enqueue("456")
    q.enqueue("789")
    print(q.dequeue())
    print(q.is_empty())
    print(q.size())

複製代碼

2. 雙端隊列

雙端隊列:(deque,全名double-ended queue),是一種具備隊列和棧的性質的數據結構。

雙端隊列中的元素能夠從兩端彈出,其限定插入和刪除操做在表的兩端進行。雙端隊列能夠在隊列任意一端入隊和出隊。

操做方法:
    Deque() 建立一個空的雙端隊列
    add_front(item) 從隊頭加入一個item元素
    add_rear(item) 從隊尾加入一個item元素
    remove_front() 從隊頭刪除一個item元素
    remove_rear() 從隊尾刪除一個item元素
    is_empty() 判斷雙端隊列是否爲空
    size() 返回隊列的大小 
複製代碼

GitHub示例

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : 2052_deque.py @Time : 2019/10/24 12:22 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """

"""雙端隊列"""
class Deque(object):
    def __init__(self):
        self.items = []

    """判斷雙端隊列是否爲空"""
    def is_empty(self):
        self.items == []

    """在隊頭添加元素"""
    def add_front(self, item):
        self.items.insert(0, item)

    """在隊尾添加元素"""
    def add_rear(self, item):
        self.items.append(item)

    """在對頭移除元素"""
    def remove_front(self):
        return self.items.pop(0)

    """在隊尾移除元素"""
    def remove_rear(self):
        return self.items.pop()

    """返回雙端隊列的大小"""
    def size(self):
        return len(self.items)


if __name__ == "__main__":
    d = Deque()
    d.add_front(1)
    d.add_front(2)
    d.add_rear(3)
    d.add_rear(4)
    print(d.size())
    print(d.remove_front())
    print(d.remove_front())
    print(d.remove_rear())
    print(d.remove_rear())
    print(d.size())

複製代碼

6、排序與搜索

1. 冒泡排序

冒泡排序:對於一序列,拿兩個數據進行比較,若是左邊的數大於右邊的數,這兩個數交換位置,繼續交換後的第二坐的數據與下一個數進行比較交換,一直到沒有交換爲止(即排序完成)。

冒泡排序

初始序列:  【54 26 93 17 77 31】
第一次交換:【26 54 17 77 31 93】  第一次比較次數:5 【n -1】
第二次交換:【26 17 54 31 77 93】  第二次比較次數:4 【n -2】
第三次交換:【17 26 31 54 77 93】  第三次比較次數:3 【n -3】
複製代碼

GitHub示例

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : 2061_bubble_sort.py @Time : 2019/10/24 12:57 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """

"""冒泡排序 最優時間複雜度:O(n) 最壞時間複雜度:O(n**2) """
def bubble_sort(alist):
    for i in range(len(alist)-1, 0, -1):
        for j in range(i):
            if alist[j] > alist[j+1]:
                alist[j], alist[j+1] = alist[j+1], alist[j]


if __name__ == "__main__":
    li = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    bubble_sort(li)
    print(li)
    
複製代碼

2. 快速排序

快速排序

(1)取出一個基準(pivot)元素
(2)分區(partition)操做:從新排序序列,比基準值小的放在基準值的前面,比基準值大的放在基準值後面,和基準值同樣大的能夠放在任意一邊。分區結束後,基準值就在序列的中間位置。
(3)遞歸(recusive)地把小於基準元素的子序列和大於基準元素的子序列排序
複製代碼

快速排序

GitHub示例

快速排序

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
""" @File : 2062_quick_sort.py @Time : 2019/10/24 15:49 @Author : Crisimple @Github : https://crisimple.github.io/ @Contact : Crisimple@foxmail.com @License : (C)Copyright 2017-2019, Micro-Circle @Desc : None """

"""快速排序 最優時間複雜度:O(nlogn) 最壞時間複雜度:O(n**2) """

def quick_sort(alist, start, end):

    # 遞歸的退出條件
    if start >= end:
        return

    # 設置其實元素爲基準元素
    mid = alist[start]

    # low爲序列左邊的由左向右移動的遊標
    low = start

    # high爲序列右邊的由右向左移動的遊標
    high = end

    while low < high:
        # 若是low與high未重合,high指向的元素不比基準元素小,則high向左移動
        while low < high and alist[high] >= mid:
            high -= 1
        # 將high指向的元素放到low的位置上
        alist[low] = alist[high]

        # 若是low與high未重合,low指向的元素比基準元素小,則low向右移動
        while low < high and alist[low] < mid:
            low += 1
        # 將low指向的元素飯到high的位置上
        alist[high] = alist[low]

    # 退出循環後,low與high重合,此時所指位置爲基準位置元素的正確位置
    # 將基準元素放到該位置
    alist[low] = mid

    # 對基準元素左邊的子序列進行快速排序
    quick_sort(alist, start, low-1)

    # 對基準元素右邊的子序列進行快速排序
    quick_sort(alist, low+1, end)


if __name__ == "__main__":
    alists = [54,26,93,17,77,31,44,55,20]
    quick_sort(alists, 0, len(alists)-1)
    print(alists)

複製代碼

3. 選擇排序

複製代碼

4. 插入排序

複製代碼

5. 希爾排序

複製代碼

6. 歸併排序

複製代碼

7. 常見排序算法效率比較

複製代碼

8. 搜索

複製代碼

7、樹與算法

1. 二叉樹

2. 二叉樹的遍歷

相關文章
相關標籤/搜索