数据结构 线性表-单链表的定义及基本操作

一.引入

对于线性表来说,有顺序存储结构,然而顺序结构是有缺点的:插入和删除时需要移动大量元素;因此我们需要解决这一系列问题,线性表的链式存储结构刚好解决了这些问题。

二.线性表链式存储结构的定义

2.1 顺序结构和链式结构的区别

在顺序结构中,每个数据元素只需要存数据元素信息就可以了,在链式结构中,除了要存储元素信息外,还要存储后继元素的存储地址,因此,我们引入了结点。

2.2 结点定义

为了表示每个数据元素 a 与其直接后继数据元素 air1 之间的逻辑关系,对数据元素 a来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。把存储数据元素信息为数据域存储直接后继位置的域称为指针域,指针域中存储的信息称做指针或链。这两部分信息组成数据元素a的存储映像,称为结点 。多个结点链接成我们的链表。
在这里插入图片描述
结点的描述:

typedef struct LNode{ //定义单链表结点类型
	int data; //数据域,可以是别的各种数据类型,本文统一用int类型
	struct LNode *next; //指针域
}LNode, *LinkList;

2.3 头指针与头结点

2.3.1头指针与头结点的定义

通常我们把链表中第一个结点的存储位置叫做头指针,整个链表的存取就从头指针开始进行,一般来说最后一个,意味着直接后继不存在了,所以我们规定,线性链表的最后一个结点为空。在这里插入图片描述
为了更方便对链表的操作,我们会在单链表的第一个结点前设置一个结点,称为头结点。
在这里插入图片描述
一般来说头结点的数据域可以不存储任何信息,头结点的指针域存储指向第一个结点的指针。

2.3.2头结点与头指针的异同

头指针头结点
头指针是指向第一结点的指针,链表有头结点是指向头结点的指针 。头结点是为了便于操作设立的,在第一元素的结点前,数据域一般没有意义。
头指针有标识作用,常用头指针冠以链表名字。有了头结点,对于第一元素结点前的插入删除操作统一了。
无论链表是否为空头指针不为空。头指针是链表的必要元素头结点不一定是链表必要元素。

三.单链表的基本操作

3.1单链表的初始化

单链表的初始化就是申请一个头结点,将指针域置空。

LinkList LinkListInit()
{
    LNode *L;
    L = (LNode *)malloc(sizeof(LNode));   //申请结点空间 
    if(L == NULL)                   //判断是否有足够的内存空间 
        printf("申请内存空间失败\n");
    L->next = NULL;       
}

3.2 单链表的建立

对于单链表的建立我们有两种创建方法,分别为:头插法和尾插法。

3.2.1头插法

头插法简单理解就是始终让新结点在第一的位置,示意图如下:
在这里插入图片描述
头插法的算法思路:

  1. 声明一个指针p;
  2. 初始化一个空链表L;
  3. 建立一个带头结点的单链表;
  4. 生成一个新结点赋值给p,将p的数据域赋值,将p插入到头结点与前一新结点之间。

代码如下:

void CreatListHead(LinkList *L,int n)
{
	LinkList p;
	int i;
	srand(time(0));
	*L=(LinkList)malloc(sizeof(LNode));
	(*L)->next=NULL;
	for(i=0;i<n;i++)
	{
		p=(LinkList)malloc(sizeof(LNode));
		p->data=rand()%100+1;
		p->next=(*L)->next;
		(*L)->next=p;
	}
}

头插法最重要的是这两行代码:

p->next=(*L)->next;//将头指针所指向的下一个结点地址赋给新结点next 
(*L)->next=p;      //将新创建的结点的地址赋给头指针的下一个结点

3.2.2 尾插法

尾插法顾名思义就是把每次新结点插在终端结点的后面,示意图如下:
在这里插入图片描述
请添加图片描述
尾插法的算法思路:

  1. 首先初始化一个单链表;
  2. 声明一个尾指针r,让r始终指向当前链表的尾结点;
  3. 循环向单链表的尾部插入新的结点*s,将尾指针r的next域指向新结点;
  4. 修改尾指针r指向新结点,也就是当前链表的尾结点;
  5. 最后别忘记将尾结点的指针域置空。

代码如下:

