10.數據結構和算法 初識

一、數據結構與算法(Python)

數據結構和算法是什麼?答曰:兵法!node

1.1算法的概念

算法是計算機處理信息的本質,由於計算機程序本質上是一個算法來告訴計算機確切的步驟來執行一個指定的任務。通常地,當算法在處理信息時,會從輸入設備或數據的存儲地址讀取數據,把結果寫入輸出設備或某個存儲地址供之後再調用。python

  • ==算法是獨立存在的一種解決問題的方法和思想==。====

對於算法而言,實現的語言並不重要,重要的是思想。算法

算法能夠有不一樣的語言描述實現版本(如C描述、C++描述、Python描述等),咱們如今是在用Python語言進行描述實現。數據結構

算法的五大特性app

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

1.2第一次嘗試

需求:已知a+b+c=1000 a2+b2=c2 求a,b,c可能的值數據結構和算法

import time

start_time = time.time()

# 注意是三重循環
for a in range(0, 1001):
    for b in range(0, 1001):
        for c in range(0, 1001):
            if a**2 + b**2 == c**2 and a+b+c == 1000:
                print("a, b, c: %d, %d, %d" % (a, b, c))

end_time = time.time()
print("elapsed: %f" % (end_time - start_time))
print("complete!")

運行結果:
a, b, c: 0, 500, 500
a, b, c: 200, 375, 425
a, b, c: 375, 200, 425
a, b, c: 500, 0, 500
elapsed: 214.583347
complete!
注意運行的時間: 160.913325秒
個人電腦運行了大概26min。。。

1.3第二次嘗試

import time

start_time = time.time()

# 注意是兩重循環
for a in range(0, 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("elapsed: %f" % (end_time - start_time))
print("complete!")

運行結果:
a, b, c: 0, 500, 500
a, b, c: 200, 375, 425
a, b, c: 375, 200, 425
a, b, c: 500, 0, 500
elapsed: 0.182897
complete!
注意運行的時間: 0.609427秒

1.4算法效率衡量

1.4.1執行時間反應算法效率

對於同一問題,咱們給出了兩種解決算法,在兩種算法的實現中,咱們對程序執行的時間進行了測算,發現兩段程序執行的時間相差懸殊(160.913325秒相比於0.609427秒),由此咱們能夠得出結論:實現算法程序的執行時間能夠反應出算法的效率,即算法的優劣。函數

1.4.2單靠時間值絕對可信嗎?

假設咱們將第二次嘗試的算法程序運行在一臺配置古老性能低下的計算機中,狀況會如何?極可能運行的時間並不會比在咱們的電腦中運行算法一的160.913325秒快多少。
==單純依靠運行的時間來比較算法的優劣並不必定是客觀準確的!==性能

程序的運行離不開計算機環境(包括硬件和操做系統),這些客觀緣由會影響程序運行的速度並反應在程序的執行時間上。那麼如何才能客觀的評判一個算法的優劣呢?測試

1.4.3時間複雜度與「大O記法」

咱們假定計算機執行算法每個基本操做的時間是固定的一個時間單位,那麼有多少個基本操做就表明會花費多少時間單位。算然對於不一樣的機器環境而言,確切的單位時間是不一樣的,可是對於算法進行多少個基本操做(即花費多少時間單位)在規模數量級上倒是相同的,由此能夠忽略機器環境的影響而客觀的反應算法的時間效率。操作系統

對於算法的時間效率,咱們能夠用「大O記法」來表示。

「大O記法」:對於單調的整數函數f,若是存在一個整數函數g和實常數c>0,使得對於充分大的n總有f(n)<=c*g(n),就說函數g是f的一個漸近函數(忽略常數),記爲f(n)=O(g(n))。也就是說,在趨向無窮的極限意義下,函數f的增加速度受到函數g的約束,亦即函數f與函數g的特徵類似。

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

1.4.4如何理解「大O記法」

對於算法進行特別具體的細緻分析雖然很好,但在實踐中的實際價值有限。對於算法的時間性質和空間性質,最重要的是其數量級和趨勢,這些是分析算法效率的主要部分。而計量算法基本操做數量的規模函數中那些常量因子能夠忽略不計。例如,能夠認爲3n2和100n2屬於同一個量級,若是兩個算法處理一樣規模實例的代價分別爲這兩個函數,就認爲它們的效率「差很少」,都爲n2級。

1.4.5最壞時間複雜度

分析算法時,存在幾種可能的考慮:

  1. 算法完成工做最少須要多少基本操做,即最優時間複雜度
  2. 算法完成工做最多須要多少基本操做,即最壞時間複雜度
  3. 算法完成工做平均須要多少基本操做,即平均時間複雜度

對於最優時間複雜度,其價值不大,由於它沒有提供什麼有用信息,其反映的只是最樂觀最理想的狀況,沒有參考價值。
對於最壞時間複雜度,提供了一種保證,代表算法在此種程度的基本操做中必定能完成工做。
對於平均時間複雜度,是對算法的一個全面評價,所以它完整全面的反映了這個算法的性質。但另外一方面,這種衡量並無保證,不是每一個計算都能在這個基本操做內完成。並且,對於平均狀況的計算,也會由於應用算法的實例分佈可能並不均勻而難以計算。

  • ==所以,咱們主要關注算法的最壞狀況,亦即最壞時間複雜度。==

1.4.6時間複雜度的幾條基本計算規則

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

1.4.7算法分析

  1. 第一次嘗試的算法核心部分
    時間複雜度:
    T(n) = O(nnn) = O(n3)
  2. 第二次嘗試的算法核心部分
    時間複雜度:
    T(n) = O(nn(1+1)) = O(n*n) = O(n2)

因而可知,咱們嘗試的第二種算法要比第一種算法的時間複雜度好多的。

二、常見時間複雜度

所消耗的時間從小到大:

O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)

