C语言数据结构与算法-C语言篇6-线性表的链式存储结构

数据结构与算法-目录

1、线性表的链式存储结构

1.1、线性表链式存储结构定义

丝性表的链式存储结构的表征是因此同组随机的存储单元存储线性表的数元素,这组存储单元可以是接连的,也得以是匪总是的。这即表示,这些因素得以在内存未吃霸占的随意位置。

以前在挨家挨户结构中,每个元素数据就待仓储数据元素信息就是足以了。现在以链式结构被,除了要抱多少元素信息外,还要存储它们的后继元素的仓储地点。

所以,为了表示每个数据元素ai与该一直后级元素ai+1之间的逻辑关系,对数码元素ai来说,除了存储其自身的音讯以外,还待贮存一个指令其直接后继的信息(即直接后继的蕴藏位置)。我们把囤积数据元素信息之域称为数据域,把仓储直接后继位置的域称为指针域。指针域中蕴藏的音讯称指针或链。这点儿局部信息做数据元素ai的仓储映像,称为结点(Node)。

n个结点(ai的贮存映像)链结成一个链表,即为线性表(a1,a2,….,an)的链式存储结构,因为是链表的每个结点中特含一个负针域,所以称为单链表。单链表正是经过每个结点的指针域将线性表的多寡元素以其论理次序链接以同。

于线性表来说,总得有身材有个尾,链表也不殊。我们将链表中率先只结点的仓储位置为做头指针,那么整个链表的存取就必是开始指针开始开展了。之后的各国一个结点,其实就是高达一个底继指针指向的位置。

最终一个,当然意味着一直后就不设有了,所以我们规定,线性链表的末段一个结点指针也“空”(通常用NULL或“^”符号表示)。

偶尔,我们为了更加便民地针对链表进行操作,会于单链表的第一只结点前附设一个结点,称为头结点。头结点的数据域可以不存储任何消息,也得以储存如丝性表的长度等附加信,头结点的指针域存储指向第一只结点的指针。

1.2、头指针与头结点的异同

头指针与头结点的异同点。

头指针 :
  • 头指针是依赖链表指向第一只结点的指针,若链表有头结点,则是凭借于头结点的指针

  • 头指针具有标识作用,所以时用头指针冠以链表的讳

  • 随便链表是否也空,头指针均无为空。头指针是链表的必不可少因素。

头结点
  • 头结点是为了操作的联和便利而立之,放在第一因素的结点之间,其数据域一般无意义。

  • 发了头结点,对以首先素结点前插入结点,其操作和另结点的操作就联合了。

  • 头结点不肯定是链表必须要素。

1.3、线性链表式存储结构代码描述

若线性链表为空表,则头结点的指针域为“空”。

单链表中,我们当C语言中而为此结构指针来描述。

//线性表的单链表存储结构
typedef struct Node
{
    ElemType data;
    struct Node *next;
}Node;
typedef struct Node *LinkList;//定义LinkList

于这结构定义着,我们啊不怕懂得,结点由存放数据元素的数据域和存放后继结点地址之指针域组成。
假设p是依靠为线性表第i单因素的指针,则该结点ai的数据域我们好为此p->data来代表,p->data的值是一个数额元素,结点ai的指针可以据此p->next来表示,p->next的价值是一个指南针。p->next指为谁也?当然是凭于第i

  • 1个要素,即指向ai+1。也就是说p->data =
    ai,那么p->next->data=ai+1

2、单链表的读取

当线性表的顺序存储结构中,我们若算任意一个元素的存储位置而好轻之。但在单链表中,由于第i单要素到底在啊?没道一开始即亮,必须从头开始找。因此,对于只有链表实现获取第i个要素的操作GetElem,在算法上,相对辛苦一些。

抱链表第i只数据的算法思路:
  1. 扬言一个指针p指向链表第一单结点,初始化j从1开始。
  2. 当j < i
    时,就全历链表,让p的指针向后走,不断对下一结点,j累加1;
  3. 若链表末尾p为空,则印证第i独结点不在;
  4. 要不查找成功,返回结点p的数量。
    落实代码如下:

//初始条件:顺序线性表L已存在,1 ≤ i ≤ ListLength(L)
//操作结果:用e返回L中第i个数据元素的值
Status GetElem(LinkList L,int i,ElemType *e)
{
    int j;
    LinkList p;//声明一指针
    p = L->next;//让p指向链表L的第一个结点
    j = 1;//j为计数器
    while(p && j < i)//p不为空且计数器j还没有等于i时,循环继续
    {
        p = p->next;//让p指向下也结点
        ++j;
    }
    if(p || j > i)
        return ERROR;//第i个结点不存在
    *e = p->data;//取第i个结点的数据
    return OK;
}

说白了,就是开开始物色,直到第i单结点为止。
出于斯算法复杂度取决于i的岗位,当i = 1常,不待变量,而当i =
n时虽遍历n – 1次才得以。因此最好老情况的时复杂度是O(n)。
是因为只链表的结构没有概念表长,所以不了解事先循环多少坏,因此为就是未方便使用for来控制循环。

