【数据结构】——单链表的基本操作(带头结点)

这篇具有很好参考价值的文章主要介绍了【数据结构】——单链表的基本操作(带头结点)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.单链表的定义

        单链表解决了顺序表需要大量连续存储单元的缺点,但单链表附加指针域,存储密度较顺序表低(考点!!)。由于单链表的元素离散地分布在存储空间中,所以单链表是非随机存取的存储结构,即不能直接找到表中某个特定的结点。当查找某个特定结点时,需要从表头开始遍历。

        通常使用头指针来标识一个单链表,如单链表L,头指针为NULL时表示一个空表。为了操作上的方便,可以在单链表的第一个结点之前附加一个头结点。头结点一般不存储数据,它的数据域可以不设任何信息,或记录表长等信息;头结点的指针域指向线性表的第一个元素结点。

为什么要引入头结点呢?

引入头结点后,可以带来两个优点

①由于第一个数据结点的位置被存放在头结点的指针域中,因此在链表第一个位置上的操作和在表的其他位置上的操作一致,无须进行特殊处理。

②无论链表是否为空,其头指针都是指向头结点的非空指针(空表中头结点的指针域为空),因此空表和非空表的操作也得到了统一。

单链表的结点类型定义

typedef int ElemType;
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

关于定义的几点说明:

1.在单链表的定义中不能省略结构体名称LNode(第2行中)。原因:在该结构体内部使用到了该结构体类型去定义next指针域。

2.第5行中的重命名LNode和*LinkList的意义:

这里相当于以下两句:

typedef struct LNode LNode;//将结构体重命名为LNode
typedef struct LNode *LinkList;//将结构体的指针重命名为LinkList
LNode和LinkList的区别:
  1. LNode是一个具象的结构体类型,指向的是包含某个数据类型的数据域和指针域的结构体类型。
  2. 而LinkList是LNode的指针类型,它占用一定的内存空间,内存空间中存放的值是一个LNode类型结构体的地址。

2.单链表的基本操作实现

(1)头插法建立单链表

LinkList list_head_insert(LinkList &L)
{
    L = (LinkList)malloc(sizeof(LNode));//申请头节点空间
    L->next=NULL;
    ElemType x;
    scanf("%d", &x);
    LNode* s;//用来指向申请的新节点

    while(x!=9999)//输入9999表示结束
    {
        s=(LinkList)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;//s的next指向原本链表的第一个节点
        L->next = s;
        scanf("%d", &x);
    }
    return L;
}

【注】:这里有一个常见的错误:分配结点空间的语句

L = (LinkList)malloc(sizeof(LNode));

错误写法L = (LinkList)malloc(sizeof(LinkList));

要谨记:LinkList是LNode的指针类型,不代表结构体(该指针一般占用8个字节的空间)而这里需要的是分配的LNode大小的空间。

头插法建立单链表 时间复杂度分析

采用头插法建立单链表时,读入数据的顺序与生成的链表中的元素顺序相反。每个结点插入的时间为O(1),设单链表长为n,则总时间复杂度为O(n).

(2)尾插法建立单链表 

尾插法的特点:

始终存在一个尾指针r指向链表的尾部,且读入数据的顺序与生成链表中元素的顺序相同。

LinkList list_tail_insert(LinkList &L)
{
    L = (LinkList)malloc(sizeof(LNode));
    L->next=NULL;
    ElemType x;
    scanf("%d", &x);
    LNode *s, *r=L;//s用来指向申请的新节点,r始终指向链表尾部
    while(x!=9999)//输入9999表示结束
    {
        s = (LinkList) malloc(sizeof(LNode));
        s->data = x;
        r->next = s;//新节点给尾节点的next指针
        r=s;//r要指向新的尾部
        scanf("%d", &x);
    }
    r->next = NULL;//让尾节点为Null
    return L;
}

尾插法建立单链表的时间复杂度分析 

 尾插法的时间复杂度与头插法相同,都是O(n)。

 (3)按位查找结点

//按位置查找
LNode* GetElem(LinkList L, int SearchPos)
{
    int i=0;
    if(SearchPos<0)
    {
        return NULL;
    }
    while(i<SearchPos && L!=NULL)
    {
        L=L->next;
        i++;
    }
    return L;
}

 按位查找的时间复杂度

按位查找的时间复杂度为:O(n)

(4)按值查找

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

 按值查找的时间复杂度 

按值查找的时间复杂度为:O(n)

(5)在第i个位置上插入结点 

LinkList ListFrontInsert(LinkList L, int InsertPos, ElemType InsertVal)//注意:插入不会改变链表的头节点(指针)L,和顺序表不同,这里的形参不需要使用&
{
    LinkList p = GetElem(L, InsertPos-1);//GetElem函数中已自带InsertPos-1是否合法的检查
    if(NULL==p)
        return false;
    LinkList q;
    q = (LNode*) malloc(sizeof (LNode));
    q->data = InsertVal;
    q->next = p->next;//①
    p->next = q;//②
    return L;
}

 【注】:①②两句的顺序不能颠倒!(代码的第9和第10行)

