數據結構與算法

 定義
咱們如何把現實中大量並且很是複雜的問題以特定的數據類型(個體)和特定的存儲結構(個體的關係)保存到相應的主存儲器(內存)中,以及在此基礎上爲實現某個功能而執行的相應操做,這個相應的操做也叫作算法

數據結構 == 個體 + 個體的關係  node

算法 == 對存儲數據的操做python

程序 = 數據的存儲 + 數據的操做 + 能夠被計算機執行的語言算法

衡量算法的標準
  • 時間複雜度 指的是大概程序執行的次數,而非程序執行的時間
  • 空間複雜度 指的是程序執行過程當中,大概所佔有的最大內存
  • 難易程度
  • 健壯性

常見的幾個排序(基於Python實現):數據庫

def BubbleSort(li):
    for i in range(len(li)): #i 0 - 8
        flag = False
        for j in range(len(li)-i-1): #j 0 - 7
            if li[j] > li[j+1]:# li[0] > li[1]
                li[j],li[j+1] = li[j+1],li[j] # [5,7,4,6.....]
                flag = True
        if not flag:
            print('你這是有序的')
            return
冒泡排序
def SelectSort(li):
    for i in range(len(li)):
        minLoc = i
        for j in range(i+1,len(li)):
            if li[j] < li[minLoc]:
                li[j],li[minLoc] = li[minLoc],li[j]
選擇排序
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]
            # print(li)
            j = j-1
        li[j+1] = tmp
插入排序
def partition(li,left,right):
    tmp= li[left]
    while left < right:
        while left < right and li[right] >= tmp:
            right = right-1
        li[left] = li[right]
        while left <right and li[left] <= tmp:
            left = left+1
        li[right] = li[left]
    li[left] = tmp
    return left

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)
快速排序
# 歸併排序 '分治法'
# O(nlogn)
# O(n) 空間複雜度
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:
        ltmp.append(li[j])
        j +=1
    li[low:high+1] = ltmp

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(n)
def count_sort(li):
    count = [0 for i in range(11)]
    print(count)
    for index in li:
        count[index] +=1
    print(count)
    li.clear()
    print(li)
    for index,val in enumerate(count):
        for i in range(val):
            li.append(index)
計數排序

小結:數組

常見的查找:瀏覽器

# 順序查找
O(n)
def lineSearch(li,val):
    for i in range(len(li)):
        if li[i] == val:
            return i
順序查找
# 二分查找,有序的。無序的也能夠
# O(logn)
def binSearch(li,low,high,val):
    if low < high :
        mid = (low+high) // 2
        if li[mid] == val:
            return mid
        elif li[mid] > val:
            binSearch(li,low,mid-1,val)
        elif li[mid] < val:
            binSearch(li,mid + 1,high,val)
    else:
        return -1
二分查找

應用場景:數據結構

  • 各類榜單
  • 各類表格
  • 給二分查找用
  • 給其餘算法用
from timeit import Timer def t1(): li = [] for i in range(10000): li.append(i) def t2(): li = [] for i in range(10000): li = li + [i] def t3(): li = [ i for i in range(10000)] def t4(): li = list(range(10000)) def t5(): li = [] for i in range(10000): li.extend([i]) tm1 = Timer("t1()", "from __main__ import t1") print("append:", tm1.timeit(1000)) tm2 = Timer("t2()", "from __main__ import t2") print("+:", tm2.timeit(1000)) tm3 = Timer("t3()", "from __main__ import t3") print("[i for i in range]:", tm3.timeit(1000)) tm4 = Timer("t4()", "from __main__ import t4") print("list:", tm4.timeit(1000)) tm5 = Timer("t5()", "from __main__ import t5") print("extend:", tm5.timeit(1000)) ### 測試往隊頭和隊尾進行添加
def t6(): li = [] for i in range(10000): li.append(i) def t7(): li = [] for i in rnage(10000): li.insert(0,i) tm6 = Timer("t6()", "from __main__ import t6") print("append:", tm6.timeit(1000)) tm7 = Timer("t7()", "from __main__ import t7") print("insert:", tm7.timeit(1000))
timeit的使用

算法的思想:app

  • 窮舉法 brute force
  • 分治法 divide conquer
  • 模擬
  • 遞歸
  • 貪心 
  • 動態規劃

線性結構

把全部的節點用一根線串起來
數組和鏈表的區別
數組須要一塊連續的內存空間來存儲,對內存的要求比較高。若是咱們申請一個 100MB 大小的數組,當內存中沒有連續的、足夠大的存儲空間時,即使內存的剩餘總可用空間大於 100MB,仍然會申請失敗。 而鏈表偏偏相反,它並不須要一塊連續的內存空間,它經過「指針」將一組零散的內存塊串聯起來使用,因此若是咱們申請的是 100MB 大小的鏈表,根本不會有問題。

