【数据结构】————单链表的定义及基本操作

116 阅读10分钟

一、链表介绍

1.链表的概念

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

看起来很麻烦,其实就是一个个的结点连起来的结构。如下图:

每一个结点的第一个data用来存放有效数据,第二个next用来存放下一个结点的地址

**注意:**最后一个结点的next存放的是NULL

链表只是在逻辑结构上是连续的,一个结点指向下一个结点,但是在物理存储结构上是不连续的。上图是为了方便大家理解,才用线条连接了结点。

二、单链表的基本操作

1.定义结点结构体

  • 单链表的有效数据不一定都是int整型的,为了后期方便修改,我们直接对int进行重定义SListDataType。因为单链表的英文可以缩写成SList

  • 再来创建结点结构体,结构体成员变量a用来存储有效数据;next用来存放下一个结构体的地址,所用指针变量,类型一定是struct SListNode*哦!!!

    typedef int SListDataType;//对int进行重定义 typedef struct SListNode //创建结点结构体 { SListDataType a; struct SListNode* next; }SListNode;

2.单链表的函数接口

学习数据结构主要就是对数据进行增删查改,在写出来一个链表后也要写一些函数接口方便对单链表的数据进行管理。写出下面的函数,用来创建和管理单链表。

//新建一个结点
SListNode* BuySListNode(SListDataType x);
//显示单链表
void SLPrint(SListNode* phead);
//尾插
void SLPushBack(SListNode** pphead, SListDataType x);
//头插
void SLPushFront(SListNode** pphead, SListDataType x);
//尾删
void SLPopBack(SListNode** pphead);
//头删
void SLPopFront(SListNode** pphead);
//查找x结点地址
SListNode* SLFind(SListNode* phead, SListDataType x);
//在pos地址之前插入x
void SLInsert(SListNode** pphead, SListNode* pos, SListDataType x);
//在pos地址后边插入x
void SLInsertAfter(SListNode* pos, SListDataType x);
//删除pos地址结点
void SLErase(SListNode** pphead, SListNode* pos);
//删除pos地址之后的结点
void SLEraseAfter(SListNode* pos);
//销毁单链表
void SLDestroy(SListNode** pphead);

3.单链表的功能实现

① 创建新结点

利用malloc函数开辟一个结点,有效数据a存放x,指针next先存放NULL,最后返回新建结点的地址newnode。如果新结点为空即newnode==NULL会执行打印错误信息,退出程序。

//开辟一个结点
SListNode* BuySListNode(SListDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("BuySListNode->malloc failed");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->a = x;
	return newnode;
}

② 显示单链表

把单链表中的每一个有效值打印在屏幕上。

//显示单链表
void SLPrint(SListNode* phead)
{
	SListNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->a);
		cur = cur->next;
	}
	printf("NULL\n");
}

③ 单链表尾插
  • phead是一级指针存放的是结点结构体的地址,当链表为空的时候,phead就是为NULL,而二级指针pphead存放的是指针变量phead的地址,phead的地址是永远存在的,所以pphead就一定不可能为空,所以需要断言pphead。

  • 调用BuySListNode函数传入参数x,返回新结点的地址存放到newnode变量中

  • 当进行尾插的时候有两种情况:

    1. 当单链表为空的时候,直接把新结点的地址存放到phead中,即**pphead==newnode
    2. 当单链表为非空的时候,创建tail指针找到该单链表的最后一个结点(标志是:tail->next==NULL),把tail的next指针指向newnode即:tail->next=newnode;

    //尾插 void SLPushBack(SListNode** pphead, SListDataType x) { assert(pphead); SListNode* newnode = BuySListNode(x); if (*pphead == NULL) { pphead = newnode; } else { SListNode tail = *pphead; while (tail->next != NULL) { tail = tail->next; } tail->next = newnode; } }

④ 单链表头插

单链表的头插相对比较简单,在创建一个新结点后,只需要把新结点的next指针指向该单链表的头指针,然后再把单链表的头指针更新一下就OK了。

