在上兩篇文章中,我們介紹了基於靜態數組和動態數組的順序表,順序表頻繁查詢而插入刪除動作少的情況下,順序表也適用於進行尾插的時候,因爲相對於鏈表而言,順序表在進行尾插時,順序表不需要通過遍歷來找到最後一個插入點,比較而言,順序表尾插效率高。
但是,在進行頭插和中插時,順序表需要將插入元素位置之後的元素整體後移才能插入數據,這樣做在有大量插入刪除場景下即爲麻煩且效率低,因此,提出了鏈表的思想。而鏈表在頭插或者中插時,只需要創建一個新節點,然後將節點鏈入所插入位置即可。
鏈表概述
鏈表是一種常見的數據結構。數組可以存放數據,但是數組存放數據時必須提前指定數組包含元素個數,即數組長度。但是數組保存數據的缺點在於如果要保存元素大於數組大小時,則不能將所有內容保存入數組,而當要保存元素遠小於數組大小時,又造成了空間的浪費。而鏈表其存儲元素個數不受限定,當進行添加元素時,存儲個數隨之增加。
鏈表是一種鏈式存儲的線性表,用一組地址任意的存儲單元存放線性表的數據元素,稱存儲單元爲一個節點。
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);
}