《大話數據結構》——學習筆記(概述&線性表)

數據

數據:是描述客觀事物的符號,是計算機中可以操作的對象,是能被計算機識別,並輸入給計算機處理的符號集合

數據元素

數據元素:是組成數據的、有一定意義的基本單位,在計算機中通常作爲整體處理,也被稱爲記錄

數據項

數據項:一個數據元素可以由若干個數據項組成,數據項是數據不可分割的最小單位

數據對象

數據對象:是性質相同的數據元素的集合,是數據的子集

數據結構

不同數據元素之間不是獨立的,而是存在特定的關係,我們將這些關係稱爲結構

數據結構:是相互之間存在一種或多種特定關係的數據元素的集合

數據結構

邏輯結構

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

集合結構

集合結構:集合結構中的數據元素除了同屬於一個集合外,它們之間沒有其他關係。各個數據元素是”平等”的,類似於數學中的集合

線性結構

線性結構:線性結構中的數據元素之間是一對一的關係

樹形結構

樹形結構:樹形結構中的數據元素之間存在一種一對多的層次關係

圖形結構

圖形結構:圖形結構的數據元素是多對多的關係

物理結構

物理結構(存儲結構):是指數據的邏輯結構在計算機中的存儲形式

順序存儲結構

順序存儲結構:是把數據元素存放在地址連續的存儲單元裏,其數據間的邏輯關係和物理關係是一致的

鏈式存儲結構

鏈式存儲結構:是把數據元素存放在任意的存儲單元裏,這組存儲單元可以是連續的,也可以是不連續的,數據元素的存儲關係並不能反映其邏輯關係,因此需要用一個指針存放數據元素的地址,這樣通過地址就可以找到相關數據元素的位置

數據類型

數據類型:是指一組性質相同的值的集合及定義在此集合上的一些操作的總稱

在C語言中,按照取值的不同,數據類型可以分爲兩類

原子類型

是不可以再分解的基本類型,包括整型、字符型等

結構類型

由若干個類型組合而成,是可以再分解的,例如整型數組

抽象數據類型

抽象數據類型(Abstract Data Type, ADT):是指一個數學模型及定義在該模型上的一組操作,如”整數”類型->整型

抽象數據類型體現了程序設計中問題分解、抽象和信息隱藏的特性

算法

算法是解決特定問題求解步驟的描述,在計算機中表現爲指令的有限序列,並且每條指令表示一個或多個操作

算法的特性

算法具有五個基本特性:輸入、輸出、有窮性、確定性和可行性

  • 算法具有零個或多個輸入
  • 算法至少有一個或多個輸出
  • 算法在執行有限的步驟後,自動結束而不會出現無限循環,並且每一個步驟在可接受的時間內完成
  • 算法的每一步驟都具有確定的含義,不會出現二義性
  • 算法的每一步都必須是可行的,也就是說,每一步都能夠通過執行有限次數完成

正確性

正確性:算法的正確性是指算法至少應該具有輸入、輸出和加工處理無歧義性、能正確反映問題的需求、能夠得到問題的正確答案

正確性大體分爲以下四個層次:

  • 算法程序沒有語法錯誤
  • 算法程序對於合法的輸入數據能夠產生滿足要求的輸出結果
  • 算法程序對於非法的輸入數據能夠產生滿足規格說明的結果(一般情況下,將此作爲判斷一個算法是否正確的標準)
  • 算法程序對於精心選擇的,甚至刁難的測試數據都有滿足要求的輸出結果

可讀性

可讀性:算法設計的另一個目的是爲了便於閱讀、理解和交流

健壯性

健壯性:當輸入數據不合法時,算法也能做出相關處理,而不是產生異常或莫名其妙的結果

時間效率高和存儲量低

設計算法應該儘量滿足時間效率高和存儲量低的需求

算法效率的度量方法

  • 事後統計方法

    這種方法主要是通過設計好的測試程序和數據,利用計算機計時器對不同算法編制的程序的運行時間進行比較,從而確定算法效率的高低

    缺陷較大,不予採納

  • 事前分析估算方法

    在計算機程序編制前,依據統計方法對算法進行估算

    一個程序的運行時間,依賴於算法的好壞和問題的輸入規模

    在分析程序的運行時間時,最重要的是把程序看成是獨立於程序設計語言的算法或一系列步驟

函數的漸近增長

函數的漸近增長:給定兩個函數f(n)和g(n),如果存在一個整數N,使得對於所有的n > N,f(n)總是比g(n)大,那麼,我們說f(n)的增長漸近快於g(n)

判斷一個算法的效率時,函數中的常數和其他次要項常常可以忽略,而更應該關注主項(最高階項)的階數

算法時間複雜度

定義

在進行算法分析時,語句總是執行次數T(n)是關於問題模型n的函數,進而分析T(n)隨n的變化情況並確定T(n)的數量級,算法的時間複雜度,也就是算法的時間量度,記作:T(n)=O(f(n))。它表示隨問題規模n的增大,算法執行時間的增長率和f(n)的增長率相同,稱作算法的漸近時間複雜度,簡稱爲時間複雜度,其中f(n)是問題規模n的某個函數