//头插
void SLPushFront(SListNode** pphead, SListDataType x)
{
	assert(pphead);
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

⑤ 单链表尾删
  • 第一个断言:传过来的是存放单链表的头结点指针的地址,所以一定不能为空。

  • 第二个断言:是单链表不能可空,单链表里面必须有元素。

  • 创建两个临时指针变量,tail找到尾结点、prev用来存放tail前一个结点的地址。找到后有两种结果:

    1. 该单链表就一个结点(利用prev是否为空就可以判断):直接free掉*pphead然后再把*pphead置空
    2. 该单链表有两个及以上个结点:这就要把tail释放掉以后,再把tail前一个结点的next置为空

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

⑥ 单链表头删

单链表的头删也是比较简单的,创建一个临时指针变量用来存放头结点下一个结点的地址,然后在把头结点释放点,再把临时变量的值,,赋给*pphead

//头删
void SLPopFront(SListNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SListNode* tmp = (*pphead)->next;
	free(*pphead);
	*pphead = tmp;
}

⑦ 查找x结点地址

遍历整个链表,同时判断结点中的值是否为x,如果相等返回该结点地址,否则返回NULL

//查找x结点地址
SListNode* SLFind(SListNode* phead, SListDataType x)
{
	assert(phead);
	SListNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->a == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

⑧ pos位置前插入x

断言pos和pphead不为空,然后再插入x,也得分两种情况:

  1. pos和*pphead的值相等,就是两个指针都指向头结点,那么直接利用单链表头插

  2. pos和*pphead的值不相等,就得先找pos前面的结点prev,找到以后,创建新结点newnode,然后把newnode的值赋给prev的next指针,再把pos的值赋给newnode的next

    //在pos位置之前插入x void SLInsert(SListNode** pphead, SListNode* pos, SListDataType x) { assert(pos != NULL); assert(pphead); if (pos == pphead) { SLPushFront(pphead, x); } else { SListNode prev = pphead; while (prev->next != pos) { prev = prev->next; } SListNode newnode = BuySListNode(x); newnode->next = pos; prev->next = newnode; } }

⑨ pos位置后插入x

这个相较于前插就比较简单,首先断言pos不能为空,然后创建一个有效数值为x的新结点newnode,然后把pos位置的下一个结点的地址放到新结点newnode的next里去,然后再把newnode的地址存放到pos的next指针中。

//在pos位置后边插入x
void SLInsertAfter(SListNode* pos, SListDataType x)
{
	assert(pos);
	SListNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

⑩ 删除pos位置结点

三个断言不用说了

  • 该单链表有两个及以上结点:要删除pos的结点,就要先找到pos前面的结点prev,然后把pos后面结点的地址存放到prev的next指针里去,在释放掉pos

  • 该单链表就一个结点:跟头删的逻辑一样哦

    //删除pos地址结点 void SLErase(SListNode** pphead, SListNode* pos) { assert(pphead); assert(pphead); assert(pos); SListNode cur = pphead; if (cur == pos) { SListNode newHead = (*pphead)->next; free(*pphead); *pphead = newHead; } else { while (cur->next != pos) { cur = cur->next; } cur->next = pos->next; free(pos); } }

⑪ 删除pos位置后结点

删除pos位置后的结点,先创建临时指针变量用来存放pos位置后的结点的地址,然后再把临时指针变量赋给pos结点的next

//删除pos地址之后的结点
void SLEraseAfter(SListNode* pos)
{
	assert(pos != NULL);
	assert(pos->next != NULL);
	SListNode* posNext = pos->next;
	pos->next = posNext->next;
	free(posNext);

}

⑫ 单链表销毁

如果单链表本身就为空直接返回,如果不为空就创建两个指针变量用来存放当前结点cur和当前结点的下一个结点curNext,然后释放当前结点cur,再把下一个结点地址curNext赋值给当前结点cur

//销毁单链表
void SLDestroy(SListNode** pphead)
{
    assert(pphead);
	if (*pphead)
		return;
	SListNode* cur = *pphead;
	SListNode* curNext = NULL;
	while (cur != NULL)
	{
		curNext = cur->next;
		free(cur);
		cur = curNext;
	}
}

三、代码汇总

3.1 SList.h

包含头文件、函数声明、结构体定义等

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

typedef int SListDataType;
typedef struct SListNode
{
	SListDataType a;
	struct SListNode* next;
}SListNode;


//新建一个结点
SListNode* BuySListNode(SListDataType x);
//显示单链表
void SLPrint(SListNode* phead);
//尾插
void SLPushBack(SListNode** pphead, SListDataType x);
//头插
void SLPushFront(SListNode** pphead, SListDataType x);
//尾删
void SLPopBack(SListNode** pphead);
//头删
void SLPopFront(SListNode** pphead);
//查找x结点地址
SListNode* SLFind(SListNode* phead, SListDataType x);
//在pos地址之前插入x
void SLInsert(SListNode** pphead, SListNode* pos, SListDataType x);
//在pos地址后边插入x
void SLInsertAfter(SListNode* pos, SListDataType x);
//删除pos地址结点
void SLErase(SListNode** pphead, SListNode* pos);
//删除pos地址之后的结点
void SLEraseAfter(SListNode* pos);
//销毁单链表
void SLDestroy(SListNode** pphead);

3.2 SList.c

对单链表相关函数的实现

#include"SList.h"
//开辟一个结点
SListNode* BuySListNode(SListDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("BuySListNode->malloc failed");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->a = x;
	return newnode;
}


//显示单链表
void SLPrint(SListNode* phead)
{
	SListNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->a);
		cur = cur->next;
	}
	printf("NULL\n");
}


//尾插
void SLPushBack(SListNode** pphead, SListDataType x)
{
	assert(pphead);
	SListNode* newnode = BuySListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}


//头插
void SLPushFront(SListNode** pphead, SListDataType x)
{
	assert(pphead);
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}


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


//头删
void SLPopFront(SListNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SListNode* tmp = (*pphead)->next;
	free(*pphead);
	*pphead = tmp;
}


//查找结点地址
SListNode* SLFind(SListNode* phead, SListDataType x)
{
	assert(phead);
	SListNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->a == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//在pos之前插入x
void SLInsert(SListNode** pphead, SListNode* pos, SListDataType x)
{
	assert(pos != NULL);
	assert(pphead);
	if (pos == *pphead)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SListNode* newnode = BuySListNode(x);
		newnode->next = pos;
		prev->next = newnode;
	}
}


//在pos后边插入x
void SLInsertAfter(SListNode* pos, SListDataType x)
{
	assert(pos);
	SListNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}


//删除pos位置结点
void SLErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	SListNode* cur = *pphead;
	if (cur == pos)
	{
		SListNode* newHead = (*pphead)->next;
		free(*pphead);
		*pphead = newHead;
	}
	else
	{
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}


//删除pos之后的结点
void SLEraseAfter(SListNode* pos)
{
	assert(pos != NULL);
	assert(pos->next != NULL);
	SListNode* posNext = pos->next;
	pos->next = posNext->next;
	free(posNext);

}

void SLDestroy(SListNode** pphead)
{
	if (*pphead)
		return;
	SListNode* cur = *pphead;
	SListNode* curNext = NULL;
	while (cur != NULL)
	{
		curNext = cur->next;
		free(cur);
		cur = curNext;
	}
}

3.3 main.c

对单链表测试,调整

#include"SList.h"

void test1()
{
	SListNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 5);
	SLPushBack(&plist, 6);
	SLPushBack(&plist, 7);
	SLPushBack(&plist, 8);
	SLPushBack(&plist, 9);
	SListNode* ret = SLFind(plist, 1);
	SLEraseAfter(ret);
	//for (int i = 1; i <= 4; i+=2)
	//{
	//	SLPrint(plist);
	//	
	//	//SLErase(&plist, ret);
	//	//SLInsertAfter(ret, 88);
	//	//printf("%d = %p\n", i, ret);
	//}
	SLPrint(plist);
}
int main()
{
	test1();


	return 0;
}