大話數據結構學習筆記 - 線性表、順序存儲、單鏈表及靜態鏈表
定義
線性表(List): 零個或多個數據元素的有限序列
數學定義: 若將線性表記爲 , 則表中 領先於 , 領先於 , 稱 是 的直接前驅元素, 是 的直接後繼元素。 當有 時, 僅有一個直接後繼, 當 時, 有且僅有一個直接前驅
線性表的抽象數據類型
ADT 線性表(List)
Data 線性表的數據對象集合爲 , 每個元素的類型均爲
DataType
。 其中, 除第一個元素 外,每一個元素有且只有一個直接前驅元素,除了最後一個元素 外,每一個元素有且只有一個直接後繼元素。數據元素之間的關係是一對一的關係Operation
InitList(*L); 初始化操作, 建立一個空的線性表 L。
ListEmpty(L); 若線性表爲空,返回
true
, 否則返回false
ClearList(*L); 將線性表清空
GetElem(L, i, *e); 將線性表
L
中的第i
個位置元素值返回給e
LocateElem(L, e); 在線性表
L
中查找與給定值e
相等的元素,如果查找成功,返回鈣元素在表中序號表 示成功;否則返回
0
表示失敗 ListInsert(*L, i, e); 在線性表
L
中的第i
個位置插入新元素e
ListDelete(*L, e, *e); 刪除線性表
L
中第i
的位置元素, 並用e
返回其值 ListLength(L); 返回線性表
L
的元素個數endADT
線性表的順序存儲結構
即用一段地址連續的存儲單元依次存儲線性表的數據元素
#define MAXSIZE 20 // 存儲空間初始分配量
typedef int ElemType; // ElemType 類型根據實際情況而定, 這是設爲 int
typedef struct
{
ElemType data[MAXSIZE]; // 數組存儲數據元素, 最大值爲 MAXSIZE
int length; // 線性表當前長度
} SqList;
由上可知,順序存儲結構需要三個屬性
- 存儲空間的起始位置:數組
data
, 它的存儲位置就是存儲空間的存儲位置 - 線性表的最大存儲容量:數組長度
MaxSize
- 線性表的當前長度:
length
順序存儲結構的插入操作
插入算法的思路:
- 如果插入位置不合理,拋出異常
- 如果線性表長度大於等於數組長度,則拋出異常或動態增加容量
- 從最後一個元素開始向前遍歷到第
i
個位置,分別將它們都向後移動一個位置 - 將要插入元素填入位置
i
處 - 表長加
1
// 操作結果:在 L 中第 i 個位置插入新的數據元素 e, L 的長度加 1
Status ListInsert(SqList *L, int i, ElemType e)
{
if(L->length == MAXSIZE) // 順序線性表已滿
return ERROR;
if(i < 1 || i > L->length + 1) // 當插入位置 i 不在範圍內時
return ERROR;
if(i <= L->length) // 若插入位置不在表尾
{
for(int k = L->length - 1; k >= i - 1; k--) // 將要插入位置後數據元素向後移動一位
L->data[k + 1] = L->data[k];
}
L->data[i - 1] = e; // 插入新元素
L->length++;
return OK;
}
順序存儲結構的刪除操作
刪除算法的思路:
- 如果刪除位置不合理,拋出異常
- 取出刪除元素
- 從刪除元素位置開始遍歷到最後一個位置,分別將它們都向前移動一個位置
- 表長減
1
Status ListDelete(SqList *L, int i, ElemType *e)
{
if(L->length == 0)
return ERROR;
if(i < 1 || i > L->length)
return ERROR;
*e = L->data[i - 1];
if(i < L->length)
{
for(int j = i - 1; j < L->length - 1; j++)
L->data[j] = L->data[j + 1];
}
L->length--;
}
線性表順序存儲結構的優缺點
線性表的鏈式存儲結構
定義
爲了表示每個數據元素 與其直接後繼數據元素 之間的邏輯關係,對數據元素 來說, 除了存儲其本身的信息之外,還需存儲一個指示其直接後繼的信息(即直接後繼的存儲位置)。我們把存儲數據元素信息的域成爲數據域,把存儲直接後繼位置的域稱爲指針域。指針域中存儲的信息稱作指針或鏈。這兩部分信息組成數據元素 的存儲映像,稱爲結點(Node
)
n
個結點( 的存儲映像)鏈結成一個鏈表,即爲線性表( )的鏈式存儲結構, 因爲此鏈表的每個節點中只包含一個指針域,所以叫單鏈表
鏈表中第一個結點的存儲位置叫做頭指針。單鏈表的第一個結點前附設一個結點,稱爲頭結點
頭指針與頭結點的異同
單鏈表
無頭結點的單鏈表
有頭結點的單鏈表
單鏈表結構
結點由存放數據元素的數據域以及存放後繼結點地址的指針域組成
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;
單鏈表的插入
單鏈表第i
個數據插入節點的算法思路:
- 聲明一個結點
p
指向鏈表第一個結點, 初始化j
從1
開始 - 當
j < i
時,就遍歷鏈表, 讓p
的指針向後移動, 不斷指向下一結點,j
累加1
- 若到鏈表末尾
p
爲空,則說明第i
個元素不存在 - 否則查找成功,在系統生成一個空結點
s
- 將數據元素
e
賦值給s->data
- 單鏈表的插入標準語句
s->next = p->next; p->next = s
- 返回成功
// 操作結果:在 L 中第 i 個位置之前插入新的數據元素 e, L 的長度加 1
Status ListInsert(LinkList *L, int i, ElemType e)
{
LinkList p, s;
p = *L;
int j = 1;
while(p && j < i) // 尋找第 i 個結點
{
p = p->next;
++j;
}
if(!p || j > i) // 第 i 個元素不存在
return ERROR;
s = (LinkList)malloc(sizeof(Node)); // 生成新結點
s->data = e;
s->next = p->next; // 將 p 的後繼結點賦值給 s 的後繼
p->next = s; // 將 s 賦值給 p 的後繼
return OK;
}
單鏈表的刪除
單鏈表第i
個數據刪除結點的算法思路:
- 聲明一個結點
p
指向鏈表第一個結點,初始化j
從1
開始 - 當
j < i
時,遍歷鏈表,讓p
的指針向後移動,不斷指向下一個結點,j
累加1
- 若到鏈表末尾
p
爲空,則說明第i
個元素不存在 - 否則查找成功,將與刪除的結點
p->next
賦值給q
, 即p
爲欲刪除結點的上一結點,q
爲欲刪除結點 - 單鏈表的刪除標準語句
p->next = q->next
- 將
q
結點中的數據賦值給e
,返回 - 釋放
q
結點 - 返回成功
Status ListDelete(LinkList *L, int i, ElemType *e)
{
LinkList p, q;
p = (*L);
int j = 1;
while(p->next && j < i)
{
p = p->next;
++j;
}
if(!(p->next) || j > i) // 第 i 個元素不存在
return ERROR;
q = p->next;
p->next = q->next; // 將 q 的後繼賦值給 p 的後繼
*e = q->data; // 將 q 結點中的數據賦值給 *e
free(q); // 讓系統回收此節點,釋放內存
return OK;
}
單鏈表結構與順序存儲結構優缺點
靜態鏈表
用數組描述的鏈表叫做靜態鏈表,也叫作遊標實現法,是爲了給某些沒有實現指針的編程語言設計的類似單鏈表
// 線性表的靜態鏈表存儲結構
#define MAXSIZE 1000
typedef struct
{
ElemType data;
int cur; // 遊標,爲 0 時表示無指向
}Component, StaticLinkList[MAXSIZE];
另外數組第一個和最後一個元素作爲特殊元素處理,不存數據。我們通常把未被使用的數組元素稱爲備用鏈表。而數組第一個元素,即下標爲0
的元素的cur
就存放備用鏈表的第一個結點的下標;而數組的最後一個元素的cur
則存放第一個有數值的元素的下標,相當於單鏈表中的頭結點作用,當整個鏈表爲空時,則爲0
。下圖所示相當於初始化的數組狀態。
靜態鏈表的插入
由於靜態鏈表中操作的是數組,而不是動態鏈表中的結點申請和釋放問題。故需實現申請結點和釋放結點的功能,方法是將所有未被使用的及被刪除的分量用遊標鏈接成備用鏈表,插入時,從備用鏈表取得第一個結點作爲待插入的新節點
// 若備用鏈表非空,則返回分配的結點下標,否則返回 0
int Malloc_SLL(StaticLinkList space)
{
int i = space[0].cur; // 獲取備用鏈表的第一個結點,即存放在數組第一個元素中的 cur
if(space[0].cur)
space[0].cur = space[i].cur; // 備用鏈表第一個結點被申請使用,故後移一個
return i;
}
// 在 L 中第 i 個元素之前插入新的數據元素 e
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
int j, k, l;
k = MAXSIZE - 1;
if(i < 1 || i > ListLength(L) + 1)
return ERROR;
j = Malloc_SLL(L);
if(j)
{
L[j].data = e;
for(l = 1; l < i; l++)
{
k = space[k].cur;
}
L[j].cur = L[k].cur;
L[k].cur = j;
return OK;
}
return ERROR;
}
靜態鏈表的刪除
// 將下標爲 k 的空閒結點回收到備用鏈表
void Free_SSL(StaticLinkList L, int k)
{
L[k].cur = L[0].cur; // 把備用鏈表的第一個元素 cur 值賦給要刪除的分量 cur
L[0].cur = k; // 把要刪除的分量下標賦給第一個元素的 cur
}
// 刪除在 L 中第 i 個數據元素 e
Status ListDelete(StaticLinkList L, int i)
{
int j, k;
k = MAXSIZE - 1;
if(i < 1 || i > ListLength(L))
return ERROR;
for(j = 1; j < i; j++)
k = L[k].cur;
j = L[k].cur;
L[k].cur = L[i].cur;
Free_SSL(L, j);
return OK;
}
靜態鏈表優缺點
Code
結語
關於鏈表的面試題後續整理, fighting