单向链表——C语言

本文详细介绍了单向链表的基础结构,包括节点的组成和链表的创建方法,重点讲解了尾部插入和头部插入的操作过程,并展示了头删和链表倒置的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

    这里只讲一下最简单的链表,也就是单向链表,链表相较于顺序表来说有很多优点,尤其是表中间插入和表头插入操作更简单,时间复杂度更低,而且空间利用率更高。单链表从表头开始,每个节点之间用指针来链接,最后一个节点的指针指向NULL,所以链表所用的计算机内存不一定是连续的,而每个节点都由数据域和指针域构成。

单链表的结构

     以上是单向链表的结构,可以看到单链表由表头指针head指向第一个节点开始,第一个节点的next指针又指向下一个节点,最后一个节点的next指针指向NULL结束。

链表的创建

    链表的节点是结构体类型,链表的创建常见的有头部插入和尾部插入,头部插入内容呈倒序连接,尾部插入呈正序连接。

    举个例子我们要做一个学生信息的链表

    尾插

    首先是链表结构体的定义

struct stu{
	char name[20];
	int age;
	int score;
};
typedef struct ChainList{
	struct stu data;
	struct ChainList *next;
}ChainList,*Node;

    其中ChainList就是我们的链表类型,里面包括数据域data(这里是另一个结构体类型,暂且叫学生信息类),以及指针域next。然后我们需要定义一个ChainList类型的表头head。

ChainList *head;
head = NULL;

    由于一开始链表里什么也没有是个空表,所以表头指向NULL。

    接下来我们需要向链表里添加内容,我们需要额外的两个指针stu(指向当前的学生信息),end(用于指向链表里最后一个节点),这两个指针都需要动态开辟内存。

    /* 创建尾指针 */
	ChainList *end;
	end = (ChainList*)malloc(sizeof(ChainList));
	end->next = NULL;

	/* 创建学生信息节点指针 */
	ChainList *stu;
	stu = (ChainList*)malloc(sizeof(ChainList));
	stu->next = NULL;

    然后我们将当前创建的学生信息节点里面的信息初始化

    /* 为学生节点信息填充内容 */
	strcpy(stu->data.name, "张三");
	stu->data.age = 20;
	stu->data.score = 59;

    然后我们将该节点与链表尾部相连,这里分两种情况:(一) 是链表是个空表,那么直接将该节点与head里的next指针相连;(二) 是链表不为空,则将链表最后一个节点的next指向该节点。

    最后再将尾指针end指向最新的节点。

    /* 将学生节点与链表的上一个节点连接 */
	if (head == NULL){  //如果为空表
		head = stu;
	}
	else{               //链表不为空
		end->next = stu;
	}
	end = stu;          //将尾指针指向刚才的节点

    这样就为链表新增了一个学生信息节点,如果还需增加信息就重复以上操作。

    完整代码:

struct stu{
	char name[10];
	int age;
	int score;
};
typedef struct ChainList{
	struct stu data;
	struct ChainList *next;
}ChainList,*Node;

int main(){

	ChainList *head;
	//head = (ChainList*)malloc(sizeof(ChainList));
	head= NULL;

	/*  创建尾指针  */
	ChainList *end;
	end = (ChainList*)malloc(sizeof(ChainList));
	end->next = NULL;

	/* 创建学生信息节点 */
	ChainList *stu;
	stu = (ChainList*)malloc(sizeof(ChainList));
	stu->next = NULL;

	/* 为学生节点信息填充内容 */
	strcpy(stu->data.name, "张三");
	stu->data.age = 20;
	stu->data.score = 59;

	/* 将学生节点与链表的上一个节点连接 */
	if (head == NULL){  //如果为空表
		head = stu;
	}
	else{               //链表不为空
		end->next = stu;
	}
	end = stu;          //将尾指针指向刚才的节点
                        //打印刚才添加的一个学生信息
	printf("%s %d %d\n", stu->data.name, stu->data.age , stu->data.score);
}

   

    如果需要添加多个信息,代码如下:

