本文实现语言:c语言

上篇文章回顾


上几篇博客我们已经实现静态顺序表和动态顺序表的基本功能,在顺序表中存取表元素非常简单,但是插入和删除需要移动大量的数据(时间复杂度O(n) )非常麻烦;因此今天我们来实现插入和删除都非常方便的链表。

其实链表分为好几类,如下;其中以不带头节点,不循环的单链表带头节点,循环的双链表最为重要。

分类方式 类别1 类别1
按照指针数量分 单链表:只有一个指向下一个节点的指针。 双链表:有一个指向下一个节点的指针,还有有一个指向上一个节点的指针。
链表是否循环分 循环链表:即链表的尾指针指向头结点 不循环链表
链表按照头结点分 带头结点的链表:链表中的第一个结点中不存放有效的数据(相当于哨兵) 不带头结点的链表:链表中的第一个节点为有效结点

本文主要实现不带头节点,不循环的单链表的增,删,插,改等功能。


在实现代码前,有一个重要的问题需要讨论,就是传参问题,什么情况下我们需要传二级指针?

假设形参是普通变量,在传递的时候,如果我们打算对普通变量进行修改,那么我们就需要传递变量的地址;如果我们不打算修改普通变量(比如只是查看一下内容),那么我们的形参传递数值就可以(此时形参是实参的一份临时copy)。

同样的,如果我们的形参是指针变量,如果我们打算修改这个指针变量,那么我们传递的时候就需要二级指针;否则传递一级指针就OK。

这个理论放到链表实现里就是说:我们在设计链表的时候会设计一个头指针pNode,这个头指针pNode会指向头结点(或者指向第一个有效结点),所以头指针里面存的就是头结点的地址。如果我们要修改头结点里面所保存的地址(比如初始化链表时,让头指针指向NULL;再比如头插的时候,头指针保存的地址会发生变化,变成新插入节点的地址)时,在设计函数的时候就需要传递二级指针;如果我们只是查看头指针指向的内容,这样并不会改变头指针本身保存的地址,此时传递一级指针就可以。

所以,一般销毁,初始化,头插等需要用到二级指针;查找,遍历等传递一级指针就OK。


代码:

#define _CRT_SECURE_NO_WARNINGS 1

//-----------------------SList.h---------------
// #pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

typedef int DataType;

typedef struct SListNode
{
	struct SListNode* next;  //指针域
	DataType data;  //数据域

}SListNode, *pSListNode, **ppSListNode;



pSListNode CreateSListNode(DataType x);//创建一个节点
void SListInit(ppSListNode pphead);//初始化
void SListPrint(pSListNode phead);//打印链表
void SListPushFront(ppSListNode pphead, DataType x);//头插
void SListPushBack(ppSListNode pphead, DataType x);//尾插
void SListInsert(ppSListNode pphead, pSListNode pos, DataType x);//插入
void SListInsert(ppSListNode pphead, size_t pos, DataType x);//将x插入第pos个节点之后
void SListPopFront(ppSListNode pphead);//头删
void SListPopBack(ppSListNode pphead);//尾删
void SListErase(ppSListNode pphead, pSListNode pos);//给定节点删除
void SListRemove(ppSListNode pphead, DataType x);//按值删除,删除遇到的第一个点
void SListRemoveALL(ppSListNode pphead, DataType x);//按值删除,删除所有的
void SListDestory(ppSListNode pphead);//销毁
pSListNode SListFind(pSListNode phead, DataType data);//按值查找,返回第一个找到的结点指针,如果没有找到,返回NULL
int SListEmpty(pSListNode phead);//判断是否为空
int SListSize(pSListNode phead);//算节点数

//-----------------------SList.c---------------

//创建一个节点
pSListNode CreateSListNode(DataType x)
{
	pSListNode node = (pSListNode)malloc(sizeof(SListNode));
	assert(node);
	node->data = x;
	node->next = NULL;
	return node;
}

