数据结构——你好,线性表(2)

简介: 数据结构——你好,线性表(2)

线性表的链式存储


单链表


顺序表结构简单,在访问表中元素的时候也特别方便,只要O(1)的时间复杂度,但是当数据规模十分庞大的时候,使用顺序表完成插入和删除操作就需要移动大量元素了。


此外,由于顺序表的空间是需要提前规定好了,再在内存中开辟相应的空间大小出来,因此容易出现空间估计过大造成内存浪费和空间估计过小导致溢出。因此引入了链式存储结构,它不需要用地址连续的存储单元实现,通过指针构建"链"建立元素之间的逻辑

单链表定义:

微信图片_20221017221739.jpg

线性表的链式存储结构是指用一组任意的存储单元(可以连续,也可以不连续)存储线性表中的数据元素。为了反映数据元素之间的逻辑关系,对于每个数据元素不仅要表示它的具体内容,还要附加一个表示它的直接后继元素存储位置信息,这样构成的链表称为线性单向链接表,简称单链表。

微信图片_20221017221753.jpg

1.单链表一般是分为带头结点和不带头结点的,它们本质是一样的,带和不带都是正确的,只是因为带了头结点可以使对单链表的操作更统一,所以大多数人习惯带上头结点。


2.头指针和头结点。头指针是链表第一个结点的地址,头指针是必须的。通过头指针中的地址获得了第一个结点以后,才能对其他数据元素实现操作。头结点是人们为了统一操作而加上的,不是必须的。举个栗子,倘若不带头结点,删除第一个结点的算法是需要额外构建的。


单链表的基本操作实现


单链表类型定义

typedef int DataType;     //定义DataType 为int 类型
typedef struct linknode{  //单链表存储类型 
  DataType data;      //数据域 
  struct linknode *next;  //指针域 
}LinkList; 

上面代码中:

LinkList 是一个标识符,说直白一点了,它就是一个名字,它的类型是linknode类型。

如果想定义指向该类型的指针head:

LinkList *head;

如果想动态申请一个结点空间,并让指针head指向该结点空间:

head = (LinkList*)malloc(sizeof(struct linknode));//malloc函数:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL。

此时这个结点的数据域就是head ->data(也可以表达为(*head).data);这个结点的指针域就是head->next(也可以表达为(*head).next)

释放动态申请的空间

free(head);

单链表的初始化


首先申请一个结点并让指针head指向该结点。

然后让指针head指向该结点,并将它的指针域赋值为空(NULL)

最后返回头指针head.

LinkList *InitList()
{
    LinkList *head;
    head=(LinkList*)malloc(sizeof(LinkList)); //动态分配一个结点空间 
    head->next=NULL;              
    return head;                //此时头结点的指针域为空,表示空链表 
}

单链表的建立


头插法建表

建立线性表从空表开始。每次读入有效的数据就申请一个结点s,

将读取的数据放到新结点s的数据域中,

然后将新结点接在当前链表head的表头后面

微信图片_20221017222048.jpg

参考实现代码:

void CreateListH(LinkList *head,int n)
{
    LinkList *s;
    int i;
    printf("请输入%d个整数:",n);
    for(i=0;i<n;i++)
    {
        s=(LinkList *)malloc(sizeof(LinkList));
        scanf("%d",&s->data);
        s->next=head->next;
        head->next=s;
    }
    printf("建立链表操作成功");
}

尾插法建表

因为头插法是将数据挨着挨着插入到开头,所以最后得到的序列是和输入次序相反的。尾插法则可实现次序一致。


实现流程:

每读入一个有效数据就申请一个结点s,并将读取的数据存放到新结点s的指针域,

将s的尾指针设置为空(NULL)

将新结点插入到当前链表的尾部(尾指针last所指的结点后面)

image.jpeg

参考实现代码:

void CreateListL(LinkList *head,int n)
{
    LinkList *s,*last;
    int i;
    last=head;
    printf("请输入%d个整数:",n);
    for(i=0;i<n;i++)
    {
        s=(LinkList *)malloc(sizeof(LinkList));
        scanf("%d",&s->data);
        s->next=NULL;
        last->next=s;
        last=s;
    }
    printf("建立链表操作成功!");
}

获得单链表的表长


从头指针的位置开始遍历,同时放置一个计数器用来记录遍历了多少个结点。最后计数器的数值就是链表的长度

参考实现代码:

int LengthList(LinkList *head)
{
    LinkList *p=head->next;
    int cnt=0;//计数器 
    while(p!=NULL)
    {
        p=p->next;
        cnt++;
    }
    return cnt;
}

查找操作


思路和顺序表的查找是一致的,换汤不换药,就不在赘述流程啦~

依旧有按值查找和按位置查找

按值查找参考代码:

//按值查找 
void Locate(LinkList *head,DataType x)
{
    int j=1;
    LinkList *p;
    p=head->next;
    while(p!=NULL&&p->data!=x)
    {
        p=p->next;
        j++;
    }
    if(p!=NULL)
        printf("在表中的第%d位找到值为%d的结点",j,x);
    else
        printf("未找到值为%d的结点",x);
}

按序号/位置查找参考代码

void SearchList(LinkList *head,int i)
{
    LinkList *p;
    int j=0;
    p=head;
    if(i>LengthList(head))
        printf("位置错误,链表中没有该位置");
    while(p->next!=NULL&&j<i)
    {
        p=p->next;
        j++;
    }
    if(j==i)
        printf("在第%d位上的元素为%d",i,p->data);
}

插入操作


倘若要在指针p所指的结点后面插入一个结点,

