Bootstrap

【C语言实现简单数据结构】单链表

【C语言实现简单数据结构】单链表

心有所向,日复一日,必有精进



前言

上回书说道,顺序表极其实现,顺序表有其有点,也有其问题:

  1. 中间/头部的插入删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
    思考:如何解决以上问题呢?下面给出了链表的结构来看看

链表

1.1链表的概念和结构

概念:==链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 ==
要想把链表链接起来,在逻辑上是连续的,所以是线性结构但是:

逻辑上:

逻辑结构

物理上:(箭头不存在,方便我们理解)

物理结构
我们看到了物理上可能不是连续的,是链接的,一个结构里存放两种信息构成一个结点,包括两个域,存储数据元素的域称为数据域,存储直接后继存储位置的域称为指针域,存储着指针或链,这就把每个结点链接起来所以叫链表。

1.2链表的分类

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

  1. 单向或双向
    单向或双向
  2. 带头或不带头带头或不带头
  3. 循环或非循环在这里插入图片描述
    虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
    在这里插入图片描述
    1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
    2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

1.3单链表的实现

1.3.1接口实现

typedef int SLDataType;
typedef struct SLNODE{
	SLDataType data;
	struct SLNODE* next;
}SListNode;

void SListPopFront(SListNode* pphead);
//打印
void SListNodePrint(SListNode* phead);
//头插
void SListNodePushFront(SListNode** pphead, SLDataType x);
//动态申请一个结点
SListNode* NewSListNode();
//尾插
void SListNodePushBack(SListNode** pphead, SLDataType x);
//头删
void SListNodePopFront(SListNode** pphead);
//尾删
void SListNodePopBack(SListNode** pphead);
// 单链表查找
SListNode* SListNodeFind(SListNode* phead, SLDataType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLDataType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);

1.3.2动态申请一个结点

生成新的结点,把结点的数据存在数据域中,结点的指针域置为空。

//动态申请一个结点
SListNode* NewSListNode(SLDataType x){
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

1.3.3单链表打印

//单链表打印
void SListNodePrint(SListNode* phead){
	SListNode* cur = phead;
	while (cur != NULL){
		printf("%d->",cur->data);
	 //停止条件
		cur = cur->next;
	}
	printf("NILL\n");
}

1.3.4单链表头插

//头插
void SListNodePushFront(SListNode** pphead ,SLDataType x){
	assert(pphead);
	SListNode* newNode = NewSListNode(x);
	newNode->next = *pphead;
	*pphead = newNode;
}

1.3.5单链表尾插

//尾插
void SListNodePushBack(SListNode** pphead, SLDataType x){
	assert(pphead);
	SListNode* newNode = NewSListNode(x);
	if (*pphead == NULL){
		*pphead = newNode;
	}else{
		SListNode* tail = *pphead;
		while (tail->next != NULL){
			tail = tail->next;
		}
		tail->next = newNode;
	}	
}

1.3.6单链表头删

void SListNodePopFront(SListNode** pphead){
	assert(pphead);
	/*if (*pphead == NULL){
		return;
	}*/
	assert(*pphead);
	SListNode* tmp = *pphead;
	*pphead = (*pphead)->next;
	free(tmp);
	tmp = NULL;
}

1.3.7单链表尾删

//尾删
void SListNodePopBack(SListNode** pphead){
	assert(pphead);
	assert(*pphead);
	SListNode* tail = *pphead;
	if (tail->next == NULL){
		free(*pphead);
		*pphead = NULL;
	}
	else{
		while (tail->next->next != NULL){
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
	
}

1.3.8单链表查找

// 单链表查找
SListNode* SListNodeFind(SListNode* phead, SLDataType x){
	//找到返回地址,找不到返回-1;
	while (phead){
		if (phead->data == x){
			return phead;
		}
		phead = phead->next;
	}
		return NULL;	
}

1.3.9单链表在pos位置之后插入x

void SListInsertAfter(SListNode* pos, SLDataType x){
		assert(pos);
		SListNode*newNode = NewSListNode(x);
		newNode->next = pos->next;
		pos->next = newNode;
}

1.3.10单链表在pos位置之前插入x

void SListNodeInsert(SListNode** pphead, SListNode* pos, SLDataType x)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SListNodePushFront(pphead, x);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;

			// 暴力检查,pos不在链表中.prev为空,还没有找到pos,说明pos传错了
			assert(prev);
		}

		SListNode* newnode = NewSListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

1.3.11单链表删除pos位置之后的值

// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos){
	assert(pos);
	if (pos->next == NULL){
		return;
	}
	else{
		SListNode* next = pos->next;
		pos->next = next->next;
		free(next);
	}
}

总结

单链表在头插和头删中有一定的优势,而对于尾插来讲,以及需要遍历寻找到最后一个结点,这是缺点,他没有完全的解决顺序表中的缺点。
如果我们删除pos原位置的结点呢?(不去找该结点的上一个结点)
我们可以找到pos结点的下一个结点,数据域进行交换然后,删除pos位置的后一个结点相对容易
在这里插入图片描述
这样我们的目的也就实现了,如果在pos位置前面插入,可以直接插入该结点后面,数据域进行交换;
单链表还又很多缺陷,但为什么还要学习,因为在后面单链表可能作为某一结构的子结构,OJ一般也出这种类型题目,所以重视链表。
最后,我把单链表的简单实现的源码放到了我的Gitee上面了,作为参考。

;