插入结点的时间复杂度分析

插入算法的主要时间开销在于查找第i-1个元素,时间复杂度为O(n)。

若在给定的结点后面插入新结点,则时间复杂度仅为O(1).

【注】:在单链表的插入操作中,不会改变头结点指针L,因此不需要使用&类型(这一点与顺序表不同,顺序表的插入操作会改变整个表,在顺序表的插入函数中形参需要使用&)

(6)删除结点操作

//删除第i个位置的元素
bool ListDelete(LinkList L, int i) 
{
    LinkList p= GetElem(L, i-1);
    if(NULL==p)
    {
        return false;
    }
    LinkList q = p->next;
    //这里可能会有疑问,如果不定义指针q,用p->next=p->next->next可以吗
    //这样是不行的,因为被删除的节点没有了指针指向,无法free
    p->next = q->next;//断链
    free(q);//释放被删除节点的空间
}

【注】:在删除节点操作中,需要定义两个指针p和q,p指向删除位置i的前一个位置的结点,p指向需要被删除的结点。这里可能会产生疑问,为什么一定要定义指针q呢?用语句p->next=p->next->next不是也可以达到删除节点的效果吗?

原因:这样是不好的!如果不定义指针q,在p->next=p->next->next之后就无法再找到删除的结点空间,无法free掉这块空间。一般前面使用malloc分配的空间在后面使用完成后都需要free掉。

删除结点的时间复杂度分析

删除结点算法的时间开销也主要在于查找第i-1个元素,时间复杂度为O(n).

整体代码演示

#include <stdio.h>
#include <stdlib.h>

typedef int ElemType;
typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode, *LinkList;

//头插法新建链表
LinkList list_head_insert(LinkList &L)
    L = (LinkList)malloc(sizeof(LNode));
    L->next=NULL;
    ElemType x;
    scanf("%d", &x);
    LNode* s;//用来指向申请的新节点

    while(x!=9999)//输入9999表示输入结束
    {
        s=(LinkList)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;//s的next指向原本链表的第一个节点
        L->next = s;
        scanf("%d", &x);
    }
    return L;
}

//尾插法新建链表
//尾插法的特点:定义一个尾指针r总指向链表的尾部
LinkList list_tail_insert(LinkList &L)
{
    L = (LinkList)malloc(sizeof(LNode));
    L->next=NULL;
    ElemType x;
    scanf("%d", &x);
    LNode *s, *r=L;//s用来指向申请的新节点,r始终指向链表尾部
    while(x!=9999)
    {
        s = (LinkList) malloc(sizeof(LNode));
        s->data = x;
        r->next = s;//新节点给尾节点的next指针
        r=s;//r要指向新的尾部
        scanf("%d", &x);
    }
    r->next = NULL;//让尾节点为Null
    return L;
}

//链表打印
void print_list(LinkList L)
{
    L = L->next;//没有使用引用,外面的L不会发生改变
    while(L!=NULL)
    {
        printf("%3d", L->data);
        L = L->next;
    }
    printf("\n");
}

//按位查找
//查找返回一个节点
LNode *GetElem(LinkList L, int SearchPos)
{
    if(SearchPos<1)
    {
        return NULL;
    }
    int j=1;//计数,初始为1
    LNode *p=L->next;
    while(p!=NULL && j<SearchPos)
    {
        p=p->next;
        j++;
    }
    return p;
}

//按值查找
LNode *LocateElem(LinkList L, ElemType e)
{
    LNode *p = L->next;
    while(p!=NULL && p->data!=e)
        p = p->next;
    return p;
}

//向第i个位置插入元素
//使用GetElem找到第i-1个节点的位置(地址、指针)
bool ListFrontInsert(LinkList L, int InsertPos, ElemType InsertVal)
{
    LinkList p = GetElem(L, InsertPos-1);//GetElem函数中已自带InsertPos-1是否合法的检查
    if(NULL==p)
        return false;
    LinkList q;
    q = (LNode*) malloc(sizeof (LNode));
    q->data = InsertVal;
    q->next = p->next;
    p->next = q;
    return true;
}

//删除第i个位置的元素
bool ListDelete(LinkList L, int i) 
{
    //判断i的合法性已经在GetElem中定义过
    //若i=1也是合法的,此时i-1=0,返回头指针
    LinkList p= GetElem(L, i-1);
    if(NULL==p)
    {
        return false;
    }
    LinkList q = p->next;
    //此时可能会说,不定义q,用p->next=p->next->next
    //这样是不行的,因为被删除的节点没有了指针指向,无法free
    p->next = q->next;//断链
    free(q);//释放被删除节点的空间
}

