動態

詳情 返回 返回

數據結構與算法 - 動態 詳情

一、算法

1.1、算法基礎

概念:算法是獨⽴存在的⼀種解決問題的⽅法和思想
算法的特性:

  1. 輸入:算法具有0個或多個輸⼊
  2. 輸出: 算法⾄少有1個或多個輸出
  3. 有窮性: 算法在有限的步驟之後會⾃動結束⽽不會⽆限循環,並且每⼀個步驟可以在可接受的時間內完成
  4. 確定性:算法中的每⼀步都有確定的含義,不會出現⼆義性
  5. 可⾏性:算法的每⼀步都是可⾏的,也就是説每⼀步都能夠執⾏有限的次數完成

1.2、算法效率衡量

⼤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)

理解“⼤O記法”

對於算法的時間性質和空間性質,最重要的是其數量級和趨勢,這些是分析
算法效率的主要部分。⽽計量算法基本操作數量的規模函數中那些常量因⼦
可以忽略不計。例如,可以認為3n^2 和100n^2 屬於同⼀個量級,如果兩個算法
處理同樣規模實例的代價分別為這兩個函數,就認為它們的效率“差不多”,
都為n^2 級
複雜度分類 解釋説明 分析哪個複雜度為最優
最優時間複雜度 算法完成⼯作最少需要多少基本操作 價值不⼤,因為它沒有提供什麼有⽤信息,其反映的只是最樂觀最理想的情況,沒有參考價值
最壞時間複雜度 算法完成⼯作最多需要多少基本操作 提供了⼀種保證,表明算法在此種程度的基本操作中⼀定能完成⼯作
平均時間複雜度 算法完成⼯作平均需要多少基本操作 對算法的⼀個全⾯評價,因此它完整全⾯的反映了這個算法的性質。但另⼀⽅⾯,這種衡量並沒有保證,不是每個計算都能在這個基本操作內完成。⽽且,對於平均情況的計算,也會因為應⽤算法的實例分佈可能並不均勻⽽難以計算

因此,只需關注算法的最壞情況,亦即最壞時間複雜度

時間複雜度的計算規則:
1.基本操作,即只有常數項,認為其時間複雜度為O(1)
2.順序結構,時間複雜度按加法進⾏計算
3.循環結構,時間複雜度按乘法進⾏計算
4.分⽀結構,時間複雜度取最⼤值
5.判斷⼀個算法的效率時,往往只需要關注操作數量的最⾼次項,其它次要項和常數項可以忽略
6.在沒有特殊説明時,我們所分析的算法的時間複雜度都是指最壞時間複雜度

空間複雜度S(n)

空間複雜度(SpaceComplexity)是對⼀個算法在運⾏過程中臨時佔⽤存儲空間⼤⼩的量度
算法的時間複雜度和空間複雜度合稱為算法的複雜度

1.3、常見的時間複雜度

執行次數函數 非正式術語
100 O(1) 常數階
2n+1 O(n) 線性階
3n²+2n+1 O(n²) 平方階
4n³+3n²+2n+1 O(n³) 立方階
2^n O(2^n) 指數階
5log2n + 1 O(logn) 對數階
3nlog2n + 2n + 1 O(nlogn) nlogn階

注意,將log2n(以2為底的對數)簡寫成logn

常⻅時間複雜度之間的關係

image.png

所消耗的時間從⼩到⼤
O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2^n) < O(n!) <O(n^n)

二、數據結構

2.1、數據結構基礎

數據是⼀個抽象的概念,將其進⾏分類後得到程序設計語⾔中的基本類型。如:int,float,char,bool等。
數據元素之間不是獨⽴的,存在特定的關係,這些關係便是結構(eg:一個人有名字和年齡,名字和年齡都屬於這個人,因此數據元素不是獨立的)
數據結構指數據對象中數據元素之間的關係

2.2、算法和數據結構的區別

算法 數據結構
⾼效的程序需要在數據結構的基礎上設計和選擇算法 數據結構只是靜態的描述了數據元素之間的關係
算法是為了解決實際問題⽽設計的 數據結構是算法需要處理的問題載體

