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