數組和鏈表的區別.jpg

連續存儲(數組)
數組,在其python語言中稱爲列表,是一種基本的數據結構類型

關於列表的問題:ide

  • 數組(列表)中的元素是如何存儲的
  • 列表提供了哪些最基本的操做?
  • 這些操做的時間複雜度是多少?
  • 爲啥數組(列表)的索引或者下標是從0開始?

關於數組(列表)的優缺點函數

  • 優勢:
    • 存取速度快
  • 缺點:
    • 事先須要知道數組的長度
    • 須要大塊的連續內存
    • 插入刪除很是的慢,效率極低
離散存儲(鏈表)

1.定義:

  • n個節點離散分配
  • 彼此經過指針相連
  • 每一個節點只有一個前驅節點,每一個節點只有一個後續節點
  • 首節點沒有前驅節點,尾節點沒有後續節點

優勢:

  • 空間沒有限制,插入刪除元素很快

缺點:

  • 查詢比較慢

鏈表的節點的結構以下:


2.專業術語:

data爲自定義的數據,next爲下一個節點的地址。

  • 首節點:第一個有效節點
  • 尾節點:最後一個有效節點
  • 頭結點:第一個有效節點以前的那個節點,頭結點並不存儲任何數據,目的是爲了方便對鏈表的操做
  • 頭指針:指向頭結點的指針變量
  • 尾指針:指向尾節點的指針變量

3.鏈表的分類:

  • 單鏈表
  • 雙鏈表 每個節點有兩個指針域
  • 循環鏈表 能經過任何一個節點找到其餘全部的節點
  • 非循環鏈表

4.算法:

  • 增長
  • 刪除
  • 修改
  • 查找
  • 總長度

有一堆數據1,2,3,5,6,7咱們要在3和5之間插入4,若是用數組,咱們會怎麼作?固然是將5以後的數據日後退一位,而後再插入4,這樣很是麻煩,可是若是用鏈表,我就直接在3和5之間插入4就行,聽着就很方便

5.若是但願經過一個函數來對鏈表進行處理操做,至少須要接受鏈表的哪些參數?

只須要一個參數,頭結點便可,由於咱們能夠經過頭結點來推算出鏈表的其餘全部的參數

6.單鏈表的算法

class Hero(object):
    def __init__(self, no=0, name="", nickname="", pNext=None):
        self.no = no
        self.name = name
        self.nickname = nickname
        self.pNext = pNext
def getHero(head, no):
    cur = head
    while cur.pNext != None:
        if cur.no == no:
            print("找到的英雄的編號是: %s,姓名是:%s,外號是:%s" % (cur.no, cur.name, cur.nickname))
            break
        cur = cur.pNext
    else:
        print("沒有這個英雄")
def add(head, hero):
    # 1. 直接在鏈表的最後加上
    # 先找到鏈表的最後
    cur = head
    # while cur.pNext != None:
    #     cur = cur.pNext
    # # 當退出鏈表的時候,就算是隊尾了
    # cur.pNext = hero
    # 2. 指定位置進行添加
    while cur.pNext != None:
        if cur.pNext.no >= hero.no:
            # 找到位置
            break
        # 繼續往下走
        cur = cur.pNext
    hero.pNext = cur.pNext
    cur.pNext = hero
def showAll(head):
    cur = head
    while cur.pNext != None:
        print("英雄的編號是: %s,姓名是:%s,外號是:%s" % (cur.pNext.no,cur.pNext.name,cur.pNext.nickname))
        cur = cur.pNext
def delHero(head, no):
    cur = head
    while cur.pNext != None:
        if cur.pNext.no == no:
            # 開始刪除
            cur.pNext = cur.pNext.pNext
            break
        cur = cur.pNext
    else:
        print('沒有找到')
def updateHero(head, no, name):
    cur = head
    while cur.pNext != None:
        if cur.pNext.no == no:
            cur.pNext.name = name
            break
        cur = cur.pNext
    else:
        print('沒有找到英雄')
def is_empty(head):
    if head.pNext == None:
        return True
    return False
def length(head):
    cnt = 0
    cur = head
    while cur.pNext != None:
        cnt = cnt + 1
        cur = cur.pNext
    return cnt
