简单的单链表构建,你学会了吗?

前言

上一篇文章中我们学习了顺序表
顺序表是用数组实现的,
优势是:物理空间连续并且支持下标随机访问。
劣势是:1.空间不够,需要扩容。但是扩容有一定的性能消耗,其次一般扩容2倍,存在一些空间浪费。
2.头部或者中间插入删除效率低,需要挪动数据

针对以上问题,链表正好可以解决这些劣势。
链表优点:1.任意位置插入删除O(1) 2.按需申请和释放空间
链表的缺点:不能够支持随机访问



一、单链表的概念和结构

概念 链表是一种物理存储结构上 非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
结构 单链表的结构类似于火车一样链起来的。
在这里插入描述
看代码

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

首先我们定义了一个结构体,结构体里的data被叫做数据域用来存放数据,next是指针域用来存放下一个节点的地址。
链接关系的代码表示:

SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n1);
	SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n2);
	SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n3);
	SLTNode* n4 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n4);
	
	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n4->data = 4;
 
	n1->next = n2;
	n2->next = n3;
	n3->next = n4;

我在这里开辟了四个节点,图示为下面的样子:
在述n1的data用来存放数据,next用来存放n2的地址,n2、n3、n4以此类推。

二. 单链表的接口实现

我们要实现如下的一些接口:
注意:我们这里的单链表是不带头的,一般对于Leetcode上考察比较多的也是单链表,常用于哈希桶中的。

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SList** plist);

2.1 打印

*注意:头指针的位置不要轻易的去改动,所以在这定义了一个Cur来代替我们的头指针。

代码示例如下

// 单链表打印
void SListPrint(SListNode* plist)
{
   SListNode* cur=plist;
    while(cur)
    {
        printf("%d->",cur->data);
        cur=cur->next;
    }
    printf("NULL\n"); }

2.2 开辟节点

**当我们插入的时候需要开辟新的节点,而我们有头插、尾插和任意位置插,每次写这些插入的时候都要<br /> 
申请节点,所以我们不妨写一个函数就直接申请节点,后面只需要调用就好了**

代码实现:

SListNode* BuySListNode(SLTDateType x)
{
    SListNode* newnode=(SListNode*) malloc(sizeof(SListNode));
    assert(newnode);
    newnode->data=x;
    newnode->next=NULL;
    return newnode;
}

2.3 尾部插入节点

首先我们要思考一下传参是传链表地址呢?还是传链表就行了?
因为要改变链表的指针,所以这个时候一定要传地址,而且因为是要修改链表的指针,所以传的是二级指针。

要尾插的话,我们首先得要开辟一个结点才能插啊,其次是要找到尾结点,那么如何找到尾结点呢?很简单,一个while循环就搞定了。
1、空节点的话,直接就插入,不用找尾了。2、非空的话就要找尾之后再删除

代码示例如下:

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
  assert(pplist);
  SListNode* newnode=BuySListNode(x);
  if(*pplist==NULL)
  {
    *pplist=newnode;
  }
  else
  {
    SListNode* tail= * pplist;
    while(tail->next)
    {
        tail=tail->next;
    }
    tail->next=newnode;
  }
}

2.4 尾部删除节点

尾删分两种情况:1、只有一个结点就不用找尾了。2、有多个结点就要找尾之后再删除
示例代码如下:

void SListPopBack(SListNode** pplist)
{
  assert(pplist);
  assert(*pplist);//空节点
  if((*pplist)->next==NULL)
  {
      free(*pplist);
      *pplist=NULL;
  }
  else
  {
      SListNode* tail=*pplist;
     while(tail->next)
     {
           SListNode* prev=tail;
          tail=tail->next;
     }
     free(tail);
     prev->next=NULL;
  }
}

2.5 头部插入节点

这里我们也是需要用到二级指针的。头插相比于尾插来说是更简单的。
示例代码如下:

void SListPushFront(SListNode** pplist, SLTDateType x)
{
   assert(pplist);
  SListNode* newnode= BuySListNode(x);
  newnode->next=*pplist;
  *pplist=newnode;
}

2.6 头部删除节点

这里我们也是需要用到二级指针的
示例代码如下:

void SListPopFront(SListNode** pplist)
{
   assert(pplist);
   assert(*pplist);//空节点的情况
  SListNode* next=(* pplist)->next;
  free(*pplist);
  *pplist=next;
}

