单链表与双链表(c语言)

1.链表的概念

1.定义

定义:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
特点:链表是由一系列结点组成,每个结点由数据域和指针域两部分组成。
数据域:存储数据元素。
指针域:存放下一个结点的地址。
在这里插入图片描述

2.链表的分类

链表的种类主要由单向/双向,是否带头结点,是否循环,这三个因素分为8种不同的链表
这里主要讨论以下两种结构:
1 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
2 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。

3.链表的操作

链表最大的作用是通过结点将内存中不连续的空间链接在一起,组成一个表存储数据,常规操作就是链表的增删查改。通常一个链表我们会规定一个根节点来确定链表的起始位置,后续操作都是以根结点为基准进行操作。

在这里插入图片描述

4.链表与数组的对比

1.存储方式:
数组:连续的内存空间中存储元素,通过索引来访问元素,可以随机访问。
链表:通过节点之间的指针链接起来,每个节点保存元素和指向下一节点的指针,不能直接访问中间的节点,需要从头节点开始遍历到目标节点。
2.插入和删除操作:
数组:插入和删除元素可能需要移动其他元素,时间复杂度为O(n)。尾部插入和删除元素的时间复杂度为O(1)。
链表:在任意位置插入或删除元素只需修改节点的指针,时间复杂度为O(1)。
3.内存占用:
数组:需要一块连续的内存空间来存储所有元素,大小固定。
链表:每个节点分别存储元素和指针,需要额外的内存空间来存储指针,没有大小限制。
4.访问效率:
数组:由于使用索引进行访问,可以直接计算出元素所在的内存地址,访问效率较高。
链表:需要从头节点开始遍历到目标节点,访问效率较低。
根据具体的需求,选择数组或链表取决于对插入、删除、访问等操作的频繁程度和对内存占用的要求。如果需要频繁进行插入和删除操作,且内存空间不确定,链表可能更适合;如果需要频繁访问元素且元素个数固定,数组可能更适合。

2.单链表实现(无头单向非循环链表)

在这里插入图片描述

1.链表的创建

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

对数据类型重命名,以便之后存储其他数据类型元素时便于修改
typedef int SLdatatypedef;

//单链表
typedef struct SListNode
{
	SLdatatypedef data;
	struct SListNode* next; 
}SL;
void SLInit(SL* head)
{
	head->next = NULL;
}

2.链表插入

插入分为头插和尾插
头插无需考虑链表是否为空,新插入的结点的next指向头结点,刷新头结点。
在这里插入图片描述

尾插若链表为空,则和头插一样,若不为空需要对链表进行遍历找到尾结点将新的结点插入到它之后
在这里插入图片描述

//创建结点
SL* BuySLNode(SLdatatypedef x)
{
	SL* Node = (SL*)malloc(sizeof(SL));
	if (Node == NULL)
	{
		printf("malloc fail");
		return;
	}
	Node->data = x;
	Node->next = NULL;

	return Node;
}
//头插
void SLPushFront(SL** head, SLdatatypedef x)
{
	SL* Node = BuySLNode(x);
	Node->next = *head;
	*head = Node;
}
//尾插
void SLPushBack(SL** head, SLdatatypedef x)
{
	SL* Node = BuySLNode(x);
	//若单链表为空
	if (*head == NULL)
	{
		*head = Node;
	}
	//非空链表
	else
	{
		SL* tail = *head;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = Node;
	}
}

3.链表删除结点

头删
在这里插入图片描述

尾删
在这里插入图片描述