三、Python內置類型性能分析——timeit模塊

timeit模塊能夠用來測試一小段Python代碼的執行速度。

timeit.Timer(stmt='pass', setup='pass', timer=<timer function>)

Timer是測量小段代碼執行速度的類。
stmt參數是要測試的代碼語句(statment);
setup參數是運行代碼時須要的設置;
timer參數是一個定時器函數,與平臺有關。
timeit.Timer.timeit(number=1000000)

Timer類中測試語句執行速度的對象方法。
number參數是測試代碼時的測試次數,默認爲1000000次。
方法返回執行代碼的平均耗時,一個float類型的秒數。
list的操做測試

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

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

def test3():
    ls = []
    for i in range(1000):
        ls.insert(0,i)

def test4():
    ls = [i for i in range(1000)]

def test5():
   ls = list(range(1000))

from timeit import Timer
t1 = Timer(stmt='test1()',setup='from __main__ import test1')
print('concat',t1.timeit(number=10000),'second')

t2 = Timer(stmt='test2()',setup='from __main__ import test2')
print('append',t2.timeit(number=10000),'second')

t3 = Timer(stmt='test3()',setup='from __main__ import test3')
print('insert',t3.timeit(number=10000),'second')

t4 = Timer(stmt='test4()',setup='from __main__ import test4')
print('list gen',t4.timeit(number=10000),'second')

t5 = Timer(stmt='test5()',setup='from __main__ import test5')
print('list',t5.timeit(number=10000),'second')

運行結果
concat 18.341660484899588 second
append 1.0099177848925684 second
insert 5.361922586350083 second
list gen 0.45405794180203074 second
list 0.18035762155434298 second
pop操做測試

from timeit import Timer
x1 = [x for x in range(2000000)]
t = Timer('x1.pop(0)','from __main__ import x1')
print('pop(0)',t.timeit(number=1000),'second')

x2 = [x for x in range(2000000)]
t = Timer('x2.pop()','from __main__ import x2')
print('pop()',t.timeit(number=1000),'second')

運行結果
pop(0) 2.033923430381945 second
pop() 0.00011084076000633658 second

測試pop操做:從結果能夠看出,pop最後一個元素的效率遠遠高於pop第一個元素

四、數據結構

4.1概念

