資料結構基礎01-基本概念和術語/線性表
本文系列
基本概念和術語
資料(data): 所有能輸入到計算機中去的描述客觀事物的符號。
- 數值性資料
- 非數值性資料(多媒體資訊處理)
資料元素(data element): 資料的基本單位,也稱結點(node) 或記錄(record)
資料項(data item): 有獨立含義的資料最小單位,也稱域(field)
資料 > 資料元素 > 資料項
例:
學生表 > 個人記錄 > 學號、姓名…
資料物件(Data Object): 相同特性資料元素的集合, 是資料的一個子集。
【例1】 整數資料物件
N = { 0, ±1, ±2, … }
【例2】 學生資料物件 學生記錄的集合
資料結構(Data Structure) 是相互之間存在一種或多種特 定關係的資料元素的集合。
資料結構是帶“結構”的資料元素的集合,“結構”就是指 資料元素之間存在的關係。
邏輯結構: 資料之間的相互關係。
集合 結構中的資料元素除了同屬於一種類型外,別無其它關係。
線性結構 資料元素之間一對一的關係
樹形結構 資料元素之間一對多的關係
圖狀結構或網狀結構 結構中的資料元素之間存在多對多的關係
物理結構
物理結構/儲存結構: 資料在計算機中的表示。
物理結構是描述資料具體在記憶體中的儲存(如:順序結構、鏈式結構、索引結構、雜湊結構)等
在資料結構中,從邏輯上可以將其分為線性結構和非線性結構
資料結構的基本操作的設定的最重要的準則是,實現應用程式與儲存結構的獨立。
實現應用程式是“邏輯結構”,儲存的是“物理結構”。
邏輯結構主要是對該結構操作的設定,物理結構是描述資料具體在記憶體中的儲存(如:順序結構、鏈式結構、索引結構、希哈結構)等。
順序儲存結構中,線性表的邏輯順序和物理順序總是一致的。但在鏈式儲存結構中,線性表的邏輯順序和物理順序一般是不同的。
什麼是資料結構
程式= 資料結構 + 演算法
資料結構為演算法服務,演算法作用於資料結構之上。
資料結構的定義
一門研究非數值計算的程式設計問題中計算機的操作物件以及 它們之間的關係和操作等等的學科。
演算法
演算法——是能被機械地執行的動作(或稱規則、指令)的有序集合。
演算法五個特性:
- 有窮性:演算法應在執行有窮步後結束
- 確定性:每步定義都必須有確定的含義,不能有二義性
- 可行性:每一條運算應足夠基本
- 輸入:有0個或多個輸入
- 輸出:有一個或多個輸出(處理結果)
演算法設計要求:
- 正確性
- 可讀性:演算法應該層次分明,思路清晰,易於人的理解
- 健壯性
- 高效率與低儲存量需求。(好的演算法)
演算法的描述有偽程式、流程圖、N-S結構圖等。E-R圖是實體聯絡模型,不是程式的描述方式。
對演算法是否“正確”的理解可以有以下四個層次:
1 程式中不含語法錯誤;
2 程式對於幾組輸入資料能夠得出滿足要求的結果;
3 程式對於精心選擇的、典型、苛刻且帶有刁難性的幾組 輸入資料能夠得出滿足要求的結果;
4 程式對於一切合法的輸入資料都能得出滿足要求的結果;
設計演算法在執行時間時需要考慮: 演算法選用的規模、問題的規模
一個特定演算法的“執行工作量”的大小,只依賴於問題的 規模(通常用整數n表示),或者說,它是問題規模 n 的 函式。
時間複雜度的漸進表示法
假如,隨著問題規模 n 的增長,演算法執行時間的增長率和 f(n) 的增長
率相同,則可記作:
T(n) = O(f(n))
稱 T(n) 為演算法的(漸近)時間複雜度。
漸進符號(O)的定義:當且僅當存在一個正的常數C和n0,使得對所 有的 n ≥ n0 ,有 T(n) ≤ Cf(n),則 T(n) = O(f(n))
時間複雜度: 演算法的執行時間與原操作執行次數之和成正比。
時間複雜度由小到大:O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3)
冪次時間複雜度由小到大:O(2n) < O(n!) < O(nn)
空間複雜度: 若輸入資料所佔空間只取決於問題本身,和演算法無關,則只需要分析除輸入和程式之外的輔助變數所佔額外空間。
演算法的儲存量包括:
(1)輸入資料所佔空間
(2)程式本身所佔空間
(3)輔助變數所佔空間
演算法的空間複雜度定義為:
S(n) = O(g(n)) 漸進空間複雜度
演算法執行所需儲存空間的增長率與 g(n) 的增長率相同
線性表
結構的儲存
- 元素本身的儲存;
- 元素之間關係的儲存。
線性表的特點
(1) 存在唯一的一個被稱為“第一個”的資料元素;
(2) 存在唯一的一個被稱為“最後一個”的資料元素;
(3) 除第一個外,集合中的每個資料元素均且只有一個前驅;
(4) 除最後一個外,集合中的每個元素均有且只有一個後繼。
線性表的定義
- n個型別相同的資料元素構成的有限序列,記作a1,a2,…,an ;
- 其中1,2, …,n是元素的序號,表示元素在表中的位置;
- n為元素的總個數;
- a1,a2,…,an 為資料元素;
- a1表示線性起點,an表示線性終點;
- ai-1是ai的直接前驅,ai+1是ai的直接後繼; 當n等於0,稱為空表。
同一性、有限性、有序性。
線性表的儲存方式
順序表:用順序方式儲存的線性表
連結串列:是用鏈式方式儲存的線性表。
順序表
把線性表的結點按邏輯順序依次存放在一組地址連續的儲存單元裡。用這種方法儲存的線性表簡稱順序表。是一種隨機存取的儲存結構。
順序儲存指記憶體地址是一塊的,隨機存取指訪問時可以按下標隨機訪問,儲存和存取是不一樣的。如果是儲存,則是指按順序的,如果是存取,則是可以隨機的,可以利用元素下標進行。
陣列比線性錶速度更快的是:原地逆序、返回中間節點、選擇隨機節點。
便於線性表的構造和任意元素的訪問
插入:插入新結點,之後結點後移。平均時間複雜度:O(n)
刪除:刪除節點,之後結點前移。平均時間複雜度:O(n)
順序儲存元素地址計算
可以在已知第一個元素的儲存起始地址,以及每個元素所佔用儲存單元個
數的基礎上,通過一個公式來直接計算表中任意指定元素的儲存地址:
LOC(ai)=LOC(a1)+(i-1)*d
d : 一個元素佔用的儲存單元個數
LOC(ai) : 線性表第i個元素的起始地址
LOC(a1) : 起始地址,基地址
順序儲存的特點
邏輯上相鄰 —— 實體地址相鄰
隨機存取
順序表的結構體資料型別
typedef struct{
Elemtype* elem; // 儲存的是陣列第一個元素的地址
int length; // 順序表的當前長度
}SqList; //定義了結構體資料型別SqList,用於表示順序表 資料型別
順序表的查詢操作
查詢的兩種情況
(1) 根據給定元素的序號進行查詢(通過陣列下標定位)
(2) 根據給定的元素值進行查詢
查詢的基本思想
將給定的元素e和順序表中的每個元素依次進行比較:
若找到與e相等的元素,則查詢成功,返回其在表中的“位序”值;
若找遍整個順序表,都沒有找到與e相等的元素,則查詢失敗,返回值0。
int Locate_Sq ( SqList L, ElemType e )
{
i = 1; n = L.length;
while ( i <= n && e != L.elem[i] )
i++;
if ( i <= n && e== L.elem[i] )
return(i);
else
return(0);
}
順序表的插入演算法:
int Insert_Sq(SqList &L, int i, Elemtype x)
{
if(i<1||i>L.length+1)
return ERROR; //i值不合法
if ( L.length >= MAXSIZE-1 )
return ERROR; // 儲存空間已滿
for ( k=L.length; k>=i; k-- )
L.elem[k+1] = L.elem[k];
L.elem[i] = x; L.length++;
return OK;
}
順序表的刪除演算法:
Status Delete_Sq(SqList &L, int i, Elemtype &e)
{
if(i<1||i>L.length)
return ERROR; //i值不合法
e = L.elem[i];
for( k=i; k<=L.length-1; k++ )
L.elem[k]=L.elem[k+1];
L.Length -- ;
return OK;
}
連結串列
用一組任意的儲存單元來依次存放線性表的結點,這組儲存單元即可以是連續的,也可以是不連續的,甚至是零散分佈在記憶體中的任意位置上的。
因此,連結串列中結點的邏輯次序和物理次序不一定相同。為了能正確表示結點間的邏輯關係,在儲存每個結點值的同時,還必須儲存指示其後繼結點的地址。
data域是資料域,用來存放結點的值。
next是指標域(亦稱鏈域),用來存放結點的直接後繼的地址(或位置)。
不需要事先估計儲存空間大小。
順序表的優點
1 邏輯上相鄰的兩個元素在物理位置上也相鄰;
2 可隨機存取;
3 它的儲存位置可用一個簡單直觀的公式來表示。
順序表的缺點
1 需預分較大空間;
2 插入、刪除需移動大量元素;
3 表的容量難以擴充。
連結串列分類
線性連結串列(單鏈表)
迴圈連結串列
雙向連結串列
單鏈表的定義
單鏈表中每個結點的儲存地址是存放在其前趨結點next域中,而開始結點無前趨,故應設頭指標head指向開始結點。同時,由於最後一個結點無後繼,故結點的指標域為空,即NULL。
頭插法建表(逆序)、尾插法建表(順序)。
增加頭結點的目的是演算法實現上的方便,但增大了記憶體開銷。
1鏈式儲存的線性表;
2每個結點中只含有一個指標域,用來指出其後繼結點的位置;
3最後一個結點沒有後繼,它的指標域為空(記為NULL或^);
4設定一個表頭指標head(H),指向連結串列的第一個結點。
概念
首結點:用於儲存線性表中第一個資料元素的結點
頭結點:連結串列的首結點前附加的一個結點
頭指標:是指向單鏈表的第一個結點的指標。
typedef struct LNode {
ElemType data; //資料域
struct LNode *next; //指標域
} LNode,* LinkList;
LinkList: 單鏈表
LNode * :指向單鏈表中任意結點的指標變數
LNode *p; <==> LinkList p;
查詢:只能從連結串列的頭指標出發,順鏈域next逐個結點往下搜尋,直到搜尋到第i個結點為止。因此,連結串列不是隨機存取結構。
插入:先找到表的第i-1的儲存位置,然後插入。新結點先連後繼,再連前驅。
刪除:首先找到ai-1的儲存位置p。然後令p–>next指向ai的直接後繼結點,即把ai從鏈上摘下。最後釋放結點ai的空間.r=p->next;p->next=r->next;delete r。
判斷一個單向連結串列中是否存在環的最佳方法是快慢指標。
靜態連結串列
用一維陣列來實現線性連結串列,這種用一維陣列表示的線性連結串列,稱為靜態連結串列。
靜態:體現在表的容量是一定的。(陣列的大小);
連結串列:插入與刪除同前面所述的動態連結串列方法相同。
靜態連結串列中指標表示的是下一元素在陣列中的位置。
靜態連結串列是用陣列實現的,是順序的儲存結構,在實體地址上是連續的,而且需要預先分配大小。
動態連結串列是用申請記憶體函式(C是malloc,C++是new)動態申請記憶體的,所以在連結串列的長度上沒有限制。
動態連結串列因為是動態申請記憶體的,所以每個節點的實體地址不連續,要通過指標來順序訪問。
靜態連結串列在插入、刪除時也是通過修改指標域來實現的,與動態連結串列沒有什麼分別
迴圈連結串列
是一種頭尾相接的連結串列。從表中任一結點出發均可找到表中其他結點,提高查詢效率。
在單鏈表中,將終端結點的指標域NULL改為指向表頭結點的或開始結點,就得到了單鏈形式的迴圈連結串列,並簡單稱為單迴圈連結串列。
由於迴圈連結串列中沒有NULL指標,故涉及遍歷操作時,其終止條件就不再像非迴圈連結串列那樣判斷p或p—>next是否為空,而是判斷它們是否等於某一指定指標,如頭指標或尾指標等。
雙向連結串列
雙向連結串列:在單鏈表的每個結點裡再增加一個指向其直接前趨的指標域prior。
這樣就形成的連結串列中有兩個方向不同的鏈。
雙鏈表一般由頭指標唯一確定的,將頭結點和尾結點連結起來構成迴圈連結串列,並稱之為雙向連結串列。
設指標p指向某一結點,則雙向連結串列結構的對稱性可用下式描述:
p—>prior—>next=p=p—>next—>prior。
從兩個方向搜尋雙鏈表,比從一個方向搜尋雙鏈表的方差要小。
插入:先搞定插入節點的前驅和後繼,再搞定後結點的前驅,最後搞定前結點的後繼。
在有序雙向連結串列中定位刪除一個元素的平均時間複雜度為O(n)
可以直接刪除當前指標所指向的節點。
而不需要像單向連結串列中,刪除一個元素必須找到其前驅。因此在插入資料時,單向連結串列和雙向連結串列操作複雜度相同,而刪除資料時,雙向連結串列的效能優於單向連結串列