C 數據結構:鏈表的實現

前言

  • 關於單鏈表:單鏈表在插入結點上的效率優於順序表,單鏈表使用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),循環雙向鏈表的頭指針的前驅結點爲最後一個結點(循環鏈表可以設置頭指針,也可以設置尾指針,也可以不設置)。

全部代碼

GitHub地址:
單鏈表
雙向鏈表

運行結果

在這裏插入圖片描述

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