三 鏈表
3.1 鏈表的基本概念
前面我們在學(xué)習(xí)順序表時,線性表的順序存儲結(jié)構(gòu)的特點(diǎn)是邏輯關(guān)系上相鄰的兩個數(shù)據(jù)元素在物理位置上也是相鄰的。我們會發(fā)現(xiàn)雖然順序表的查詢很快,時間復(fù)雜度為O(1),但是增刪的效率是比較低的,因?yàn)槊恳淮卧鰟h操作都伴隨著大量的數(shù)據(jù)元素移動。為了解決這個問題我們可以使用另外一種存儲結(jié)構(gòu)實(shí)現(xiàn)線性表,鏈?zhǔn)酱鎯Y(jié)構(gòu)。
線性表的鏈?zhǔn)酱鎯Y(jié)構(gòu)(也稱之為鏈表)的特點(diǎn)是邏輯關(guān)系上相鄰的兩個數(shù)據(jù)元素在物理位置上不一定是相鄰的,換言之?dāng)?shù)據(jù)元素在存儲器中的位置可以是任意的。為了表示每個數(shù)據(jù)元素ai與其直接后繼 ai+1之間的邏輯關(guān)系,對于數(shù)據(jù)元素ai來說,除了存儲其本身的信息外,還需存儲一個能夠保存直接后繼的存儲位置的指針,這兩部分信息組成數(shù)據(jù)元素ai的存儲映像,我們稱之為結(jié)點(diǎn)(node)。
結(jié)點(diǎn)包含兩個或者三個域:存儲數(shù)據(jù)元素信息的域叫做數(shù)據(jù)域,存儲直接后繼存儲位置的域叫做指針域,存儲直接前驅(qū)存儲位置的域也叫做指針域。
????????如果只有一個指針域保存直接后繼存儲位置,這樣的鏈表我們稱之為單向鏈表。
????????如果既有指針域名保存直接后繼存儲位置,又有指針域存儲直接前驅(qū)存儲位置,這樣的鏈表我們稱之為雙向鏈表。
????????為了方便對鏈表進(jìn)行插入結(jié)點(diǎn)和刪除結(jié)點(diǎn)的操作,一般地鏈表中的第一個結(jié)點(diǎn)不存儲實(shí)際的數(shù)據(jù)元素,該結(jié)點(diǎn)我們稱之為:頭結(jié)點(diǎn)。
單向鏈表的頭結(jié)點(diǎn)中數(shù)據(jù)域不存儲實(shí)際數(shù)據(jù)元素,雙向鏈表的頭結(jié)點(diǎn)中數(shù)據(jù)域不存儲實(shí)際數(shù)據(jù)元素,并且直接前驅(qū)指針域?yàn)榭眨ㄒ驗(yàn)轭^結(jié)點(diǎn)沒有直接前驅(qū)結(jié)點(diǎn))
3.2 單向鏈表
在對單向鏈表進(jìn)行訪問時,需要使用一個指針指向鏈表中的第一個結(jié)點(diǎn)(頭結(jié)點(diǎn)),這個指針我們稱之為:頭指針。頭指針保存了鏈表中頭結(jié)點(diǎn)的存儲位置。
1、單向鏈表結(jié)點(diǎn)
typedef int ElemType;
typedef struct LNode{
ElemType data; //數(shù)據(jù)域
struct LNode *next; //指針域,指向當(dāng)前結(jié)點(diǎn)的直接后繼
}LNode, *LinkList; //LinkList 的類型為 LNode*
2、單向鏈表初始化
當(dāng)鏈表為空時,頭結(jié)點(diǎn)的指針域?yàn)榭眨?/p>
初始化一個鏈表:
-
創(chuàng)建一個頭結(jié)點(diǎn),頭結(jié)點(diǎn)的指針域?yàn)榭?/p>
-
創(chuàng)建一個頭指針,頭指針指向頭結(jié)點(diǎn)(將頭結(jié)點(diǎn)的地址賦值給頭指針)
LinkList list_init(){
LNode *t;
t = (LNode *)malloc(sizeof(LNode)); //創(chuàng)建一個頭結(jié)點(diǎn)
t->next = NULL; //頭結(jié)點(diǎn)的指針域?yàn)榭?
LinkList head; //定義一個頭指針
head = t; //頭指針指向頭結(jié)點(diǎn)
return head;
}
3、單向鏈表頭插法
邏輯:
-
創(chuàng)建一個新的結(jié)點(diǎn)p
-
將新的結(jié)點(diǎn)p的next指向頭節(jié)點(diǎn)的下一個結(jié)點(diǎn)(head->next)
-
頭節(jié)點(diǎn)的next指向新的結(jié)點(diǎn)p
/*
* @brief 頭插法插入一個結(jié)點(diǎn)
* @param 需要插入的鏈表的頭指針
* @param 需要插入的數(shù)據(jù)
* @return 成功返回TRUE,失敗返回FALSE
* */
int head_insert(LinkList head, LNode **tail, ElemType data){
if (NULL == head){
printf("[%s %d] head pointer is NULL ...\n", __FUNCTION__ , __LINE__);
return FALSE;
}
//創(chuàng)建一個新的結(jié)點(diǎn)p
LNode *p;
p = (LNode *)malloc(sizeof(LNode));
p->data = data;
p->next = NULL;
//如果鏈表為空, 那么第一個插入的結(jié)點(diǎn)就是整個鏈表的尾結(jié)點(diǎn)
if (NULL == head->next)
*tail = p;
//將新的結(jié)點(diǎn)p的next指向頭結(jié)點(diǎn)的下一個節(jié)點(diǎn)(head->next)
p->next = head->next;
//頭結(jié)點(diǎn)的next指向新的結(jié)點(diǎn)p
head->next = p;
return TRUE;
}
4、單向鏈表的打印
邏輯:
-
使用一個臨時指針指向頭節(jié)點(diǎn)后的第一個結(jié)點(diǎn)
-
使用臨時指針進(jìn)行遍歷,知道臨時指針為NULL
/*
* @brief 輸出鏈表中所有的結(jié)點(diǎn)
* @param head 鏈表的頭指針
* @return 成功返回TRUE,失敗返回FALSE
* */
int print_list(LinkList head){
if (NULL == head)
return FALSE;
//使用一個臨時指針對鏈表進(jìn)行遍歷
LNode *t;
t = head->next;
while (t != NULL){
printf("%d", t->data);
t = t->next;
}
return TRUE;
}
5、單向鏈表尾插法
邏輯:
-
新建一個新的結(jié)點(diǎn)
-
將尾指針的next指向新的結(jié)點(diǎn)
-
將尾指針指向新的結(jié)點(diǎn)
改造head_insert函數(shù),將鏈表的尾結(jié)點(diǎn)指針的地址傳入:
/*
* @brief 尾插法插入一個結(jié)點(diǎn)
* @param head 需要插入的鏈表的頭指針
* @param tail 尾結(jié)點(diǎn)指針的地址
* @param data 需要插入的數(shù)據(jù)
* @return 成功返回TRUE,失敗返回FALSE
* */
int tail_insert(LinkList head, LNode **tail, ElemType data){
if (NULL == head){
printf("[%s %d] head pointer is NULL ...\n", __FUNCTION__ , __LINE__);
return FALSE;
}
//創(chuàng)建一個新的結(jié)點(diǎn)p
LNode *p;
p = (LNode *)malloc(sizeof(LNode));
p->data = data;
p->next = NULL;
//如果鏈表為空,將頭結(jié)點(diǎn)的next指向新的結(jié)點(diǎn)
if (NULL == head->next)
head->next = p;
else{
(*tail)->next = p;
}
*tail = p;
return TRUE;
}
6、獲取鏈表上指定的元素
邏輯:
-
從鏈表的第一個數(shù)據(jù)結(jié)點(diǎn)開始遍歷
-
將遍歷到的每一個結(jié)點(diǎn)上的數(shù)據(jù)域與需要獲取/查找的元素比較
-
如果相等返回該結(jié)點(diǎn)的地址
-
如果不相等則繼續(xù)往后遍歷
-
如果遍歷到鏈表末尾依然沒有找到則返回NULL
/*
* @brief 獲取鏈表上指定的元素
* @param head 需要查找的鏈表的頭指針
* @param data 需要查找的元素
* @return 成功返回元素所在的結(jié)點(diǎn)的地址,失敗返回NULL
* */
LNode *get_elem(LinkList head, ElemType data){
if (NULL == head){
printf("[%s %d] head pointer is NULL ...\n", __FUNCTION__, __LINE__);
return NULL;
}
LNode *t;
t = head->next;
while (t != NULL){
if (t->data == data)
return t;
t = t->next;
}
return NULL;
}
7、獲取鏈表上指定位置的元素
邏輯:
-
從鏈表的第一個數(shù)據(jù)結(jié)點(diǎn)開始遍歷
-
每遍歷一個結(jié)點(diǎn)遍歷次數(shù)+1,同時判斷是否遍歷到了鏈表的末尾
-
如果遍歷到了鏈表末尾返回NULL
-
或者遍歷到了指定位置返回結(jié)點(diǎn)指針
/*
* @brief 獲取指定位置的元素
* @param 需要遍歷的鏈表的頭指針
* @param index 需要遍歷到的位置(從1開始)
* @return 成功返回對應(yīng)位置的結(jié)點(diǎn)指針,失敗返回NULL
* */
LNode *get_elem_by_index(LinkList head, uint index){
if (NULL == head){
printf("[%s %d] head pointer is NULL ...\n", __FUNCTION__, __LINE__);
return NULL;
}
//用一個臨時指針指向鏈表的第一個數(shù)據(jù)結(jié)點(diǎn)
LNode *t = head->next;
int i=1;
//判斷是否遍歷到了鏈表末尾或者遍歷到了指定的位置
while (i<=index && t!=NULL){
i++;
t = t->next;
}
return t;
}
8、刪除鏈表上指定位置的元素
邏輯:
-
使用臨時指針 t 從鏈表的第一個數(shù)據(jù)結(jié)點(diǎn)開始遍歷
-
使用臨時指針 p 保存遍歷到的結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)
-
每遍歷一個結(jié)點(diǎn)遍歷次數(shù)+1,指針 p 與隨之往后移動,同時判斷是否遍歷到了鏈表的末尾
-
如果遍歷到了鏈表的末尾則返回FALSE
-
如果遍歷到的位置恰好是最后一個結(jié)點(diǎn)(尾結(jié)點(diǎn)),則將尾指針指向該結(jié)點(diǎn)的前一個結(jié)點(diǎn),尾指針的next賦值為NULL,并且刪除最后一個結(jié)點(diǎn)
-
如果遍歷到的結(jié)點(diǎn)是中間結(jié)點(diǎn),則將前驅(qū)結(jié)點(diǎn)指向遍歷到的結(jié)點(diǎn)的下一個結(jié)點(diǎn)(指針 p->next = t->next)
/*
* @brief 刪除指定位置的結(jié)點(diǎn)
* @param head 需要刪除的鏈表的頭指針
* @param tail 需要刪除的鏈表的尾指針地址
* @param index 需要刪除的元素的位置
* @return 成功返回 TRUE, 失敗返回FALSE
* */
int delete_by_index(LinkList head, LNode **tail, uint index){
if (NULL == head){
printf("[%s %d] head pointer is NULL ...\n", __FUNCTION__, __LINE__);
return FALSE;
}
LNode *t = head->next;
LNode *p = head;
int i = 1;
while (i < index && t != NULL){
i++;
t = t->next;
p = p->next;
}
if (t == NULL){
printf("[%s %d] can not delete\n", __FUNCTION__, __LINE__);
return FALSE;
}
//如果剛好是遍歷到了最后一個結(jié)點(diǎn)
if (t->next == NULL){
*tail = p;
(*tail)->next = NULL;
free(t);
return TRUE;
}
//如果是中間的結(jié)點(diǎn)
p->next = t->next;
free(t);
return TRUE;
}
9、刪除鏈表上指定元素所在的結(jié)點(diǎn)
邏輯:
-
使用臨時指針t從鏈表的第一個數(shù)據(jù)結(jié)點(diǎn)開始遍歷,一直遍歷到鏈表末尾
-
使用臨時指針 p 保存遍歷到的結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)
-
將遍歷到的每一個結(jié)點(diǎn)上的數(shù)據(jù)域與需要刪除的元素比較
-
如果遍歷到的結(jié)點(diǎn)恰好是尾結(jié)點(diǎn),將尾指針tail指向需要刪除的結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)p,尾指針tail->next=NULL,釋放當(dāng)前結(jié)點(diǎn)free(t),返回TRUE
-
如果遍歷到的結(jié)點(diǎn)是中間結(jié)點(diǎn),驅(qū)結(jié)點(diǎn)指向當(dāng)前結(jié)點(diǎn)的下一個結(jié)點(diǎn)p->next = t->next, 釋放當(dāng)前結(jié)點(diǎn)free(t),臨時指針t指向下一個結(jié)點(diǎn)繼續(xù)往后遍歷 t = p->next
/*
* @brief 刪除指定元素所在的結(jié)點(diǎn)
* @param head 需要刪除的鏈表的頭指針
* @param tail 需要刪除的鏈表的尾指針地址
* @param data 需要刪除的元素的值
* @return 成功返回TRUE, 失敗返回FALSE
* */
int delete_by_elem_value(LinkList head, LNode **tail, ElemType data){
if (NULL == head){
printf("[%s %d] head pointer is NULL ...\n", __FUNCTION__, __LINE__);
return FALSE;
}
LNode *t = head->next;
LNode *p = head;
//遍歷到鏈表的末尾
while (t != NULL){
//如果遍歷到了需要刪除的結(jié)點(diǎn)
if (t->data == data){
//如果當(dāng)前結(jié)點(diǎn)為尾結(jié)點(diǎn)
if (t->next == NULL){
*tail = p;
(*tail)->next = NULL;
free(t);
return TRUE;
}
//如果是中間的結(jié)點(diǎn),前驅(qū)結(jié)點(diǎn)指向當(dāng)前結(jié)點(diǎn)的下一個結(jié)點(diǎn)
p->next = t->next;
free(t);
t = p->next;
}
else{
t = t->next;
p = p->next;
}
}
return TRUE;
}
10、在鏈表指定位置/指定位置前插入一個結(jié)點(diǎn)
邏輯:
-
使用臨時指針 t 從鏈表的第一個數(shù)據(jù)結(jié)點(diǎn)開始遍歷
-
使用臨時指針 p 保存遍歷到的結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)
-
每遍歷一個結(jié)點(diǎn)遍歷次數(shù)+1,指針 p 與隨之往后移動,同時判斷是否遍歷到了鏈表的末尾
-
如果遍歷到了鏈表的末尾則返回FALSE
-
如果遍歷到了指定的位置
-
創(chuàng)建一個新的結(jié)點(diǎn)n
-
將前驅(qū)結(jié)點(diǎn)p指向新的結(jié)點(diǎn)n,p->next = n,將新的結(jié)點(diǎn)指向遍歷到的結(jié)點(diǎn), n->next = t
-
返回TRUE
/*
* @brief 在指定的位置插入一個元素
* @param head 需要插入的鏈表的頭指針
* @param index 需要插入的元素的位置
* @param data 需要插入的元素的值
* @return 成功返回TRUE,失敗返回FALSE
* */
int insert_by_index(LinkList head, uint index, ElemType data){
if (NULL == head){
printf("[%s %d] head pointer is NULL ...\n", __FUNCTION__, __LINE__);
return FALSE;
}
LNode *t = head->next;
LNode *p = head;
int i = 1;
while (i < index && t != NULL){
i++;
t = t->next;
p = p->next;
}
//如果將整個鏈表遍歷完畢,就說明需要插入在不在鏈表
if (NULL == t){
printf("[%s %d] index: %d out of range ...\n", __FUNCTION__ , __LINE__);
return FALSE;
}
//創(chuàng)建一個新的結(jié)點(diǎn)p
LNode *n;
n = (LNode *)malloc(sizeof(LNode));
n->data = data;
n->next = NULL;
p->next = n;
n->next = t;
return TRUE;
}
11、插入一個元素使得整個鏈表依然保持為升序(原鏈表為升序)
邏輯:找到一個比需要插入的元素大的結(jié)點(diǎn),在這個結(jié)點(diǎn)的前面插入新的結(jié)點(diǎn)
-
使用臨時指針 p 保存遍歷到的結(jié)點(diǎn)的前驅(qū)結(jié)點(diǎn)
-
使用臨時指針 t 從鏈表的第一個數(shù)據(jù)結(jié)點(diǎn)開始遍歷
-
每遍歷一個結(jié)點(diǎn)遍歷次數(shù)+1,指針 p 與隨之往后移動,同時判斷遍歷到的結(jié)點(diǎn)是否比需要插入的元素大
-
如果比需要插入的元素小則繼續(xù)往后遍歷
-
如果比需要插入的元素大,新創(chuàng)建一個結(jié)點(diǎn)n
-
判斷該結(jié)點(diǎn)是否為尾結(jié)點(diǎn),如果是尾結(jié)點(diǎn) t->next = n, tail = n
-
如果不是尾結(jié)點(diǎn)將前驅(qū)結(jié)點(diǎn)p指向新的結(jié)點(diǎn)n,p->next = n,將新的結(jié)點(diǎn)指向遍歷到的結(jié)點(diǎn), n->next = t
-
如果將整個鏈表遍歷完畢依然沒有找到比需要插入的元素大的結(jié)點(diǎn),則說明該結(jié)點(diǎn)將會是整個鏈表上最大的結(jié)點(diǎn),應(yīng)該插入到鏈表的末尾。 p->next = n, tail = n ,或者調(diào)用尾插法函數(shù)
/*
* @brief 插入一個元素使得整個鏈表依然保持為升序(原鏈表為升序)
* @param head 鏈表的頭指針
* @param pTail 鏈表的尾指針地址
* @param data 需要插入的元素的值
* @return 成功返回TRUE,失敗返回FALSE
* */
int ascending_insert(LinkList head, LNode **pTail, ElemType data)
{
if (NULL == head)
{
printf("[%s %d] head pointer is NULL ...\n", __FUNCTION__ , __LINE__);
return FALSE;
}
//創(chuàng)建一個新的結(jié)點(diǎn)p
LNode *n;
n = (LNode *)malloc(sizeof(LNode));
n->data = data;
n->next = NULL;
LNode *t = head->next;
LNode *p = head;
while (t != NULL)
{
if (t->data <= data)
{
t = t->next;
p = p->next;
continue;
}
//前驅(qū)結(jié)點(diǎn)p指向新的結(jié)點(diǎn)n,p->next = n,將新的結(jié)點(diǎn)指向遍歷到的結(jié)點(diǎn), n->next = t
p->next = n;
n->next = t;
break;
}
//如果將整個鏈表遍歷完畢依然沒有找到比需要插入的元素大的結(jié)點(diǎn),則說明該結(jié)點(diǎn)將會是整個鏈表上最大的結(jié)點(diǎn),應(yīng)該插入到鏈表的末尾
// p->next = n, tail = n ,或者調(diào)用尾插法函數(shù)
p->next = n;
*pTail = n;
return TRUE;
}
12、銷毀鏈表
邏輯:
-
使用臨時指針 t 從鏈表的第一個數(shù)據(jù)結(jié)點(diǎn)開始遍歷,直到遍歷到鏈表的末尾
-
每遍歷到一個結(jié)點(diǎn)首先將頭結(jié)點(diǎn)的next賦值為當(dāng)前節(jié)點(diǎn)的下一個結(jié)點(diǎn) head->next = t->next, 然后臨時指針t指向下一個結(jié)點(diǎn) t = head->next文章來源:http://www.zghlxwxcb.cn/news/detail-560632.html
-
如果整個鏈表遍歷完畢則刪除結(jié)點(diǎn)文章來源地址http://www.zghlxwxcb.cn/news/detail-560632.html
/*
* @brief 銷毀鏈表
* @param head 需要銷毀的鏈表的頭指針
* @return 成功返回TRUE, 失敗返回FALSE
* */
int list_destroy(LinkList head)
{
if (NULL == head)
{
printf("[%s %d] head pointer is NULL ...\n", __FUNCTION__ , __LINE__);
return FALSE;
}
LNode *t = head->next;
while (t != NULL)
{
head->next = t->next;
free(t);
t = head->next;
}
//把頭結(jié)點(diǎn)也釋放掉
free(head);
return TRUE;
}
到了這里,關(guān)于02-鏈表 (數(shù)據(jù)結(jié)構(gòu)和算法)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!