綜上:程序 = 數據結構 + 算法

2.3、抽象數據類型(ADT)

抽象數據類型 面向對象中的封裝
指⼀個數學模型以及定義在此數學模型上的⼀組操作 class包含屬性和方法,屬性就是存儲的數據,方法就是對數據的操作
抽象數據類型就相當於面向對象中的封裝,因此相當於class的概念

常用的數據運算:插入、刪除、修改、查找、排序

2.4、基本的數據結構

2.4.1、順序表

2.4.1.1、順序表概念相關

將一組元素看成一個序列,用元素在序列裏的位置和順序,表示實際應用中的某種有意義的信息,或者表示數據之間的某種關係,這樣的一組序列元素的組織形式,抽象的稱為線性表,一個線性表是某類元素的一個集合,還記錄着元素之間的一種順序關係;

線性表兩種存儲形式:

  1. 順序表,將元素順序地存放在⼀塊連續的存儲區⾥,元素間的順序關係由它們的存儲順序⾃然表示
  2. 鏈表,將元素存放在通過鏈接構造起來的⼀系列存儲塊中
2.4.1.2、順序表的形式

image.png

圖a 圖b
順序表的基本形式,數據元素本身連續存儲,每個元素所佔的存儲單元⼤⼩固定相同 如果元素的⼤⼩不統⼀,則須採⽤圖b的元素外置的形式,將實際數據元素另⾏存儲,⽽順序表中各單元位置保存對應元素的地址信息(即鏈接)
元素的下標是其邏輯地址,⽽元素存儲的物理地址(實際內存地址)可以通過存儲區的起始地址Loc (e )加上邏輯地址(第i個元素)與存儲單元⼤⼩(c)的乘積計算⽽得,即:Loc(e ) = Loc(e ) + c* 圖b中的c不再是數據元素的⼤⼩,⽽是存儲⼀個鏈接地址所需的存儲量,這個量通常很⼩

**訪問指定元素時⽆需從頭遍歷,通過計算便可獲得對應地址,其時間復
雜度為O(1)**

2.4.1.3、順序表的結構與實現

結構:
image.png
順序表的完整信息包含兩個部分:

1、表中的元素集合
2、實現正確操作而需記錄的信息(有關表的整體情況的信息,
這部分信息主要包括存儲區的**容量**和當前表中已有的**元素個數**)

兩種實現方式:
image.png

圖a 圖b
⼀體式結構,存儲表信息的單元與元素存儲區以連續的⽅式安排在⼀塊存儲區⾥,兩部分數據的整體形成⼀個完整的順序表對象 分離式結構,表對象⾥只保存與整個表有關的信息(即容量和元素個數),實際數據元素存放在另⼀個獨⽴的元素存儲區⾥,通過鏈接與基本表對象關聯。
⼀體式結構整體性強,易於管理。但是由於數據元素存儲區域是表對象的⼀部分,順序表創建後,元素存儲區就固定了

元素存儲區替換

⼀體式結構 分離式結構
由於順序表信息區與數據區連續存儲在⼀起,所以若想更換數據區,則只能整體搬遷,即整個順序表對象(指存儲順序表的結構信息的區域)改變了 若想更換數據區,只需將表信息區中的數據區鏈接地址更新即可,⽽該順序表對象不變

元素存儲區擴充

採⽤分離式結構的順序表,若將數據區更換為存儲空間更⼤的區域,則可以
在不改變表對象的前提下對其數據存儲區進⾏了擴充,所有使⽤這個表的地
⽅都不必修改,這種技術實現的順序表稱為動態順序表,因為其容量可以在使⽤中動態變化

擴充的兩種策略:

每次擴充增加固定數⽬ 每次擴充容量加倍
每次擴充增加固定數⽬的存儲位置,如每次擴充增加10個元素位置,這種策略可稱為線性增⻓ 每次擴充容量加倍,如每次擴充增加⼀倍存儲空間
特點:節省空間,但是擴充操作頻繁,操作次數多 特點:減少了擴充操作的執⾏次數,但可能會浪費空間資源。以空間換時間,推薦的⽅式
2.4.1.4、順序表的操作

