完整代碼需結合前面一篇順序表數據結構學習-線性表之順序表各種操作
網易云課堂小甲魚課程鏈接:數據結構與算法
線性表的鏈式存儲結構
線性表的順序存儲結構,最大的缺點就是插入和刪除時需要移動大量的元素,這顯然需要耗費時間。導致這個問題的原因是在于相鄰元素的存儲位置具有鄰居關系,它們在內存中的位置是緊挨著的,中間沒有間隙,當然無法快速插入和刪除。
定義
- 線性表的鏈式存儲結構的特點是用一組任意的存儲單元存儲線性表中的數據元素,這組存儲單元可以存放在內存中未被占用的任意位置。
- 相比順序存儲結構,鏈式存儲結構中,除了需要存儲數據元素信息之外,還需要存儲它的后繼元素的存儲地址(指針)。
-
數據域:存儲數據元素信息的域,指針域:存儲直接后繼位置的域。指針域中存儲的信息成為指針或鏈。這兩部分信息組成數據元素成為存儲映像,成為結點(Node)。
結點結構node - 鏈式結構:n個結點鏈接成一個鏈表,即為線性表(a1,a2,a3,...an);
- 如果鏈表的每個結點中只包含一個指針域,那就叫做單鏈表。
單鏈表
小甲魚視屏中的單鏈表
- 頭指針:鏈表中的第一個結點的存儲位置。
- 線性表中最后一個結點的指針域為空(NULL)。
-
看圖說明:
單鏈表的特點
頭結點和頭指針
-
頭結點:
- 頭結點是加在單鏈表之前附設的一個頭結點。
- 頭結點的數據域一般不存儲任何信息,也可以存放一些關于線性表的長度的附加信息。
- 頭結點的指針域存放指向第一個結點的指針(即第一個結點的存儲位置)。
- 頭結點不一定是鏈表的必要元素。
-
頭指針:
- 頭指針是指鏈表指向第一個結點的指針,若鏈表有頭結點,則是指向頭結點的指針。
- 頭指針具有標識作用,所以常用頭指針冠以鏈表的名字(指針變量的名字)。
- 無論鏈表是否為空,頭指針均不為空。
-
頭指針是鏈表的必要元素。
單鏈表的代碼實現
#include <stdio.h>
#define OK 1;
#define ERROR 0;
#define TRUE 1;
#define FALSE 0;
#define MAXSIZE 20 /* 定義線性表可能達到的最大長度 */
typedef int ElemType; /* 定義數據元素類型,類型名為ElemType,此處所定義的數據元素只包含一個int型的數據項*/
typedef struct { /* 定義順序表類型,類型名為SqList,包含兩個數據項:數組data,用于存放數據元素,整數length表示線性表當前長度*/
ElemType data[MAXSIZE]; /*定義線性表占用的數組空間*/
int length; /*線性表當前長度*/
}SqList;
typedef int Status; /*Status是函數的類型,其值是函數結果狀態碼,如OK等*/
// 用結構指針描述單鏈表
typedef struct Node{
ElemType data; // 數據域
struct Node *Next; // 指針域
}Node;
// 取別名
typedef struct Node *LinkList;
通過這種鏈式存儲方式,若果
p->data=ai
,那么p->next->data=ai+1
.
單鏈表的讀取(工作指針后移)
- 獲取鏈表第
i
個數據的算法思路:
1.聲明一個結點p
指向鏈表的第一個結點,初始化j
從1開始;
2.當j<i
時,就遍歷鏈表,讓p
的指針向后移動,不斷指向下一結點,j+1
;
3.若到鏈表末尾p
為空,則說明第i
個元素不存在;
4.否則查找成功,返回結點p數據。 - 代碼:
/**
* 獲取鏈表第`i`個數據的
*/
Status GetElem(LinkList L, int i, ElemType *e){
int j;
LinkList p; // 聲明指針p
p = L->next; // p指向鏈表L的第一個結點
j = 1; // 當前位置計數器設置為1
while (p && j<i) { // 當p不為空 切j<i時候 j繼續向后找
p = p->next;
j++;
}
if (!p || j>i) return ERROR; // 到達結尾且沒找到
*e = p->data; // 獲取倒找的結果
return OK;
}
單鏈表的插入算法
- 算法思路:
1.聲明一結點p
指向鏈表頭結點,初始化j
從1開始;
2.當j<1
時,就遍歷鏈表,讓p
的指針向后移動,不斷指向下一節點,j
累加1;
3.若到鏈表末尾p
為空,則說明第i
個元素不存在;
4.否則查找成功,在系統中生成一個空節點s
;
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) { // 用于尋找第i個結點
p = p->next;
j++;
}
if (!p || j>i) return ERROR; // 到達結尾且沒找到
s = (LinkList)malloc(sizeof(Node)); //生成一個空節點s
// 插入語句
s->next = p->next;
p->next = s;
return OK;
}
單鏈表的刪除算法(前繼結點的指針繞過繞過后繼結點)
- 算法實現思路:
p->next = p->next->next,即 q = p->next; p->next = q->next
1.聲明一結點p
指向鏈表頭結點,初始化j
從1開始;
2.當j<1
時,就遍歷鏈表,讓p
的指針向后移動,不斷指向下一節點,j
累加1;
3.若到鏈表末尾p
為空,則說明第i
個元素不存在;
4.否則查找成功,將欲刪除結點p->next賦值給q
;
5.單鏈表的刪除標準語句:p->next = q->next
;
6.將結點中的數據賦值給e
,作為返回;
7.釋放q
結點。 - 代碼:
/**
* 單鏈表的刪除
*/
Status ListDelete(LinkList *L, int i, ElemType e){
int j;
LinkList p,q;
p = *L;
j = 1;
while (p && j<i) { // 用于尋找第i個結點
p = p->next;
j++;
}
if (!p || j>i) return ERROR; // 到達結尾且沒找到
q = p->next;
// 刪除語句
p->next = q->next;
free(q);
return OK;
}
單鏈表的插入和刪除圖解
特點:先找到,再刪除或者增加。時間復雜度為
o(1)
;
單鏈表的整表創建
對于順序存儲結構的線性表的整表創建,可以用數組的初始化來直觀理解。
但是對于單鏈表,和順序存儲結構就不一樣,不像順序存儲結構數據這么幾種,它的數據可以是分散在各個角落的,增長也是動態的。
對于每個鏈表來說,它所占用空間的大小和位置是不需要預先分配劃定的,可以根據系統的情況和實際的需要及時生成。
單鏈表整表創建的算法思路:
1.聲明一個結點p
和計數器變量i
;
2.初始化一個空鏈表L
;
3.讓L
的頭結點的指針指向NULL
,即建立一個帶頭結點的單鏈表;
4.循環實現后繼結點的賦值和插入。
一、頭插法建立單鏈表
頭插法從一個空表開始,生成新結點,讀取數據存放到新節點的數據域中,然后將新結點插入到當前鏈表的表頭,知道結束。
簡單來說,就是把新加進的元素放在表頭的第一個位置(如同插隊):
先讓新節點的
next
指向頭結點之后;讓后讓表頭的
next
指向新節點。代碼:
/**
* 頭插法
*/
void CreatListHead(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;
}
}
這里使用了c語言里面的生成隨機數的函數rand()
來構造結點里面的數據。
二、尾插法建立單鏈表
頭插法建立鏈表隨讓算法簡單,但是生成的鏈表中結點的次序和輸入的順序相反。
若把新結點都插入到最后,那么這種算法就是尾插法。
- 代碼:
/**
* 尾插法
*/
void CreatListTail(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
,下一結點賦值給q
;
3.循環執行釋放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;
}
注意:整表刪除的時候,需要一個個結點的刪除。
單鏈表結構和順序存儲結構對比
存儲分配方式:
- 順序存儲結構用一段連續的存儲單元依次存儲線性表的數據元素;
- 單鏈表采用鏈式存儲結構,用一組任意的儲存單元存放線性表的元素;
時間性能:
- 查找:
- 順醋存儲結構
O(1)
; - 單鏈表
O(n)
;
- 順醋存儲結構
- 插入和刪除:
- 順序存儲結構需要平均移動表長一般的元素,時間為
O(n)
; - 單鏈表在計算機處某位置的指針后,插入和刪除時間為
O(1)
;
- 順序存儲結構需要平均移動表長一般的元素,時間為
空間性能
- 順序存儲結構需要預分配存儲空間,分大了,容易造成空間浪費,分小了,容易發生溢出。
- 單鏈表不需要分配存儲空間,只要有就可以分配,元素個數也不受限制。