数据结构之单链表(C语言实现)

数据结构之单链表(C语言实现)

本次介绍三种单链表:普通单链表、循环链表和双向链表,后面的博客会继续介绍后两种链表


首先介绍单链表的特点:

1.链表是由一个个内存地址不连续的节点组成

2.每个节点最多只有一个前驱,一个后记(第一个节点只有后继没有前驱,最后一个节点只有前驱没有后继)

3.链表不支持随机存取(意思就是不能通过下标来进行找某个节点,只能从头到尾进行遍历)

4.链表的查找只能从头到尾对链表进行遍历,时间复杂度为O(n);而顺序表查找元素只要给一个下标即可,时间复杂度为O(1)

5.删除链表的某个节点时只需改变节点的指针,不需要移动大量元素


然后从三个方面来介绍三种链表的通用操作:

1.链表的初始化

2.申请一个链表节点

3.链表的头插法

4.链表的尾插法

5.获取链表长度

6.删除链表节点

7.查找指定值的节点

8.销毁链表(释放链表所有节点的内存空间)

9.输出单链表(输出单链表所有节点的数据域)


说明:以下单链表的实现,是数据域以整型为例,而且带有头结点。


一、普通单链表

1.单链表的结构

typedef struct _Node

{

int data;//数据域

struct _Node* next;//指针域

}Node, *List;


2.链表的操作

(1)链表的初始化(带头结点)

这里的初始化只要是指初始化头结点的指针域

void InitList(List plist)
{
	if (NULL == plist)
		return;
	plist->next = NULL;
}

(2).申请一个链表节点

从堆中申请一个节点,注意这里是从堆中申请的内存,只能通过free(p)显式释放内存。即使是局部变量,该内存也不会随着函数调用完成而释放该内存。

static Node* BuyNode(int val)
{
	Node* pTmp = (Node*)malloc(sizeof(Node));
	pTmp->next = NULL;
	return pTmp;
}


(3).链表头插法

这里的链表是带有头结点的,所以每次新插入的节点应插入头结点后面。

void InsertHead(List plist, int val)
{
	Node *pTmp = BuyNode(val);	//申请一个节点,数据域为val
	pTmp->next = plist->next;	
	plist->next = pTmp;
}


(4).链表尾接法

每次将新插入的节点插入到最后一个节点后面,所以采用尾接法插入节点时首先要找到尾节点

void InsertTail(List plist, int val)
{
	Node *pTmp = BuyNode(val);
	Node *pCur;
	for (pCur = plist; NULL != pCur; pCur = pCur->next)	//查找尾节点
	{
		;		//空语句
	}
	pCur->next = pTmp;	//将新节点插入到尾节点之后
}


(5).获取链表长度

对链表进行遍历,每遍历一个节点,计数器加一。

int GetListLen(List plist)
{
	Node *pTmp = plist->next;
	int iCount = 0;
	while (NULL != pTmp)
	{
		++iCount;
		pTmp = pTmp->next;
	}
	return iCount;
}


(6).删除链表节点

删除指定值的链表节点时,需要遍历该链表,找到对应节点后想要删除该节点必须要知道该节点的前驱节点,这样才能正确删除该节点。

bool Delete(List plist, int val)
{
	Node* pPre = plist;		//指向前驱节点
	Node* pCur = plist->next;	//指向当前比较的节点
	while (NULL != pCur)	//当链表没找完就继续找
	{
		if (pCur->data != val)	//链表没找到就更新当前节点和它的前驱节点
		{
			pPre = pCur;		//更新前驱节点为当前节点
			pCur = pCur->next;	//更新当前节点为下一个节点
		}
		else
		{
			pPre->next = pCur->next;	
			free(pCur);		//释放待删节点的内存
			return true;		//找到该节点返回true
		}
	}
	return false;
}


(7).查找指定值的节点

查找指定值的节点也需要从头到尾遍历链表,若找到则返回该节点,没找到则返回NULL。

Node* Search(List plist, int val)
{
	Node *pCur = plist->next;
	while (NULL != pCur)
	{
		if (pCur->data == val)
		{
			return pCur;
		}
		pCur = pCur->next;
	}
	return NULL;
}


(8).销毁链表

销毁链表就是释放链表中所有节点的内存。

void Destroy(List plist)
{
	Node* pCur = plist->next;	//注意销毁链表后头结点的内存空间还是存在的,即空链表就是只有一个头结点的单链表
	while (NULL != pCur)
	{
		plist = pCur->next;
		free(pCur);
		pCur = plist->next;
	}
}


(9).输出单链表

输出单链表的操作也比较简单,从头到尾遍历单链表,每遍历一个节点就输出该节点的指针域

void Show(List plist)
{
	Node* pCur = plist->next;
	while (NULL != pCur)
	{
		printf("%5d", pCur->data);
		pCur = pCur->next;
	}
	printf("\n");
}


最后附上完整代码和运行结果:

//Link.h
#include<stdio.h>
#include<stdlib.h>

typedef struct _Node
{
	int data;
	struct _Node* next;
}Node, *List;