//初始化:这里使用了二级指针,也可以使用一级指针的引用型参数。
void SListInit(ppSListNode pphead)
{
	assert(pphead);
	*pphead = NULL;
}

//打印链表
void SListPrint(pSListNode phead)
{
	pSListNode cur = phead;
	for(cur = phead; cur != NULL; cur = cur->next)
	{
		printf("%2d->", cur->data);
	}
	printf("NULL\n");
	
}

//头插
void SListPushFront(ppSListNode pphead, DataType x)
{
	assert(pphead);
	//先检查是否为空
	if (NULL == *pphead)
	{
		//链表为空时,创建一个头结点,该节点的next为NULL,指针*pphead指向它。
		*pphead = CreateSListNode(x);		
	}
	else 
	{
		pSListNode pNode = CreateSListNode(x);
		pNode->next = *pphead;
		*pphead = pNode;
	}
	
}

//尾插
void SListPushBack(ppSListNode pphead, DataType x)
{
	assert(pphead);
	if (NULL == *pphead)//先检查是否为空
	{
		pSListNode pNode = CreateSListNode(x);
		return;
	}
	else
	{
		pSListNode cur = *pphead;
		while (NULL != cur->next)
		{
			cur = cur->next;
		}
		cur->next = CreateSListNode(x);
				
	}
}

//给定节点插入:1.先检查pos的合法性 2.创建一个以x为值的新节点newNode(一级指针) 
void SListInsert(ppSListNode pphead, pSListNode pos, DataType x)
{

	assert(pphead);
	assert(*pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPushFront(pphead, x);
		return;
	}
	pSListNode newNode = CreateSListNode(x);

#if 0  //如果常量为真,执行程序段1,否则执行程序段2

	//3.在单链表上找到这个位置的前一个节点tmp 4.newNOde的next指向tmp的next 5. tmp的next指向newNode。   此功能主要运算时间耗费在查找上,时间复杂度为O(n)
	pSListNode tmp = *pphead;
	while (tmp->next != pos)
	{
		tmp = tmp->next;
	}
	newNode->next = tmp->next;
	tmp->next = newNode;
#else
	newNode->data = pos->data;
	pos->data = x;
	newNode->next = pos->next;
	pos->next = newNode;

#endif
}

//将x插入第pos个节点之后
void SListInsert(ppSListNode pphead, size_t pos, DataType x)
{
	assert(pphead);
	int size = SListSize(*pphead);
	assert(pos >= 0 && pos <= size);
	pSListNode node = CreateSListNode(x);//创建一个节点
	pSListNode tmp = *pphead;
	if (0 == pos) {//头插
		SListPushFront(pphead, x);
	}
	else 
	{
		while (pos-1)//有疑问
		{
			tmp = tmp->next;
			pos--;
		}
		node->next = tmp->next;
		tmp->next = node;
	}
}

//头删
void SListPopFront(ppSListNode pphead)
{
	assert(pphead);
	assert(*pphead);
	pSListNode tmp = *pphead;
	*pphead = (*pphead)->next;
	free(tmp);
	tmp = NULL;
}

//尾删
void SListPopBack(ppSListNode pphead)
{
	assert(pphead);
	assert(*pphead);
	//如果只有一个头节点
	if (NULL == (*pphead)->next)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}

	pSListNode first = *pphead;
	while (NULL != first->next->next)
	{
		first = first->next;

	}
	free(first->next);
	first->next = NULL;

}

//给定节点删除:1.先检查pos的合法性  
void SListErase(ppSListNode pphead, pSListNode pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPopFront(pphead);
		return;
	}
#if 0

	//2.在单链表上找到删除位置的前一个节点cur 3.cur的next指向删除pos的next    此功能主要运算时间耗费在查找上,时间复杂度为O(n)
	pSListNode cur = *pphead;
	while (pos != cur->next)
	{
		cur = cur->next;
	}
	cur->next = pos->next;
	free(pos);
	pos = NULL;