int main() {
    //输入1 2 3 4 5 6 7 8 9 9999
    LinkList L;//L是链表头,是结构体指针类型,大小:8个字节
//    list_head_insert(L);//头插法新建链表
    list_tail_insert(L);
    print_list(L);
    //查找
    LinkList search;//查找指针
    //按位置查找
    search=GetElem(L, 2);//返回一个指针
    if(search!=NULL)
    {
        printf("Succeeded in searching by serial number\n");
        printf("%d\n", search->data);
    }else{
        printf("Failed in searching by serial number\n");
    }
    //按值
    search=LocateElem(L, 6);
    if(search!=NULL)
    {
        printf("Succeeded in searching by serial number\n");
        printf("%d\n", search->data);
    }else{
        printf("Failed in searching by serial number\n");
    }

    //在第2个位置上插入99
    bool ret;
    ret=ListFrontInsert(L, 2, 99);
    print_list(L);
    //删除链表第4个位置的元素
    ListDelete(L, 4);
    print_list(L);
    return 0;
}

输入:1 2 3 4 5 6 7 8 9999(用空格隔开) 

实验结果:

带头结点的链表,数据结构,算法

 文章来源地址https://www.toymoban.com/news/detail-742913.html

 

到了这里,关于【数据结构】——单链表的基本操作(带头结点)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构上机练习——单链表的基本操作、头文件、类定义、main函数、多种链表算法的实现,含注释

      头文件和源文件分开有很多好处:可以提高编译速度、提高代码的可维护性、提高代码的可重用性和可扩展性,同时也可以使代码结构更清晰,方便代码的管理和维护。 LinkList.h test.cpp                  (下面所有函数都默认在类中实现)   我们以

    2024年02月07日
    浏览(38)
  • 数据结构——单链表基本操作实现 (c++)

    单链表链式存储结构的特点是:用一组任意的存储单元存储线性表的数据元素(这里存储单元可以是连续的,也可以是不连续的),为了表示每个数据元素a与其直接后继数据元素之间的逻辑关系,除了存储信息本身外还要存储一个指示其直接后继的信息(地址). 这两部分信

    2024年02月03日
    浏览(42)
  • 数据结构——单链表上基本操作的实现

    1.按位序插入(带头结点) : ==ListInsert(L, i, e): ==在表L 中的第 i 个位置上插入指定元素 e = 找到第 i-1 个结点 ( 前驱结点 ) ,将新结点 插入其后;其中头结点可以看作第 0 个结点,故 i=1 时也适用。 typedef struct LNode{ ElemType data; struct LNode *next; }LNode, *LinkList; // 在第 i 个位置插入

    2024年01月21日
    浏览(35)
  • 【数据结构】单链表基本操作:查找、插入、删除、创建

     链表由结点组成,结点由数据域和指针域组成。其中,数据域存放的就是数据元素,指针域存放下一个结点的地址。数据元素可以只有一个,也可以有多个不同类型的数据元素,甚至是数组。下图和代码来自《C Primer Plus》,该链表每个节结点同时含char类型和int类型。 ​​

    2024年02月02日
    浏览(39)
  • 数据结构---双向链表的基本操作

    头插法 遍历链表 尾插法 头删法 尾删法 按位置插入数据 按位置删除数据 dooublelinklist.c doublelinklist.h doublemain.c

    2024年02月22日
    浏览(32)
  • 数据结构 2.1 线性表的定义和基本操作

    数据结构三要素——逻辑结构、数据的运算、存储结构(物理结构) 线性表是具有 相同数据类型 的n(n=0)个数据元素的 有限序列 ,其中n为表长,当n=0时,线性表是一个空表。 每个数据元素所占空间一样大,有次序。 几个概念 1.ai是线性表中的第i个 i表示元素线性表中的

    2024年02月07日
    浏览(30)
  • c语言数据结构——链表的实现及其基本操作

    顺序表的问题及思考 问题: 中间/头部的插入删除,时间复杂度为O(N) 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后面没有数据插

    2023年04月09日
    浏览(55)
  • 【数据结构】C语言实现双链表的基本操作

    大家好,很高兴又和大家见面啦!!! 经过前面几个篇章的内容分享,相信大家对顺序表和单链表的基本操作都已经熟练掌握了。今天咱们将继续分享线性表的链式存储的第二种形式——双链表。在今天的内容中,咱们将介绍双链表的创建以及一些基本操作,接下来跟我一起

    2024年02月04日
    浏览(41)
  • 【数据结构】 循环双链表的基本操作 (C语言版)

    目录 一、循环双链表 1、循环双链表的定义: 2、循环双链表的优缺点: 二、循环双链表的基本操作算法(C语言)    1、宏定义  2、创建结构体 3、循环双链表的初始化  4、循环双链表按位查找 5、循环双链表插入 6、循环双链表查找 7、循环双链表删除 8、求循环双链表长

    2024年01月22日
    浏览(36)
  • 数据结构实验---顺序表的合并---链表的基本操作---重点解析约瑟夫问题

    实验的写法多种多样,但本文并未采用 #define 定义容量的写法,这样写已经是很老旧过时的写法。所有实验主体采用均为动态开辟,后续如果利用 C++ 来写或许会应用更多语法… 本篇展示数据结构的两个实验 其中,重点分析约瑟夫问题 实验中代码的命名风格等均与下方博客

    2024年02月16日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包