一般情況下,隨着n的增大,T(n)增長最慢的算法爲最優算法

  • 常數階:O(1)

    不管n爲多少,執行的次數都是恆定的,不會隨着n的變大而發生變化,其時間複雜度爲O(1)

  • 線性階:O(n)

    循環結構中的代碼需要執行n次,其時間複雜度爲O(n)

    for(i = 0; i < n; i++){
    
    }
    
  • 對數階:O(logn)

    有多少個2相乘後大於n,則會退出循環

    2^x=n
    int count = 1;
    while(count < n){
        count = count * 2;
    }
    
  • 平方階

    兩層循環嵌套

    int i,j;
    for(i = 0; i < n; j++){
        for(j = i; j < n; j++){
    
        }
    }
    

常見的時間複雜度

<img shuju_001>

算法空間複雜度

算法的空間複雜度通過計算算法所需的存儲空間實現,算法空間複雜度的計算公式記作:S(n)=O(f(n)),其中,n爲問題的規模,f(n)爲語句關於n所佔存儲空間的函數

當不用限定詞地使用”複雜度”時,通常都是指時間複雜度

線性表

定義

線性表(List):零個或多個數據元素的有限序列

除第一個元素外,每一個元素有且只有一個直接前驅元素,除了最後一個元素外,每一個元素有且只有一個直接後繼元素,數據元素之間的關係是一對一的關係

在較複雜的線性表中,一個數據元素可以由若干個數據項組成

線性表的順序存儲結構

線性表的順序存儲結構,指的是用一段地址連續的存儲單元一次存儲線性表的數據元素

順序存儲方式

在內存中佔據一定的內存空間,然後把相同數據類型的數據元素依次存放在這塊空地中

屬性:

  • 存儲空間的起始位置:數組data,它的存儲位置就是存儲空間的存儲位置
  • 線性表的最大存儲容量:數組長度MaxSize
  • 線性表的當前長度:length

存儲器中的每個存儲單元都有自己的編號,這個編號稱爲地址

對於每個線性表位置的存入或者取出數據,對於計算機來說都是相等的時間(LOC(ai)=LOC(a1)+(i1)c ),也就是一個常數,因此它的時間複雜度爲O(1)

順序存儲結構的插入與刪除

插入:

  • 如果插入位置不合理,拋出異常
  • 如果線性表長度大於等於數組長度,則拋出異常或動態增加容量
  • 從最後一個元素開始向前遍歷到第i個位置,分別將它們都向後移動一個位置
  • 將要插入元素填入位置i處
  • 表長加1

刪除:

  • 如果刪除位置不合理,拋出異常
  • 取出刪除元素
  • 從刪除元素位置開始遍歷到最後一個元素位置,分別將它們都向前移動一個位置
  • 表長度減1

插入或刪除時,平均移動次數和最中間的那個元素移動次數相等,爲(n-1)/2

插入和刪除時,時間複雜度爲O(n)

線性表順序存儲結構的優缺點

優點:

  • 無須爲表示表中元素之間的邏輯關係而增加額外的存儲空間
  • 可以快速地存取表中任一位置的元素

缺點:

  • 插入和刪除操作需要移動大量元素
  • 當線性表長度變化較大時,難以確定存儲空間的容量
  • 造成存儲空間的”碎片”

線性表的鏈式存儲結構

線性表的鏈式存儲結構的特點是用一組任意的存儲單元存儲線性表的數據元素,這組存儲單元可以是連續的,也可以是不連續的,這就意味着,這些數據元素可以存在內存未被佔用的任意位置

鏈式結構中,除了要存儲數據元素信息外,還要存儲它的後繼元素的存儲地址

<img shuju_002>

鏈表中第一個結點的存儲位置叫做頭指針,之後的每一個結點,其實就是上一個的後繼指針指向的位置,最後一個結點的指針爲null

爲了更加方便地對鏈表進行操作,有時會在單鏈表的第一個結點前附設一個結點,稱爲頭結點,頭結點的數據域可以不存儲任何信息,也可以存儲如線性表的長度等附加信息,頭結點的指針域存儲指向第一個結點的指針

頭指針與頭結點的異同:

  • 頭指針是指鏈表指向第一個結點的指針,若鏈表有頭結點,則是指向頭結點的指針
  • 頭指針具有標識作用,所以常用頭指針冠以鏈表的名字
  • 無論鏈表是否爲空,頭指針均不爲空,頭指針是鏈表的必要元素
  • 頭結點是爲了操作的統一和方便而設立的,放在第一元素的結點之前,其數據域一般無意義(也可存放鏈表的長度)
  • 有了頭結點,對在第一元素結點前插入結點和刪除第一結點,其操作與其它結點的操作就統一了
  • 頭結點不一定是鏈表必須要素

單鏈表的讀取

單鏈表中查找某一個元素,必須要從頭開始找

獲得鏈表第i個數據的算法思路:

  • 聲明一個結點p指向鏈表第一個結點,初始化j從1開始
  • 當j<i時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一結點,j累加1,
  • 若到鏈表末尾p爲空,則說明第i個元素不存在
  • 否則查找成功,返回結點p的數據