增加元素:
a、尾端加⼊元素,時間複雜度為O(1)
b、保序的元素加⼊,時間複雜度為O(n)

刪除元素:
a、刪除表尾元素,時間複雜度為O(1)
b、保序的元素刪除,時間複雜度為O(n)

2.4.1.5、Python中的順序表
list和tuple兩種類型採⽤了順序表的實現技術,tuple是不可變類型,即不變的順序表,因此不⽀持改變其內部狀態的任何操作,⽽其他⽅⾯,則與list的性質類似

2.4.2、鏈表

1.定義
鏈表(Linked list)是⼀種常⻅的基礎數據結構,是⼀種線性表,但是不像順
序表⼀樣連續存儲數據,⽽是在每⼀個節點(數據存儲單元)⾥存放下⼀個
節點的位置信息(即地址)
image.png

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

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

3.節點的實現與節點的操作實現

class SingleNode(object):
    """
    單鏈表的節點實現
    """

    def __int__(self, item):
        self.item = item
        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
        while cur is not None:
            count += 1
            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(self, item):
        node = SingleNode()
        node.item = item
        node.next = self.__head
        self.__head = node

    # 尾部添加元素
    def append(self, item):
        node = SingleNode()
        node.item = item
        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(self, pos, item):
        if pos <= 0:
            self.add(item)
        elif pos >= self.length():
            self.append(item)
        else:
            cur = self.__head
            count = 0
            node = SingleNode()
            node.item = item
            while count < (pos - 1):
                count += 1
                cur = cur.next
            node.next = cur.next
            cur.next = node

    # 刪除元素
    def remove(self, item):
        cur = self.__head
        pre = None
        while cur is not None:
            if cur.item == item:
                if cur == self.__head:
                    self.__head = cur.next
                else:
                    pre.next = cur.next
                return
            pre = cur
            cur = cur.next

    # 查找元素
    def search(self, item):
        cur = self.__head
        while cur is not None:
            if cur.item == item:
                return True
            cur = cur.next
        return False

2.4.3、雙向鏈表

每個節點有兩個鏈接:⼀個指向前⼀個節點,當此節點為第⼀個節點時,指向空值;⽽另⼀個指向下⼀個節點,當此節點為最後⼀個節點時,指向空值
image.png

雙向鏈表實現與操作:

class DoubleNode(object):
    """
    雙向鏈表的節點實現
    """

    def __int__(self, item):
        self.item = item
        self.next = None
        self.pre = None


class DoubleLinkList(object):
    """
    雙向鏈表的操作實現
    """

    def __init__(self):
        self.__head = None

    # 判斷鏈表為空
    def is_empty(self):
        return self.__head is None

    # 鏈表長度
    def length(self):
        cur = self.__head
        count = 0
        while cur is not None:
            count += 1
            cur = cur.next
        return count

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

    # 查找元素
    def search(self, item):
        cur = self.__head
        while cur is not None:
            if cur.item == item:
                return True
            cur = cur.next
        return False

    # 頭部添加元素
    def add(self, item):
        node = DoubleNode()
        node.item = item
        node.next = self.__head
        self.__head = node
        if node.next:
            node.next.pre = node

    # 尾部添加元素
    def append(self, item):
        node = DoubleNode()
        node.item = item
        if self.is_empty():
            self.__head = node
        else:
            cur = self.__head
            while cur.next is not None:
                cur = cur.next
            node.pre = cur
            cur.next = node

    # 指定位置添加元素
    def insert(self, pos, item):
        if pos <= 0:
            self.add(item)
        elif pos >= self.length():
            self.append(item)
        else:
            cur = self.__head
            count = 0
            node = DoubleNode()
            node.item = item
            while count < pos:
                count += 1
                cur = cur.next
            node.next = cur
            node.pre = cur.pre
            cur.pre.next = node
            cur.pre = node

    # 刪除元素
    def remove(self, item):
        cur = self.__head
        while cur is not None:
            if cur.item == item:
                if cur == self.__head:
                    self.__head = cur.next
                    if cur.next:
                        self.__head.pre = None
                else:
                    cur.pre.next = cur.next
                    if cur.next:
                        cur.next.pre = cur.pre
                return
            cur = cur.next

