c語言之單向鏈表

0x00 什麼是鏈表

鏈表能夠說是一種最爲基礎的數據結構了,而單向鏈表更是基礎中的基礎。鏈表是由一組元素以特定的順序組合或連接在一塊兒的,不一樣元素之間在邏輯上相鄰,可是在物理上並不必定相鄰。在維護一組數據集合時,就可使用鏈表,這一點和數組很類似。可是,鏈表有着數組所不具備的優點。一方面,鏈表在執行插入刪除操做時擁有更高的效率;另外一方面,鏈表是在堆區動態的開闢存儲空間,而大多數的數據在編譯時大小並不能肯定,所以這種動態開闢空間的特性也能夠說是鏈表的一個優勢。node

0x01 鏈表的應用

  • 多項式計算
  • 滾動列表
  • 郵件列表
  • 文件的鏈式分配
  • 內存管理
    ……

0x02 單向鏈表初見

就像圖像所示,單向鏈表各個元素之間經過一個指針前後連接起來。每一個元素包含兩個部分,分別是數據域和指針域。前一個元素經過next指針指向後一個元素,鏈表的開始的元素爲鏈表頭,即head指針所指,鏈表結束的元素爲鏈表尾,尾部元素的next指針指向NULL。可見,單向鏈表爲線性結構。數組

若想訪問鏈表中的一個元素,咱們只能從鏈表的頭部開始,順着指針指向逐查找。若是從鏈表頭移動到指定的元素,而這時候咱們又想訪問當前元素以前的某個元素,這時候只能從頭在次遍歷鏈表。這相比數組可以經過下標直接訪問要麻煩的多,不過咱們應該根據不一樣的應用場景選擇數組仍是鏈表,它們只有在對的地方纔能發揮出巨大的威力。數據結構

0x03 單向鏈表的操做

0x00 鏈表結構

typedef struct node//鏈表元素的結構
{
    void *data;//節點中的數據域,設置爲無類型指針,數據類型大小由使用者定義
    struct node *next;//指向下一節點的指針
}Node;

typedef struct list//鏈表的結構
{
    int size;//鏈表中節點個數
    void (*destroy)(void *data);//因爲鏈表節點中的數據是用戶自定義的,故須要調用者提供釋放空間的函數
    void (*print_data)(const void *data);//同,由用戶自定義打印數據的函數
    int (*match)(const void *key1, const void *key2);//同,由用戶自定義數據的比較方式

    Node *head;//記錄鏈表的頭部位置
    Node *tail;//記錄鏈表的尾部位置
}List;

示意圖以下:
函數

0x01 接口

下面是鏈表操做函數的接口,以及簡單介紹:優化

extern void list_init(List *list, void (*destroy)(void *data), void (*print_data)(const void *data), \
    int (*match)(const void *key1, const void *key2));//初始化一個鏈表
extern int list_ins_head(List *list, const void *data);//鏈表的插入,將節點從頭部插入
extern int list_ins_tail(List *list, const void *data);//鏈表的插入,將節點從尾部插入
extern int list_ins_sort(List *list, const void *data);//鏈表的插入,插入後鏈表是一個有序的鏈表
extern void* list_search(List *list, const void *data);//在鏈表中查找指定數據,若找到返回數據的地址
extern void* list_remove(List *list, const void *data);//在鏈表中刪除指定數據,若找到刪除節點並將數據地址返回
extern void list_reverse(List *list);//將鏈表逆置
extern void list_sort(List *list);//將鏈表按照必定方式排序
extern void print_list(List *list);//打印鏈表
extern void list_destroy(List *list);//刪除整個鏈表

#define list_size(list) (list->size)  //返回鏈表節點個數

0x02 list_init

使用list_init函數初始化一個鏈表,以便鏈表的其餘操做。3d

void list_init(List *list, void (*destroy)(void *data), void (*print_data)(const void *data), \
    int (*match)(const void *key1, const void *key2))
{
    list->size = 0;//初始時,鏈表沒有節點,設置爲0
    list->head = NULL;//頭和尾置空
    list->tail = NULL;
    list->match = match;//初始化鏈表的成員函數
    list->destroy = destroy;
    list->print_data = print_data;

    return;
}

