《数据结构》c语言版学习笔记——单链表结构(线性表的链式存储结构Part1)

简介: 《数据结构》c语言版学习笔记——单链表结构(线性表的链式存储结构Part1)

前言


提示:本系列文章均使用Visual Studio 2019编程,编程语言为c语言。


一、单链表的建立


为了使单链表中每个数据元素与其直接后继的数据元素之间存在逻辑关系,除了存储其本身的信息之外,还需要存储一个指示其直接后继存储位置的信息(存储后继元素的存储地址,即指针)。


存储数据元素信息的域称为数据域,将存储直接后继位置的域称为指针域,其中指针域中存储的信息称为指针或链,同时这两部分信息组成数据元素的存储映像称为结点。结点由存放数据元素的数据域存放后继结点地址的指针域组成。n个结点链从而结合成一个链表,即为线性表的链式存储结构,又由于链表的每个结点中只包含一个指针域,所以称为单链表。


代码


#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
//单链表的建立
typedef int Status;
typedef int ElemType;
typedef struct Node
{
  ElemType data;           //数据域
  struct Node *next;       //指针域
}Node;
typedef struct Node* LinkList;   //将Node替换为LinkList


二、单链表的读取


由于单链表与线性表的顺序存储结构不一样,当我们要查找任意一个元素的存储位置时,单链表的查找得从头开始找。我们来看看怎么查找吧,可以说

单链表的读取分为以下几步:(例如读取链表中第n个元素的值)

1、首先声明一个指针p(结点)指向单链表的第一个结点,即p=L->next,同时设定一个计数器j从1开始计数;

2、开始查找,当j<n时,遍历整个链表,让p的指针向后移动查找下一个结点,同时计数器j累加1(由于没有定义表长,所以不知道要进行多少次循环语句,所以不用for语句);

3、若至链表末尾,指针p仍为空,即未找到该元素,第n个元素不存在,返回错误;

4、否则,查找成功,用e返回L中第n个元素的数据;

5、返回成功。


代码


//单链表的读取
typedef int Status;
Status GetElem(LinkList L, int n, ElemType* e)
{
  int j=1;                      //j为计数器
  LinkList p;                   //声明一结点
  p = L->next;                  //让p指向链表L的第一个结点
  while (p &&j<n)               //当p为空或者计数器j等于n时,结束循环
  {
    p = p->next;              //让p指向下一个结点
    ++j;
  }
  if (!p || j > n)
    return ERROR;             //第n个元素不存在
  *e = p->data;                 //取第n个元素的数据
  return OK;
}


三、单链表的插入


对于实现单链表的插入操作,我们以以下图示来解释,我们设要插入的元素e的结点为s,我们要实现将结点s插入到结点p和p->next之间,即可以将p的后继结点改为s的后继结点,再把结点s变成p的后继结点,通过代码改变其指针实现即s->next=p->next ; p->next=s。(特别注意这两句的顺序不可写反)

1666885339299.jpg

单链表的插入分为以下几步:(例如链表中第i个数据元素位置之前插入数据元素e)

1、首先声明一个指针p(结点)指向单链表的第一个结点并且声明一个空结点s,同时设定一个计数器j从1开始计数;

2、由于要找第i-1个结点,因为要插入的是第i个结点。当j<i且p为空时,遍历整个链表,让p的指针向后移动查找下一个结点,同时计数器j累加1(循环用于遍历到需要插入的结点);

3、若至链表末尾,指针p仍为空,即未找到该元素,第i个元素不存在,返回错误;

4、否则此时查找成功,此时用动态分配函数生成一个新结点s,即分配整型存储单元,并将连续的整型存储单元的首地址存储到指针变量s当中;

5、将数据元素e赋值给s->data;

6、将p结点的后缀结点赋值给s的后继并将s赋值给p的后继;

7、返回成功。


代码


//单链表的插入
typedef int Status;
Status ListInsert(LinkList* L, int i, ElemType e)
{
  int j = 1;
  LinkList p, s;
  p = *L;
  while (p && j<i)             //当p为空或者计数器j等于i时,结束循环(即寻找第i个结点)
  {
    p = p->next;
    ++j;
  }
  if (!p || j > i)
    return ERROR;                            //第i个元素不存在
  s = (LinkList)malloc(sizeof(Node));      //malloc()是动态分配函数,用来向系统请求分配内存空间,即分配整型存储单元,并将连续的整型存储单元的首地址存储到指针变量s当中
  s->data = e;
  s->next = p->next;               //将p的后缀结点赋值给s的后继
  p->next = s;                     //将s赋值给p的后继
  return OK;
}