數據是一個抽象的概念,將其進行分類後獲得程序設計語言中的基本類型。如:int,float,char等。數據元素之間不是獨立的,存在特定的關係,這些關係即是結構。數據結構指數據對象中數據元素之間的關係。

Python給咱們提供了不少現成的數據結構類型,這些系統本身定義好的,不須要咱們本身去定義的數據結構叫作Python的內置數據結構,好比列表、元組、字典。而有些數據組織方式,Python系統裏面沒有直接定義,須要咱們本身去定義實現這些數據的組織方式,這些數據組織方式稱之爲Python的擴展數據結構,好比棧,隊列等。

4.2算法與數據結構的區別

  • ==程序 = 數據結構 + 算法==
  • 總結:算法是爲了解決實際問題而設計的,數據結構是算法須要處理的問題載體

4.3抽象數據類型(Abstract Data Type)

抽象數據類型(ADT)的含義是指一個數學模型以及定義在此數學模型上的一組操做。即把數據類型和數據類型上的運算捆在一塊兒,進行封裝。

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

最經常使用的數據運算有五種:

  1. 插入
  2. 刪除
  3. 修改
  4. 查找
  5. 排序

五、順序表

在程序中,常常須要將一組(一般是同爲某個類型的)數據元素做爲總體管理和使用,須要建立這種元素組,用變量記錄它們,傳進傳出函數等。一組數據中包含的元素個數可能發生變化(能夠增長或刪除元素)。

對於這種需求,最簡單的解決方案即是將這樣一組元素當作一個序列,用元素在序列裏的位置和順序,表示實際應用中的某種有意義的信息,或者表示數據之間的某種關係。

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

根據線性表的實際存儲方式,分爲兩種實現模型:

  1. 順序表,將元素順序地存放在一塊連續的存儲區裏,元素間的順序關係由它們的存儲順序天然表示。
  2. 鏈表,將元素存放在經過連接構造起來的一系列存儲塊中。

5.1順序表的結構與實現

一個順序表的完整信息包括兩部分,一部分是表中的元素集合,另外一部分是爲實現正確操做而需記錄的信息,即有關表的總體狀況的信息,這部分信息主要包括元素存儲區的容量和當前表中已有的元素個數兩項。

5.2順序表的兩種基本實現方式

  1. 一體式結構,存儲表信息的單元與元素存儲區以連續的方式安排在一塊存儲區裏,兩部分數據的總體造成一個完整的順序表對象。
    一體式結構總體性強,易於管理。可是因爲數據元素存儲區域是表對象的一部分,順序表建立後,元素存儲區就固定了。
  2. 爲分離式結構,表對象裏只保存與整個表有關的信息(即容量和元素個數),實際數據元素存放在另外一個獨立的元素存儲區裏,經過連接與基本表對象關聯。

元素存儲區替換:

  1. 一體式結構因爲順序表信息區與數據區連續存儲在一塊兒,因此若想更換數據區,則只能總體搬遷,即整個順序表對象(指存儲順序表的結構信息的區域)改變了。
  2. 分離式結構若想更換數據區,只需將表信息區中的數據區連接地址更新便可,而該順序表對象不變。

元素存儲區擴充
採用分離式結構的順序表,若將數據區更換爲存儲空間更大的區域,則能夠在不改變表對象的前提下對其數據存儲區進行了擴充,全部使用這個表的地方都沒必要修改。只要程序的運行環境(計算機系統)還有空閒存儲,這種表結構就不會由於滿了而致使操做沒法進行。人們把採用這種技術實現的順序表稱爲動態順序表,由於其容量能夠在使用中動態變化。

擴充的兩種策略
每次擴充增長固定數目的存儲位置,如每次擴充增長10個元素位置,這種策略可稱爲線性增加。

特色:節省空間,可是擴充操做頻繁,操做次數多。

每次擴充容量加倍,如每次擴充增長一倍存儲空間。

特色:減小了擴充操做的執行次數,但可能會浪費空間資源。以空間換時間,推薦的方式。

5.3順序表的操做

增長元素,刪除元素