#else
	if (pos != NULL && NULL != pos->next)
	{
		pSListNode node = pos->next;
		pos->data = pos->next->data;
		pos->next = node->next;
		free(node);
		node = NULL;
	}
	else if (NULL == pos->next)
	{
		//即要删除的是最后一个元素
		SListPopBack(pphead);
	}
#endif
}

//按值删除,删除遇到的第一个点
void SListRemove(ppSListNode pphead, DataType x)
{
	assert(pphead);
	assert(*pphead);
	pSListNode pos = SListFind(*pphead, x);
	if (NULL == pos)
	{
		printf("没有这个数值\n");
	}
	else
	{
		SListErase(pphead, pos);
	}
	
}

//按值删除,删除所有的
void SListRemoveALL(ppSListNode pphead, DataType x)
{
	assert(pphead);
	assert(*pphead);

	pSListNode pos = NULL;
	while (NULL != (pos = SListFind(*pphead, x)))
	{
		SListErase(pphead, pos);
	}

}

//销毁
void SListDestory(ppSListNode pphead)
{
	assert(pphead);
	pSListNode node = *pphead;
	while (node)
	{
		pSListNode newNode = node;
		node = node->next;
		free(newNode);
		newNode = NULL;
	}
	*pphead = NULL;
}

//按值查找,返回第一个找到的结点指针,如果没有找到,返回NULL
pSListNode SListFind(pSListNode phead, DataType data)
{
	assert(phead);
	pSListNode node = phead;
	while (node)
	{
		if (data == node->data)
		{
			return node;
		}
		node = node->next;
	}
	return NULL;

}

//判断是否为空,为空返回0
int SListEmpty(pSListNode phead)
{
	return phead == NULL ? 0 : 1;
}

//算节点数
int SListSize(pSListNode phead)
{
	assert(phead);	
	size_t size = 0;
	pSListNode node = phead;
	while (node)
	{
		node = node->next;
		size++;
	}
	return size;
}

//逆序打印(递归)
void SListReverse(pSListNode phead)
{
	if (NULL == phead)
	{
		return;
	}
	SListReverse(phead->next);
	printf("%2d->", phead->data);
}

//-----------------test.c----------------
void Test()
{
	pSListNode list = NULL;
	SListInit(&list);
	
	SListPushFront(&list, 1);//头插
	SListPushFront(&list, 2);//头插
	SListPushFront(&list, 3);//头插
	SListPushFront(&list, 4);//头插
	SListPrint(list);
	
	SListPushBack(&list, 101);//尾插
	SListPushBack(&list, 102);//尾插
	SListPushBack(&list, 103);//尾插
	SListPushBack(&list, 104);//尾插
	SListPrint(list);

	SListInsert(&list, list->next, 100);//任意位置插
	SListPrint(list);

	SListErase(&list, list->next);//任意位置删
	SListPrint(list);

	int ret = SListSize(list);//算节点数
	printf("节点数%d\n", ret);
	int cur = SListEmpty(list);//判断是否为空
	if (cur == 0)
		printf("链表为空\n");
	else
		printf("链表不为空\n");
	SListInsert(&list, list->next->next, 1);//任意位置插
	SListInsert(&list, list->next->next->next->next, 1);//任意位置插
	SListPushBack(&list, 1);//尾插
	SListPushFront(&list, 1);//头插
	SListPrint(list);
	SListRemove(&list, 1);//删除第一个x
	SListPrint(list);

	SListRemoveALL(&list, 1);//删除所有x
	SListPrint(list);

	SListDestory(&list);//销毁
	SListPrint(list);

	SListPushFront(&list, 1);//头插
	SListPushFront(&list, 2);//头插
	SListPushFront(&list, 3);//头插
	SListPushFront(&list, 4);//头插
	SListPrint(list);

	SListInsert(&list, 1, 100);
	SListPrint(list);

	SListReverse(list);//逆序打印
	printf("NULL\n");
}

int main()
{
	Test();
	system("pause");
	return 0;
}


结果: