链表详解

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