寫在前面,本章我們可以瞭解到:
1.線性表的鏈式存儲結構定義
2.線性錶鏈式存儲結構代碼描述
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;
3.對單鏈表的整表創建、整表刪除、插入、刪除、讀取
4.比較鏈式存儲結構和線性存儲結構
1.線性表的鏈式存儲結構
鏈式存儲結構的特點
- 用一組任意的存儲單元存儲線性表的數據元素,這組存儲單元可以是連續的,也可以是不連續的,這些數據元素可以存在內存未被佔用的任意位置
- 鏈式結構中,除了要存數據元素外,還要存儲它的後繼元素的存儲地址
爲了表示每個數據元素 ai 與其後繼元素 ai+1 之間的邏輯關係,數據元素 ai 來說,除了存儲其本身的信息之外,還需要存儲一個指示其直接後繼的信息。
我們把存儲數據元素信息的域稱爲數據域,把存儲直接後繼位置的域稱爲指針域。這兩部分信息組成數據元素 ai 的存儲映像,稱爲結點(Node)
n個結點鏈結成一個鏈表,就是線性表的鏈式存儲結構,每個結點中只包含一個指針域,所以叫單鏈表,如下圖
鏈表中第一個結點的存儲位置叫做頭指針,整個鏈表的存取就必須是從頭指針開始,那麼最後一個結點指針爲“空”(NULL),如下圖
爲了和其他結點一樣對鏈表進行操作,會在單鏈表的第一個結點前加一個結點,稱爲頭結點,頭結點的數據域可以不存儲任何信息,也可以存儲線性表的長度等附加信息,如下圖
2.線性錶鏈式存儲結構代碼描述
單鏈表中,我們在C語言中可用結構指針來描述,結點由存放數據元素的數據域存放後繼結點地址的指針域組成。
typedef struct Node
{
ElemType data;
struct Node *next;
}Node;
typedef struct Node *LinkList;
我們假設 p 是指向線性表第 i 個元素的指針,則該結點 ai 的數據域,我們可以用 p->data 來表示,結點 ai 的指針域可以用 p->next 來表示, p->next 指向第 i + 1個元素, p->next->data 是 第i+1個結點的值,如下圖所示:
3.單鏈表的操作
① 單鏈表的整表創建
對比順序存儲結構的創建,順序存儲結構的創建其實就是一個數組的初始化,單鏈表是一種動態結構,所佔用空間的大小和位置是不需要預先分配劃定的,可以根據系統的情況和實際需求即時生成,所以說,創建單鏈表的過程就是一個動態生成鏈表的過程。
算法思路:
- 1.聲明一結點p和計數器變量i;
- 2.初始化一空鏈表L;
- 3.讓L的頭結點的指針指向NULL,即建立一個帶頭結點的單鏈表
- 4.循環:
生成一新結點賦值給 p;
隨機生成一數字賦值給 p 的數據域 p->data;
將 p 插入到頭結點與前一新節點之間;(該插入方法有兩種,頭插法和尾插法)
我們先來介紹頭插法,其實就是插隊的辦法,始終讓新結點在第一的位置
void CreateListHead(LinkList *L, int n)
{
LinkList p;
int i;
srand (time(0));
*L = (LinkList)malloc(sizeof(Node));
(*L) ->next = NULL;
for ( i = 0; i<n; i++)
{
p = (LinkList)malloc(sizeof(Node));
p->data = rand()%100 +1;
p->next = (*L)->next;
(*L)->next = p;
}
}
尾插法:所謂的先來後到,每次新結點來的時候都插在終端結點的後面,稱之爲尾插法
void CreateListTail(LinkList *L,int n)
{
LinkList p,r;
int i;
srand(time(0));
*L = (LinkList)malloc(sizeof(Node));
r = *L;
for (i = 0; i<n; i++)
{
p = (Node*)malloc(sizeof(Node));
p->data = rand()%100+1;
r->next = p;
r = p;
}
r->next = NULL;
}
②單鏈表的整表刪除
算法思路:
- 1.聲明一結點p和q;
- 2.將第一個結點賦值給p;
- 3.循環:
將下一結點賦值爲q;
釋放p;
將q賦值給p;
Status ClearList(LinkList *L)
{
LinkList p,q;
p = (*L) -> next;
while(p)
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
③獲取第 i 個元素的數據的操作
思路:
- 1.聲明一個結點p指向鏈表第一個結點,初始化 j 從 1開始
- 2.當j < i 時,讓p的指針不斷向後移動,j 累計+1
- 3.若到鏈表末尾 p 爲空,說明第 i 個元素不存在
- 4.若查找成功,返回結點p 的數據
Status GetElem (LinkList L, int i, ElemType *e)
{
int j = 1;
LinkList p;
p = L->next;
while(p && j < i)
{
p = p->next;
++j;
}
if (!p || j > j)
return ERROR;
*e = p->data;
return OK;
}
④單鏈表的插入
如下圖所示,假設存儲元素爲 e 的結點 是,要實現結點 p,p->next 和 s 的邏輯關係,將 s 插入到兩個結點之間, 我們只需要 修改 s->next 和 p->next 即可,不需要修改其他元素。。。
讓p的後置結點變成 s 的後置結點,s變成 p的後置結點
思路:
- 1.聲明一個結點 p 指向鏈表第一個結點, 初始化 j從 1開始
- 2.當 j < i 時,就遍歷鏈表,讓 p 的指針不斷往後移動,j 累加 1
- 3.鏈表末尾爲 p 爲空,說明第 i 個元素不存在
- 4.查找成功 ,生成一個空結點
- 5.將數據元素 e 賦值爲 s->data
- 6.單鏈表標準插入 s->next = p->next; p->next = s;
- 7.返回成功
Status ListInsert(LinkList *L, int i, ElemType e)
{
int j;
LinkList p,s;
p = *L;
j = 1;
while(p && j < i)
{
p = p->next;
++j;
}
if (!p || j > i)
return ERROR;
s = (LinkList)malloc(sizeof(Node));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
⑤單鏈表的刪除
如下圖,將結點q刪除,就是將q的前繼結點的指針繞過q,直接指向q的後繼結點就好
思路:
- 1.聲明結點p指向第一個結點,初始化 j 從1 開始
- 2.當 j < i 時,遍歷鏈表, p 的指針向後移動,不斷指向下一個結點, j 累加 1
- 3.若到鏈表末尾 p 爲空 ,則說明第 i 個元素不存在
- 4.若查找成功,就要刪除的結點 p->next 賦值給q;
- 5.單鏈表標準刪除語句 p->next = q->next;
- 6.將q中的值賦值給e
- 7.釋放q
- 8.返回成功
Status ListDelete(LinkList *L, int i , ElemType *e)
{
int j = 1;
LinkList p ,q;
p = *L;
while(p && j < i)
{
p = p->next;
++j;
}
if ( !(p->next) || j > i)
return ERROR;
q = p->next;
*e = q->data;
p->next = q->next;
free(q);
return OK;
}
4.單鏈表結構與順序存儲結構的優缺點
從存儲分配方式上來說:
順序存儲結構用一段連續的存儲單元依次存儲線性表的數據元素
單鏈表採用鏈式存儲結構,一組任意的存儲單元存放數據元素
從時間性能上來說:
查找操作時,順序存儲結構O(1) 單鏈表 O(n)
插入和刪除,順序存儲結構需要移動部分元素,時間爲O(n),單鏈表爲O(1)
從空間性能上來說:
順序存儲結構需要預分配存儲空間,分大了浪費,分小了容易超出
單鏈表不需要分配,只要有空間就可以分配
總結,若線性表需要頻繁查找,很少進行插入和刪除操作時,採用順序存儲結構,頻繁使用插入刪除時,宜採用單鏈表結構