2.7 查找

这里可以不用二级指针,因为查找和打印一样它是不会改变你传过来的值的。
这个地方是可以和 修改一起共用的。

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
   SListNode* cur=plist;
   while(cur)
   {
     if(cur->data==x)
     {
         return cur;
     }
       cur=cur->next;
   }
return NULL;
}

2.8 在Pos位置后插入

在pos位置之后插入,不需要直接找尾,新建一个节点,直接和POS 位置链接就可以了
这里就不用考虑pos是头的情况了,反正是在pos位置之后插入。

// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
   assert(pos);
    SListNode* newnode= BuySListNode(x);
    newnode->next=pos->next;
    pos->next=newnode;
}

2.9 在Pos位置后删除

如果pos位置是尾的话,直接返回就行了,因为pos位置之后没有什么可以删的了

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

2.10 销毁链表

注意:这里因为是要修改链表,所以要用到二级指针,,

// 单链表的销毁
void SListDestroy(SList** plist)
{
   SListNode* cur=*plist;
   while(cur)
   {
      SListNode* next=cur->next;
      free(cur);
     cur=next;
   }
}

总结

SList.h完整代码:

#pragma once//防止头文件重复包含
 
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType* data;
	struct SListNode* next;
}SLTNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SList** plist);

SList.c
完整代码:


#define _CRT_SECURE_NO_WARNINGS 1
 
#include "SList.h"
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)

{

    SListNode* newnode=(SListNode*) malloc(sizeof(SListNode));
    assert(newnode);

    newnode->data=x;

    newnode->next=NULL;

    return newnode;

}

// 单链表打印

void SListPrint(SListNode* plist)
{



   SListNode* cur=plist;

    while(cur)

    {

        

        printf("%d\n",cur->data);

        cur=cur->next;

    }

    printf("NULL\n");

}



// 单链表尾插

void SListPushBack(SListNode** pplist, SLTDateType x)

{

  assert(pplist);
  SListNode* newnode=BuySListNode(x);

  if(*pplist==NULL)

  {

    *pplist=newnode;

  }

  else

  {

    SListNode* tail= * pplist;

    while(tail->next)

    {

        tail=tail->next;

    }

    tail->next=newnode;

  }

}



// 单链表的头插

void SListPushFront(SListNode** pplist, SLTDateType x)

{
   assert(pplist);
  SListNode* newnode= BuySListNode(x);
  newnode->next=*pplist;
  *pplist=newnode;
}

// 单链表的尾删

void SListPopBack(SListNode** pplist)

{

  assert(pplist);

  assert(*pplist);

  if((*pplist)->next==NULL)

  {

      free(*pplist);

      *pplist=NULL;

  }

  else

  {
      SListNode* tail=*pplist;
     while(tail->next)
     {
           SListNode* prev=tail;
          tail=tail->next;
     }
     free(tail);
     prev->next=NULL;
  }
}

// 单链表头删

void SListPopFront(SListNode** pplist)

{
   assert(pplist);
   assert(*pplist);
  SListNode* next=(* pplist)->next;
  free(*pplist);
  *pplist=next;
}



// 单链表查找

SListNode* SListFind(SListNode* plist, SLTDateType x)

{
   SListNode* cur=plist;
   while(cur)
   {
     if(cur->data==x)
     {
         return cur;
     }
       cur=cur->next;
   }
return NULL;
}





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

// 分析思考为什么不在pos位置之前插入?

void SListInsertAfter(SListNode* pos, SLTDateType x)

{
   assert(pos);
    SListNode* newnode= BuySListNode(x);
    newnode->next=pos->next;
    pos->next=newnode;
}



// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
assert(pos);
assert(pos->next);
SListNode* next=pos->next->next;
free(pos->next);
pos->next=next;
}
// 单链表的销毁

void SListDestroy(SList** plist)

{
   SListNode* cur=*plist;
   while(cur)
   {
      SListNode* next=cur->next;
      free(cur);
     cur=next;
   }
}

其实单链表还不是最好的,还有一些缺陷,那就需要用到双链表,想知道知道如何写,请期待博主的下一篇新作。

码字不易,如果觉得内容有用的话,就给博主三连吧!!!

在这里插入图片描述

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值