1. 程式人生 > 實用技巧 >標準連結串列實現合併有序連結串列、逆序單鏈表功能

標準連結串列實現合併有序連結串列、逆序單鏈表功能

直接貼上已經碼好的:

list_sort.c:

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>

/**** 雙向連結串列,非雙向迴圈連結串列哦!
 * 
 *  gcc -m32 : 在64位系統上編譯出32位的程式(指標大小4位元組), 
 *  這樣編譯本程式碼不會編譯報警告  
 * 
 *****/

#define use_double_direction_list    //
關閉雙向非迴圈連結串列模式,則預設開啟單向非迴圈連結串列模式 /*計算member在type中的位置*/ #define offsetof(type, member) (unsigned int)(&((type*)0)->member) /*根據member的地址獲取type的起始地址*/ #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member)*__mptr = (ptr); \ (type
*)((char *)__mptr - offsetof(type, member)); }) typedef struct _inside_link{ struct _inside_link* pNext; #if defined(use_double_direction_list) struct _inside_link* pFront; #endif }inside_link; typedef struct _usr_data_pack{ unsigned char* name0; unsigned int data0; }usr_data_pack; typedef
struct _usrdata_templ{ inside_link link; usr_data_pack usr_data; }usrdata_templ; void usrdata_set(usrdata_templ* pusr_data_list, usr_data_pack* pdata_pack){ memset(&pusr_data_list->usr_data, 0, sizeof(usr_data_pack)); memcpy(&pusr_data_list->usr_data, pdata_pack, sizeof(usr_data_pack)); } static void print_usr_data_pack(usr_data_pack* pdata_pack){ printf("pdata_pack->name0 = \033[0;31m %s,\t\033[0m pdata_pack->data0 = \033[0;33m %d \033[0m\n", \ pdata_pack->name0, pdata_pack->data0); } void usrdata_print(usrdata_templ* pusr_data_list){ if(pusr_data_list != NULL){ print_usr_data_pack(&pusr_data_list->usr_data); } inside_link* pNext_link = pusr_data_list->link.pNext; while(pNext_link){ usrdata_templ* pNext_templ = NULL; pNext_templ = container_of(pNext_link, usrdata_templ, link); print_usr_data_pack(&pNext_templ->usr_data); pNext_link = pNext_link->pNext; } } void link_init(inside_link* plink){ #if defined(use_double_direction_list) plink->pFront = NULL; #endif plink->pNext = NULL; } /***** 對比核心連結串列 下面是核心從尾部新增函式: static inline void list_add_tail(struct list_head *newer, struct list_head *head) { __list_add(newer, head->prev, head); } 核心連結串列使用了雙向迴圈連結串列,這裡找到尾巴節點,只要從頭節點向前推一個節點就找到了,很方便。 而我使用了雙向非迴圈連結串列,就需要遍歷了,程式碼也更難看了。 心得: 雙向迴圈連結串列相對於雙向非迴圈連結串列,幾乎不增加記憶體成本,而且能夠提高效率。 以後寫程式碼,編寫元件,都要使用雙向迴圈連結串列。 *******/ /** * list_add_tail沒有檢查同一個連結串列節點兩次被加入的情況。 * 但是我這個函式檢查了 * **/ int link_tail_add(inside_link* plink, inside_link* pnode){ inside_link* pcurrent_node = plink->pNext; inside_link* pformer_node = plink; if(plink == pnode){ /****檢查同一個連結串列節點兩次及以上次數被加入*****/ return -1; } while(pcurrent_node){ if(pnode == pcurrent_node){ /****檢查同一個連結串列節點兩次及以上次數被加入*****/ return -1; } pformer_node = pcurrent_node; pcurrent_node = pcurrent_node->pNext; } #if defined(use_double_direction_list) pnode->pFront = pformer_node; #endif pformer_node->pNext = pnode; return 0; } void create_usrdaralist1(usrdata_templ* phead){ link_init(&phead->link); // add first node, then print usr_data_pack pack0 = {"jack 170", 170}; usrdata_set(phead, &pack0); #if 0 // add second node, then print usrdata_templ* pnode1 = (usrdata_templ*)malloc(sizeof(usrdata_templ)); usr_data_pack pack1 = {"jack 171", 171}; link_init(&pnode1->link); usrdata_set(pnode1, &pack1); if(!link_tail_add(&phead->link, &pnode1->link)){ }else{ printf("\033[0;33m this Node add Fail! \033[0m \n"); } #endif // add third node, then print usrdata_templ* pnode2 = (usrdata_templ*)malloc(sizeof(usrdata_templ)); usr_data_pack pack2 = {"jack 172", 172}; link_init(&pnode2->link); usrdata_set(pnode2, &pack2); if(!link_tail_add(&phead->link, &pnode2->link)){ }else{ printf("\033[0;33m this Node add Fail! \033[0m \n"); } // add 4th node, then print // 這裡嘗試將實驗3處的節點再次新增到連結串列上, // 即實驗一個連結串列節點多次被新增 if(!link_tail_add(&phead->link, &pnode2->link)){ }else{ printf("\033[0;33m this Node add Fail! \033[0m \n"); } #if 1 // add 4th node agian, then print usrdata_templ* pnode3 = (usrdata_templ*)malloc(sizeof(usrdata_templ)); usr_data_pack pack3 = {"jack 178", 178}; link_init(&pnode3->link); usrdata_set(pnode3, &pack3); if(!link_tail_add(&phead->link, &pnode3->link)){ //usrdata_print(phead); //printf("Creat List1 Done ----------\n\n"); }else{ printf("\033[0;33m this Node add Fail!! \033[0m \n"); } #endif usrdata_print(phead); printf("Creat List1 Done ----------\n\n"); } void create_usrdaralist2(usrdata_templ* phead){ link_init(&phead->link); // add first node, then print usr_data_pack pack0 = {"merry 172", 172}; usrdata_set(phead, &pack0); //usrdata_print(phead); // add second node, then print usrdata_templ* pnode1 = (usrdata_templ*)malloc(sizeof(usrdata_templ)); usr_data_pack pack1 = {"merry 173", 173}; link_init(&pnode1->link); usrdata_set(pnode1, &pack1); if(!link_tail_add(&phead->link, &pnode1->link)){ //usrdata_print(phead); }else{ printf("\033[0;33m this Node add Fail! \033[0m \n"); } // add third node, then print usrdata_templ* pnode2 = (usrdata_templ*)malloc(sizeof(usrdata_templ)); usr_data_pack pack2 = {"merry 174", 174}; link_init(&pnode2->link); usrdata_set(pnode2, &pack2); if(!link_tail_add(&phead->link, &pnode2->link)){ //usrdata_print(phead); }else{ printf("\033[0;33m this Node add Fail! \033[0m \n"); } // add 4th node, then print // 這裡嘗試將實驗3處的節點再次新增到連結串列上, // 即實驗一個連結串列節點多次被新增 if(!link_tail_add(&phead->link, &pnode2->link)){ //usrdata_print(phead); }else{ printf("\033[0;33m this Node add Fail! \033[0m \n"); } // add 4th node agian, then print usrdata_templ* pnode3 = (usrdata_templ*)malloc(sizeof(usrdata_templ)); usr_data_pack pack3 = {"merry 175", 175}; link_init(&pnode3->link); usrdata_set(pnode3, &pack3); if(!link_tail_add(&phead->link, &pnode3->link)){ usrdata_print(phead); printf("Create List2 Done ----------\n\n"); }else{ printf("\033[0;33m this Node add Fail!! \033[0m \n"); } } void combine_2_becomes_1(usrdata_templ*plist1, usrdata_templ*plist2){ inside_link* p_list1_curlink = &plist1->link; inside_link* p_list2_curlink = &plist2->link; //inside_link* p_list1_baklink = NULL; //inside_link* p_list2_baklink = NULL; usrdata_templ* p_cur_usrdata1 = NULL; usrdata_templ* p_cur_usrdata2 = NULL; usrdata_templ newlist = { .link = {0}, .usr_data = { .name0 = "我是頭節點", .data0 = 0, } }; link_init(&newlist.link); int flagloop = 1; do{ usleep(100000); printf(" time: 100 ms \n"); // 1. 取 if(NULL != p_list1_curlink){ p_cur_usrdata1 = container_of(p_list1_curlink, usrdata_templ, link); //printf("%s, %d \n", p_cur_usrdata1->usr_data.name0, p_cur_usrdata1->usr_data.data0); } else{ break; } if(NULL != p_list2_curlink){ p_cur_usrdata2 = container_of(p_list2_curlink, usrdata_templ, link); //printf("%s, %d \n", p_cur_usrdata2->usr_data.name0, p_cur_usrdata2->usr_data.data0); } else{ break; } // 2.比 if(p_cur_usrdata1->usr_data.data0 <= p_cur_usrdata2->usr_data.data0){ // 3. 存 usrdata_templ *pnode = (usrdata_templ *)malloc(sizeof(usrdata_templ)); usr_data_pack pack = {0}; memcpy(&pack, &p_cur_usrdata1->usr_data, sizeof(usr_data_pack)); link_init(&pnode->link); usrdata_set(pnode, &pack); if(!link_tail_add(&newlist.link, &pnode->link)){ printf("\033[0;34m Add Success. Line: %d \033[0m \n", __LINE__); } else{ printf("\033[0;33m this Node add Fail! Line: %d \033[0m \n", __LINE__); } // 4. 移 p_list1_curlink = p_list1_curlink->pNext; }else{ // 3. 存 usrdata_templ *pnode = (usrdata_templ *)malloc(sizeof(usrdata_templ)); usr_data_pack pack = {0}; memcpy(&pack, &p_cur_usrdata2->usr_data, sizeof(usr_data_pack)); link_init(&pnode->link); usrdata_set(pnode, &pack); if(!link_tail_add(&newlist.link, &pnode->link)){ printf("\033[0;34m Add Success. Line: %d \033[0m \n", __LINE__); } else{ printf("\033[0;33m this Node add Fail! Line: %d \033[0m \n", __LINE__); } // 4. 移 p_list2_curlink = p_list2_curlink->pNext; } }while(1); // 4. 比 printf("p_list1_curlink = 0x%x, p_list2_curlink = 0x%x \n", \ (unsigned int)p_list1_curlink, (unsigned int)p_list2_curlink); inside_link* p_list_left = NULL; printf("====下面列印的排序後的兩個有序連結串列的前半部分======\n"); usrdata_print(&newlist); if(NULL != p_list1_curlink){ p_list_left = p_list1_curlink; } if(NULL != p_list2_curlink){ p_list_left = p_list2_curlink; } while(p_list_left){ usrdata_templ* p_left_usrdata = container_of(p_list_left, usrdata_templ, link); usrdata_templ *pnode = (usrdata_templ *)malloc(sizeof(usrdata_templ)); usr_data_pack pack = {0}; memcpy(&pack, &p_left_usrdata->usr_data, sizeof(usr_data_pack)); link_init(&pnode->link); usrdata_set(pnode, &pack); if(!link_tail_add(&newlist.link, &pnode->link)){ printf("\033[0;34m Add Success. Line: %d \033[0m \n", __LINE__); } else{ printf("\033[0;33m this Node add Fail! Line: %d \033[0m \n", __LINE__); } p_list_left = p_list_left->pNext; } printf("====下面完整列印排序後的兩個有序連結串列======\n"); usrdata_print(&newlist); } usrdata_templ* reverse_single_direction_list(usrdata_templ*plist){ usrdata_templ* plist_local = plist; inside_link *p_list_curlink = &plist_local->link; inside_link *p_list_baklink_former = NULL, *p_list_baklink_former_former = NULL; unsigned int the_single_list_node_cnt = 0; usrdata_templ* p_cur_usrdata; while(p_list_curlink->pNext){ /** 如果單鏈表有N個節點,那麼退出該while時,the_single_list_node_cnt值為(N-1) **/ the_single_list_node_cnt++; p_list_baklink_former_former = p_list_baklink_former; p_list_baklink_former = p_list_curlink; p_list_curlink = p_list_curlink->pNext; if(the_single_list_node_cnt >= 2){ p_list_baklink_former->pNext = p_list_baklink_former_former; } } p_list_curlink->pNext = p_list_baklink_former; if(the_single_list_node_cnt >= 1){ /**只要存在兩個以上節點,就要把第一個節點的pNext指標值為NULL(即將其設定為尾節點)**/ plist_local->link.pNext = NULL; } if(0 == the_single_list_node_cnt){ printf("\033[0;33m 該list只有1個節點,不需要逆序! \033[0m \n"); } p_cur_usrdata = container_of(p_list_curlink, usrdata_templ, link); return p_cur_usrdata; } int main(){ usrdata_templ* phead1 = (usrdata_templ*)malloc(sizeof(usrdata_templ)); create_usrdaralist1(phead1); usrdata_templ* phead2 = (usrdata_templ*)malloc(sizeof(usrdata_templ)); create_usrdaralist2(phead2); #if 1 printf(" 合併兩個有序連結串列測試 開始\n\n"); combine_2_becomes_1(phead1, phead2); printf(" 合併兩個有序連結串列測試 完畢 \n\n\n"); #endif #if 1 printf("\n 逆序單鏈表List1 測試 開始\n"); usrdata_templ* p_reverse = reverse_single_direction_list(phead1); usrdata_print(p_reverse); printf(" 逆序單鏈表測試 完畢\n\n"); #endif return 0; }

makefile:

do:
    #gcc list_sort.c
    gcc -m32 list_sort.c
    ./a.out

執行:

個人心得:

  對於連結串列的使用,不僅僅是學習其侵入式連結串列的特點,還要領會其迴圈連結串列的優點, 雙向、迴圈、侵入式、每個都是值得學習的地方。

話外(吐槽):

  對於面試造飛機,要在一小時內寫完一張卷子,還包括幾道這種題目,難度的確大。

備戰面試筆試,我們要做到不假思索就能寫出來,不僅靠除錯能力(面試筆試只有筆和紙,甚至都不能除錯),可能還需要記和背了。

  本例子實現的連結串列不僅完成了基本功能,還具有一定的可複用特點,具有一定的工程意義,所以足以應付筆試標準。

但是對外提供的API還不夠豐富,且未做執行緒安全處理, 達不到實際工程標準,在實際工程中,我們應該直接使用Linux核心連結串列,而不要自己去實現。

.