0x03 list_ins_head

使用list_ins_head函數,在鏈表的頭部插入數據。示意圖以下:指針

從示意圖能夠看出,單向鏈表的部插入邏輯很是簡單。仔細觀察,標綠的部分代碼有重複,能夠優化code

/*在鏈表的頭部插入數據*/
int list_ins_head(List *list, const void *data)
{
    Node *new_node = (Node *)calloc(1, sizeof (Node)); //建立插入的節點
    if(new_node == NULL)
        return -1;

    new_node->data = (void *)data;//關聯節點與數據
/*
    if(list_size(list) == 0)//鏈表爲空時,插入節點
    {
        list->tail = new_node;
        new_node->next = NULL;
        list->head = new_node;
    }
    else //鏈表非空時將節點插入頭部
    {
        new_node->next = list->head;
        list->head = new_node;
    }
*/
    if(list_size(list) == 0)//鏈表爲空時,插入節點
        list->tail = new_node;

    new_node->next = list->head;
    list->head = new_node;

    list->size ++;//維護鏈表size屬性

    return 0;
}

0x04 list_ins_tail

使用list_ins_tail函數,在鏈表的尾部插入數據,示意圖以下:blog

/*在鏈表的尾部插入數據*/
int list_ins_tail(List *list, const void *data)
{

    Node *new_node = (Node *)calloc(1, sizeof (Node)); //建立插入的節點
    if(new_node == NULL)
        return -1;
    new_node->data = (void *)data;//關聯節點與數據

    if(list_size(list) == 0)
        list->head = new_node;
    else
        list->tail->next = new_node;

    list->tail = new_node;
    new_node->next = NULL;

    list->size ++;
    return 0;
}

0x05 list_ins_sort

使用list_ins_sort函數,進行鏈表的有序插入。排序

鏈表的有序插入大體能夠分爲兩種狀況:
其一,鏈表爲空時直接插入;
其二,鏈表非空時,在此時又分爲三種小狀況;

  1. 在鏈表頭部插入
  2. 在鏈表中部插入
  3. 在鏈表尾部插入

鏈表爲空時,操做方法和頭尾部插入相似。鏈表非空時,咱們須要先尋找到插入位置,而後在將數據插入鏈表。
在此以前咱們已經瞭解瞭如何在鏈表的頭部和尾部插入元素,那麼,如今惟一須要處理的即是 在鏈表中部插入節點 ,這是鏈表插入操做的核心。

注意:在鏈表中部插入節點時,必須獲得前一節點的位置,即圖中指向藍色節點的指針p_pre.

插入節點的邏輯瞭解後,處理在非空鏈表狀況下插入節點就清晰多了。

/*在鏈表的有序插入數據*/
int list_ins_sort(List *list, const void *data)
{
    Node *new_node = (Node *)calloc(1, sizeof (Node)); //建立插入的節點
    if(new_node == NULL)
        return -1;
    new_node->data = (void *)data;//關聯節點與數據

    if(list_size(list) == 0)//鏈表爲空時,插入節點
    {
        list->tail = new_node;
        new_node->next = NULL;
        list->head = new_node;
    }
    else//鏈表非空
    {
        Node *p_cur = list->head;
        Node *p_pre = list->head;

        while(p_cur != NULL && list->match(new_node->data, p_cur->data) > 0)//查找鏈表的插入位置
        {
            p_pre = p_cur;
            p_cur = p_cur->next;
        }
        if(p_cur != NULL)//插入位置在頭部和中間時
        {
            if(p_cur == list->head)//插入位置在頭部
            {
                new_node->next = list->head;
                list->head = new_node;
            }
            else//位置在鏈表中間
            {
                new_node->next = p_pre->next;
                p_pre->next = new_node;
            }
        }
        else//插入位置在鏈表尾部
        {
            list->tail->next = new_node;
            list->tail = new_node;
            new_node = NULL;
        }
    }
    list->size ++;
    return 0;
}

使用list_search函數,查找鏈表中與數據匹配的節點,並返回節點指針。
此處查找邏輯與list_ins_sort中的查找邏輯基本相似,不作贅述。

