文章目錄
前言
- 關於
單鏈表
:單鏈表在插入結點上的效率優於順序表,單鏈表使用malloc函數爲鏈表的結點分配空間,通過頭結點來訪問鏈表。 - 爲什麼要建立
頭結點
?
建立頭結點統一了空表與非空表的區別,簡化了鏈表的操作;如果直接刪除鏈表的第一個結點,可能會導致表的丟失。 - 想要學好單鏈表,就得動手畫一畫,鏈表中頻繁使用指針來訪問結點,寫寫畫畫能讓我們的思路保持清晰
單鏈表
單鏈表的存儲結構
單鏈表的結點由數據域
和指針域
兩部分構成。
結構體表示:
typedef struct Node
{
ElemType data; //ElemType可以根據實際需求更改爲需要的數據類型
struct Node * next; //本類型指針
}Node, * LinkList;
單鏈表ADT實現
單鏈表的邏輯結構:頭結點的next指針指向鏈表的首個結點
- CreateList(LinkList L, int *a, int i): 創建單鏈表,L爲指向鏈表第一個元素的指針,即頭指針;a爲數組的地址,數組a用來接收從鍵盤獲得數據;
//尾插法建立單鏈表,不斷向鏈表的末尾添加結點,
//每一個插入的結點的next指針都爲NULL
void CreateList(LinkList L, int * a, int i)
{
Node * node;
Node * head = L;
while(i < length)
{
node = (Node *)malloc(sizeof(Node));
node->data = *(a+i);
node->next = NULL;
L->next = node;
L = L->next;
printf("%d",node->data);
i++;
}
L = head;
}
//頭插法建立單鏈表,每個結點都插入爲鏈表的第一個結點
//頭結點的next指針始終指向插入結點
void CreateList(LinkList L, int *a, int i)
{
Node * head;
Node * node;
while(i < length)
{
node = (Node *)malloc(sizeof(Node));
node->data = *(a + length - ++i);
node->next = L->next;
L->next = node;
}
}
- PrintList(LinkList L): 打印鏈表
void PrintList(LinkList L)
{
printf("打印結果:\n");
for(int j = 0; j < length; j++)
{
L = L->next;
printf("%d\t",L->data);
}
printf("\n");
}
- DeleteList(LinkList L): 刪除鏈表
void DeleteList(LinkList L)
{
while(L->next != NULL)
{
Node *t = L->next;
L->next = L->next->next;
free(t);
}
printf("刪除成功\n");
}
- GetNode(LinkList L, int i): 查找結點,i 爲的結點位置
Node* GetNode(LinkList L, int i)
{
for(int j = 0; j < i; j++)
{
L = L->next;
}
return L;
}
- Locate(LinkList L, int x): 查找結點,x爲結點的數據值
int Locate(LinkList L, int x)
{
for(int j = 0; j < length; j++)
{
L = L->next;
if(L->data == x)
break;
}
return ++j;
}
- DeleteNode(LinkList L, int i): 刪除結點,i 爲結點位置
void DeleteNode(LinkList L, int i)
{
for(int j = 0; j < i - 1; j++)
{
L = L->next;
}
Node *t;
t = L->next;
L->next = L->next->next;
free(t);
length--;
}
- DeleteData(LinkList L, int x): 刪除結點,x爲結點的數據值
void DeleteData(LinkList L, int x)
{
int i = Locate(L, x);
if(i > length)
printf("刪除元素不存在");
else
DeleteNode(L, i);
}
鏈表初始化問題
在鏈表的結構體中,*LinkList 爲結構體指針類型,LinkList L,L表示指向結構體的指針;LinkList *L,L是「指針的指針」
,表示指向結構體指針的指針。
問題: 僅僅聲明頭指針,而未使用malloc函數爲頭指針分配空間,然後初始化鏈表,會出現程序異常結束問題。
原因:
聲明一個指向結構體的指針並不創建該結構,而是給出足夠的空間容納結構可能會使用的地址。創建未被聲明過的記錄的唯一方法是使用malloc庫函數。
例如
//定義頭指針後變將,頭指針的next指針域指向NULL
LinkList L;
L->next = NULL;
L = (LinkList)malloc(sizeof(Node));
鏈表刪除問題
鏈表通過訪問頭指針來,遍歷結點,如果直接刪除結點會導致,部分結點無法訪問到,從而無法完成整個鏈表的刪除的操作;故我們需要一箇中間結點來暫存即將刪除的結點。
例如
Node *t = L->next;
L->next = L->next->next;
free(t);
雙向鏈表
雙向鏈表的結構體
雙向鏈表在單鏈表的基礎上添加一個指針指向結點的前驅結點。當鏈表爲空時,頭結點的兩個指針都指向NULL。
typedef struct Node
{
int data;
struct Node * prior, *next; //prior指向前驅結點,next指向後繼結點
}Node,*DoubleList;
雙向鏈表的邏輯結構
雙向鏈表的ADT實現
雙向鏈表的操作和單鏈表的操作相似,如果從尾部遍歷鏈表,雙向鏈表比單鏈表更加方便。
雙向鏈表的結點刪除問題
與單鏈表結點刪除不同,當刪除單鏈表的最後一個結點時,只需要將前一個結點的next指針指向最後一個結點的next指針指向,即指向NULL。
而雙向鏈表刪除結點不僅需要修改next指針的指向,還需要修改prior指針的指向,故需要判斷刪除的結點是否爲最後一個結點。
//刪除指定位置結點
void DeleteNode(DoubleList L, int i)
{
for(int j = 0; j < i - 1; j++)
{
L = L->next;
}
Node *t;
t = L->next;
if(t->next != NULL)
{
L->next = L->next->next;
L->next->prior = L->next;
}
free(t);
length--;
}
循環鏈表
循環鏈表邏輯結構
循環鏈表有循環單鏈表和循環雙鏈表,循環鏈表的最後一個結點指向頭結點(單鏈表和雙鏈表中指向NULL),循環雙向鏈表的頭指針的前驅結點爲最後一個結點(循環鏈表可以設置頭指針,也可以設置尾指針,也可以不設置)。