鏈表詳解

C語言鏈表

概述

當每一個數據元素都和它下一個數據元素用指針鏈接在一起時,就形成了一個鏈,這個鏈子的頭就位於第一個數據元素,這樣的存儲方式就是鏈式存儲。它是動態的進行存儲分配的一種結構。

數組和鏈表

數組的優勢,在於可以方便的遍歷查找需要的數據。但是,這種時間上的便利性,是因爲數組在內存中佔用了連續的空間,在進行類似的查找或者遍歷時,本質是指針在內存中的定向偏移。然而,當需要對數組成員進行添加和刪除的操作時,數組內完成這類操作的時間複雜度則變成了O(n)。

鏈表的特性,使其在某些操作上比數組更加高效。例如當進行插入和刪除操作時,鏈表操作的時間複雜度僅爲O(1)。另外,因爲鏈表在內存中不是連續存儲的,所以可以充分利用內存中的碎片空間。除此之外,鏈表還是很多算法的基礎,最常見的哈希表就是基於鏈表來實現的。

鏈表節點元素的構成

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

每個元素由兩部分組成:數據域(本身的信息)和指針域(指向後繼的指針)。

typedef struct student
{
	char name[10]; //數據元素
	float score; //數據元素
	struct student *next; //指向下一個結點的位置
} pstudent;

通過一個簡單的程序來熟悉對鏈表的基本操作

void main()
{
	int a = 0;
	pstudent* head;
	head = creat(3);
	change(head,2);
	head = Insert(head, 4);
	head = Delete(head,4);
	Traversal(head);//遍歷的是執行Delete()函數後的鏈表
	a = Length(head);
	printf("%d\n",a);
}
  1. 調用函數時,當head(頭指針)的指向發生改變時,我們應該返回一個新的指針(head),避免別的函數傳參時發生錯誤。
靜態鏈表(簡單鏈表)

靜態鏈表有助於很好的理解動態鏈表:

struct student
{
	long num;
	float score;
	struct student* next;
};

void main()
{
	struct student a,b,c,*head,*p;
	a.num = 10101;	a.score = 89;
	b.num = 10101;	b.score = 90;
	c.num = 10101;	c.score = 99;

	head = &a;
	a.next = &b;
	b.next = &c;
	c.next = NULL;

	p = head;
	do
	{
		printf("%ld %5.1f\n",p->num,p->score);
		p = p->next;
	}while(p != NULL);
}
動態鏈表的創建

頭指針:只有指針的元素,並沒有數據元素。
頭節點:頭節點的數據類型與首節點的數據類型相同,並且頭節點是首節點前面的那個節點,並不存放有效數據;頭節點的存在只是爲了方便鏈表的操作。

創建鏈表的方法有好多種,可以參考下文:創建鏈表的四種方法
以下代碼是自己對鏈表的理解所寫,理解起來更容易

pstudent* creat(int n)
{
	pstudent* head;//頭指針
	pstudent* node;//頭節點(數據域和指針域)
	pstudent* next;//下一節點(數據域和指針域)
	node = (pstudent*)malloc(sizeof(pstudent));
	head = NULL;//n=0時爲空鏈表
	if(n != 0)
	{
		head = node;//頭指針指向頭節點
		for (int i = 0; i < n; i++) 
		{
			next = (pstudent*)malloc(sizeof(pstudent));
			printf("please input %d node value:\n",i+1);
			printf("please input name:\n");
			fflush(stdin);//情除緩存區
			gets(node->name);
			printf("please input score:\n");
			fflush(stdin);//(注1)
			scanf("%f", &node->score);
			system("cls");//清屏
			node->next = next;//頭節點指向下一個節點
			node = next;//指向下一個元素(注2)
		}
		node->next = NULL;//結束創建(注3)
	}
	return head;
}
  1. 第一次輸入的回車可能會被第二次輸入操作所捕捉,這個的作用就是清空緩衝,這樣就不會出現這種情況了。
  2. node = next,頭節點輸入完成,該next節點輸入,由於使用for循環,第二次循環仍爲node節點(node->name),而我們需要的是第二個節點,故在第一次循環結束時,將node指向了第二個節點next,即node = next,此時的node不再是頭節點。注意不能寫成next = node,node節點是我們每次錄入的節點,該地址應該在本次錄入完成後賦予下一個節點的地址,故node爲左值。
  3. node->next = NULL,循環結束後,最後一個節點之後不再有節點,故下一個節點爲空。
鏈表之遍歷輸出
void Traversal(pstudent* head)
{
	printf("Output Traversal Message:\n");   
	while (head->next != NULL) 
	{
		printf("%s\n", head->name);
		printf("%f\n\n", head->score);
		head = head->next;
	}
}
鏈表之長度
int Length(pstudent* head)
{
    int length = 0;
    while(head->next != NULL)
    {
        ++length;
		head = head->next;
    }
    return length;
}
鏈表之修改節點
void change(pstudent* head,int n) 
{	
	int i = 1;
	pstudent* node = head;
	if (n == 1)
	{
		printf("please input change name:\n");
		fflush(stdin);
		scanf("%s", &node->name);
		printf("please input change score:\n");
		fflush(stdin);
		scanf("%f", &node->score);
	}
	else
	{
		while (i < n && node != NULL) 
		{
			node = node->next;
			i++;
		}
		if (node != NULL) 
		{
			printf("please input change name:\n");
			fflush(stdin);
			scanf("%s", &node->name);
			printf("please input change score:\n");
			fflush(stdin);
			scanf("%f", &node->score);
		}
		else 
		{
			printf("not exit node");
		}
	}
}
鏈表之插入節點
pstudent* Insert(pstudent* head, int position)
{
	int i = 2;
	pstudent* node = head;//定義一個指針node,用來移動指向鏈表節點的位置
	pstudent* insert = (pstudent*)malloc(sizeof(pstudent));
	printf("please input insert name:\n");
	fflush(stdin);
	gets(insert->name);
	printf("please input insert score:\n");
	fflush(stdin);
	scanf("%f", &insert->score);
	
	if(position == 1)
	{	
		insert->next = head;
		head = insert;
	}
	else if(position == 2)
	{
		insert->next = node->next;
		node->next = insert;
	}
	else
	{
		while (i < position && node != NULL)
		{
			node = node->next;
			i++;
		}
		if (node != NULL)
		{
			insert->next = node->next;
			node->next = insert;
		}
		else if(node == NULL)
		{
			node->next = insert;
			insert->next = NULL;
		}
		else 
		{
			printf("not exit node");
		}
	}
	return head;
}
鏈表之刪除節點
pstudent* Delete(pstudent* head, int n) 
{	
	int i = 1;
	pstudent* node = head;
	pstudent* front;

	if(n == 1)
	{
		head = node->next;
		free(node);
	}
	else
	{
		while (i < n && node != NULL) 
		{
			front = node;
			node = node->next;
			i++;
		}
		if (node != NULL) 
		{
			front->next = node->next;
			free(node);
		}
		else
		{
			puts("not exit node");
		}
	}
	return head;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章