/*查找鏈表中與數據匹配的節點,並返回節點指針*/
void* list_search(List *list, const void *data)
{
    if(list_size(list) == 0)
    {
        printf("list is empty\n");
        return NULL;
    }
    else
    {
        Node *p_cur = list->head;
        while(p_cur != NULL && list->match(p_cur->data, data) != 0)//查找數據在鏈表中的位置
            p_cur = p_cur->next;

        if(p_cur != NULL)//找到返回數據地址,不然返回NULL
            return p_cur->data;
        else
            return NULL;
    }

}

0x07 list_remove

使用list_remove函數,刪除節點,並將節點中的數據返回,交由用戶處理。
此處查找邏輯與list_ins_sort中的查找邏輯基本相似,不作贅述。

和插入節點中分爲頭部、中部尾部相似,刪除也分爲頭中尾部。

刪除頭部節點

不過刪除頭部節點時須要注意一點,就是當鏈表僅有一個節點時,咱們須要維護一下tail指針。

刪除中部節點

刪除尾部節點

注意:代碼中將中部與尾部的刪除進行了合併。

/*刪除指定數據的節點*/
void* list_remove(List *list, const void *data)
{
    void *old_data = NULL;
    Node *p_cur = list->head;
    Node *p_pre = list->head;
    while (p_cur != NULL && list->match(p_cur->data, data) !=0)
    {
        p_pre = p_cur;
        p_cur = p_cur->next;
    }
    if(p_cur != NULL && list->match(p_cur->data, data) ==0)//刪除位置在頭部和中間時
    {
        if(p_cur == list->head)//刪除位置在頭部
        {
            list->head = p_cur->next;
            if(p_cur->next == NULL)
                list->tail = NULL;

        }
        else//中部時或尾部
        {
            p_pre->next = p_cur->next;
            if(p_cur->next == NULL)//判斷是否爲尾部
                list->tail = p_pre;
        }
        old_data = p_cur->data;
        free(p_cur);
        list->size --;
    }
    return old_data;
}

0x08 list_reverse

使用list_reverse函數將鏈表逆置。

逆置過程:

觀察能夠發現,逆置的過程本質上就是將原來的鏈表逐個摘下頭節點,插入逆置後的鏈表的頭部

void list_reverse(List *list)
{
    if(list_size(list) != 0)
    {
        Node *p_pre = list->head;
        Node *p_cur = list->head->next;
        list->head->next = NULL;
        list->tail = list->head;
        while(p_cur!= NULL)
        {
            p_pre = p_cur;
            p_cur = p_cur->next;
            p_pre->next = list->head;
            list->head = p_pre;
        }
    }
    return;
}

0x09 list_sort

使用list_sort函數對鏈表進行排序,此處使用選擇排序法。

void list_sort(List *list)
{
    if(list_size(list) != 0)
    {
        Node *p_i = list->head;
        while(p_i->next != NULL)
        {
            Node *p_min = p_i;
            Node *p_j = p_min->next;
            while (p_j != NULL)
            {
                if(list->match(p_min->data, p_j->data) > 0)
                    p_min = p_j;
                p_j = p_j->next;
            }
            if(p_min != p_i)
            {
                void *data = p_i->data;
                p_i->data = p_min->data;
                p_min->data = data;
            }
            p_i = p_i->next;
        }
    }
    return;
}

0x10 print_list和list_destroy

void print_list(List *list)
{

    if(list->head == NULL)//鏈表爲空
    {
        printf("list is empty\n");
    }
    else //鏈表非空
    {
        Node * p_cur = list->head;
        while (p_cur)
        {
            list->print_data(p_cur->data);
            p_cur = p_cur->next;
        }
    }

    return;
}
void list_destroy(List *list)
{
    Node *p_cur = list->head;
    while (p_cur != NULL)
    {
        list->head = list->head->next;
        list->destroy(p_cur->data);//釋放節點中的數據
        free(p_cur);//釋放節點
        p_cur = list->head;
    }
    memset(list, 0, sizeof (List));
    return;
}
相關文章
相關標籤/搜索