# 建立一個head頭,該head只是一個頭,不存放數據
head = Hero()
# print(is_empty(head))
hero = Hero(1, '宋江', '及時雨')
# head.pNext = hero
add(head, hero)
hero = Hero(2, '盧俊義', '玉麒麟')
# hero.pNext = hero2
add(head, hero)
hero = Hero(6, '林沖', '豹子頭')
add(head, hero)
hero = Hero(3, '吳用', '智多星')
add(head, hero)
# 遍歷全部的英雄
showAll(head)
# delHero(head, 3)
# print('刪除以後的排列:')
updateHero(head,3,'張三')
print('###############修改以後的排列:################')
showAll(head)
print(length(head))
# getHero(head, 3)
View Code

7.雙向鏈表

雙鏈表中每一個節點有兩個指針:一個指向後面節點、一個指向前面節點

 

class Node(object):   def __init__(self, data=None):     self.data = data     self.next = None     self.prior = None

插入:雙向鏈表的操做:

p.next = curNode.next curNode.next.prior = p curNode.next = p p.prior = curNode

刪除:

p = curNode.next curNode.next = p.next p.next.prior = curNode del p

8.循環鏈表

循環鏈表是另外一種形式的鏈式存貯結構。它的特色是表中最後一個結點的指針域指向頭結點,整個鏈表造成一個環。

9.約瑟夫問題

設編號爲1,2,… n的n我的圍坐一圈,約定編號爲k(1<=k<=n)的人從1開始報數,數到m 的那我的出列,它的下一位又從1開始報數,數到m的那我的又出列,依次類推,直到全部人出列爲止,由此產生一個出隊編號的序列
 循環鏈表 class Child(object): first = None def __init__(self, no = None, pNext = None): self.no = no self.pNext = pNext def addChild(self, n=4): cur = None for i in range(n): child = Child(i + 1) if i == 0: self.first = child self.first.pNext = child cur = self.first else: cur.pNext = child child.pNext = self.first cur = cur.pNext def showChild(self): cur = self.first while cur.pNext != self.first: print("小孩的編號是:%d" % cur.no) cur = cur.pNext print("小孩的編號是: %d" % cur.no) def countChild(self, m, k): tail = self.first while tail.pNext != self.first: tail = tail.pNext # 出來後,已是在first前面
        # 從第幾我的開始數
        for i in range(k-1): tail = tail.pNext self.first = self.first.pNext # 數兩下,就是讓first和tail移動一次
        # 數三下,就是讓first和tail移動兩次
        while tail != self.first:  # 當tail == first 說明只剩一我的
            for i in range(m-1): tail = tail.pNext self.first = self.first.pNext self.first = self.first.pNext tail.pNext = self.first print("最後留在圈圈中的人是:%d" % tail.no) c = Child() c.addChild(4) c.showChild() c.countChild(3,2)
View Code
 
數組和鏈表的性能比較

線性結構的兩種應用方式之棧
棧的定義
一種能夠實現「先進後出」的存儲結構

棧相似於一個箱子,先放進去的書,最後才能取出來,同理,後放進去的書,先取出來

棧的定義.png

棧的分類
  • 靜態棧

    • 靜態棧的核心是數組,相似於一個連續內存的數組,咱們只能操做其棧頂元素
  • 動態棧

    • 動態棧的核心是鏈表

棧的分類.png

棧的算法

棧的算法主要是壓棧和出棧兩種操做的算法,下面我就用代碼來實現一個簡單的棧。

首先要明白如下思路:

  • 棧操做的是一個一個節點
  • 棧自己也是一種存儲的數據結構
  • 棧有初始化、壓棧、出棧、判空、遍歷、清空等主要方法
class Stack(object): def __init__(self): self.pTop = None self.pBottom = None class Node(object): def __init__(self, data=None, pNext = None): self.data = data self.pNext = pNext def push(s, new): new.pNext = s.pTop s.pTop = new def pop(s): cur = s.pTop # while cur != s.pBottom:
    # if cur.data == val:
    # s.pTop = cur.pNext
    # break
    # cur = cur.pNext
    # else:
    # print('沒有找到此元素')
    while cur != s.pBottom: s.pTop = cur.pNext print("出棧的元素是: %d" % cur.data) cur = cur.pNext else: print("出棧失敗") def getAll(s): cur = s.pTop while cur != s.pBottom: print(cur.data) cur = cur.pNext def is_empty(s): if s.pTop == s.pBottom: return True else: return False def clear(s): if is_empty(s): return None p = s.pTop q = None while p != s.pBottom: q = p.pNext del p p = q else: s.pBottom = s.pTop head = Node() s = Stack() s.pTop = s.pBottom = head n1 = Node(2) push(s, n1) n1 = Node(5) push(s, n1) n1 = Node(89) push(s, n1) print("##############遍歷元素##########") getAll(s) # print("##############出棧元素#######") # pop(s)