该要核心思想是“工作指针后更换”,这实质上呢是无数算法常用技术。

3、单链表的插与删除

3.1、单链表的插

比方存储元素e的结点为s,要贯彻结点p、p->next和s之间的逻辑关系的转移,只待将s插到了点p和p->next之间即可。
从无需惊动其他结点,只需要为s->next和p->next的指针做一些改观。

单链表第i单数据插入结点的算法思路:
  1. 宣称一指针p指向链表头结点,初始化j从1开端;
  2. 当j < i时,就不折不扣历链表,让p的指针向后走,不断对下一结点,j累加1
  3. 假若到链表末尾p为空,则证实第i只结点不设有;
  4. 倘若查找成功,在系统中生成一个空节点s;
  5. 以数据元素e赋给s->data;
  6. 单链表的插标准语句s->next = p->next; p->next = s;
  7. 返回成功
    实现代码如下:

//初始条件:顺序线性表L已存在,1≤i≤ListLength(L)
//操作结果:在L中第i个结点位置之前插入新的数据元素,L的长度加1
Status ListInsert(LinkList *L , int i , ElemType e)
{
    int j = 1;
    LinkList p,s;
    p = *L;
    while( p && j < i) //寻找第i个结点
    {
        p = p->next;
        ++j;
    }
    if( !p || j > i)
    {
        return ERROR;//第i个结点不存在
    }
    s = (LinkList)malloc(sizeof(Node));//生成新结点
    s->data = e;
    s->next = p->next;//将p的后继结点赋值给s的后继
    p->next = s;//将s赋给p的后继
    return OK;
}

以及时段算法代码中,我们之所以到了C语言的malloc标准函数,它的意就是是生成一个新的结点,其品种和Node是同等的,其精神就是是在内存中开发了一致段空间,用了存放数据e的s结点。

留神:下面两句不可交换顺序(否则死循环)
s->next = p->next;
p->next = s;
3.2、单链表的去

设存储元素ai的结点为q,要兑现以结点q删除单链表的操作,其实就是是用她的前继结点的指针绕了,指为外的继结点即可。

我们所假设举行的,实际上就是同等步:

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

用q来取代p->next即是:

q = p->next;
p->next = q->next;

也就是说把p的晚结点改成p的继的继结点。

单链表第i独数据删除结点的算法思路:
  1. 宣示一指针p指向链表头指针,初始化j从1开始;
  2. 当j <
    i时,就全历链表,让p的指针向后走,不断对下一个结点,i累加1;
  3. 而到链表末尾p为空,则证明第i单结点不有;
  4. 不然查找成功,将急需删除的结点p->next 赋给q;
  5. 单链表的勾标准以及p->next = q->next;
  6. 用q结点中之多少赋给e,作为返回;
  7. 释放q结点
  8. 回成功

贯彻代码如下:

/初始条件:顺序线性表L已存在,1≤ i ≤ListLength(L)
//操作结果:删除L的i个结点,并用e返回其值,L的长度减1
Status ListDelete(LinkList *L,int i,ElemType *e)
{
    int j=1;
    Link p,q;
    p = *L;
    while(p->next && j < i)//遍历寻找第i - 1个结点
    {
        p = p->next;
        ++j;
    }
    if( !(p->next) || j > i)//列表末端或找不到
        return ERROR;
    q = p->next;
    p->next = q->next;//将q的后继赋给p的后继
    *e = q->data;//将q结点中的数据给e
    free(q);//让系统回收此结点,释放内存
    return OK;
}

浅析一下顷咱们上课的单链表插入和去算法,我们发现,它们其实还是出于简单片段组成:
首先部分即使是遍历查找第i只结点;
第二有纵是插和去结点。

自所有算法来说,我们好轻推导出:它们的年月复杂度都是O(n)。

如若我们不了解第i独结点的指针位置,单链表数据结构在插入和去操作及,与线下顺序存储结构是不曾太可怜优势的。但如果,我们愿意从第i只位置,插入10独结点,对于顺序结构意味着,每次都如移动n

i个结点,每次都是O(n)。而单链表,我们无非需要于率先糟糕时,找到第i个职位的指针,此时呢O(n),接下只是略地经赋值移动指针而已,事件复杂度为O(1)。

分明,对于插入和去数据更频繁之操作,单链表的优势就是一发显。

4、单链表的整表创建

顺序存储结构的创造,其实就是一个数组的初始化,即宣称一个色及尺寸的数组并赋值的长河。而单链表和顺序存储结构即无平等,它不像顺序存储结构这么几种植,它好生散,是同样栽动态结构。对于每个链表来说,它所占有空间的高低与位置设非欲先分配划定的,可以依据网的情形和实在的求即可生成。

4.1、头插法建立单链表

