您好,登錄后才能下訂單哦!
今天小編給大家分享一下C語言如何實現動態鏈表的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
鏈表是一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是通過鏈表中的指針鏈接次序實現的。鏈表由一系列結點(鏈表中每一個元素稱為結點)組成,結點可以在運行時動態生成。每個結點包括兩個部分:一個是存儲數據元素的數據域,另一個是存儲下一個結點地址的指針域。
節點結構體定義
typedef struct SNode{ void *pdata; //存儲數據,這個數據可以是任意類型 struct SNode *next; //節點的下一個節點地址 }SNode; typedef struct SNode* SLink; //定義一個鏈表
函數聲明
//創建一個鏈表 SLink create_slink(); //初始化一個鏈表 void init_slink(SLink *link); //判斷鏈表是否為空 bool is_empty(SLink link); //獲得鏈表存儲的個數 size_t size(SLink link); //插入元素 元素存儲在pdata,一共size個字節 int insert(SLink link,size_t index,void *pdata,size_t size); //獲得指定下標的節點元素,并且返回其地址 void *get(SLink link,size_t index); //刪除一個節點 int remove_data(SLink link,size_t index); //鏈表逆序 SLink reverse(SLink link); //清空鏈表 void clear(SLink link); //銷毀鏈表 void destroy(SLink link); //遍歷鏈表(通過函數指針完成用戶需要的要求) void foreach(SLink link,void (*func)(const void *)); //通過值查找某個節點 void *find(SLink link,void *pdata,int (*compare)(const void *,const void*)); //通過值刪除所有需要刪除的節點 int remove_all(SLink link,void *pdata,int (*compare)(const void *,const void *)); //鏈表排序,通過冒泡排序 void sort(SLink link,int (*compare)(const void *,const void *));
首先動態鏈表一般有一個頭結點(也不是必須有,但是頭結點可以讓后面的算法變得簡單些),這個頭結點不存儲數據,只存放第一個節點(存放數據的節點,也叫作首節點)的地址,所以我們可以讓節點的pdata為null。
具體實現如下:
首先我們寫一個函數實現生成一個節點,這樣在我們以后只要有插入節點的操作就可以用這個靜態方法來實現;這個函數我們需要傳數據的地址,數據的大小(這樣我們才能把數據拷貝到節點里面去),還有下一個節點的地址;
static SNode *create_node(void *pdata,size_t size,SNode *next){ SNode *node = malloc(sizeof(SNode)); //先用malloc申請一塊動態內存 if(pdata == NULL){ //如果傳進來的數據地址為null node->pdata = NULL; }else{ node->pdata = malloc(size); //否則為數據也申請一塊大小為size的動態內存, memcpy(node->pdata,pdata,size); //把pdata指向的數據通過memcpy拷貝到node->pdata里去,拷貝size個字節 } node->next = next; //讓節點指向下一個節點 return node; //返回這個節點 }
所以有了上面這個靜態方法,后面的創建一個鏈表還是插入一個節點就變得很簡單了
因為我們剛開始創建的是一個空鏈表,即只有一個頭結點,因此不傳數據
SLink create_slink(){ //return create_node(NULL,0,NULL); SNode *head = create_node(NULL,0,NULL); return head; }
除了創建一個鏈表,我們還可以初始化一個鏈表,(即在其他函數里先定義一個節點)再給它初始化。
這里我們傳進來一個鏈表的地址,鏈表類型為SNode *,它的地址即為SNode **類型,因為只有傳遞一個元素得地址,我們才能在一個函數中改變這個元素得值(函數間的傳參是值賦值)。
void init_slink(SLink *link){ *link = create_node(NULL,0,NULL); //同樣調用生成節點的靜態方法 }
所謂空鏈表就是指只有一個頭結點,這個頭結點并不存儲數據,只是記錄首節點的地址,如果這個首節點的地址為空,那么這就是一個空鏈表
bool is_empty(SLink link){ return link->next == NULL; }
鏈表中的節點是指存儲了元素的節點,所以不能包含頭結點,我們只需要把這個節點遍歷一遍,如果某個節點的下一個地址(next)為空,那這個節點就是尾結點
} size_t size(SLink link){ size_t cnt = 0; //用來記錄節點個數 SNode *node = link->next; //從首節點開始算起 while(node != NULL){ //遍歷這個鏈表 ++cnt; node = node->next; } return cnt; }
在index的位置插入一個元素,就是我們需要把index的位置變成我們新插入的節點。
在鏈表中插入一個節點,并不像在線性表(數組)中那么復雜,在線性表中插入一個元素我們需要把插入節點后面的元素都往后移,這樣增加了很多負擔,但是在鏈表中的插入節點(或者刪除節點),都只需要改變一下附近節點的指針指向就OK了,所以操作變得簡單了很多,下面我們來詳細講解一下如何插入一個節點。
首先肯定是找到我們插入的位置
如上圖所示,我們需要在下標為3的位置插入一個節點,那我們該怎么做呢?
是的我們可以想辦法獲得下標為2這個節點,然后斷開2和3之間的連線,再把new節點插入進去
如圖,我們把2節點的next指向了新插入的new節點,把new的next指向了3的節點,那2和3之間的連系就順利成章的斷掉了,那我們的插入就算完成了。
所以我們來看一下代碼
首先當然是獲得我們需要插入位置的前一個節點,即上圖的2節點
static SNode *get_prev_node(SLink link,size_t index){ //獲得前一個節點 SNode *node = link;//從頭結點開始找,比如我們插入在鏈表首插入一個節點就是插入到頭結點后面 //我們在鏈表尾插入一個節點就是當node->next為null的時候插入 size_t i; for(i=0;i<index && node != NULL;i++){ node = node->next; } return node; //這里的返回值可能為null,比如當node為尾結點的時候,它的node->next就為null }
插入操作
int insert(SLink link,size_t index,void *pdata,size_t size){ if(pdata == NULL){ //如果沒有傳進來數據,那就插入失敗 return -1; } SNode *prev = get_prev_node(link,index); if(prev == NULL){ //獲得插入位置的前一個節點,當它為Null時也不能插入數據, return -1; } SNode *node = create_node(pdata,size,prev->next); //調用生成節點的靜態方法生成一個節點, //并且傳入pdata,size,如上圖所示,新插入的節點的next指向的是原本前一個節點prev的next prev->next = node; //把prev->next重新指向新插入的節點,這樣插入就完成了 //完成了new節點旁邊兩條線的鏈接工作 //prev->next = create_node(pdata,size,prev->next); return 0; }
關于在鏈表首或者鏈表尾插入數據
這里其實很簡單,就是新插入的節點的前一個節點為頭結點或者尾結點的問題,大家可以自己寫一下
這個操作比較簡單,只需要在滿足條件下把這個下標遍歷完就可以了,沒有什么難點
void *get(SLink link,size_t index){ SNode *node = link->next; //這里我們不能從頭結點開始遍歷,因為頭結點并不存儲數據所以只能從首節點開始遍歷 size_t i; for(i=0;i<index && node != NULL;i++){ node = node->next; } if(node == NULL){ return NULL; } return node->pdata; //遍歷完成并且返回這個數據的地址即可 }
刪除可以說是插入的反過來的操作
比如上圖,我們需要刪除3這個節點,那我們該怎么辦呢?其實比插入更簡單,我們只需要讓2的next指向需要刪除節點的next(也就是3的next),并且把3節點給釋放掉,把里面的數據也釋放掉就OK了
首先我們也可以寫一個靜態方法來實現刪除某個節點
static void remove_node(SNode *prev){ //這里的prev為需要被刪除的節點的前一個節點 SNode *node = prev->next; //記錄被刪除的節點 SNode *next = node->next; //記錄被刪除的節點的下一個節點 free(node->pdata); free(node); prev->next = next; }
然后刪除節點的操作
int remove_data(SLink link,size_t index){ SNode *prev = get_prev_node(link,index); //獲得被刪除節點的前一個節點 if(link == NULL || prev == NULL || prev->next == NULL){ //如果鏈表不存在或者被刪除節點的前一個節點不存在或者被刪除的節點不存在,那就刪除失敗 return -1; } remove_node(prev); return 0; }
大家自己也可以寫一下刪除首節點或者尾結點
所謂鏈表逆序就是將鏈表的存儲順序反過來,
比如上圖,它的逆序結果是什么呢?
我們來看一下,就是讓5節點的next指向4節點,4指向3,3指向2,2指向1,1的next指向NULL,然后再把頭結點指向5,就完成了逆序,如下圖所示
那我們該怎么用代碼實現呢?
SLink reverse(SLink link){ if(link==NULL || link->next == NULL || link->next->next == NULL){ //如果鏈表不存在或者只存在頭結點或者只存在一個節點,那么我們就不需要進行逆序操作 return link; } SNode *prev = link->next;//記錄第一個節點 SNode *curr = link->next->next;//記錄第二個節點 SNode *next; while(curr != NULL){ //只要當前節點不為空就繼續執行下去 next = curr->next; //記錄curr的下一個節點 curr->next = prev; //讓指針反過來指向,即當前節點的指針指向前一個節點 prev = curr; //然后往后移 curr = next; } //最后還有兩條線沒有連上 //即以前的首節點(即上圖的節點1)的next應該指向null,首節點再變為prev,即上圖的節點5 link->next->next = NULL; // link->next = prev; return link; }
清空鏈表只需要一個一個的遍歷,把節點里的數據釋放掉,再釋放掉該節點
void clear(SLink link){ SNode *node = link->next; //從首節點開始遍歷 while(node != NULL){ SNode *next = node->next; //記錄需要被釋放的節點的下一個節點 free(node->pdata); free(node); node = next; } link->next = NULL; }
這個更為簡單,只需要先清空里面的節點,在把頭結點釋放掉即可
void destroy(SLink link){ clear(link); free(link); }
鏈表的遍歷也無需多說,我們只需要從首節點開始遍歷,并且把節點的數據傳給函數指針,這樣也更加靈活,一直遍歷到null為止
void foreach(SLink link,void (*func)(const void *)){ SNode *node = link->next; //從首節點開始遍歷 for(;node != NULL; node = node->next){ func(node->pdata); //把節點的數據傳給func這個函數, //然后用戶需要對這個數據進行什么樣的處理由用戶自己決定,不僅僅是局限于把這些數據給打印出來 //這樣的好處是對數據的處理更加靈活了 } }
我們也是從首節點開始遍歷,對首節點里的數據進行比較,如果找到相同的數據,那就返回這個數據的地址
void *find(SLink link,void *pdata,int (*compare)(const void *,const void*)){ SNode *node = link->next; //從首節點開始查找 for(;node != NULL; node = node->next){ if(compare(node->pdata,pdata)==0){ //如果該節點的數據和帶查找的數據相等 return node->pdata; //那就返回這個數據的地址 } } return NULL; //如果沒有找到就返回null }
這里的compare也是函數指針,都是同樣的道理,對于需要找什么由用戶自己決定,不在局限于查找某個特定的元素
比如在一個學生信息的結構體中,我們可以按學號進行查找,也可以按姓名進行查找,也可以按年齡、班級、等等進行查找,讓這些使用起來更加靈活
比如我給大家寫一個查找的函數,就按學生的學號進行查找
int compare(const void* v1,const void* v2){ Stu *stu1 = (Stu *)v1; Stu *stu2 = (Stu *)v2; return v1->no-v2->no; }
這個刪除的操作其實和上面的刪除特定下標的節點的操作大同小異,都是找到待刪除節點的前一個節點,然后進行操作,這里找到后并不急于退出,而是繼續往下查找,直到找完整個鏈表并且刪除所有符合條件的節點
int remove_all(SLink link,void *pdata,int (*compare)(const void *,const void *)){ SNode *prev = link; int cnt = 0; while(prev->next != NULL){ if(compare(prev->next->pdata,pdata)==0){ //找到待刪除節點的前一個節點 remove_node(prev); //刪除該節點 cnt++; //刪除的個數++ }else{ prev = prev->next; //否則沒找到就是往下繼續查找 } } return cnt>0?0:-1; }
排序我這里選擇冒泡排序,如果大家想看更多的排序方法也可以看我以前寫的博客,我總共寫了12種排序方法
這里的排序和我以前寫的幾乎一模一樣,我就不再多說了,唯一需要講解的還是函數指針的應用,比如我們可以選擇對學生的學號進行排序,也可以對學生的姓名、成績、身高、年齡等等都可以進行升降序的排序
void sort(SLink link,int (*compare)(const void *,const void *)){ if(link->next == NULL || link->next->next == NULL){ return; } size_t i; SNode *tail = NULL; SNode *n = link->next; for(;n != NULL;n = n->next){ SNode *node; bool swap = false; for(node = link->next;node->next != tail;node = node->next){ SNode *prev = node; SNode *next = prev->next; if(compare(prev->pdata,next->pdata)>0){ //這里選擇的排序方式或者進行排序的元素 swap(prev->pdata,next->pdata); swap = true; } } tail = node; if(!swap){ break; } } }
我再來寫一個對學生的姓名按照升序進行排序的方法
int cmopare(const void *v1,const void* v2){ Stu *s1 = (Stu *)v1; Stu *s2 = (Stu *)v2; return strcmp(s1->name,s2->name); }
以上就是“C語言如何實現動態鏈表”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。