单链表专题

链表的概念及结构

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

单链表专题_第1张图片
每个节点对应的结构体代码:

struct SListNode
{
	int val;
	struct SListNode* next;
};

单链表的实现

test.h(包括所有程序的头文件和函数的声明)

#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data; //节点数据
	struct SListNode* next; //指针保存下⼀个节点的地址
}SLTNode;



//打印链表
void SLTPrint(SLTNode* phead);

//尾部插⼊
void SLTPushBack(SLTNode** pphead, SLTDataType x);

//头部插⼊
void SLTPushFront(SLTNode** pphead, SLTDataType x);

//尾部删除
void SLTPopBack(SLTNode** pphead);

//头部删除
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x);

//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);

//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);

//销毁链表
void SListDesTroy(SLTNode** pphead);

test.c(代码运行测试)

#include"test.h"

void test()
{
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	//打印
	SLTPrint(plist);
	//头插
	SLTPushFront(&plist, 11);
	SLTPrint(plist);
	//尾删
	SLTPopBack(&plist);
	SLTPrint(plist);
	//头删
	SLTPopFront(&plist);
	SLTPrint(plist);
	//在指定位置之前插入
	SLTInsert(&plist, SLTFind(&plist, 3),20);
	SLTPrint(plist);
	//在指定位置之后插⼊数据
	SLTInsertAfter(SLTFind(&plist, 3), 20);
	SLTPrint(plist);
	//删除pos结点
	SLTErase(&plist,SLTFind(&plist,3));
	SLTPrint(plist);
	//删除pos结点后一个结点
	SLTEraseAfter(SLTFind(&plist,3));
	SLTPrint(plist);
	//结点销毁
	SListDesTroy(&plist);
	SLTPrint(plist);
}


int main()
{
	test();
	return 0;
}

move.c

  • 打印链表
#include"test.h"

//结点创建
SLTNode* SLBuyNode(int x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	node->data = x;
	node->next = NULL;
	return node;
}

//打印链表
void SLTPrint(SLTNode* phead)//此时仅需一级指针即可实现程序
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
  • 尾插
  • 思路:(1)我们先创建一个需要插入的结点node(即调用SLBuyNode函数);
    (2)再创建一个结构体指针pcur代替pphead进行while循环,当pcur的下一个结点位为NULL时,则说明pcur为链表的末尾
    (3)此时我们就可以将node结点插入到最后即可
    单链表专题_第2张图片
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{   //此时需要二级指针,因为链表需要被修改,应当传址调用
	assert(pphead);
	SLTNode* node = SLBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = node;
		return ;
	}
	SLTNode* pcur = *pphead;
	while (pcur->next)
	{
		pcur = pcur->next;
	}
	pcur->next = node;
}

单链表专题_第3张图片

  • 头插
  • 思路:(1)创建插入的结点node
    (2)由于头插不需要进行遍历(只需注意pphead不能为空),直接插入即可
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* node = SLBuyNode(x);
	node->next = *pphead;
	*pphead = node;
}

单链表专题_第4张图片

  • 尾删
  • 思路:(1)断言pphead*pphead都不能为空
    (2)若链表只有一个结点,可直接将*pphead进行free后置空;若链表不止一个结点,需要创建两个结构体指针,分别为prevptailprev的作用是指向ptail的前一个结点,ptail的作用则是确定最后一个结点的位置
    (3)确认完成后便可将prevnext指向ptailnext,再将ptail进行free后置空即可
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return ;
	}
	SLTNode* prev = *pphead;
	SLTNode* ptail = *pphead;
	while (ptail->next)
	{
		prev = ptail;
		ptail=ptail->next;
	}
	prev->next = ptail->next;
	free(ptail);
	ptail = NULL;
}

单链表专题_第5张图片

  • 头删
  • 思路:(1)断言pphead*pphead不为空
    (2)创建结构体指针del代表要删除的结点位置(即为pphead的位置)
    (3)将头结点*pphead指向*ppheadnext即可,最后将待删除的del指针free后置空。
void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead && pphead);
	SLTNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

单链表专题_第6张图片

  • 查找结点
  • 思路:(1)因需要找到结点地址,因此函数返回值为结构体指针,断言pphead不为空
    (2)创建新结构体指针pcur代替pphead进行遍历,当pcurdata值与x相等时,即可返回pcur;若遍历完一遍还没找到,则返回NULL
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x)
{	
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
  • 在指定位置之前插入数据
  • 思路:(1)断言pphead、*pphead、pos不为空
    (2)创建需要插入的结点node和进行遍历的结构体指针prev
    (3)当prevnext等于pos时则退出遍历
    (4)prevnext指向nodenodenext指向pos
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* node = SLBuyNode(x);
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev=prev->next;
	}
	prev->next = node;
	node->next = pos;
}

单链表专题_第7张图片

  • 在指定位置之后插入数据
  • 思路:(1)不再需要遍历链表,因此函数参数没有头结点,断言pos不为空
    (2)只需创建待插入结点node,然后将nodenext指向posnextposnext指向node
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* node = SLBuyNode(x);
	node->next = pos->next;//
	pos->next = node;//需要注意这两行代码不能交换顺序,否则node无法与pos的下一个结点进行连接
}

单链表专题_第8张图片

  • 删除pos节点
  • 思路:(1)断言pos、pphead、*pphead不为空
    (2)若pos为头结点或者链表只要一个结点时,*pphead等于*ppheadnext,将pos进行free后置空即可
    (3)若不为上述的情况,则创建进行遍历的结构体指针pcur,当pcurnext等于pos时退出循环,将pcurnext指向posnext,再将pos进行free即可
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pos && pphead && *pphead);
	if (pos == *pphead)
	{
		*pphead = (*pphead)->next;
		free(pos);
		pos = NULL;
		return;
	}
	SLTNode* pcur = *pphead;
	while (pcur->next != pos)
	{
		pcur = pcur->next;
	}
	pcur->next = pos->next;
	free(pos);
	pos = NULL;
}

单链表专题_第9张图片

  • 删除pos之后的节点
  • 思路:(1)无需遍历链表,因此函数参数不需要头结点,断言pos、pos->next不为空
    (2)创建需要删除的结构体指针del,注意赋值为pos->next而不是pos
    (3)将posnext指向delnext,将del进行free置空
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

单链表专题_第10张图片

  • 销毁链表
  • 思路:(1)断言pphead不为空
    (2)创建新结构体指针pcur代替pphead循环,pcur等于NULL则退出循环
    (3)在循环过程中创建一个新的结构体指针next,作用是指向pcurnext,让pcur进行free后,再将pcur赋值为next,如此循环将链表销毁
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

单链表专题_第11张图片

链表的分类

  • 带头单向循环链表
  • 带头单向不循环链表
  • 带头双向循环链表
  • 带头双向不循环链表
  • 不带头单向循环链表
  • 不带头单向不循环链表(上述写的链表)
  • 不带头双向循环链表
  • 不带头双向不循环链表

你可能感兴趣的:(数据结构)