void InitList(List plist);
void InsertHead(List plist, int val);
void InsertTail(List plist, int val);
bool Delete(List plist, int val);
Node* Search(List plist, int val);
int GetListLen(List plist);
void Destroy(List plist);
static Node* BuyNode(int val);
void Show(List plist);



//Link.c
#include "test.h"


int main()
{
	Node head;
	InitList(&head);
	for (int i = 0; i < 13; ++i)
	{
		InsertTail(&head, i);	//尾插法
	}
	ShowList(&head);
	printf("链表长度:%d\n", GetListLen(&head));
	
	for (int i = 0; i < 12; ++i)
	{
		InsertHead(&head, i);	//头插法
	}
	ShowList(&head);
	printf("链表长度:%d\n", GetListLen(&head));
	printf("search 12:\n");
	Node *p = Search(&head, 12);	//查找节点
	if (p != NULL)
	{
		printf("%d\n", p->data);
	}
	else
	{
		printf("Not Found\n");
	}
	
	printf("删除节点12:\n");	//删除节点
	if (Delete(&head, 12))
	{
		ShowList(&head);
	}
	else
	{
		printf("链表中无此节点\n");
	}
		
	Destroy(&head);		//销毁链表
	return 0;
}


void InitList(List plist)
{
	if (NULL == plist)
		return;
	plist->next = NULL;
}
void InsertHead(List plist, int val)
{
	Node *pTmp = BuyNode(val);
	pTmp->next = plist->next;
	plist->next = pTmp;
}
void InsertTail(List plist, int val)
{
	Node *pTmp = BuyNode(val);
	Node *pCur;
	for (pCur = plist; NULL != pCur->next; pCur = pCur->next)
	{
		;		//空语句
	}
	pCur->next = pTmp;
}
bool Delete(List plist, int val)
{
	Node* pPre = plist;
	Node* pCur = plist->next;
	while (NULL != pCur)
	{
		if (pCur->data != val)
		{
			pPre = pCur;
			pCur = pCur->next;
		}
		else
		{
			pPre->next = pCur->next;
			free(pCur);
			return true;
		}
	}
	return false;
}
Node* Search(List plist, int val)
{
	Node *pCur = plist->next;
	while (NULL != pCur)
	{
		if (pCur->data == val)
		{
			return pCur;
		}
		pCur = pCur->next;
	}
	return NULL;
}
int GetListLen(List plist)
{
	Node *pTmp = plist->next;
	int iCount = 0;
	while (NULL != pTmp)
	{
		++iCount;
		pTmp = pTmp->next;
	}
	return iCount;
}
void Destroy(List plist)
{
	Node* pCur = plist->next;	//注意销毁链表后头结点的内存空间还是存在的,即空链表就是只有一个头结点的单链表
	while (NULL != pCur)
	{
		plist = pCur->next;
		free(pCur);
		pCur = plist->next;
	}
}
static Node* BuyNode(int val)
{
	Node* pTmp = (Node*)malloc(sizeof(Node));
	pTmp->next = NULL;
	pTmp->data = val;
	return pTmp;
}
void ShowList(List plist)
{
	Node* pCur = plist->next;
	while (NULL != pCur)
	{
		printf("%5d", pCur->data);
		pCur = pCur->next;
	}
	printf("\n");
}


运行结果:


循环单链表是链表的一种特殊形式,它的特点是链表中的最后一个节点的指针指向第一个节点,形成一个闭合的环。在C语言中,操作循环单链表通常涉及创建、插入、删除和遍历等基本操作。 以下是一些基本操作的C语言实现概览: 1. **创建循环链表**: - 定义节点结构体,包含数据域和指向下一个节点的指针(如果是在头结点的最后一个节点,指针会指向自身)。 - 创建链表时,可以初始化一个头节点,然后递归地添加新节点到链尾。 ```c struct Node { int data; struct Node* next; }; // 创建循环链表 void createCircularList(struct Node** head, int size, ...) { ... } ``` 2. **插入节点**: - 可以在链表的头部、尾部或指定位置插入节点。 - 在头部插入,更新头节点的next;在尾部插入,找到当前尾节点,然后更新其next指向新节点,并将新节点next指向头节点。 ```c void insertNode(struct Node** head, int data, insertionPosition) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = data; newNode->next = (insertionPosition == 1) ? head : (*(head)->next); // 如果在尾部插入,更新尾节点和头节点的next指针 if (insertionPosition == 0) { (*head)->next = newNode; } } ``` 3. **删除节点**: - 删除某个节点时,需要找到前一个节点,然后更新其next指针跳过要删除的节点。 - 在循环链表中删除特定位置的节点需要特别处理头节点的情况。 ```c void deleteNode(struct Node** head, int position) { if (position == 1) { // 删除头节点 struct Node* temp = head->next; *head = temp; free(head); } else { struct Node* current = *head, *previous = NULL; for (int i = 1; i < position && current != NULL; ++i) { previous = current; current = current->next; } if (current == NULL) return; // 未找到节点 previous->next = current->next; } } ``` 4. **遍历循环链表**: - 使用while循环,每次迭代都更新当前节点指向下一个节点,直到遇到第一个节点。 ```c void printList(struct Node* head) { struct Node* temp = head; do { printf("%d ", temp->data); temp = temp->next; } while (temp != head); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值