2.4.4、單向循環鏈表

單鏈表的⼀個變形是單向循環鏈表,鏈表中最後⼀個節點的next域不再為
None,⽽是指向鏈表的頭節點

image.png

單向循環鏈表的實習與操作實現:

class SingleNode(object):
    """
    單向循環鏈表的節點實現
    """

    def __int__(self, item):
        self.item = item
        self.next = None


class SingleLinkList(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
        cur = self.__head
        count = 1
        while cur.next != self.__head:
            count += 1
            cur = cur.next
        return count

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

    # 頭部添加元素
    def add(self, item):
        node = SingleNode()
        node.item = item
        if self.is_empty():
            self.__head = node
            node.next = node
        cur = self.__head
        while cur.next != self.__head:
            cur = cur.next
        node.next = self.__head
        self.__head = node
        cur.next = self.__head

    # 尾部添加元素
    def append(self, item):
        node = SingleNode()
        node.item = item
        if self.is_empty():
            self.__head = node
            node.next = node
        else:
            cur = self.__head
            while cur.next != self.__head:
                cur = cur.next
            cur.next = node
            node.next = self.__head

    # 指定位置添加元素
    def insert(self, pos, item):
        if pos <= 0:
            self.add(item)
        elif pos >= self.length():
            self.append(item)
        else:
            cur = self.__head
            count = 0
            node = SingleNode()
            node.item = item
            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 = self.__head
        pre = None
        while cur.next != self.__head:
            if cur.item == item:
                if cur == self.__head:
                    rear = self.__head
                    while rear.next != self.__head:
                        rear = rear.next
                    self.__head = cur.next
                    rear.next = self.__head
                else:
                    pre.next = cur.next
                return
            pre = cur
            cur = cur.next
        if cur.item == item:
            if cur == self.__head:
                self.__head = None
            else:
                pre.next = self.__head

    # 查找元素
    def search(self, item):
        if self.is_empty():
            return False
        cur = self.__head
        while cur.next != self.__head:
            if cur.item == item:
                return True
            cur = cur.next
        if cur.item == item:
            return True
        return False

2.4.5、棧

棧(stack),也稱為堆棧,是⼀種容器,可存⼊數據元素、訪問元素、刪除元素,它的特點在於只能允許在容器的⼀端(稱為棧頂端指標,英語:top)進⾏加⼊數據(英語:push)和輸出數據(英語:pop)的運算。
由於棧數據結構只允許在⼀端進⾏操作,因⽽按照後進先出(LIFO, Last In First Out)的原理運作。

image.png

棧結構實現:可以⽤順序表實現,也可以⽤鏈表實現
棧的操作:

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[-1]

    # 返回棧的長度
    def size(self):
        return len(self.__items)

2.4.6、隊列

隊列(queue)是隻允許在⼀端進⾏插⼊操作,⽽在另⼀端進⾏刪除操作的線性表。
隊列是⼀種先進先出的(First In First Out)的線性表,簡稱FIFO。允許插⼊
的⼀端為隊尾,允許刪除的⼀端為隊頭。隊列不允許在中間部位進⾏操作!
假設隊列是q=(a1,a2,……,an),那麼a1就是隊頭元素,⽽an是隊尾
元素。這樣我們就可以刪除時,總是從a1開始,⽽插⼊時,總是在隊列最
後。這也⽐較符合我們通常⽣活中的習慣,排在第⼀個的優先出列,最後來
的當然排在隊伍最後。

隊列操作:

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)

2.4.7、雙端隊列

雙端隊列(deque,全名double-ended queue),是⼀種具有隊列和棧的性質的數據結構。
雙端隊列中的元素可以從兩端彈出,其限定插⼊和刪除操作在表的兩端進⾏。
雙端隊列可以在隊列任意⼀端⼊隊和出隊

image.png

Add a new 評論

Some HTML is okay.