5.4Python中的順序表

Python中的list和tuple兩種類型採用了順序表的實現技術,具備前面討論的順序表的全部性質。
tuple是不可變類型,即不變的順序表,所以不支持改變其內部狀態的任何操做,而其餘方面,則與list的性質相似。

六、鏈表

6.1爲何須要鏈表

  • 鏈表結構能夠充分利用計算機內存空間,實現靈活的內存動態管理。

6.2鏈表的定義

鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,可是不像順序表同樣連續存儲數據,而是在每個節點(數據存儲單元)裏存放下一個節點的位置信息(即地址)。

6.3單向鏈表

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

image

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

#定義節點類
class SingleNode(object):
    def __init__(self,item):
        #信息域:存儲節點數據的
        self.item = item
        #連接域:連接下一個節點的
        self.next = None


#定義單向鏈表
class SingleLinkList(object):
    def __init__(self):
        #head:頭節點的引用
        self._head = None

    def is_empty(self):
        #鏈表是否爲空
        return self._head == None

    def length(self):
        #鏈表長度
        count = 0
        #cur指向鏈表的首節點
        cur = self._head
        #判斷cur是否爲None
        while cur != None:
            #cur不等於None,就表示一個節點存在
            #給計數器加1
            count += 1
            #cur指向當前節點的下一個節點
            cur = cur.next
        return count

    def travel(self):
        # 遍歷整個鏈表
        # cur指向鏈表的首節點
        cur = self._head
        # 判斷cur是否爲None
        while cur != None:
            #打印當前節點的數據
            print(cur.item)
            #cur指向下一個節點
            cur = cur.next

    def add(self,item):
        #鏈表頭部添加元素
        #生成新的節點對象
        node = SingleNode(item)
        #設置node節點的next指向原來的頭節點
        node.next = self._head
        #把node節點設置成了新的頭節點
        self._head = node

    def append(self,item):
        #鏈表尾部添加元素
        #定義新的節點
        node = SingleNode(item)
        if self.is_empty():
            self._head = node
        else:
            #cur指向鏈表的開頭
            cur = self._head
            while cur.next != None:
                cur = cur.next
            #cur是鏈表的最後一個節點
            cur.next = node

    def insert(self,pos, item):
        #指定位置添加元素
        if pos<=0:
            #在鏈表的頭部添加節點
            self.add(item)
        elif pos>=self.length():
            #在鏈表的尾部添加節點
            self.append(item)
        else:
            node = SingleNode(item)
            #計數器,用來肯定插入位置
            count = 0
            cur = self._head
            while count < pos - 1:
                count+=1
                cur = cur.next;
            node.next = cur.next
            cur.next = node

    def remove(self,item):
        #刪除節點
        cur = self._head
        #pre:保存cur的上一個節點
        pre = None
        while cur != None:
            if cur.item == item:
                #肯定要刪除的節點
                if not pre:
                    #刪除的是第一個節點
                    #把當前節點的下一個節點看成首節點
                    self._head = cur.next
                else:
                    # 刪除的不是第一個節點
                    pre.next = cur.next
                break
            else:
                #不相等,不是要刪除的節點
                #遍歷下一個節點
                pre = cur
                cur = cur.next

    def search(self,item):
        #查找節點是否存在
        cur = self._head
        while cur != None:
            #斷定是否是要查找的節點
            if cur.item == item:
                return True
            else:
                cur = cur.next
        return False

if __name__ == '__main__':
    sl = SingleLinkList()
    sl.add(1)
    sl.add(2)
    sl.append(3)
    sl.insert(2,6)
    print('length:',sl.length())
    sl.travel()
    print(sl.search(2))
    sl.remove(2)
    print('length:', sl.length())

鏈表與順序表的對比

鏈表失去了順序表隨機讀取的優勢,同時鏈表因爲增長告終點的指針域,空間開銷比較大,但對存儲空間的使用要相對靈活。

6.4單向循環鏈表

