C语言数据结构——链表

1. 链表的概念及结构

概念:链表是一种 物理存储结构上非连续 、非顺序的存储结构,数据元素的 逻辑顺序是通过链表
中的 指针链接 次序实现的 。
单向链表结构图

 注意:

        1.从上可以发现,链式结构在逻辑上是连续的,但是在物理上不一定连续

        2.申请的节点空间都是从堆上开辟的。

        3.从堆上申请的空间是按照一定策略分配的,两次申请的空间可能连续也可能不连续

1.1链表的设计

注:

        1.将int 重命名为为STLDataType 是为了修改储存类型时更为轻松,增加了兼容性。

        2.next为链表下一个节点的指针,方便寻找到下一个节点。

2. 链表的分类

实际中链表的结构非常多样,以下情况组合起来就有 8 种链表结构:

1. 单向或者双向

2. 带头或者不带头

3. 循环或者非循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

1. 无头单向非循环链表: 结构简单 ,一般不会单独用来存数据。实际中更多是作为 其他数据结
    构的子结构,如哈希桶、图的邻接表等等。
2. 带头双向循环链表: 结构最复杂 ,一般用在单独存储数据。实际中使用的链表数据结构,都
    是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
    来很多优势,另一篇文章介绍。
3.3 链表的实现
// 动态申请一个节点并初始化节点
SListNode* BuySListNode(SLTDateType x)
{
    //和顺序对比则更加节省空间
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("BuySListNode Error");
		assert(0);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}


// 单链表打印
void SListPrint(SListNode* plist)
{
	SListNode* pf = plist;
	while (pf)
	{
		printf("%d-> ", pf->data);
		pf = pf->next;
	}
	printf("NULL");
}


// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)    
//注意设计时传过来的是二级指针,当链表为空时修改函数外的指针时需要用二级指针。
//修改结构体内部指针时只需一级指针。
{
	SListNode *newnode = BuySListNode(x);
	SListNode* end = *pplist;
	if (end == NULL)
	{
		*pplist = newnode;
	}
	else
	{
		while (end->next != NULL) //判断条件为当前节点的下一个节点不为空。
		{
			end = end->next;
		}
		end->next = newnode;       //如果循环结束则在末尾的节点连接新的节点。
	}
}




// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	SListNode* newnode = BuySListNode(x);
	SListNode* onenode = *pplist;
	*pplist = newnode;
	newnode->next = onenode;

}


// 单链表的尾删
void SListPopBack(SListNode** pphead)
{
	assert(*pphead != NULL);    //防止传来空链表。

	SListNode* upend = NULL;
	SListNode* end = *pphead;

	while (end->next != NULL)    //寻找到尾节点和尾节点上一个。
	{
		upend = end;
		end = end->next;
	}

	if (upend == NULL)    //如果只有一个节点时需要用到设计时传过来的头指针的二级指针。
	{
		*pphead = NULL;
		free(end);
		return;
	}

	upend->next = NULL;    
	free(end);
}




// 单链表头删
void SListPopFront(SListNode** pphead)
{
	assert(*pphead != NULL);        //注意修改时要使用二级指针修改头指针

	SListNode* head = *pphead;
	SListNode* headnextnode = head->next;

	free(head);
	*pphead = headnextnode;
}


// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	SListNode* pfind = plist;
	while (pfind)
	{

		if (pfind->data == x)
		{
			return pfind;
		}
		pfind = pfind->next;
	}
	return NULL;
}




// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos );
	SListNode* newnode = BuySListNode(x);
	if (pos->next == NULL)
	{
		pos->next = newnode;
		return;
	}
	newnode->next = pos->next;
	pos->next = newnode;
}




// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置? 
// 无法找到pos前一位置的节点连接pos后一节点
void SListEraseAfter(SListNode* pos)
{
	assert(pos != NULL );

    //分为一个节点,删除最后一个节点,和在这之间这三种情况。
	if(pos->next == NULL)
	{
		return;
	}

	if ((pos->next)->next != NULL)
	{
		//如何删除头节点?
		SListNode* nextnode = (pos->next)->next;
		free(pos->next);
		pos->next = nextnode;
	}
	else if (pos->next->next == NULL)
	{
		free(pos->next);
		pos->next = NULL;
	}
	
}




// 在pos的前面插入
SListNode* SLTInsert(SListNode** pphead, SListNode* pos, SLTDateType x)
{
	assert(pphead);

	assert(pos);//如何处理pos为空且*pphead为空?

	assert(*pphead);

	SListNode* pfind = *pphead;
	SListNode* paerv = NULL;
	SListNode* newnode = BuySListNode(x);

	while (pfind)
	{
		if (pfind == pos)
		{
			newnode->next = pos;
			if (paerv != NULL)    //如果第一个节点就是pos
			{
				paerv->next = newnode;
			}
			else
			{
				*pphead = newnode;
			}
			return *pphead;
		}
		paerv = pfind;
		pfind = pfind->next;
	}
	return NULL;
}


//void SLTInsert(SLNode** pphead, SLNode* pos, STLDataType x)
//{
//	assert(pphead);
//	assert(pos);
//	assert(*pphead);
//	SLNode* prev = *pphead;
//	if (pos == prev)
//	{
//		STLpushFront(pphead,x);
//	}
//	else
//	{
//		while (prev->next != pos)
//		{
//			prev = prev->next;
//		}
//		SLNode* newnode = Createcode(x);
//		newnode->next = pos;
//		prev->next = newnode;
//	}
//}


//pos位置删除前面节点
void SLTErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);

	SListNode* prev = *pphead;

	if (pos == prev)        //如果第一个节点为删除对象。
	{
		SListPopFront(pphead);
	}
	else
	{
		while ((prev->next)->next != pos)    //prev后面第二个节点为pos
		{
			prev = prev->next;
		}
		free(prev->next);
		prev->next = pos;
	}
}




void SLTDestroy(SListNode** pphead)
{
	SListNode* slow = *pphead;
	SListNode* fast = *pphead;
	while (fast != NULL)
	{
		fast = fast->next;
		free(slow);
		slow = fast;
	}
	*pphead = NULL;
}

3.顺序表和链表的区别

不同点
顺序表
链表
存储空间上
物理上一定连续
逻辑上连续,但物理上不一定
连续
随机访问
支持 O(1)
不支持: O(N)
任意位置插入或者删除
元素
可能需要搬移元素,效率低O(N)
只需修改指针指向
插入
动态顺序表,空间不够时需要
扩容
没有容量的概念
应用场景
元素高效存储 + 频繁访问
任意位置插入和删除频繁
缓存利用率

与程序员相关的CPU缓存知识 | 酷 壳 - CoolShellicon-default.png?t=N7T8https://coolshell.cn/articles/20793.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值