数据结构之无头单向非循环链表

本文详细介绍了链表这种数据结构的基础概念及其实现方法,并针对无头单向非循环链表提供了多种实用的操作示例,包括初始化、销毁、插入、删除等核心功能。

链表在数据结构中相当的重要,在以后的实际工作中使用率也很高,所以今天分享一些关于链表的基本操作:

1.链表的概念以及定义:

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,形式如下图所示:
在这里插入图片描述

typedef struct Node         //链表的定义
{
	int value;               //结点的数据域
	struct Node* next;       //结点的指针域
}Node;

2.无头单向非循环链表的一些基本操作:

1.链表的初始化
这里说一下为什么要使用二级指针,首先是定义一个指针指向首结点,因为链表的操作全程靠指针完成,凡是牵扯到首结点指针指向的改变都得用二级指针。

void SListInit(Node **ppFirst)
{
	*ppFirst = NULL;
}

2.销毁链表

void SListDestroy(Node *first)
{
	Node* ptr;                //定义ptr指针记录当前结点的下一个结点位置,然后再free当前结点
	for (Node* cur = first; cur != NULL; cur = ptr) //定义cur指针来操作链表,而不是直接使用头指针。
	{
		ptr = cur->next;
		free(cur);
	}
}

3.链表的头插

//头插不需要担心头指针为空的问题,因为这个方法一样适用
void SListPushFront(Node **ppFirst, int v)
{
		Node* node = (Node*)malloc(sizeof(Node));  //先为要插入的新结点申请空间
		node->next = *ppFirst;            
		*ppFirst = node;
		node->value = v;
}

4.链表的尾插

void SListPushBack(Node **ppFirst, int v)
{
	if (*ppFirst == NULL)      //头指针为空的话就相当于头插
	{
		Node* node = (Node*)malloc(sizeof(Node));
		node->next = *ppFirst;
		*ppFirst = node;
		node->value = v;
		return; 
	}
	//找到尾结点
	Node* cur = *ppFirst;
	while (cur->next != NULL){
		cur = cur->next;
	}

	Node* node = (Node*)malloc(sizeof(Node));
	node->next = NULL;           //这行和下面这行的代码顺序不能改变,否则会出错
	cur->next = node;
	node->value = v;
}

5.链表的头删

void SListPopFront(Node **ppFirst)
{
	assert(*ppFirst != NULL);       //首先判断是否为空
	Node* ptr = (*ppFirst)->next;   //首先定义指针ptr记录首结点的下一个结点的位置,然后再释放首结点,否则会导致后面的数据全部丢失
	free(*ppFirst);
	*ppFirst = ptr;
}

6.链表的尾删

void SListPopBack(Node **ppFirst)
{
	assert(*ppFirst != NULL);         
	if ((*ppFirst)->next == NULL)     //这是链表中只有一个结点的情况
	{
		free(*ppFirst);
		*ppFirst = NULL;
		return;
	}
	Node* ptr = *ppFirst;
	while (ptr->next->next!= NULL)     //找到倒数第二个结点
	{
		ptr = ptr->next;
	}
	free(ptr->next);    //先释放尾结点所占内存,否则会导致内存泄漏
	ptr->next = NULL;   //再让倒数第二个结点的next=NULL
}

7.查找数据为指定值的结点

Node * SListFind(Node* first, int v)
{
	for (Node* cur = first; cur != NULL; cur = cur->next) 
	{
		if (cur->value == v)
			return cur;
	}
}

8.在pos后插入一个结点

void SListInsertAfter(Node *pos, int v)
{
	Node* node = (Node*)malloc(sizeof(Node));
	node->value = v;
	node->next = pos->next;
	pos->next = node;
}

9.删除pos后的一个结点

void SListEraseAfter(Node *pos)
{
	Node* ptr = pos->next;              //定义一个指针ptr记录pos的下一个结点地址
	pos->next = pos->next->next;        //改变pos指针域,让pos和他后面的第二个结点相连
	free(ptr);                          //此时释放ptr
}

10.修改指定值的结点的值

void SListModify(Node *ppFirst,int u,int v)
{
	if (ppFirst == NULL)
	{
		return;
	}
	Node* ret = SListFind(ppFirst,u);
	if (ret != NULL)
	{
		if (u != v)
		{
			ret->value = v;
		}
		else
			return;
	}
	else
		return;
}

11.打印链表

void SLprint(Node* first)
{
	if (first == NULL)
	{
		return;
	}
	for (Node* cur = first; cur != NULL; cur = cur->next)
	{
		printf("%d ",cur->value);
	}
}

3.总结

将顺序表和链表做一个对比:
顺序表:
空间连续,支持随机访问且方便;但是中间或前面插入的时间复杂度为O(n),容量的代价比较大。
链表:
是以结点为单位存储,不支持随机访问,在任意位置插入删除时间复杂度O(1),没有增容问题,插入一个开辟一个空间,但是查找的时间复杂度就为O(n),对于不清楚总量的数据,空间利用率高。
链表和顺序表的使用场景还得因情况而定。

无头单向循环链表是一种链表结构,其中每个节点包含一个指向下一个节点的指针,并且最后一个节点的指针指向头节点,从而形成一个循环。这种结构没有单独的“头节点”,只有“头指针”来指向链表的起始位置。 ### 实现代码如下: ```c #include <stdio.h> #include <stdlib.h> // 定义链表节点结构 typedef struct Node { int data; struct Node* next; } Node; // 创建新节点 Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); if (!newNode) { printf("内存分配失败\n"); exit(1); } newNode->data = data; newNode->next = NULL; return newNode; } // 初始化循环链表(插入第一个节点) Node* initializeList(int data) { Node* head = createNode(data); head->next = head; // 指向自己,形成循环 return head; } // 在链表尾部插入新节点 Node* insertAtEnd(Node* head, int data) { Node* newNode = createNode(data); if (head == NULL) { newNode->next = newNode; // 单节点自循环 return newNode; } Node* current = head; while (current->next != head) { current = current->next; } newNode->next = head; current->next = newNode; return head; } // 打印链表 void printList(Node* head) { if (head == NULL) { printf("链表为空\n"); return; } Node* current = head; do { printf("%d -> ", current->data); current = current->next; } while (current != head); printf("(回到头节点 %d)\n", head->data); } // 删除整个链表并释放内存 void freeList(Node* head) { if (head == NULL) return; Node* current = head; Node* nextNode; do { nextNode = current->next; free(current); current = nextNode; } while (current != head); } // 主函数测试 int main() { Node* head = NULL; // 插入节点 head = insertAtEnd(head, 10); head = insertAtEnd(head, 20); head = insertAtEnd(head, 30); // 打印链表 printList(head); // 释放内存 freeList(head); head = NULL; return 0; } ``` ### 解释: 1. **createNode**:用于动态创建一个新节点并初始化其数据。 2. **initializeList**:用于初始化链表(插入第一个节点),并使其指向自己,形成循环。 3. **insertAtEnd**:在链表末尾插入一个新节点。如果链表为空,则直接创建新节点作为头节点;否则遍历到链表末尾插入。 4. **printList**:遍历链表打印所有节点的数据,直到回到头节点为止。 5. **freeList**:释放整个链表所占用的内存,防止内存泄漏。 ### 运行结果: ``` 10 -> 20 -> 30 -> (回到头节点 10) ``` --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值