print("##############清空棧##########") clear(s) print("##############遍歷元素##########") getAll(s)
View Code
 
棧的應用
  • 函數調用
  • 瀏覽器的前進或者後退
  • 表達式求值
  • 內存分配
線性結構的兩種應用方式之隊列
隊列的定義
一種能夠實現「先進先出」的數據結構
隊列的分類
  • 鏈式隊列
  • 靜態隊列
鏈式隊列的算法
class Node: def __init__(self, value): self.data = value self.next = None class Queue: def __init__(self): self.front = Node(None) self.rear = self.front def enQueue(self, element): n = Node(element) self.rear.next = n self.rear = n def deQueue(self): if self.empty(): print('隊空') return temp = self.front.next self.front = self.front.next if self.rear == temp: self.rear = self.front del temp def getHead(self): if self.empty(): print('隊空') return
        return self.front.next.data def empty(self): return self.rear == self.front def printQueue(self): cur = self.front.next while cur != None: print(cur.data) cur = cur.next def length(self): cur = self.front.next count = 0 while cur != None: count += 1 cur = cur.next return count if __name__ == '__main__': queue = Queue() queue.enQueue(23) queue.enQueue(2) queue.enQueue(4) queue.printQueue() queue.deQueue() # queue.printQueue()
    l = queue.length() print("長度是: %d" % l) queue.deQueue() # queue.printQueue()
    l = queue.length() print("長度是: %d" % l) queue.deQueue() # queue.printQueue()
    l = queue.length() print("長度是: %d" % l)
View Code  
隊列的實際應用
全部和時間有關的操做都和隊列有關
遞歸 定義
一個函數本身或者間接調用本身
多個函數調用
當有多個函數調用時,按照「先調用後返回」的原則,函數之間的信息傳遞和控制轉移必須藉助棧來實現,即系統將整個程序運行時所須要的數據空間安排在一個棧中,每當調用一個函數時,就在棧頂分配一個存儲區,進行壓棧操做,每當一個函數退出時,就釋放他的存儲區,即進行出棧操做,當前運行的函數永遠在棧頂的位置
def f():   print('FFFFFFF')   g() def g():   print('GGGGGGG')   k() def k():   print('KKKKKKK') if __name__ == "__main__":   f()
def f(n):   if n == 1:     print('hello')   else:     f(n-1)

本身調用本身也是和上面的原理同樣

遞歸知足的條件
  • 遞歸必須有一個明確的終止條件
  • 該函數所處理的數據規模是必須遞減的
  • 這個轉化必須是可解的
求階乘
n規模的實現,得益於n-1規模的實現
# for循環實現
multi = 1
for i in range(3):   multi = multi * (i+1)   print(multi) # 遞歸實現
 
def f(n):   if 1 == n:     return 1
  else:     return f(n-1)*n
1+2+3…+100的和
def sum(n):   if 1 == n:     return n   else:     return sum(n-1) + n
遞歸和循環的區別
  • 遞歸
    • 不易於理解
    • 速度慢
    • 存儲空間大
  • 循環
    • 易於理解
    • 速度快
    • 存儲空間小
遞歸的應用

樹和森林就是以遞歸的方式定義的
樹和圖的算範就是以遞歸的方式實現的
不少數學公式就是以遞歸的方式實現的(斐波那楔序列)

階段總結

數據結構:
從狹義的方面講:

  • 數據結構就是爲了研究數據的存儲問題
  • 數據結構的存儲包含兩個方面:個體的存儲 + 個體關係的存儲

從廣義方面來說:

  • 數據結構既包含對數據的存儲,也包含對數據的操做
  • 對存儲數據的操做就是算法

算法
從狹義的方面講:

  • 算法是和數據的存儲方式有關的

從廣義的方面講:

  • 算法是和數據的存儲方式無關,這就是泛型的思想

非線性結構:樹

樹的定義

咱們能夠簡單的認爲:

  • 樹有且僅有一個根節點
  • 有若干個互不相交的子樹,這些子樹自己也是一顆樹

通俗的定義:
1.樹就是由節點和邊組成的
2.每個節點只能有一個父節點,但能夠有多個子節點。但有一個節點例外,該節點沒有父節點,此節點就稱爲根節點

樹的專業術語
  • 節點
  • 父節點
  • 子節點
  • 子孫
  • 堂兄弟
  • 兄弟
  • 深度
    • 從根節點到最底層節點的層數被稱爲深度,根節點是第一層
  • 葉子節點
    • 沒有子節點的節點
    • 子節點的個數
