C語言實現不帶頭節點的單鏈表

在上兩篇文章中,我們介紹了基於靜態數組和動態數組的順序表,順序表頻繁查詢而插入刪除動作少的情況下,順序表也適用於進行尾插的時候,因爲相對於鏈表而言,順序表在進行尾插時,順序表不需要通過遍歷來找到最後一個插入點,比較而言,順序表尾插效率高。
但是,在進行頭插和中插時,順序表需要將插入元素位置之後的元素整體後移才能插入數據,這樣做在有大量插入刪除場景下即爲麻煩且效率低,因此,提出了鏈表的思想。而鏈表在頭插或者中插時,只需要創建一個新節點,然後將節點鏈入所插入位置即可。

鏈表概述

鏈表是一種常見的數據結構。數組可以存放數據,但是數組存放數據時必須提前指定數組包含元素個數,即數組長度。但是數組保存數據的缺點在於如果要保存元素大於數組大小時,則不能將所有內容保存入數組,而當要保存元素遠小於數組大小時,又造成了空間的浪費。而鏈表其存儲元素個數不受限定,當進行添加元素時,存儲個數隨之增加。
鏈表是一種鏈式存儲的線性表,用一組地址任意的存儲單元存放線性表的數據元素,稱存儲單元爲一個節點。

typedef int DataType;

typedef int DataType;

typedef struct LinkNode
{
    DataType _data;//當前節點保存數據
    struct LinkNode* _pNext;//指向鏈表中下一結點的指針

}Node;

如下圖,爲鏈表結構示意圖:
這裏寫圖片描述
在鏈表中有一個頭指針變量,圖中head表示的就是頭指針,這個指針變量保存一個地址。從圖中的箭頭可以看到該地址爲一個變量的地址,也就是說頭指針指向一個變量。這個變量稱爲元素,在鏈表中每一個元素包括兩個部分:數據部分和指針部分。數據部分用來存放元素所包含的數據,而指針部分用來指向下一個元素。最後一個元素指針指向NULL,表示指向的地址爲空。
從圖可以看到,head頭結點指向第一個元素,第一個元素中的指針又指向第二個元素,第二個元素的指針又指向第三個元素的地址,第三個元素的指針指向第四個元素,第四個元素的指針就指向爲空。

鏈表的分類

  • 單鏈表
  • 雙向鏈表
  • 雙向循環鏈表

需要注意的是,上述三種鏈表都有兩種形式,分別爲帶頭結點和不帶頭結點。

這裏寫圖片描述

C語言實現不帶頭節點的單鏈表

具體代碼實現如下:

#pragma once 

#include <stdio.h>
#include <assert.h>
#include <malloc.h>

/***************************************/

//鏈表的定義
typedef int DataType;

typedef struct LinkNode
{
    DataType _data;//當前節點保存數據
    struct LinkNode* _pNext;//指向鏈表中下一結點的指針

}Node;

/***************************************/

//鏈表的初始化
void LinkListInit(Node** pHead);

//創建新結點
Node* LinkListCreatNode(DataType data);

//銷燬結點
void LinkListIDestroyNode(Node** pHead);

//遍歷打印鏈表
void LinkListPrint(Node** pHead);

//尾插元素
void LinkListPushBack(Node** pHead, DataType data);

//尾刪元素
void LinkListPopBack(Node** pHead);

//頭插元素
void LinkListPushFront(Node** pHead, DataType data);

//頭刪元素
void LinkListPopFront(Node** pHead);

//查找元素
size_t LinkListFind(Node** pHead, DataType data);

//任意位置的插入
void LinkListInsert(Node** pHead, DataType data, Node* pos);

//任意位置的刪除
void LinkListErase(Node** pHead, Node* pos);

//求單鏈表長度
size_t LinkListSize(Node** pHead);

//銷燬一個單鏈表
void LinkListDestroy(Node** pHead);

//判空
size_t LinkListEmpty(Node** pHead);


/***************************************/
//鏈表的初始化
void LinkListInit(Node** pHead)
{
    //考慮指針爲空
    assert(pHead);
    *pHead = NULL;
}

//創建新結點
Node* LinkListCreatNode(DataType data)
{
    Node* pNewNode = (Node*)malloc(sizeof(Node));
    assert(pNewNode);

    pNewNode->_data = data;
    pNewNode->_pNext = NULL;
    return pNewNode;
}

//銷燬結點
void LinkListIDestroyNode(Node** pHead)
{
    //釋放該結點,並將頭指針指向NULL,防止出現野指針
    free(pHead);
    *pHead = NULL;
}

//遍歷打印鏈表
void LinkListPrint(Node** pHead)
{
    Node* pCur = *pHead;
    for (pCur; pCur != NULL; pCur = pCur->_pNext)
    {
        printf("%d--->", pCur->_data);
    }
    printf("NULL\n");
}

//尾插元素
void LinkListPushBack(Node** pHead, DataType data)
{
    Node* NewNode = LinkListCreatNode(data);
    assert(pHead);
    //如果鏈表爲空,則直接讓頭指針指向新申請的節點即可
    if (*pHead == NULL)
    {
        *pHead = NewNode;
        return;
    }
    //否則從頭開始遍歷鏈表,直到當前節點的指針域指向NULL,然後讓當前節
    //點的指針域指向新申請的節點即可
    Node* pTail = *pHead;
    while (pTail->_pNext)
    {
        pTail = pTail->_pNext;
    }
    pTail->_pNext = NewNode;
}