从而创建单链表的进程即是一个动态生成链表的经过。即由“空表”的开头状态起,一坏建立各国因素结点,并逐一个插入链表。
单链表整表创建的思绪算法:

  1. 声称一指针p和计数器变量1;
  2. 初始化一空链表;
  3. 让L的头结点的指针指向NULL,即成立一个为首结点的单链表;
  4. 循环:
    (1).生成一初结点赋值给p;
    (2).随机生成一数字赋给p的数据域p->data;
    (3).将p插到头结点与前面一个初节点内的职位。

兑现代码如下:

//随机产生n个元素的值,建立带表头结点的单链表线性表L(头插法)
void CreateListHead(LinkList *L,int n)
{
    LinkList p;
    int i;
    srand(time(0));//初始化随机数种子
    *L = (LinkList)malloc(sizeof(Node));
    (*L)->next = NULL;//先建立一个带头结点的单链表
    for(i = 0;i < n;i++)
    {
        p = (LinkList)malloc(sizoef(Node));//生成新的结点
        p->data = rand() % 100 + 1;//随机生成100以内的数字
        p->next = (*L)->next;
        (*L)->next = p; //插入到表头
    }
}
4.2、尾插法建立单链表

可事实上,根据排队时的常规思维,我们尚好管新结点放在最后。我们每次新结点都插在顶峰结点的后,这种算法称之为尾插法。
落实代码如下:

//随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)
void CreateListTail(LinkList *L,int n)
{
    LinkList p,r;
    int i;
    srand(time(0));//初始化随机数种子
    *L = (LinkList)malloc(sizeof(Node));//为整个线性表
    r = *L;//r为指向尾部的结点
    for(i = 0;i < n;i++)
    {
        p = (Node *)malloc(sizeof(Node));//生成新结点
        p->data = rand() % 100 + 1;//随机生成100以内的数字
        r->next = p;//将表尾终端结点的指针指向新结点
        r = p; //就那个当前新结点定义为表尾终端结点
    }
    r->next = NULL;//表示当前链表结束
}
注意:

L与r的涉及,L指整个单链表,而r指向尾节点之变量,r会随着不断地变结点,而L则是就循环增长也一个几近结点的链表。
r->next = p的意思,其实就算是将刚之表尾终端结点r的指针指于新了点p。

5、单链表的整表删除

当我们无打算采取是单链表时,我们需要把她销毁,其实为即是当内存中将其放掉,以便让留有空间为其它程序要软件用。

单链表整表删除的算法思路如下:

  1. 扬言一结点p暨q;
  2. 拿率先独结点赋值给p;
  3. 循环 :
    (1).将下一结点赋值给q;
    (2).释放p;
    (3).将q赋值给p。
    贯彻代码如下:

//初始条件:顺序线性表L已经存在,操作结果:将L重置为空表
Status ClearList(LinkList *L)
{
    LinkList p,q;
    p = (*L)->next;//p指向第一个结点
    while(p)//没到表尾
    {
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;//头结点指针域为空
    return OK;
}

6、单链表结构以及顺序存储结构优缺点

简地对准单链表结构与顺序存储结构作对比。

6.1、存储分配办法:

顺序存储结构产生同段落连接的存储单元依然存储线性表的数量元素。
单链表采用链式存储结构,用相同组随机的存储单元存放线性表的东西。

6.2、时间性能:

查找:

  • 顺序存储结构O(1)
  • 单链表O(n)

插入与删除

  • 顺序存储结构亟待平均移动表长一半之元素,时间为O(n)
  • 单链表在线出有位置的指针后,插入和去时只是为O(1)
6.3、空间性
  • 顺序存储结构需要预分配存储空间,分大了,浪费,分多少了好有上泛滥。
  • 单链表不需要分配存储空间,只要有就是可分配,元素个数也未为限制。

经者的对立统一,我们好得出有些经验性的定论:

  • 若线性表需频繁查找,很少上插入和去操作时,宜利用顺序存储结构。
    一旦需数插入和去时,宜利用单链表结构。
    按部就班玩支付被,对于用户注册之个人信息,除了注册时插入数据外,绝大多数情形下还是读取,所以该考虑就此顺序存储结构。而游戏中之玩家的兵或装备列表,随着玩家游戏经过中,可能天天增加还是去,此时应有据此单链表比较适度。当然,这只有是简约地类比。现实生活中之软件开发,要考虑的问题会见复杂得多。

  • 当线性表中的元素个数变化较生还是向未知底出多格外时,最好用单链表结构,这样好不要考虑存储空间尺寸问题。
    假设而先知情线性表的光景长度,比如同年12只月,这种用顺序存储结构效率会大多。

一言以蔽之,线性表的顺序存储结构和单链表结构各有其长,不是简简单单地游说谁不好,需要基于实际情况,来综合平衡下哪种多少还能满足与上要求跟总体性。

特别感谢:
鱼C工作室小甲鱼