struct stu{
	char name[10];
	int age;
	int score;
};
typedef struct ChainList{
	struct stu data;
	struct ChainList *next;
}ChainList,*Node;

int main(){

	ChainList *head;
	head= NULL;

	/*  创建尾指针  */
	ChainList *end;
	end = (ChainList*)malloc(sizeof(ChainList));
	end->next = NULL;
	
	int n = 3; //增添三个学生信息
	while (n--){
	    /* 创建学生信息节点 */
		ChainList *stu;
	    stu = (ChainList*)malloc(sizeof(ChainList));
	    stu->next = NULL;

	    /* 为学生节点信息填充内容 */
		scanf("%s %d %d", &stu->data.name, &stu->data.age, &stu->data.score);
        
		/* 将学生节点与链表的上一个节点连接 */
		if (head == NULL){  //如果为空表
			head = stu;
		}
		else{               //链表不为空
			end->next = stu;
		}
		end = stu;          //将尾指针指向刚才的节点
	}

	/*  打印所有学生信息  */
	n = 3;
	ChainList *cur=head;
	printf("打印所有学生信息:\n");
	while (n--){
		printf("%s %d %d\n", cur->data.name, cur->data.age, cur->data.score);
		cur = cur->next;
	}
}

    头插

       头插的话稍微有些不同,有一个注意点:不能先将头指针指向准备插入的节点,如果先这么做了,那后面的节点就无法找到了。

    如上图,我们要想把一个节点插入开头,操作顺序必须是先进行①:将准备插入的节点的next指向第一个节点;然后再进行②:将头指针head指向该节点。

    如果先进行②的话,将head指向准备插入的节点后,后面的1就无法找到:

    知道了思路后就是实现代码:这里我将其写在函数里方便查看

struct stu{
	char name[10];
	int age;
	int score;
};
typedef struct ChainList{
	struct stu data;
	struct ChainList *next;
}ChainList, *Node;

void HeadInsertList(ChainList **head,int n){    //尾插
	//注意这里的head一定要传入二级指针才能修改主函数中的head

	ChainList *sta;
	while (n--){
		/* 创建学生信息节点 */
		ChainList *stu;
		stu = (ChainList*)malloc(sizeof(ChainList));
		stu->next = NULL;

		/* 为学生节点信息填充内容 */
		scanf("%s %d %d", &stu->data.name, &stu->data.age, &stu->data.score);
	
		if (NULL == *head)
			*head = stu;
		else{
			stu->next = sta;
			*head = stu;
		}
		sta = stu;
	}
}

void Print(ChainList *head, int n){  //打印学生信息
	ChainList *cur = head;
	printf("打印所有学生信息:\n");
	while (n--){
		printf("%s %d %d\n", cur->data.name, cur->data.age, cur->data.score);
		cur = cur->next;
	}
}
int main(){
	ChainList *head;
	head = NULL;
	int n = 3;
	HeadInsertList(&head,n);  //头插
	Print(head,n);           //打印
}

     以上头插代码还可以优化:

void HeadInsertList(ChainList **head,int n){    //尾插
	//注意这里的head一定要传入二级指针才能修改主函数中的head

	ChainList *sta=*head;
	while (n--){
		/* 创建学生信息节点 */
		ChainList *stu;
		stu = (ChainList*)malloc(sizeof(ChainList));
		stu->next = NULL;

		/* 为学生节点信息填充内容 */
		scanf("%s %d %d", &stu->data.name, &stu->data.age, &stu->data.score);
	    
		/*这里将head为NULL和不为NULL的情况合并在了一起,将原来的代码简化到只剩3行*/
		stu->next = sta;
		*head = stu;
		sta = stu;
	}
}

头删

    这里写一下头删,尾删的话挺简单就不写了:

    注意头删不能直接free第一个节点,会导致第二个节点无法被查找;也不能将head直接指向第二个节点,会导致第一个节点无法被查找、无法被删除。

