在上一節 :數據結構之順序表html
咱們提到了順序表的一些缺陷,那有沒有什麼數據結構能夠減小這些問題呢?node
答案天然就是今天咱們所要說的鏈表。面試
本節大綱:數據結構
鏈表是一種物理存儲結構上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的 。ide
鏈表有好多種:單雙向鏈表,帶頭不帶頭鏈表,循環非循環鏈表 ;它們兩兩組合能夠組成 8 種鏈表結構,以下圖所示:函數
鏈表又包括 數據域 和 指針域。測試
雖然有這麼多的鏈表的結構,可是咱們實際中最經常使用仍是兩種結構:
spa
1. 無頭單向非循環鏈表:3d
結構簡單,通常不會單獨用來存數據。實際中更可能是做爲其餘數據結構的子結構,如哈希桶、圖的鄰接表等等。指針
另外這種結構在筆試面試中出現不少。
2. 帶頭雙向循環鏈表:
結構最複雜,通常用在單獨存儲數據。實際中使用的鏈表數據結構,都是帶頭雙向循環鏈表。
另外這個結構雖然結構複雜,可是使用代碼實現之後會發現結構會帶來不少優點,實現反而簡單了。
因此咱們今天從 無頭單向非循環鏈表 開始:
首先咱們確定要先建立一個自定義數據類型:
//進行自定義類型,方便修改 typedef int DataType; //進行自定義數據類型 typedef struct SLTNode { DataType data;//存放數據 struct SLTNode* next;//指向下一塊空間 }SLTNode;
假設咱們如今有一個數據徹底的單鏈表,可是咱們想在看到它的值,這時就須要一個打印函數了。
如今咱們來想想咱們剛剛所提到的單鏈表結構,它是由數據域和指針域組合而成,而它中的指針指向的是下一塊儲存空間,若是該指針爲NULL,就說明該鏈表已結束。
//單鏈表打印函數 void SLTPrint(SLTNode *p) { if (p == NULL)//若是該鏈表爲空 { printf("該鏈表中並沒有值可供打印\n"); return;//直接退出 } //正常狀況下 //cur指向當前位置 SLTNode *cur = p; while (cur)//若是cur是NULL,循環就中止 { printf("%d -> ", cur->data); cur = cur->next;//cur指向下一塊空間 } printf("NULL\n"); }
在這會有一些人問 :while 中的判斷條件是否能夠寫爲 cur->next ,咱們來看一下:
從上圖中,咱們能夠明顯看出:若循環條件爲 cur->next ,這樣循環到達最後一個節點就退出了,最後一個節點內的值並未打印,固然要是你在循環結束後,再加一句printf,那天然也沒錯
因後面的頭插、尾插、任意位置插入,都須要去建立節點,因此在這就把它單獨列一個模塊:
//建立節點函數 SLTNode *BuySLTNode(DataType data) { //動態開闢一塊空間 SLTNode *new_node = (SLTNode *) malloc(sizeof(SLTNode)); if (new_node == NULL)//若是未開闢成功 { perror("BuyNode Error!");//打印錯誤信息 exit(-1); } else { //新結點的值爲傳進來的值 new_node->data = data; new_node->next = NULL; } //返回新創建的節點 return new_node; }
4.單鏈表尾插
尾插,咱們確定要先找到該鏈表的尾,而後將尾的next指向咱們新創建的結點,將新節點的next指向NULL;
//單鏈表尾插函數 void SLTPushBack(SLTNode *p, DataType InsertData) { //建立一個新節點 SLTNode *new_node = BuySLTNode(InsertData); if (p == NULL)//說明該鏈表爲空 { p = new_node; } else//非空就找尾 { SLTNode *cur = p; while (cur->next)//循環遍歷找尾,注意在這,咱們的循環條件爲cur->next,而非cur // 由於咱們要用最後一個節點的next // 不然,如果cur的話,就直接走到NULL了,咱們沒法給將一個節點加在它上面 { cur = cur->next;//指向下一個 } cur->next = new_node;//尾插 } }
但是,上面這段代碼有沒有問題呢? 咱們來運行一下:
void test1() { SLTNode *p = NULL; SLTPushBack(p,0); SLTPushBack(p,1); SLTPushBack(p,2); SLTPushBack(p,3); SLTPrint(p); }
咱們明明尾插了4次,最後爲何仍是無值可供打印呢? 咱們再來調試一下:
咱們發現,它在函數裏都換過了,卻出了函數又變爲了NULL。咱們思來想去總以爲這種情形好像在哪見過 --- 哦,原來曾在這個例子中見過:
#include<stdio.h> void swap(int a, int b) { int c = a; a = b; b = c; } int main() { int a = 2, b = 3; printf("before:\na=%d, b=%d\n", a, b); swap(a, b); printf("after:\na=%d, b=%d\n", a, b); return 0; }
當時,咱們便已提到,改變其值時,要傳值,但要改變其指向,要傳址;
在舉一個例子:
void change(char *p) { p[0] = '!'; } void change_2(char **p) { static char *a = "12334"; *p = a; } int main() { char a[] = "dadasfsfa"; char *p = a; //改變指針所指向的值 change(p); printf("%s\n", p);//!adasfsfa //改變指針的指向 change_2(&p); printf("%s\n", p);//12334 return 0; }
因此正確的尾插怎麼寫呢:
1.函數參數爲STLNode** ,即改成址傳遞,由於當 p爲空的時候,咱們須要改變其指向,指向咱們新建的new_node
//單鏈表尾插函數 void SLTPushBack(SLTNode **p, DataType InsertData) { //建立一個新節點 SLTNode *new_node = BuySLTNode(InsertData); if (*p == NULL)//說明該鏈表爲空 { *p = new_node;//改變p的指向 } else//非空就找尾 { SLTNode *cur = *p; while (cur->next)//循環遍歷找尾,注意在這,咱們的循環條件爲cur->next,而非cur // 由於咱們要用最後一個節點的next // 不然,如果cur的話,就直接走到NULL了,咱們沒法給將一個節點加在它上面 { cur = cur->next;//指向下一個 } cur->next = new_node;//尾插 } }
如今有了以前的錯誤經驗,是否是一下就知道這個確定是傳址啊:
//單鏈表頭插函數 void SLTPushFront(SLTNode **p, DataType InsertData) { SLTNode *new_node = BuySLTNode(InsertData);//建立新節點 //在頭插中,並不關心*p爲不爲空,反正最後它們的處理都是同樣的 //將原鏈表內容鏈接在new_node上 new_node->next = *p; //改變指向 *p = new_node; }
循環遍歷找尾,釋放,置NULL
//單鏈表尾刪函數 void SLTPopBack(SLTNode **p) { if (*p == NULL)//表示該鏈表爲空 { printf("該鏈表中並沒有值可供刪除!\n"); return; } else if ((*p)->next == NULL)//表示該鏈表中只有一個值,刪除以後就爲空 { free(*p);//釋放空間 *p = NULL;//置爲NULL; } else { //循環遍歷找尾 SLTNode *cur = *p;//指向當前位置 SLTNode *prev = NULL;//指向前一個位置 while (cur->next) { prev = cur; cur = cur->next; } free(cur);//釋放空間 cur = NULL;//及時置NULL //刪除以後prev即是最後一個節點了 prev->next = NULL; } }
釋放改指向
//單鏈表頭刪函數 void SLTPopFront(SLTNode **p) { if (*p == NULL)//表示該鏈表爲空 { printf("該鏈表中並沒有值可供刪除!\n"); return; } else { SLTNode* pop_node=*p;//指向頭節點 *p=(*p)->next;//指向下一個元素 free(pop_node);//釋放空間 pop_node=NULL;//及時置NULL } }
循環遍歷
//單鏈表查找函數 SLTNode *SLTFindNode(SLTNode *p, DataType FindData) { assert(p);//確保傳進來的指針不爲空 SLTNode *cur = p; while (cur) { if (cur->data == FindData) { return cur;//找到就返回該節點的地址 } cur = cur->next;//循環遍歷 } return NULL;//找不到就返回NULL }
//單鏈表修改函數 void SLTModify(SLTNode *pos, DataType ModifyData) { assert(pos);//確保pos不爲空 pos->data = ModifyData;//修改 }
//單鏈表在pos位置以後插入InsertData void SLTInsertAfter(SLTNode* pos,DataType InsertData) { assert(pos);//保證pos不爲NULL SLTNode* new_node=BuySLTNode(InsertData);//建立一個新節點 //該節點的下一個指向本來該位置的下一個 new_node->next=pos->next; //該位置的下一個就是new_node pos->next=new_node; }
這時就要考慮到若位置是第一個位置時,那就至關於頭插了
此外,咱們還得找到它的前一個位置
//單鏈表在pos位置以前插入InsertData void SLTInsertBefore(SLTNode **p, SLTNode *pos, DataType InsertData) { assert(pos); SLTNode *new_node = BuySLTNode(InsertData); if (pos == *p)//這就說明是第一個位置前插入 { //頭插 new_node->next = *p; *p = new_node; } else { SLTNode *cur = *p; SLTNode *prev = NULL; //新節點的下一個指向這個位置 new_node->next = pos; //循環遍歷找到它的前一個位置 while (cur != pos) { prev = cur; cur = cur->next; } //前一個位置指向新節點 prev->next = new_node; } }
除此以外,還有哦另一種方法,能夠不用去知道它鏈表的第一個位置
咱們在傳過去的那個位置,來進行一個後插,再交換這兩個節點的數據 ,這也是一種方法:
//單鏈表在pos位置以前插入InsertData void SLTInsertBefore_2(SLTNode *pos, DataType InsertData) { assert(pos); SLTNode *new_node = BuySLTNode(InsertData); new_node->next = pos->next; pos->next = new_node; //進行了一個後插 //交換這兩個變量的值 DataType tmp = pos->data; pos->data = new_node->data; new_node->data = tmp; }
如圖:
//刪除pos位後的數據 void SLTEraseAfter(SLTNode *pos) { assert(pos); if (pos->next == NULL) { //說明後面並無元素了 printf("該位置後無數據能夠刪除\n"); } else { //建立一個指針指向下一個位置 SLTNode *next = pos->next; //原位置的下一個位置,指向 下一個位置 的下一個位置 pos->next = next->next; //釋放內存 free(next); next = NULL; } }
2.刪除pos位的數據 --- 這就包含了頭刪
//刪除pos位的數據 --- 含有頭刪 void SLTEraseCur(SLTNode **p, SLTNode *pos) { assert(pos); if (*p == pos) { //說明是頭刪 *p = pos->next; free(pos); pos = NULL; } else { //普通位置 SLTNode *prev = *p; SLTNode *cur = *p; //找到頭一個位置 while (cur != pos) { prev = cur; cur = cur->next; } //頭一個位置指向下下個位置 prev->next = pos->next; free(pos); pos = NULL; } }
1.把該位置 後一個節點的數據存下;
2.刪除該位置後的節點
3.將頭節點的值改成以前存下的數據
單鏈表的實現到這便就結束了
//確保頭文件只包含一次 #ifndef FINASLT_SLT_H #define FINASLT_SLT_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <assert.h> //進行自定義類型,方便修改 typedef int DataType; //進行自定義數據類型 typedef struct SLTNode { DataType data;//存放數據 struct SLTNode *next;//指向下一塊空間 } SLTNode; //單鏈表打印函數 void SLTPrint(SLTNode *p); //建立節點函數 SLTNode *BuySLTNode(DataType data); //單鏈表尾插函數 void SLTPushBack(SLTNode **p, DataType InsertData); //單鏈表頭插函數 void SLTPushFront(SLTNode **p, DataType InsertData); //單鏈表尾刪函數 void SLTPopBack(SLTNode **p); //單鏈表頭刪函數 void SLTPopFront(SLTNode **p); //單鏈表查找函數 SLTNode *SLTFindNode(SLTNode *p, DataType FindData); //單鏈表修改函數 void SLTModify(SLTNode *pos, DataType ModifyData); //單鏈表在pos位置以後插入InsertData void SLTInsertAfter(SLTNode *pos, DataType InsertData); //單鏈表在pos位置以前插入InsertData void SLTInsertBefore(SLTNode **p, SLTNode *pos, DataType InsertData); //單鏈表在pos位置以前插入InsertData void SLTInsertBefore_2( SLTNode *pos, DataType InsertData); //刪除pos位後的數據 void SLTEraseAfter(SLTNode *pos); //刪除pos位的數據 --- 含有頭刪 void SLTEraseCur(SLTNode **p, SLTNode *pos); #endif //FINASLT_SLT_H
#include "SLT.h" //單鏈表打印函數 void SLTPrint(SLTNode *p) { if (p == NULL)//若是該鏈表爲空 { printf("該鏈表中並沒有值可供打印\n"); return;//直接退出 } //正常狀況下 //cur指向當前位置 SLTNode *cur = p; while (cur)//若是cur是NULL,循環就中止 { printf("%d -> ", cur->data); cur = cur->next;//cur指向下一塊空間 } printf("NULL\n"); } //建立節點函數 SLTNode *BuySLTNode(DataType data) { //動態開闢一塊空間 SLTNode *new_node = (SLTNode *) malloc(sizeof(SLTNode)); if (new_node == NULL)//若是未開闢成功 { perror("BuyNode Error!");//打印錯誤信息 exit(-1); } else { //新結點的值爲傳進來的值 new_node->data = data; new_node->next = NULL; } //返回新創建的節點 return new_node; } ////單鏈表尾插函數 //void SLTPushBack(SLTNode *p, DataType InsertData) //{ // //建立一個新節點 // SLTNode *new_node = BuySLTNode(InsertData); // if (p == NULL)//說明該鏈表爲空 // { // p = new_node; // } // else//非空就找尾 // { // SLTNode *cur = p; // while (cur->next)//循環遍歷找尾,注意在這,咱們的循環條件爲cur->next,而非cur // // 由於咱們要用最後一個節點的next // // 不然,如果cur的話,就直接走到NULL了,咱們沒法給將一個節點加在它上面 // { // cur = cur->next;//指向下一個 // } // cur->next = new_node;//尾插 // } //} //單鏈表尾插函數 void SLTPushBack(SLTNode **p, DataType InsertData) { //建立一個新節點 SLTNode *new_node = BuySLTNode(InsertData); if (*p == NULL)//說明該鏈表爲空 { *p = new_node;//改變p的指向 } else//非空就找尾 { SLTNode *cur = *p; while (cur->next)//循環遍歷找尾,注意在這,咱們的循環條件爲cur->next,而非cur // 由於咱們要用最後一個節點的next // 不然,如果cur的話,就直接走到NULL了,咱們沒法給將一個節點加在它上面 { cur = cur->next;//指向下一個 } cur->next = new_node;//尾插 } } //單鏈表頭插函數 void SLTPushFront(SLTNode **p, DataType InsertData) { SLTNode *new_node = BuySLTNode(InsertData);//建立新節點 //在頭插中,並不關心*p爲不爲空,反正最後它們的處理都是同樣的 //將原鏈表內容鏈接在new_node上 new_node->next = *p; //改變指向 *p = new_node; } //單鏈表尾刪函數 void SLTPopBack(SLTNode **p) { if (*p == NULL)//表示該鏈表爲空 { printf("該鏈表中並沒有值可供刪除!\n"); return; } else if ((*p)->next == NULL)//表示該鏈表中只有一個值,刪除以後就爲空 { free(*p);//釋放空間 *p = NULL;//置爲NULL; } else { //循環遍歷找尾 SLTNode *cur = *p;//指向當前位置 SLTNode *prev = NULL;//指向前一個位置 while (cur->next) { prev = cur; cur = cur->next; } free(cur);//釋放空間 cur = NULL;//及時置NULL //刪除以後prev即是最後一個節點了 prev->next = NULL; } } //單鏈表頭刪函數 void SLTPopFront(SLTNode **p) { if (*p == NULL)//表示該鏈表爲空 { printf("該鏈表中並沒有值可供刪除!\n"); return; } else { SLTNode *pop_node = *p;//指向頭節點 *p = (*p)->next;//指向下一個元素 free(pop_node);//釋放空間 pop_node = NULL;//及時置NULL } } //單鏈表查找函數 SLTNode *SLTFindNode(SLTNode *p, DataType FindData) { assert(p);//確保傳進來的指針不爲空 SLTNode *cur = p; while (cur) { if (cur->data == FindData) { return cur;//找到就返回該節點的地址 } cur = cur->next;//循環遍歷 } return NULL;//找不到就返回NULL } //單鏈表修改函數 void SLTModify(SLTNode *pos, DataType ModifyData) { assert(pos);//確保pos不爲空 pos->data = ModifyData;//修改 } //單鏈表在pos位置以後插入InsertData void SLTInsertAfter(SLTNode *pos, DataType InsertData) { assert(pos);//保證pos不爲NULL SLTNode *new_node = BuySLTNode(InsertData);//建立一個新節點 //該節點的下一個指向本來該位置的下一個 new_node->next = pos->next; //該位置的下一個就是new_node pos->next = new_node; } //單鏈表在pos位置以前插入InsertData void SLTInsertBefore(SLTNode **p, SLTNode *pos, DataType InsertData) { assert(pos); SLTNode *new_node = BuySLTNode(InsertData); if (pos == *p)//這就說明是第一個位置前插入 { //頭插 new_node->next = *p; *p = new_node; } else { SLTNode *cur = *p; SLTNode *prev = NULL; //新節點的下一個指向這個位置 new_node->next = pos; //循環遍歷找到它的前一個位置 while (cur != pos) { prev = cur; cur = cur->next; } //前一個位置指向新節點 prev->next = new_node; } } //單鏈表在pos位置以前插入InsertData void SLTInsertBefore_2(SLTNode *pos, DataType InsertData) { assert(pos); SLTNode *new_node = BuySLTNode(InsertData); new_node->next = pos->next; pos->next = new_node; //進行了一個後插 //交換這兩個變量的值 DataType tmp = pos->data; pos->data = new_node->data; new_node->data = tmp; } //刪除pos位後的數據 void SLTEraseAfter(SLTNode *pos) { assert(pos); if (pos->next == NULL) { //說明後面並無元素了 printf("該位置後無數據能夠刪除\n"); } else { //建立一個指針指向下一個位置 SLTNode *next = pos->next; //原位置的下一個位置,指向 下一個位置 的下一個位置 pos->next = next->next; //釋放內存 free(next); next = NULL; } } //刪除pos位的數據 --- 含有頭刪 void SLTEraseCur(SLTNode **p, SLTNode *pos) { assert(pos); if (*p == pos) { //說明是頭刪 *p = pos->next; free(pos); pos = NULL; } else { //普通位置 SLTNode *prev = *p; SLTNode *cur = *p; //找到頭一個位置 while (cur != pos) { prev = cur; cur = cur->next; } //頭一個位置指向下下個位置 prev->next = pos->next; free(pos); pos = NULL; } }
#include "SLT.h" void test1() { SLTNode *p = NULL; SLTPushBack(&p, 0); SLTPushBack(&p, 1); SLTPushBack(&p, 2); SLTPrint(p); SLTPushFront(&p, 0); SLTPushFront(&p, 1); SLTPushFront(&p, 2); SLTPrint(p); SLTPopBack(&p); SLTPrint(p); SLTPopFront(&p); SLTPrint(p); SLTNode *pos = SLTFindNode(p, 1); if (pos == NULL) { printf("該鏈表中無所查找的值!\n"); } else { SLTModify(pos,123); } SLTPrint(p); SLTInsertAfter(pos,321); SLTPrint(p); SLTInsertBefore(&p,pos,000); SLTPrint(p); SLTInsertBefore_2(SLTFindNode(p,1),666); SLTPrint(p); SLTEraseAfter(SLTFindNode(p,1)); SLTEraseAfter(SLTFindNode(p,666)); SLTEraseCur(&p,SLTFindNode(p,0)); SLTEraseCur(&p,SLTFindNode(p,123)); SLTEraseCur(&p,SLTFindNode(p,321)); SLTEraseCur(&p,SLTFindNode(p,0)); SLTEraseCur(&p,SLTFindNode(p,0)); SLTEraseCur(&p,SLTFindNode(p,666)); SLTPrint(p); } int main() { setbuf(stdout, NULL); test1(); return 0; }
|--------------------------------------------------------
關於單鏈表的知識到這便結束了
因筆者水平有限,如有錯誤,還望指正!