四、单链表的删除


对于实现单链表的插入操作,我们以以下图示来解释,我们要删除的结点是q,只要绕过它的前继结点的指针,直接指向它的后继结点就行,即①q=p->next②p->next=q->next,我们可以用一步来描述,这一步是p->next=p->next->next。

1666885394976.jpg

单链表的删除分为以下几步:(例如删除链表中第i个数据元素)

1、首先声明一个指针p(结点)指向单链表的第一个结点,同时设定一个计数器j从1开始计数;

2、当j<i且p为空时,遍历整个链表,让p的指针向后移动查找下一个结点,同时计数器j累加1(循环用于遍历到需要删除的结点);

3、若至链表末尾,指针p仍为空,即未找到该元素,第i个元素不存在,返回错误;

4、否则此时查找成功,将准备删除的结点p->next赋值给q;

5、执行删除操作,p->next=q->next;

6、将q结点中的数据赋给e,作为返回,用e返回其值;

7、使用free()函数,释放q结点;

8、返回成功。


代码


//单链表的删除
typedef int Status;
Status ListDelete(LinkList *L, int i, ElemType *e)
{
  int j = 1;
  LinkList p, q;
  p = *L;
  while (p->next && j < i)             //遍历寻找第i个元素
  {
    p = p->next;
    ++j;
  }
  if (!(p->next) || j > i)
    return ERROR;                    //第i个元素不存在
  q = p->next; 
  p->next = q->next;                  
  *e = q->data;                       //将q结点中的数据给e
  free(q);                //free()函数,让系统回收此结点,释放内存
  return OK;
}


五、单链表的整表创建


1.头插法建立单链表


头插法创建单链表,即始终让新结点处于表中第一的位置,最后输入的元素和输出的元素顺序刚好相反。从一个空表开始,生成新结点,读取数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头位置上,直到结束为止。(先让新结点的next指向头结点之后,然后让表头的next指向新结点)


代码


#include <stdio.h>
#include <stdlib.h>
typedef int Status;
typedef int ElemType;
typedef struct Node* LinkList;    //将Node替换为LinkList,即结构指针LinkList
typedef struct Node               //定义一个结构体
{
  ElemType data;           //数据域
  struct Node* next;       //指针域
}Node;
Status ListInit(LinkList& L)
{
  L = (LinkList)malloc(sizeof(Node));                 //生成新结点
  if (!L)
    exit(1);                   //当单链表L为空时,执行exit(1)即表示异常退出
  L->next = NULL;                //否则创建一个带头结点的单链表,初始化指向NULL
  return 1;                      //return(1)代表函数非正常终止
}
//头插法
void CreateListHead(LinkList* L, int n)
{
  LinkList p;
  int i;
  *L = (LinkList)malloc(sizeof(Node));
  (*L)->next = NULL;              //创建一个带头结点的单链表,初始化指向NULL
  for (i = 0; i < n; i++)
  {
    p = (LinkList)malloc(sizeof(Node));     //生成新结点
    scanf("%d", &i);
    p->data = i;
    p->next = (*L)->next;
    (*L)->next = p;               //插入到表头
  }
}
//遍历函数
void TraverseList(LinkList* L)
{
  Node* p, * q;
  p = *L;
  while (p->next != NULL)       //遍历输出数据元素
  {
    q = p->next;
    printf("%d ", q->data);
    p = p->next;
  }
}
//主函数
int main(int argc, char const* argv[])                  
{
  int n;
  LinkList L;
  ListInit(L);
  printf("请输入创建数据元素的个数:");
  scanf("%d", &n);
  CreateListHead(&L, n);
  printf("头插法创建单链表如下:");
  TraverseList(&L);
  printf("\n");
  return 0;
}

测试输入五个数据元素:0、1、2、3、4。

(输出的顺序与输入的顺序是相反的)

1666885454018.jpg


2.尾插法建立单链表


第二种方法就是尾插法,也就是将所加入的新结点全部插在终端结点的后面依次排下去,就相当于正常排队的思维来运行程序, 其中