void HeadDelList(ChainList **head){
	ChainList *del=*head;      //此时del指向第一个节点
	ChainList *cur=*head;
	cur = cur->next;           //cur指向第二个节点
	free(del);                 //删除第一个节点
	*head = cur;               //将head指向第二个节点使其成为新的第一节点
}

链表倒置

    链表倒置是个很常见的操作,这里需要定义三个指针来完成:

cur先指向第二个节点,prev一直在cur的前一个节点,而next一直在cur的后一个节点。(注意这里的next和节点里面的next不是同一个)

由于倒置之后第一个节点就变成了最后一个,所以我们先把prev的next指向空,于是prev不再指向第二个节点。

 

 然后再把cur的next指向prev:

 

然后再把三个指针都往后偏移一位,重复上面操作:

 当cur移动到NULL时退出循环,然后再将head指向prev:

 实现代码:

void ReverseList(ChainList **head){
	if (*head == NULL)
		return;
	ChainList *cur = *head;
	ChainList *prev = *head;
	ChainList *next = *head;
	cur = cur->next;          //cur指向第二个节点
	prev->next = NULL;        //先将第一个节点的next指向NULL
	
	while (cur){
        next = cur->next;
		cur->next = prev;
		prev = cur;
		cur = next;
	}
	*head = prev;//此时prev指向最后一个节点,于是将head指向prev使最后一个节点成为第一个节点
}

以上就是对单向链表的一些理解。

### C语言实现单向链表 #### 单向链表简介 单向链表是一种线性数据结构,在内存中不是连续存储的数据元素,而是通过指针链接起来的一系列节点。每个节点包含两部分:一部分用于保存实际数据;另一部分是一个指向下一个节点地址的指针。 #### 定义节点结构体 为了创建单向链表,首先定义一个表示列表项(即节点)的`struct`: ```c typedef struct Node { int data; struct Node *next; } Node, *NodePtr; ``` 这里声明了一个名为`Node`的新类型以及其指针类型的别名`NodePtr`[^1]。 #### 初始化链表 可以编写如下函数来分配新节点并返回该节点的指针作为链表头部: ```c NodePtr createList(void) { return NULL; /* 初始情况下,空链表 */ } ``` 此代码片段展示了如何初始化一个新的、完全空白的单向链表——简单地让头指针等于`NULL`即可[^2]。 #### 插入操作 下面展示的是在指定位置之前插入新元素的方法之一: ```c void insertBefore(NodePtr *headRef, int newData, int key) { NodePtr newNode = (NodePtr)malloc(sizeof(struct Node)); newNode->data = newData; if (*headRef == NULL || (*headRef)->data >= key ) { newNode->next = *headRef; *headRef = newNode; } else { NodePtr current = *headRef; while(current->next!=NULL && current->next->data<key){ current=current->next; } newNode->next =current->next ; current->next=newNode; } } ``` 这段程序实现了在一个已有的有序序列里按顺序插入新的数值而不破坏原有排列的功能。 #### 删除操作 当需要移除特定值的第一个匹配实例时可采用下述方式: ```c int deleteKey(NodePtr *head_ref, int key) { NodePtr temp = *head_ref, prev; // 如果要删除的就是第一个节点的情况 if (temp != NULL && temp->data == key) { *head_ref = temp->next; // 修改头指针 free(temp); // 解放旧头结点所占空间 return 0; } // 查找待删节点及其前置节点 while (temp != NULL && temp->data != key) { prev = temp; temp = temp->next; } // 若未找到对应键,则退出 if (temp == NULL) return -1; // 断开连接并释放资源 prev->next = temp->next; free(temp); return 0; } ``` 上述逻辑处理了多种情形下的删除请求,包括但不限于目标位于首部的情形。 #### 遍历打印链表内容 最后提供一段遍历整个链条并将各成员依次输出至屏幕上的实用工具函数: ```c void printList(NodePtr n) { printf("\n"); while(n != NULL) { printf("%d -> ", n->data); n=n->next; } puts("NULL\n"); } ``` 以上就是利用C编程语言建立基本功能完备之单向链表所需的主要组件介绍。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值