//头删
void SLPopFront(SL** head)
{
	assert(*head);
	SL* tail = *head;
	*head = (*head)->next;
	free(tail);
}
//尾删
void SlPopBack(SL** head)
{
	assert(*head);
	if ((*head)->next == NULL)
	{
		free(*head);
		*head = NULL;
	}
	else
	{
		SL* tail = *head;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

4.查找值为x的结点

//查找/修改
SL* SLFind(SL* head,SLdatatypedef x)
{
	SL* cur = head;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

5.在指定位置插入结点

遍历链表找到该结点,其次插入新结点

//在pos之前插入
void SLInsert(SL** head, SL* pos, SLdatatypedef x)
{
	assert(head);
	assert(pos);
	if(pos == *head)
	{
		SLPushFront(head, x);
	}
	else
	{
		SL* tail = *head;
		while (tail->next != pos)
		{
			tail = tail->next;
		}
		SL* Node = BuySLNode(x);
		tail->next = Node;
		Node->next = pos;
	}
	
}

//在pos之后插入
void SLInsertAfter(SL** head, SL* pos, SLdatatypedef x)
{
	//pos后插入
	SL* Node = BuySLNode(x);
	Node->next = pos->next;
	pos->next = Node;
}

6.删除指定位置的结点

查找到该结点,若不是头尾结点,先将前后结点链接起来,再删除该结点


//删除pos位置的值
void SLErase(SL** head, SL* pos)
{
	assert(head);
	assert(pos);
	if (pos == head)//头
	{
		SLPopFront(head);
	}
	else if (pos->next == NULL)//尾
	{
		SlPopBack(head);
	}
	else
	{
		SL* tail = *head;
		while (tail->next != pos)
		{
			tail = tail->next;
		}
		tail->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

//删除pos位置后的值
void SLEraseAfter(SL* pos)
{
	assert(pos);
	assert(pos->next);

	SL* tail = pos->next;
	pos->next = tail->next;
	free(tail);
}

7.销毁链表

遍历链表释放掉每个结点的空间

//销毁
void SLDestroy(SL** head)
{
	SL* cur = *head;
	while (cur)
	{
		SL* next = cur->next;
		free(cur);
		cur = next;
	}
	*head = NULL;
}

3.双向链表实现(带头双向循环链表)

1.链表的创建

带头双向循环链表需要一个非数据位的结点(哨兵位)作为头结点,该结点只作为链表的头使用,不存储数据。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int ListtypedefData;

typedef struct List
{
	struct List* next;//指向下一个结点
	struct List* prev;//指向上个结点
	ListtypedefData val;

}ListNode;
ListNode* BuyListNode(ListtypedefData x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
	{
		printf("malloc fail");
		return;
	}
	node->val = x;
	node->prev = node;
	node->next = node;
	return node;
}


//初始化
ListNode* ListInit()
{
	ListNode* head = BuyListNode(-1);
	head->next = head;
	head->prev = head;
	return head;
}

2.链表插入

因为该链表是一个循环链表,并且带有头结点,所以头插只需要将其插入到头结点的next位置,尾插只需要将其插入到头结点的prev位置。(插入新结点注意将其prev与插入位置的前结点链接起来)
在这里插入图片描述


//尾插
void ListPushBack(ListNode* head, ListtypedefData x)
{
	assert(head);
	ListNode* tail = head->prev;
	ListNode* newnode = BuyListNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = head;
	head->prev = newnode;
}

//头插
void ListPushFront(ListNode* head, ListtypedefData x)
{
	assert(head);
	ListNode* newNode = BuyListNode(x);
	newNode->next = head->next;
	head->next->prev = newNode;
	head->next = newNode;
	newNode->prev = head;
}

3.链表删除结点

因为本身就具有一个具有标记作用的结点,所以判空操作与上述链表不同。


//判断是否为空
bool ListEmpty(ListNode* head)
{
	assert(head);
	return head->next == head;
}

//头删
void ListPopFront(ListNode* head)
{
	assert(head);
	assert(!ListEmpty(head));
	ListNode* tail = head->next;
	ListNode* tailnext = tail->next;
	head->next = tailnext;
	tailnext->prev = head;
	free(tail);

}

//尾删
void ListPopBack(ListNode* head)
{
	assert(head);
	assert(!ListEmpty(head));
	ListNode* tail = head->prev;
	ListNode* tailprev = tail->prev;
	tailprev->next = head;
	head->prev = tailprev;
	free(tail);

}

4.查找值为x的结点


//查找
ListNode* ListFind(ListNode* head, ListtypedefData x)
{
	assert(head);
	ListNode* tail = head->next;
	while (tail != head)
	{
		if (tail->val == x)
		{
			return tail;
		}
		else
		{
			tail = tail->next;
		}
	}
	return NULL;
}

5.在指定位置插入结点

//pos前插入
void ListInsert(ListNode* pos, ListtypedefData x)
{
	ListNode* tail = pos->prev;
	ListNode* newNode = BuyListNode(x);
	tail->next = newNode;
	newNode->prev = tail;
	newNode->next = pos;
	pos->prev = newNode;

}

6.删除指定位置的结点

//删除pos位置
void ListErse(ListNode* head, ListNode* pos)
{
	assert(head);
	assert(!ListEmpty(head));
	ListNode* tail = pos->prev;
	pos->next->prev = tail;
	tail->next = pos->next;
	free(pos);
}

7.销毁链表

与上述链表不同的是该链表需要将具有标记作用的头结点释放。

//销毁
void ListDestroy(ListNode* head)
{
	assert(head);
	ListNode* tail = head->next;
	while (tail != head)
	{
		ListNode* next = tail->next;
		free(tail);
		tail = next;
		
	}
	free(head);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值