q->next = p;这一语句即将表尾终端结点q的指针指向新的下一个结点p,然后q=p;这一语句q就相当于一个中继(是当前链表的最后结点),在进行下一个元素的创建时,q本来是上一个数据元素的结点,但后面仍有结点,所以这时应该将这个p结点(也就是当前链表的最后结点)赋值给q,此时q又是当前链表的尾结点。循环结束后q->next = NULL;即让这个链表的指针域置空,以便在之后遍历时可以知道是其当前链表尾部。


代码


#include<stdio.h>
#include<stdlib.h>
typedef int Status;
typedef int ElemType;
typedef struct Node* LinkList;             //将Node替换为LinkList,即结构指针LinkList
typedef struct Node                        //定义一个结构体
{
    ElemType data;                 //数据域
    struct Node* next;             //指针域
}Node;
Status ListInit(LinkList& L)
{
    L = (LinkList)malloc(sizeof(Node));                 //生成新结点
    if (!L)
        exit(1);                   //当单链表L为空时,执行exit(1)即表示异常退出
    L->next = NULL;                //否则创建一个带头结点的单链表,初始化指向NULL
    return 1;                      //return(1)代表函数非正常终止
}
//尾插法
void CreatListTail(LinkList& L, int n)
{
    LinkList p, q;
    q = L;
    int a;
    for (int i = 0; i < n; i++)
    {
        p = (LinkList)malloc(sizeof(Node));              //生成新结点
        scanf("%d", &a);
        p->data = a;
        q->next = p;                             //将当前的新结点定义为单链表尾终端结点
        q = p;
    }
    q->next = NULL;                              //表示当前单链表结束
}
//遍历函数
void TraverseList(LinkList& L)              //输出单链表函数
{
    LinkList r;
    r = L->next;
    while (r)
    {
        printf("%d ", r->data);
        r = r->next;
    }
}
//主函数
int main(int argc, char const* argv[])                  
{
    int n;
    LinkList L;
    ListInit(L);
    printf("请输入创建数据元素的个数:");
    scanf("%d", &n);
    CreatListTail(L, n);
    printf("尾插法创建单链表如下:");
    TraverseList(L);
    printf("\n");
    return 0;
}

测试输入五个数据元素:0、1、2、3、4。

(输出的顺序与输入的顺序相同)

1666885496907.jpg


六、单链表的整表删除


当要删除这个单链表时,即在内存中释放它时,我们给出以下算法:

1、声明一结点p和q;

2、将第一个结点赋值给p;

3、循环语句:将下一结点赋值给q,释放p,将q赋值给p。


代码


#include<stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
typedef int Status;
typedef int ElemType;
typedef struct Node* LinkList;    //将Node替换为LinkList,即结构指针LinkList
typedef struct Node               //定义一个结构体
{
  ElemType data;           //数据域
  struct Node* next;       //指针域
}Node;
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;
}


总结


以上就是本次的笔记内容,本文仅仅通过文字和代码简单介绍了单链表结构的各项操作,建议自己分析总结其各个操作并结合自己的编程能力选择编程语言再写一遍代码从而加深印象,可以使自己的编程能力提升,其实数据结构不难,要有心地去学习和总结,才能事半功倍,若有错误欢迎指正。



