標準連結串列實現合併有序連結串列、逆序單鏈表功能
阿新 • • 發佈:2020-12-22
直接貼上已經碼好的:
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核心連結串列,而不要自己去實現。
.