單鏈表的一個變形是單向循環鏈表,鏈表中最後一個節點的next域再也不爲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 == None

    def length(self):
        """返回鏈表的長度"""
        # 若是鏈表爲空,返回長度0
        if self.is_empty():
            return 0
        count = 1
        cur = self._head
        while cur.next != 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.next != self._head:
            cur = cur.next
            print(cur.item)


    def add(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(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(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(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(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__":
    ll = SinCycLinkedlist()
    ll.add(1)
    ll.add(2)
    ll.append(3)
    ll.insert(2, 4)
    ll.insert(4, 5)
    ll.insert(0, 6)
    print("length:",ll.length())
    ll.travel()
    print(ll.search(3))
    print(ll.search(7))
    ll.remove(1)
    print("length:",ll.length())
    ll.travel()

6.5雙向鏈表

一種更復雜的鏈表是「雙向鏈表」或「雙面鏈表」。每一個節點有兩個連接:一個指向前一個節點,當此節點爲第一個節點時,指向空值;而另外一個指向下一個節點,當此節點爲最後一個節點時,指向空值。

class Node(object):
    #節點的類
    def __init__(self,item):
        self.item = item
        self.prev = None
        self.next = None

class DLinkList(object):
    #雙向鏈表的類
    def __init__(self):
        #指向鏈表的頭節點
        self._head = None

    def is_empty(self):
        #鏈表是否爲空
        return self._head == None


    def length(self):
        #鏈表長度
        cur = self._head
        #計數器
        count = 0
        while cur != None:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        #遍歷鏈表
        cur = self._head
        while cur != None:
            print(cur.item)
            cur = cur.next

    def add(self,item):
        #鏈表頭部添加
        node = Node(item)
        if self.is_empty():
            #若是是空鏈表,將_head指向node
            #給鏈表添加第一個元素
            self._head = node
        else:
            #若是鏈表不爲空,在新的節點和原來的首節點之間創建雙向連接
            node.next = self._head
            self._head.prev = node
            #讓_head指向鏈表的新的首節點
            self._head = node


    def append(self,item):
        #鏈表尾部添加
        #建立新的節點
        node = Node(item)
        if self.is_empty():
            #空鏈表,
            self._head = node
        else:
            #鏈表不爲空
            cur = self._head
            while cur.next != None:
                cur = cur.next
            #cur的下一個節點是node
            cur.next = node
            #node的上一個節點是
            node.prev = cur

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

    def remove(self,item):
        #刪除節點
        if self.is_empty():
            return
        else:
            cur = self._head
            if cur.item == item:
                #首節點是要刪除的節點
                if cur.next == None:
                    #說明鏈表中只有一個節點
                    self._head = None
                else:
                    #鏈表多於一個節點的狀況
                    cur.next.prev = None
                    self._head = cur.next
            else:
                # 首節點不是要刪除的節點
                while cur != None:
                    if cur.item == item:
                        cur.prev.next = cur.next
                        cur.next.prev = cur.prev
                        break
                    cur = cur.next

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


if __name__ == '__main__':
    dls = DLinkList()
    dls.add(10)
    dls.add(12)
    dls.append(15)
    dls.append(16)
    dls.insert(2,32)
    dls.insert(3,36)
    print('dls lenght:',dls.length())
    dls.travel()
    print(dls.search(15))
    dls.remove(32)
    print('dls length:',dls.length())
    dls.travel()

七、棧

棧(stack),有些地方稱爲堆棧,是一種容器,可存入數據元素、訪問元素、刪除元素,它的特色在於只能容許在容器的一端(稱爲棧頂端指標,英語:top)進行加入數據(英語:push)和輸出數據(英語:pop)的運算。沒有了位置概念,保證任什麼時候候能夠訪問、刪除的元素都是此前最後存入的那個元素,肯定了一種默認的訪問順序。
因爲棧數據結構只容許在一端進行操做,於是按照後進先出(LIFO, Last In First Out)的原理運做。

7.1棧結構實現

棧能夠用順序表實現,也能夠用鏈表實現。

7.2棧的操做

class Stack(object):
    #棧的實現
    def __init__(self):
        self.items = []

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

    def push(self,item):
        #壓棧:在棧中加入數據元素
        self.items.append(item)

    def pop(self):
        #彈棧操做:從棧中彈出元素
        return self.items.pop()

    def peek(self):
        #返回棧頂的數據元素
        return self.items[len(self.items)-1]

    def size(self):
        #返回棧的尺寸
        return len(self.items)

if __name__ == '__main__':
    stack = Stack()
    print(stack.is_empty())
    stack.push('hello')
    stack.push('python')
    stack.push('qiku')
    stack.push('zhengzhou')
    print(stack.is_empty())
    print(stack.size())
    print(stack.peek())
    print(stack.pop())
    print(stack.pop())
    print(stack.pop())
    print(stack.pop())

八、隊列

隊列(queue)是隻容許在一端進行插入操做,而在另外一端進行刪除操做的線性表。

隊列是一種先進先出的(First In First Out)的線性表,簡稱FIFO。

容許插入的一端爲隊尾,容許刪除的一端爲隊頭。隊列不容許在中間部位進行操做!假設隊列是q=(a1,a2,……,an),那麼a1就是隊頭元素,而an是隊尾元素。這樣咱們就能夠刪除時,老是從a1開始,而插入時,老是在隊列最後。這也比較符合咱們一般生活中的習慣,排在第一個的優先出列,最後來的固然排在隊伍最後。

8.1隊列的實現

同棧同樣,隊列也能夠用順序表或者鏈表實現。

8.2操做

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()
    q.enqueue("hello")
    q.enqueue("sssss")
    q.enqueue("aaaaa")
    print(q.size())
    print(q.is_empty())
    print(q.dequeue())
    print(q.size())
    print(q.dequeue())
    print(q.size())

九、雙端隊列

雙端隊列(deque,全名double-ended queue),是一種具備隊列和棧的性質的數據結構。
雙端隊列中的元素能夠從兩端彈出,其限定插入和刪除操做在表的兩端進行。雙端隊列能夠在隊列任意一端入隊和出隊。

#雙端列表的定義
class Deque(object):
    def __init__(self):
        self.items = []

    def add_front(self,item):
        #從隊頭加入一個item元素
        self.items.insert(0,item)

    def add_rear(self,item):
        #從隊尾加入一個item元素
        self.items.append(item)

    def remove_front(self):
        #從隊頭刪除一個item元素
        return self.items.pop(0)

    def remove_rear(self):
        #從隊尾刪除一個item元素
        return self.items.pop()

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

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


if __name__ == '__main__':
    deqeue = Deque()
    print(deqeue.is_empty())
    deqeue.add_front(22)
    deqeue.add_front(33)
    deqeue.add_rear(44)
    deqeue.add_rear(55)
    print(deqeue.is_empty())
    print(deqeue.size())
    print(deqeue.remove_front())
    print('size:',deqeue.size())
    print(deqeue.remove_rear())
    print('size:',deqeue.size())

十、排序算法

排序算法(英語:Sorting algorithm)是一種能將一串數據依照特定順序進行排列的一種算法。

10.1排序算法的穩定性

穩定性:穩定排序算法會讓本來有相等鍵值的紀錄維持相對次序。也就是若是一個排序算法是穩定的,當有兩個相等鍵值的紀錄R和S,且在本來的列表中R出如今S以前,在排序過的列表中R也將會是在S以前。

當相等的元素是沒法分辨的,好比像是整數,穩定性並非一個問題。然而,假設如下的數對將要以他們的第一個數字來排序。

(4, 1) (3, 1) (3, 7)(5, 6)

在這個情況下,有可能產生兩種不一樣的結果,一個是讓相等鍵值的紀錄維持相對的次序,而另一個則沒有:

(3, 1) (3, 7) (4, 1) (5, 6) (維持次序)

(3, 7) (3, 1) (4, 1) (5, 6) (次序被改變)

不穩定排序算法可能會在相等的鍵值中改變紀錄的相對次序,可是穩定排序算法歷來不會如此。不穩定排序算法能夠被特別地實現爲穩定。做這件事情的一個方式是人工擴充鍵值的比較,如此在其餘方面相同鍵值的兩個對象間之比較,(好比上面的比較中加入第二個標準:第二個鍵值的大小)就會被決定使用在原先數據次序中的條目,看成一個同分決賽。然而,要記住這種次序一般牽涉到額外的空間負擔。

10.2冒泡排序

冒泡排序(英語:Bubble Sort)是一種簡單的排序算法。它重複地遍歷要排序的數列,一次比較兩個元素,若是他們的順序錯誤就把他們交換過來。遍歷數列的工做是重複地進行直到沒有再須要交換,也就是說該數列已經排序完成。這個算法的名字由來是由於越小的元素會經由交換慢慢「浮」到數列的頂端。

冒泡排序算法的運做以下:

  1. 比較相鄰的元素。若是第一個比第二個大(升序),就交換他們兩個。
  2. 對每一對相鄰元素做一樣的工做,從開始第一對到結尾的最後一對。這步作完後,最後的元素會是最大的數。
  3. 針對全部的元素重複以上的步驟,除了最後一個。
  4. 持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。
def bubble_sort(alist):
    for j in range(len(alist)-1,0,-1):
        # j表示每次遍歷須要比較的次數,是逐漸減少的
        for i in range(j):
            if alist[i] > alist[i+1]:
                alist[i], alist[i+1] = alist[i+1], alist[i]

li = [54,26,93,17,77,31,44,55,20]
bubble_sort(li)
print(li)

時間複雜度

  • 最優時間複雜度:O(n) (表示遍歷一次發現沒有任何能夠交換的元素,排序結束。)
  • 最壞時間複雜度:O(n2)
  • 穩定性:穩定

10.2選擇排序

選擇排序(Selection sort)是一種簡單直觀的排序算法。

它的工做原理以下:

  1. 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,
  2. 再從剩餘未排序元素中繼續尋找最小(大)元素
  3. 放到已排序序列的末尾。
  4. 以此類推,直到全部元素均排序完畢。

選擇排序的主要優勢與數據移動有關。若是某個元素位於正確的最終位置上,則它不會被移動。選擇排序每次交換一對元素,它們當中至少有一個將被移到其最終位置上,所以對n個元素的表進行排序總共進行至多n-1次交換。在全部的徹底依靠交換去移動元素的排序方法中,選擇排序屬於很是好的一種。

def selection_sort(alist):
    n = len(alist)
    # 須要進行n-1次選擇操做
    for i in range(n-1):
        # 記錄最小位置
        min_index = i
        # 從i+1位置到末尾選擇出最小數據
        for j in range(i+1, n):
            if alist[j] < alist[min_index]:
                min_index = j
        # 若是選擇出的數據不在正確位置,進行交換
        if min_index != i:
            alist[i], alist[min_index] = alist[min_index], alist[i]

alist = [54,226,93,17,77,31,44,55,20]
selection_sort(alist)
print(alist)

時間複雜度

  • 最優時間複雜度:O(n2)
  • 最壞時間複雜度:O(n2)
  • 穩定性:不穩定(考慮升序每次選擇最大的狀況)

10.3插入排序

插入排序(英語:Insertion Sort)是一種簡單直觀的排序算法。它的工做原理是經過構建有序序列,對於未排序數據,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在實現上,在從後向前掃描過程當中,須要反覆把已排序元素逐步向後挪位,爲最新元素提供插入空間。

def insert_sort(alist):
    # 從第二個位置,即下標爲1的元素開始向前插入
    for i in range(1, len(alist)):
        # 從第i個元素開始向前比較,若是小於前一個元素,交換位置
        for j in range(i, 0, -1):
            if alist[j] < alist[j-1]:
                alist[j], alist[j-1] = alist[j-1], alist[j]

alist = [54,26,93,17,77,31,44,55,20]
insert_sort(alist)
print(alist)

時間複雜度

  • 最優時間複雜度:O(n) (升序排列,序列已經處於升序狀態)
  • 最壞時間複雜度:O(n2)
  • 穩定性:穩定
相關文章
相關標籤/搜索