相关文章
|
存储 安全 C语言
【C语言程序设计——选择结构程序设计】预测你的身高(头歌实践教学平台习题)【合集】
分支的语句,这可能不是预期的行为,这种现象被称为“case穿透”,在某些特定情况下可以利用这一特性来简化代码,但在大多数情况下,需要谨慎使用。编写一个程序,该程序需输入个人数据,进而预测其成年后的身高。根据提示,在右侧编辑器补充代码,计算并输出最终预测的身高。分支下的语句,提示用户输入无效。常量的值必须是唯一的,且在同一个。语句的作用至关重要,如果遗漏。开始你的任务吧,祝你成功!,程序将会继续执行下一个。常量都不匹配,就会执行。来确保程序的正确性。
474 10
|
小程序 C语言
【C语言程序设计——基础】顺序结构程序设计(头歌实践教学平台习题)【合集】
目录 任务描述 相关知识 编程要求 测试说明 我的通关代码: 测试结果: 任务描述 相关知识 编程编写一个程序,从键盘输入3个变量的值,例如a=5,b=6,c=7,然后将3个变量的值进行交换,使得a=6,b=7,c=5。面积=sqrt(s(s−a)(s−b)(s−c)),s=(a+b+c)/2。使用输入函数获取半径,格式指示符与数据类型一致,实验一下,不一致会如何。根据提示,在右侧编辑器补充代码,计算并输出圆的周长和面积。
358 10
|
存储 算法 测试技术
【C++数据结构——线性表】求集合的并、交和差运算(头歌实践教学平台习题)【合集】
本任务要求编写程序求两个集合的并集、交集和差集。主要内容包括: 1. **单链表表示集合**:使用单链表存储集合元素,确保元素唯一且无序。 2. **求并集**:遍历两个集合,将所有不同元素加入新链表。 3. **求交集**:遍历集合A,检查元素是否在集合B中存在,若存在则加入结果链表。 4. **求差集**:遍历集合A,检查元素是否不在集合B中,若满足条件则加入结果链表。 通过C++代码实现上述操作,并提供测试用例验证结果。测试输入为两个集合的元素,输出为有序集合A、B,以及它们的并集、交集和差集。 示例测试输入: ``` a c e f a b d e h i ``` 预期输出:
415 7
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
600 5
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
432 5
|
存储 编译器 C语言
【C语言程序设计——选择结构程序设计】求一元二次方程的根(头歌实践教学平台习题)【合集】
本任务要求根据求根公式计算并输出一元二次方程的两个实根,精确到小数点后两位。若方程无实根,则输出提示信息。主要内容包括: - **任务描述**:使用求根公式计算一元二次方程的实根。 - **相关知识**:掌握 `sqrt()` 函数的基本使用方法,判断方程是否有实根。 - **编程要求**:根据输入的系数,计算并输出方程的根或提示无实根。 - **测试说明**:提供两组测试数据及预期输出,确保代码正确性。 - **通关代码**:包含完整的 C 语言代码示例,实现上述功能。 通过本任务,你将学会如何处理一元二次方程的求解问题,并熟悉 `sqrt()` 函数的使用。
283 5
|
存储 算法 安全
【C语言程序设计——选择结构程序设计】按从小到大排序三个数(头歌实践教学平台习题)【合集】
本任务要求从键盘输入三个数,并按从小到大的顺序排序后输出。主要内容包括: - **任务描述**:实现三个数的排序并输出。 - **编程要求**:根据提示在编辑器中补充代码。 - **相关知识**: - 选择结构(if、if-else、switch) - 主要语句类型(条件语句) - 比较操作与交换操作 - **测试说明**:提供两组测试数据及预期输出。 - **通关代码**:完整代码示例。 - **测试结果**:展示测试通过的结果。 通过本任务,你将掌握基本的选择结构和排序算法的应用。祝你成功!
260 4
|
存储 算法 安全
【C语言程序设计——选择结构程序设计】求阶跃函数的值(头歌实践教学平台习题)【合集】
本任务要求输入x的值,计算并输出特定阶跃函数的结果。主要内容包括: 1. **选择结构基本概念**:介绍if、if-else、switch语句。 2. **主要语句类型**:详细解释if、if-else、switch语句的使用方法。 3. **跃迁函数中变量的取值范围**:说明如何根据条件判断变量范围。 4. **计算阶跃函数的值**:通过示例展示如何根据给定条件计算函数值。 编程要求:在右侧编辑器Begin-End之间补充代码,实现阶跃函数的计算和输出。测试说明提供了多个输入及其预期输出,确保代码正确性。最后提供通关代码和测试结果,帮助理解整个过程。
304 0
|
存储 算法 安全
【C语言程序设计——选择结构程序设计】判断一个数是不是5和7的倍数(头歌实践教学平台习题)【合集】
本任务要求输入一个正整数,判断其是否同时是5和7的倍数,若是输出&quot;Yes&quot;,否则输出&quot;No&quot;。内容涵盖选择结构的基本概念、主要语句类型(if、if-else、switch)及条件判断逻辑,帮助理解编程中的分支执行与条件表达式。测试用例包括正数、负数及非倍数情况,确保代码逻辑严谨。通关代码示例如下: ```cpp #include &quot;stdio.h&quot; int main(){ int a; scanf(&quot;%d&quot;, &a); if (a &lt;= 0){ printf(&quo
586 0
|
存储 程序员 C语言
程序员之路:C语言中存储类别
程序员之路:C语言中存储类别
197 0

热门文章

最新文章