void CreatListtTail(LinkList *L,int n)
{
	LinkList p,r;
	int i;
	srand(time(0));
	*L=(LinkList)malloc(sizeof(LNode));
	r=*L;
	for(i=0;i<n;i++)
	{
		p=(Node *)malloc(sizeof(LNode));
		P->data = rand()%100+1;
		r->next=p;  //表尾终结结点的指针指向新结点
		r=p;        //将当前的新结点定义为表尾终端结点
	}
	r->next = NULL;
}

尾插法重要代码:

	r->next=p;//表尾终结结点的指针指向新结点
	r=p;      //将当前的新结点定义为表尾终端结点

我们需要注意L与r的关系,r会随着循环不断变化结点,而L则是随着循环增长为一个多结点的链表。

3.2.3两种方法总结

  1. 头插法相对简便,但插入的数据与插入的顺序相反;
  2. 尾插法操作相对复杂,但插入的数据与插入顺序相同。

根据自己需求来使用不同的方法。

3.3单链表的插入操作

插入操作只需要将p的后继结点改成s的后继结点,再把s变成p的后继结点。
在这里插入图片描述
算法思想:
从表头开始遍历,查找第 i-1个结点,即插入位置的前驱结点为p,然后令新结点s的指针域指向p的后继结点,再令结点p的指针域指向新结点*s。

代码如下:

void Insert(LinkList &L, int i, int x){
    LNode *p = GetElem(L,i-1);
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = x;
    s->next = p->next;
    p->next = s;
}

核心为:

s->next=p->next;
p->next=s;

注意: 这两句的顺序不可以进行交换。
假如交换后就会导致拥有a(i+1)数据元素的结点没了上级,这样的插入操作是无效的。

3.4单链表的删除操作

删除操作只需把p的后继结点改成p的后继的后继结点。
在这里插入图片描述
算法思想:

  1. 声明一指针p指向链表头指针,初始j从1开始;
  2. j<1时,遍历链表让指针p向后移,不断指向下一个结点,j累加1;
  3. 若末尾p为空,则说明第i个接点不存在;
  4. 否则成功,将p->next赋值给q;
  5. 将q结点中的数据赋值给e,作为返回;
  6. 释放q 。

代码如下:

 void Delete(LinkList *L, int i,int *e)
 {
 int j;
 linklist p,q;
 p=*l;
 j=1;
 while(p->next&&j<1)
{
	p=p->next;
	++j;
}
 if(i<1 || i>Length(L))
    return error;
 q=p->next;
 p->next=q->next;
 *e=q->data;
 free(q);
}

核心如下:

 p->next=q->next;

3.5单链表的查找

3.5.1按位查找

查找单链表中中第 i 个位置的元素。

算法思想:

  1. 声明一个指针p指向链表的第一个结点,初始化j;
  2. j<1,遍历链表,让p的指针向后移动,不断指向下一个结点,j累加;
  3. 若末尾p为空,则说明第i个接点不存在;
  4. 否则查找成功,返回p的数据。

代码如下:

LNode *GetElem(LinkList L, int i){
    int j=1;
    LNode *p = L->next;
    if(i==0)return L;
    if(i<1)return NULL;
    while(p && j<i){
        p = p->next;
        j++;
    }
    return p; //如果i大于表长,p=NULL,直接返回p即可
}

说白了其实就是从前向后挨个寻找,直到第i个结点为止。

3.5.2按值查找

查找值x在单链表L中的结点指针。

算法思想:
从单链表的第一个结点开始,依次比较表中各个结点的数据域的值,若某结点数据域的值等于x,则返回该结点的指针;若整个单链表中没有这样的结点,则返回空。

代码如下:

LNode *LocateElem(LinkList L, int x){
    LNode *p = L->next;
    while(p && p->data != x){
        p = p->next;
    }
    return p;
}

3.6单链表的销毁

这里千万要注意的是,动态分配的内存是需要我们手动去回收的,所以要养成一个好的习惯,在程序的必要位置回收那些动态分配的内存。

算法思想:

  1. 声明一结点p和q;
  2. 将第一个结点赋值给p;
  3. 将下一结点赋值给q,释放p,将q赋值给p。

代码如下:

Status ClearList(LinkList *L)
{
	LinkList p,q;
	p=(*L)->next;
	while(p)
	{
		q=p->next;
		free(p);
		p=q;
	}
	(*L)->next=NULL;
	return OK;
}

四.总结

对于单链表来说,我们需要将一系列操作的核心思想理解并牢记,这样会对后续数据结构的学习有所帮助。

(小白一位,如有错误欢迎指正)

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mi ronin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值