時間複雜度爲O(n)

單鏈表的插入與刪除

<img shuju_003>

在ai和ai+1之間插入一個數據,只需要

s->next=p->next;
p->next=s;

單鏈表第i個數據插入結點的算法思路:

  • 聲明一結點p指向鏈表第一個結點,初始化j從1開始
  • 當j<i時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一結點,j累加1
  • 若到鏈表末尾p爲空,則說明第i個元素不存在
  • 否則查找成功,在系統中生成一個空結點s
  • 將數據元素e賦值給s->data
  • 單鏈表的插入標準語句s->next=p->next;p->next=s;
  • 返回成功

<img shuju_004>

在ai-1和ai+1之間刪除ai結點,只需要

q=p->next;
p->next=q->next;

單鏈表第i個數據刪除結點的算法思路:

  • 聲明一結點p指向鏈表第一個結點,初始化j從1開始
  • 當j<i時,就遍歷鏈表,讓p的指針向後移動,不斷指向下一個節點,j累加1
  • 若到鏈表末尾p爲空,則說明第i個元素不存在
  • 否則查找成功,將欲刪除的結點p->next賦值給q
  • 單鏈表的刪除標準語句p->next=q->next
  • 將q結點中的數據賦值給e,作爲返回
  • 釋放q結點
  • 返回成功

單鏈表在查詢、插入和刪除操作上的時間複雜度都是O(n),但是如果需要從第i個位置,插入10個元素,對於順序存儲結構意味着,每一次插入都需要移動n-i個元素,每次都是O(n),而單鏈表,只需要在第一次時,找到第i個位置的指針,此時爲O(n),接下來的時間複雜度都是O(1),所以對於插入或者刪除數據越頻繁的操作,單鏈表的效率優勢就越是明顯

單鏈表的整表創建

單鏈表整表創建的算法思路:

  • 聲明一結點p和計數器變量i
  • 初始化一空鏈表L
  • 讓L的頭結點的指針指向NULL,即建立一個帶頭結點的單鏈表
  • 循環:

    • 生成一新結點賦值給p
    • 隨機生成一數字賦值給p的數據域p->data
    • 將p插入到頭結點與前一新結點之間(頭插法,始終讓新結點在第一的位置),也可以將p插入到終端結點的後面(尾插法)

單鏈表的整表刪除

單鏈表整表刪除的算法思路如下:

  • 聲明一結點p和q
  • 將第一個結點賦值給p
  • 循環

    • 將下一結點賦值給q
    • 釋放p
    • 將q賦值給p

單鏈表結構與順序存儲結構優缺點

  • 存儲分配方式

    • 順序存儲結構用一段連續的存儲單元依次存儲線性表的數據元素
    • 單鏈表採用鏈式存儲結構,用一組任意的存儲單元存放線性表的元素
  • 時間性能

    • 查找

      • 順序存儲結構O(1)
      • 單鏈表O(n)
    • 插入和刪除

      • 順序存儲結構需要平均移動表長一半的元素,時間爲O(n)
      • 單鏈表在線出某位置的指針後,插入和刪除時間僅爲O(1)
  • 空間性能

    • 順序存儲結構需要預分配存儲空間,分大了,浪費,分小了易發生上溢
    • 單鏈表不需要分配存儲空間,只要有就可以分配,元素個數也不受限制

靜態鏈表

用數組描述的鏈表叫做靜態鏈表(數組中的元素由兩個數據域組成,data和cur)

<img shuju_005>

數組中的第一個元素(下標爲0)的cur存放備用鏈表的第一個結點的下標(即下一個元素插入存放的位置),數組的最後一個元素的cur則存放第一個有數值的元素的下標(即存放鏈頭的位置)

靜態鏈表的插入操作

將元素”丙”插入到”乙”和”丁”之間

<img shuju_006>

靜態鏈表的刪除操作

將元素”甲”刪除

<img shuju_007>

靜態鏈表優缺點

  • 優點

    • 在插入和刪除操作時,只需要修改遊標,不需要移動元素,從而改進了在順序存儲結構中的插入和刪除操作需要移動大量元素的缺點
  • 缺點

    • 沒有解決連續存儲分配帶來的表長難以確定的問題
    • 失去了順序存儲結構隨機存儲的特性

循環鏈表

將單鏈表中終端結點的指針端由空指針改爲指向頭指針,就使整個單鏈表形成一個環,這種頭尾相接的單鏈表稱爲單循環鏈表,簡稱循環鏈表

雙向鏈表

雙向鏈表是在單鏈表的每個結點中,再設置一個指向其前驅結點的指針域

非空的循環的帶頭結點的雙向鏈表如下圖所示

<img shuju_008>

雙向鏈表在插入和刪除時,需要更改兩個指針變量

<img shuju_009>

s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;

刪除結點p,只需要下面兩步驟

<img shuju_010>

p->prior->next = p->next;
p->next->prior = p->prior;
free(p);
發佈了84 篇原創文章 · 獲贊 135 · 訪問量 41萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章