樹的分類
  • 通常樹
    • 任意一個節點的子節點的個數不受限制
  • 二叉樹
    • 定義:任意一個節點的子節點的個數最可能是兩個,且子節點的位置不可更改
      • 滿二叉樹
        • 定義:在不增長層數的前提下,沒法再多添加一個節點的二叉樹
      • 徹底二叉樹
        • 定義:只是刪除了滿二叉樹最底層最右邊連續的若干個節點
      • 通常二叉樹
  • 森林
    • n個互不相交的數的集合

二叉樹.jpg

樹的操做(僞算法)

如何把一個非線性結構的數據轉換成一個線性結構的數據存儲起來?

  • 通常樹的存儲
    • 雙親表示法
      • 求父節點方便
    • 孩子表示法
      • 求子節點方便
    • 雙親孩子表示法
      • 求父節點和子節點都很方便
    • 二叉樹表示法
      • 即把通常樹轉換成二叉樹,按照二叉樹的方式進行存儲
      • 具體的轉化辦法:
        • 設法保證任意一個節點的:
          • 左指針域指向它的第一個孩子
          • 右指針域指向它的下一個兄弟
        • 只要能知足上述的條件就可以轉化成功
  • 二叉樹的操做

    • 連續存儲 (徹底二叉樹,數組方式進行存儲)
      • 優勢:查找某個節點的父節點和子節點很是的快
      • 缺點:耗用內存空間過大
      • 轉化的方法:先序 中序 後序
    • 鏈式存儲 (鏈表存儲)
      • data區域 左孩子區域 右孩子區域
  • 森林的操做

    • 把全部的樹轉化成二叉樹,方法同通常樹的轉化
二叉樹具體的操做

1.二叉樹的先序遍歷[先訪問根節點]

  • 先訪問根節點
  • 再先序遍歷左子樹
  • 再先序遍歷右子樹

2.二叉樹的中序遍歷 [中間訪問根節點]

  • 先中序遍歷左子樹
  • 再訪問根節點
  • 再中序遍歷右子樹

3.二叉樹的後序遍歷 [最後訪問根節點]

  • 先中序遍歷左子樹
  • 再中序遍歷右子樹
  • 再訪問根節點

4.已知先序和中序,如何求出後序?

#### 例一
先序:ABCDEFGH 中序:BDCEAFHG 求後序? 後序:DECBHGFA #### 例二
先序:ABDGHCEFI 中序:GDHBAECIF 求後序? 後序:GHDBEIFCA

5.已知中序和後序,如何求出先序?

中序:BDCEAFHG
後序:DECBHGFA
 
求先序?
先序:ABCDEFGH
class Node(object): """節點類"""
    def __init__(self, elem=-1, lchild=None, rchild=None): self.elem = elem self.lchild = lchild self.rchild = rchild class Tree(object): """樹類"""
    def __init__(self): self.root = Node() self.myli = [] def add(self, elem): """爲樹添加節點""" node = Node(elem) if self.root.elem == -1:  # 若是樹是空的,則對根節點賦值
            self.root = node self.myli.append(self.root) else: treeNode = self.myli[0]  # 此結點的子樹尚未齊。
            if treeNode.lchild == None: treeNode.lchild = node self.myli.append(treeNode.lchild) else: treeNode.rchild = node self.myli.append(treeNode.rchild) self.myli.pop(0) def front_digui(self, root): """利用遞歸實現樹的先序遍歷"""
        if root == None: return
        print(root.elem) self.front_digui(root.lchild) self.front_digui(root.rchild) def middle_digui(self, root): """利用遞歸實現樹的中序遍歷"""
        if root == None: return self.middle_digui(root.lchild) print(root.elem) self.middle_digui(root.rchild) def later_digui(self, root): """利用遞歸實現樹的後序遍歷"""
        if root == None: return self.later_digui(root.lchild) self.later_digui(root.rchild) print(root.elem) if __name__ == '__main__': """主函數""" elems = range(10)           #生成十個數據做爲樹節點
    tree = Tree()               #新建一個樹對象
    for elem in elems: tree.add(elem) #逐個添加樹的節點
    print('遞歸實現先序遍歷:') tree.front_digui(tree.root)
View Code
樹的應用
  • 樹是數據庫中數據組織的一種重要形式
  • 操做系統子父進程的關係自己就是一顆樹
  • 面型對象語言中類的繼承關係

總結

數據結構研究的就是數據的存儲和數據的操做的一門學問

數據的存儲又分爲兩個部分:

  • 個體的存儲
  • 個體關係的存儲

從某個角度而言,數據存儲最核心的就是個體關係如何進行存儲
個體的存儲能夠忽略不計

相關文章
相關標籤/搜索