实现思想

①先将结点s的指针域指向结点p的指针域所指向的位置(s->next = p->next)

②再将结点p的指针域指向新结点s(p->next = s)

链表按位置插入微信图片_20221017222331.jpg

注意:语句①和语句②不能颠倒。

微信图片_20221017222340.jpg


插入操作参考代码

void InsList(LinkList *head,int i,DataType x)
{
  //按位置插入 
    int j=0;
    LinkList *p,*s;
    p=head;
    while(p->next!=NULL&&j<i-1) //定位插入的位置 
    {
        p=p->next;
        j++;
    }
    if(p!=NULL)         //p不为空则将新结点插到p后面 
    {
        s=(LinkList *)malloc(sizeof(LinkList));
        s->data=x;
        s->next=p->next;
        p->next=s;
        printf("插入元素成功");
    }
    else
        printf("插入元素失败");
}

删除操作


实现思想

①通过循环定位出第i个结点的直接前驱(即第i-1个结点)p的地址

②然后将指针s指向要被删除的结点

③修改p->next指针,使其指向s后的结点,释放指针s所指向的结点


注意if(p->next!=NULL&&j==i-1)。只有当第i-1个结点存在(j == i-1) 并且 p不是终点结点(p->next != NULL)时 ,才能确实被删除的结点存在。

微信图片_20221017222456.jpg

参考实现代码

void DelList(LinkList *head,int i)
{
    int j=0;
    DataType x;
    LinkList *p=head,*s;
    while(p->next!=NULL&&j<i-1)
    {
        p=p->next;
        j++;
    }
    if(p->next!=NULL&&j==i-1)
    {
        s=p->next;          //s指向要被删除的结点 
        x=s->data;        //将要删除的数据放入指针变量x中 
        p->next=s->next;    //将p指针所指向的结点的指针域与s指针所指向的结点的后一个结点建立联系 
        free(s);        
        printf("删除第%d位上的元素%d成功",i,x);
    }
    else
        printf("删除结点位置错误,删除失败");
}

输出操作


获取头指针中存放的第一个结点的地址,开始逐一遍历输出各个结点中的数据就可以了


参考实现代码:

void DisList(LinkList *head)
{
    LinkList *p;
    p=head->next;
    while(p!=NULL)
    {
        printf("%5d",p->data);
        p=p->next;
    }
}

对于循环链表和双向链表,它俩除了会在考场上出现,其他地方几乎没有它的勇武之地。把玩会了单链表,再回眸它们,应该会是满脑子——就这。


总结——顺序表和链表的比较


顺序表本质是数组,所以实现起来容易,逻辑也简单,查询直接把索引扔到数组里就像,插入和删除也只用移动数组。但是要提前估计使用的数据的规模,因为数组开辟出来以后就不能再改变了(可以扩容,但是很麻烦),而且当数据过于庞大的时候,插入和删除操作效率很低


因为链表实现逻辑主要依靠指针,就显得就复杂了很多,它查询的效率没有顺序表高,但是执行插入和删除时候比顺序表香。


可以看出来,顺序表的优点就是链表的缺点,而其缺点就是链表的优点。在算法竞赛中,就几乎使用她俩的结合版:静态链表


相关文章
|
存储 C语言
数据结构中的线性表链式存储介绍及其基本操作
链式存储是线性表的一种重要存储方式,它通过节点和指针的结构,实现了灵活的动态存储管理。本文介绍了单向链表的基本操作,并提供了相应的C语言代码示例。理解和掌握链表的操作对学习和应用数据结构具有重要意义。希望这篇博客能帮助你更好地理解线性表的链式存储。
409 2
|
10月前
|
存储 算法 测试技术
【C++数据结构——线性表】求集合的并、交和差运算(头歌实践教学平台习题)【合集】
本任务要求编写程序求两个集合的并集、交集和差集。主要内容包括: 1. **单链表表示集合**:使用单链表存储集合元素,确保元素唯一且无序。 2. **求并集**:遍历两个集合,将所有不同元素加入新链表。 3. **求交集**:遍历集合A,检查元素是否在集合B中存在,若存在则加入结果链表。 4. **求差集**:遍历集合A,检查元素是否不在集合B中,若满足条件则加入结果链表。 通过C++代码实现上述操作,并提供测试用例验证结果。测试输入为两个集合的元素,输出为有序集合A、B,以及它们的并集、交集和差集。 示例测试输入: ``` a c e f a b d e h i ``` 预期输出:
275 7
|
10月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
459 5
|
10月前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
292 5
|
算法
数据结构和算法学习记录——线性表之双向链表(上)-结点类型定义、初始化函数、创建新结点函数、尾插函数、打印函数、尾删函数
数据结构和算法学习记录——线性表之双向链表(上)-结点类型定义、初始化函数、创建新结点函数、尾插函数、打印函数、尾删函数
141 0
|
存储 Java
数据结构第二篇【关于java线性表(顺序表)的基本操作】
数据结构第二篇【关于java线性表(顺序表)的基本操作】
156 6
|
存储
【数据结构】线性表和顺序表
【数据结构】线性表和顺序表
115 1
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之王道第2.3章节之线性表精题汇总二(5)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
IKU达人之数据结构与算法系列学习×单双链表精题详解、数据结构、C++、排序算法、java 、动态规划 你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
01(数据结构考研)线性表相关操作代码
01(数据结构考研)线性表相关操作代码
178 0
|
存储 C语言
数据结构之线性表的初始化及其操作
数据结构之线性表的初始化及其操作
201 0

热门文章

最新文章