//尾刪元素
void LinkListPopBack(Node** pHead)
{
    assert(pHead);
    if (*pHead == NULL)
    {
        printf("鏈表爲空,無法刪除!\n");
        return;
    }
    //如果當前只有一個結點,則釋放該結點,
    //並將頭指針指向NULL,防止出現野指針--相當於銷燬結點
    if ((*pHead)->_pNext == NULL)
    {
        free(pHead);
        *pHead = NULL;
    }
    //定義兩個指針,依次向後走
    //pTail不爲空時,將其前一個結點指針pPreTail賦給pTail
    //當pTail爲空時,釋放pTail,pPreTail指向NULL
    Node* pTail = *pHead;
    Node* pPreTail = NULL;
    while (pTail->_pNext)
    {
        pPreTail = pTail;
        pTail = pTail->_pNext;
    }
    free(pTail);
    pPreTail->_pNext = NULL;

}

//頭插元素
void LinkListPushFront(Node** pHead, DataType data)
{
    assert(pHead);
    Node* NewNode = LinkListCreatNode(data);
    NewNode->_pNext= (*pHead);
    *pHead = NewNode;
}

//頭刪元素
void LinkListPopFront(Node** pHead)
{
    Node* pDel = NULL;
    assert(pHead);
    //考慮鏈表爲空情況
    if ((*pHead) == NULL)
    {
        printf("鏈表爲空!無法刪除!\n");
        return;
    }
    pDel = *pHead;
    *pHead = pDel->_pNext;
    free(pDel);
}

//查找元素
size_t LinkListFind(Node** pHead, DataType data)
{
    assert(pHead);
    //考慮鏈表爲空的情況
    if (*pHead == NULL)
    {
        printf("鏈表爲空!無法查找!\n");
        return -1;
    }
    Node* pFind = *pHead;
    while (pFind && pFind->_data != data)
        pFind = pFind->_pNext;
    if (pFind != NULL)
    {
        printf("找到了數據%d\n", pFind->_data);
        return 1;
    }   
    else
        printf("沒有找到數據%d\n",data);
    return -1;

}

//任意位置的插入
void LinkListInsert(Node** pHead, Node* pos, DataType data)
{
    assert(pHead);
    //考慮pos位置的合法性
    assert(pos);
    //考慮空鏈表情況
    Node* pNewNode = LinkListCreatNode(data);
    if (*pHead == NULL)
    {
        *pHead = pNewNode;
        return;
    }
    if (pos == NULL)
    {
        LinkListPushBack(pHead, data);
        return;
    }
    if (pos == *pHead)
    {
        LinkListPushFront(pHead, data);
        return;
    }
    Node* pCur = *pHead;
    while (pCur->_pNext != NULL)
    {
        if (pCur->_pNext == pos)
        {
            pNewNode->_pNext = pos;
            pCur->_pNext = pNewNode;
        }
        pCur = pCur->_pNext;
    }
    return;
}

//任意位置的刪除
void LinkListErase(Node** pHead, Node* pos)
{
    assert(pHead);
    assert(pos);
    //考慮鏈表爲空
    if ((*pHead) == NULL)
    {
        printf("鏈表爲空!無法刪除!\n");
        return;
    }
    if (pos == (*pHead))
    {
        LinkListPopFront(pHead);
        return;
    }
    Node* pE = *pHead;
    while ( pE != NULL)
    {
        if (pE->_pNext != NULL)
        {
            pE->_pNext = pos->_pNext;
            LinkListIDestroyNode(pos);
            return;
        }
        pE = pE->_pNext;    
    }
    return;
}

//求單鏈表長度
size_t LinkListSize(Node** pHead)
{
    assert(pHead);
    Node* pSize = *pHead;
    size_t count = 0;
    while (pSize != NULL)
    {
        pSize = pSize->_pNext;
        count++;
    }
    printf("鏈表長度爲%d\n",count);
    return count;
}

//銷燬一個單鏈表
void LinkListDestroy(Node** pHead)
{
    assert(pHead);
    Node* pDpre = NULL;
    Node* pD = *pHead;
    //定義兩個指針同時向鏈尾走,pD不爲空時,用pDpre標記pD
    //pD不斷向後移同時釋放pDpre,直至將整個鏈表結點全部釋放
    while (pD)
    {
        pDpre = pD;
        pD = pD->_pNext;
        free(pDpre);
    }
    //切記將頭指針指向空,不然將變成野指針
    *pHead = NULL;
}

//判空
size_t LinkListEmpty(Node** pHead)
{
    assert(pHead);
    return ((*pHead) == NULL);
}


/***************************************/
//測試部分
void LinkListTest()
{
    Node* p;
    LinkListInit(&p);

    LinkListPushBack(&p, 1);
    LinkListPushBack(&p, 2);
    LinkListPushBack(&p, 3);
    LinkListPushBack(&p, 4);
    LinkListPushBack(&p, 5);
    LinkListPrint(&p);

    LinkListPopBack(&p);
    LinkListPrint(&p);

    LinkListPushFront(&p, 5);
    LinkListPushFront(&p, 6);
    LinkListPrint(&p);

    LinkListPopFront(&p); 
    LinkListPopFront(&p);
    LinkListPrint(&p);

    LinkListFind(&p, 2);
    LinkListFind(&p, 5);

    LinkListSize(&p);

    LinkListInsert(&p,p,8);
    LinkListPrint(&p);


    //LinkListErase(&p,2);
    //LinkListPrint(&p);


}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章