数据结构——单链表

269 阅读5分钟

单链表

单链表由0个或多个节点构成,每个节点都是一个结构体,

该结构体 包括 存储的数据下一个节点的地址

image.png

这里节点1为头节点,节点3为尾节点.

节点的地址不一定连续,但当前节点通过访问自身结构体的内容可以找到下一个节点的地址.

因此知道头节点后,就可以顺藤摸瓜找到后面节点的数据。

单链表的实现

不能像顺序表一样直接定义单链表的结构体,而是要定义节点的结构体

因为单链表的特点是“按需申请空间”,即需要一个数据申请一块空间。

定义节点的结构体

typedef int SLTDataType;
typedef struct SingleListNode   //单链表的节点
{
    SLTDataType data;           //存储的数据 
    struct SingleListNode* next;//下一个节点的地址
}SLTNode;

构成一个单链表最简单的方法

void testSLTPrint()
{
    //先分别malloc三个节点的空间,这里n1、n2、n3都是结构体指针,通过结构体指针可以修改结构体的内容
    SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
    assert(n1);
    SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
    assert(n2);
    SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
    assert(n3);
    //分别给这三个节点存值
    n1->data = 1;
    n2->data = 2;
    n3->data = 3;
    //再把他们连接起来
    n1->next = n2;
    n2->next = n3;
    n3->next = NULL;
}

此时头节点是n1指向的结构体。

打印单链表的数据

传单链表的头节点指针,才能找到后面的全部节点.若单链表为空,则他的头节点指针为NULL.

    //phead是局部变量,函数内部局部变量的改变不会影响外面的参数
    void SLTPrint(SLTNode* phead)
    {
	while (phead != NULL)
	{
		printf("%d->", phead->data);
		phead = phead->next;
	}
	printf("NULL\n");
    }

单链表的增删查改

尾插与尾删 ( 时间复杂度均为O(N) )

前言:要在函数内部改变外面的int类型,就要传int*,解引用改变

要在函数内部改变外面的int*类型,就要传int**,解引用改变

尾插有两种情况:

链表为空,传过来的头节点指针为NULL,此时我们需要申请节点,改变头节点指针phead。

链表不为空,此时我们要申请节点接在节点3的后面,即改变节点3结构体里的next,改变结构体。

image.png

如果传的是头节点指针,可以顺利找到节点3结构体的地址,从而修改结构体;

然而我们还可能需要改变头节点指针phead,所以应该传头节点指针的指针,即pphead.

void SLTPushBack(SLTNode** pphead,SLTDataType x)//单链表的尾插
{
	//二级指针一定不为空
	assert(pphead);
	//按需申请节点
	SLTNode* tmp = (SLTNode*)malloc(sizeof(SLTNode));
	assert(tmp);
	tmp->data = x;
	tmp->next = NULL;

	//若头节点指针为空,则可以解引用二级指针改变头节点指针
	if (*pphead == NULL)
	{
            *pphead = tmp;
	}

	else
	{
            SLTNode* cur = *pphead;
            while (cur->next != NULL)//找尾节点
            {
		cur = cur->next;
            }
            cur->next = tmp;//改变尾节点的next
	}	
}

尾删也有两种情况:

1 链表只有一个节点,把最后一个节点删除后,要把头节点指针置为NULL,即改变头节点指针。

2 链表有多个节点,找到尾节点的前一个节点后,先把尾节点的空间free掉,再把新尾节点的next置为NULL。

image.png

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead && *pphead);//节点为空删不了
    
    if ((*pphead)->next == NULL)//只有一个节点
    {
	free(*pphead);
        *pphead = NULL;
    }
    
    else//多个节点
    {
	SLTNode* cur = *pphead;
        //前面已经知道cur->next开始一定不为空
	while (cur->next->next != NULL)//找尾节点的前一个节点
	{
            cur = cur->next;
	}
        //此时cur为尾节点的前一个节点
        free(cur->next);//释放掉尾节点的空间
        cur->next = NULL;//把新尾节点的next置NULL
    }
}

头插和头删(时间复杂度为O(1))

头插和头删一定会改变头节点的指针,要传二级指针。

头插的两种情况:

1 链表为空,申请新节点,使头节点指针指向该节点;

2 链表不为空

image.png

void SLTPushFront(SLTNode** pphead, SLTDataType x)//头插
{
    assert(pphead);
    //申请新节点
    SLTNode* tmp = (SLTNode*)malloc(sizeof(SLTNode));
    assert(tmp);
    tmp->data = x;
    
    if (*pphead == NULL)//链表为空,直接使头节点指针指向新节点,同时新节点的next置NULL
    {
	tmp->next = NULL;
	*pphead = tmp;
    }
    else//链表不为空
    {
	tmp->next = *pphead;
	*pphead = tmp;
    }
}

头删的两种情况:

1 只有一个节点,删除后头节点指针置NULL;

2 多个节点

image.png

void SLTPopFront(SLTNode** pphead)//头删
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;//保留第二个节点的地址
	SLTNode* phead = *pphead;//第一个节点的地址
	free(phead);//把第一个节点释放掉
	*pphead = next;//头节点改成第二个节点
}

查找与修改

查找不会修改单链表的数据,只需传头指针遍历单链表

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)//通过数据查找,返回节点的指针
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
            if (cur->data == x)
		return cur;
            else
		cur = cur->next;
	}
	return NULL;

//还可以顺便修改
SLTNode* pos = SLTFind(phead, 1);
if(pos != NULL)
    pos->data = 55;

在任意位置之前插入

与查找的函数互相配合,先找到插入的位置,再传给该函数

有2种情况:

1 头插,直接调用即可;

2

image.png

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)//在pos位置之前插入
{
    assert(pos && pphead);
    SLTNode* tmp = (SLTNode*)malloc(sizeof(SLTNode));
    assert(tmp);
    tmp->data = x;
    SLTNode* cur = *pphead;
    if (cur == pos)
    {
	//头插
	SLTPushFront(pphead, x);
    }
    else
    {
	while (cur->next != pos)//找pos的前一个节点
	{
            cur = cur->next;
	}
        //此时cur为pos的前一个节点的指针
        cur->next = tmp;
        tmp->next = pos;
    }
}

删除任意位置的节点

image.png

void SLTErase(SLTNode** pphead, SLTNode* pos)//删除pos位置的节点
{
    assert(pphead && *pphead && pos);
    if (pos == *pphead)//头删
    {
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
    }
    else
    {
        SLTNode* cur = *pphead;
	while (cur->next != pos)//找到pos的前一个节点指针
	{
            cur = cur->next;
	}
	cur->next = pos->next;
        free(pos);
        pos = NULL;
    }
}