Chapter2.3:线性表的链式表示
创始人
2025-05-30 12:31:52

该系列属于计算机基础系列中的《数据结构基础》子系列,参考书《数据结构考研复习指导》(王道论坛 组编),完整内容请阅读原书。



3.线性表的链式表示

3.1 单链表的定义
  • 顺序表可以随时存取表中的任意一个元素,其存储位置可以用一个简单的公式表示,但插入和删除操作需要移动大量的元素;

  • 链式存储线性表,不需要使用地址连续的存储单元,即不要求逻辑上相邻的元素在物理位置上相邻,通过"链"建立起数据元素间的逻辑关系,因此,插入和删除元素不需要移动元素,只需要修改指针,但会失去顺序表可随机存取的优点;

  • 线性表的链式存储亦称单链表,指通过一组任意的存储单元来存储线性表中的数据元素;

  • 为了建立数据元素间的线性关系,对每个链表结点,除存放数据元素本身的信息外,还需要存放一个指向其后继的指针;

  • 单链表结点包括数据域(data)({\rm data})(data)和指针域(next)({\rm next})(next),数据域存放数据元素,指针域存放后继结点的地址;

  • 单链表的结点类型描述:

    // 定义单链表结点类型
    typedef struct LNode{ElemType data;			// 数据域struct LNode *next;		// 指针域
    }LNode,*LinkList;
    
  • 单链表可以解决顺序表需要大量连续存储单元的缺点,但单链表附加指针域,存在浪费存储空间的缺点;

  • 单链表的元素离散地分布在存储空间中,故单链表是非随机存取的存储结构,即不能直接找到表中某个特定的结点;单链表中查找某个特定的结点时,需要从表头开始遍历,依次查找;

  • 通常用头指针来标识一个单链表,如单链表LLL,头指针为NULL{\rm NULL}NULL时表示一个空表;

  • 为了操作上的方便,在单链表第一个结点前附加一个结点,称为头结点;头节点的数据域可不设任何信息,亦可记录表长等信息;头结点的指针域指向线性表的第一个元素结点;

  • 带头结点的单链表表示:

    1

  • 头结点和头指针区分:不管带不带头结点,头指针始终指向链表的第一个结点,头结点是带头结点的链表中的第一个结点,结点内通常不存储信息;

  • 引入头结点的优点:

    • 由于第一个数据结点的位置被存放在头结点的指针域,因此,在链表的第一个位置上的操作和在表的其他位置上的操作一致,无须进行特殊处理;
    • 无论链表是否为空,其头指针都指向头结点的非空指针,空表中头结点的指针域为空,因此,空表和非空表的处理得到统一;
3.2 单链表上基本操作的实现
3.2.1 头插法建立单链表
  • 头插法从一个空表开始,生成新结点,将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点后;

  • 头插法建立单链表图解:

    2

  • 头插法建立单链表算法核心代码:

    // 逆向建立单链表
    LinkList List_HeadInsert(LinkList &L){LNode *s;int x;// 创建头结点L=(LinkList) malloc(sizeof(LNode));// 初始化为空链表L->next=NULL;// 输入结点的值scanf("%d",&x);while(x!=9999){// 由系统生成一个LNode型结点,同时将该结点的起始位置赋给指针变量ss=(LNode*) malloc(sizeof(LNode));s->data=x;s->next=L->next;L->next=s;		// 将新结点插入表中,L为头指针scanf("%d",&x);}return L;
    }
    
  • 采用头插法建立单链表,读入数据的顺序与生成的链表中元素的顺序相反;

  • 每个结点插入的时间为O(1)O(1)O(1),设单链表表长nnn,则总时间复杂度为O(n)O(n)O(n);

3.2.2 尾插法建立单链表
  • 尾插法将新结点插入到当前链表的表尾,需要增加一个尾指针r{\rm r}r,始终指向当前链表的尾结点;

  • 尾插法建立单链表图解:

    3

  • 尾插法建立单链表算法核心代码:

    // 正向建立单链表
    LinkList List_TailInsert(LinkList &L){int x;				// 元素类型为整型L=(LinkList)malloc(sizeof(LNode));LNode *s,*r=L;		// r为表尾指针scanf("%d",&x);		// 输入结点的值while(x != 9999){s=(LNode *)malloc(sizeof(LNode));s->data=x;r->next=s;r=s;			// s指向新的表尾结点scanf("%d",&x);}r->next=NULL;return L;
    }
    
3.2.3 按序号查找结点值
  • 在单链表中,从第一个结点出发,顺指针next{\rm next}next域逐个往下搜索,直到找到第i{\rm i}i个结点为止,否则返回最后一个结点指针域NULL{\rm NULL}NULL;

  • 按序号查找结点值算法核心代码:

    LNode *GetElem(LinkList L,int i){int j=1;LNode *p=L->next;		// 头结点指针赋给p// 若i等于0,返回头结点if(i == 0)return L;// 若i无效,返回NULLif(i < 1)return NULL;// 查找第i个结点while(p && j < i){p=p->next;j++;}return p;
    }
    
  • 按序号查找操作时间复杂度为O(n)O(n)O(n);

3.2.4 按值查找表结点
  • 从单链表第一个结点开始,由前往后依次比较表中各结点数据域的值,若某结点数据域的值等于给定值e{\rm e}e,则返回该结点的指针;若整个单链表中没有这样的结点,则返回NULL{\rm NULL}NULL;

  • 按值查找表结点算法核心代码:

    LNode *LocateElem(LinkList L,ElemType e){LNode *p=L->next;// 查找data域为e的结点while(p!=NULL && p->data!=e)p=p->next;return p;
    }
    
  • 按值查找操作时间复杂度为O(n)O(n)O(n);

3.2.5 插入结点操作
  • 插入结点操作:将值为x{\rm x}x的新结点插入到第i{\rm i}i个位置上;先检查插入位置的合法性,然后找到待插入位置的前驱结点,即第i−1{\rm i-1}i−1个结点,再在其后插入新结点;

  • 算法过程:先调用按序号查找算法GetElem(L,i−1){\rm GetElem(L,i-1)}GetElem(L,i−1),查找第i−1{\rm i-1}i−1个结点;假设返回的第i−1{\rm i-1}i−1个结点为∗p{\rm *p}∗p,然后令新结点∗s{\rm *s}∗s的指针域指向∗p{\rm *p}∗p的后继结点,再令结点∗p{\rm *p}∗p的指针域指向新插入的结点∗s{\rm *s}∗s;

  • 单链表插入结点操作图解:

    5

  • 单链表插入新结点操作核心代码:

    // STEP1:查找插入位置的前驱结点
    p=GetElem(L,i-1);// STEP2:将新结点s指针域指向p后继结点
    s->next=p->next;// STEP3:将结点p的指针域指向新结点s
    p->next=s;
    
  • 单链表插入新结点操作时间复杂度分析:

    该插入新结点操作主要时间开销在于查找第i−1{\rm i-1}i−1个元素,时间复杂度为O(n)O(n)O(n);若在给定的结点后插入新结点,时间复杂度为O(1)O(1)O(1);

3.2.6 删除结点操作
  • 删除结点操作:将单链表的第i{\rm i}i个结点删除;

  • 算法思想:先检查删除位置的合法性,后查找表中第i−1{\rm i-1}i−1个结点,即被删除结点的前驱结点,将其删除;

  • 单链表删除结点操作图解:

    6

  • 单链表删除结点操作核心代码:

    // STEP1:查找删除位置的前驱结点
    p=GetElem(L,i-1);// STEP2:令q指向被删除结点
    q=p->next;// STEP3:将*q结点从单链表中断开
    p->next=q->next;// STEP4:释放结点的存储空间
    free(q);
    
  • 单链表删除结点操作时间复杂度分析:

    单链表删除结点操作主要时间耗费在查找操作上,时间复杂度为O(n)O(n)O(n);

3.2.7 求表长操作
  • 求表长操作:计算单链表中数据结点(不含头结点)的个数;
  • 算法思想:从第一个结点开始顺序依次访问表中每个结点,需要设置一个计数器变量,每访问一个结点,计数器加111,直到访问到空结点为止;
  • 求表长操作时间复杂度为O(n)O(n)O(n);
3.3 双链表
3.3.1 双链表定义
  • 双链表结点中有两个指针:prior{\rm prior}prior和next{\rm next}next,分别指向其前驱结点和后继结点;

  • 双链表图解:

    7

  • 双链表结点类型描述:

    // 定义双链表结点类型
    typedef struct DNode{	ElemType data;					// 数据域struct DNode *prior,*next;		// 结点前驱和后继指针
    }DNode,*DLinklist;
    
  • 双链表在单链表结点中增加一个指向其前驱的prior{\rm prior}prior指针,故双链表中的按值查找和按位查找的操作与单链表相同;

  • 双链表的插入和删除结点操作和单链表操作不同,且双链表插入和删除操作的时间复杂度为O(1)O(1)O(1);

3.3.2 双链表的插入操作
  • 双链表中p{\rm p}p所指结点后插入结点∗s{\rm *s}∗s,插入过程图解如下:

    8

  • 双链表插入结点操作核心代码:

    // 将结点*s插入到结点*p后
    s->next=p->next;
    p->next->prior=s;
    s->prior=p;
    p->next=s;
3.3.3 双链表的删除操作
  • 删除双链表结点∗p{\rm *p}∗p的后继结点∗q{\rm *q}∗q,删除过程图解如下:

    9

  • 双链表删除结点操作核心代码:

    // 双链表删除结点
    p->next=q->next;
    q->next->prior=p;
    free(q);				// 释放结点q的空间
3.4 循环链表
3.4.1 循环单链表
  • 循环单链表最后一个结点的指针不是NULL{\rm NULL}NULL,而是指向头结点,整个链表形成一个环;

  • 循环单链表结构图解如下:

    10

  • 循环单链表中,表尾结点∗r{\rm *r}∗r的next{\rm next}next域指向L{\rm L}L,故表中没有指针域为NULL{\rm NULL}NULL,因此,循环单链表的判空条件不是头结点的指针是否为空,而是是否等于头指针;

3.4.2 循环双链表
  • 循环双链表中,头结点的prior{\rm prior}prior指针需要指向表尾结点;

  • 循环双链表结构图解如下:

    11

  • 在循环双链表L{\rm L}L中,某结点∗p{\rm *p}∗p为尾结点时,p−>next==L{\rm p->next==L}p−>next==L;当循环双链表为空表时,头结点的prior{\rm prior}prior域和next{\rm next}next域都等于L{\rm L}L;

3.5 静态链表
  • 静态链表借助数组描述线性表的链式存储结构,结点也包括数据域data{\rm data}data和指针域next{\rm next}next;

  • 静态链表的指针是结点的相对地址(数组下标),亦称游标,静态链表亦要预先分配一块连续的内存空间;

  • 静态链表和单链表结构对应关系如下:

    13

  • 静态链表结构类型描述:

    #define MaxSize 50			// 静态链表的最大长度
    typedef struct{				// 静态链表结构类型的定义ElemType data;			// 存储数据元素int next;				// 下一个元素的数组下标
    }SLinkList[MaxSize];
  • 静态链表以next==−1{\rm next==-1}next==−1作为结束标志,静态链表插入、删除操作与动态链表相同,只需要修改指针,不需要移动元素;

3.6 顺序表和链表比较
3.6.1 顺序表和链表比较
  • 存取(读写)方式。
    • 顺序表可以顺序存取,亦可随机存取;链表只能从表头顺序存取元素;
  • 逻辑结构与物理结构。
    • 顺序存储时,逻辑上相邻的元素,对应的物理存储位置亦相邻;
    • 链式存储时,逻辑上相邻的元素,物理位置不一定相邻,对应的逻辑关系通过指针链接表示;
  • 查找、插入和删除操作。
    • 对于按值查找,顺序表无序时,顺序表和链表的时间复杂度均为O(n)O(n)O(n);顺序表有序时,采用折半查找,时间复杂度为O(log⁡2n)O(\log_2n)O(log2​n);
    • 对于按序号查找,顺序表支持随机访问,时间复杂度为O(1)O(1)O(1);链表平均复杂度为O(n)O(n)O(n);
    • 顺序表的插入、删除操作,平均需要移动半个表长的元素;链表的插入、删除操作,只需要修改相关结点的指针域即可;链表的每个结点都带有指针域,故存储密度不够大;
  • 空间分配。
    • 顺序存储在静态存储分配情形下,一旦存储空间装满就不能扩充,若再加入新元素,则会出现内存溢出,因此,需要预先分配足够大的存储空间;
    • 顺序存储预先分配存储空间过大,可能会导致顺序表后部大量闲置;顺序存储预先分配存储空间过小,可能造成溢出;
    • 顺序动态存储的存储空间可以扩充,但需要移动大量元素,导致操作效率降低,且若内存没有更大块的连续存储空间,则会导致分配失败;
    • 链式存储的结点空间只在需要时申请分配,只要内存有空间就可以分配,操作灵活、高效;
3.6.2 选取存储结构方法
  • 基于存储的考虑。
    • 难以估计线性表的长度或存储规模时,不宜采用顺序表;
    • 链表不用预先估计存储规模,但链表的存储密度较低,链表的存储密度是小于111的;
  • 基于运算的考虑。
    • 顺序表中按序号访问aia_iai​的时间复杂度为O(1)O(1)O(1),链表中按序号访问的时间复杂度为O(n)O(n)O(n),若经常做的运算是按序号访问数据元素,则顺序表优于链表;
    • 顺序表中插入、删除操作时,平均移动表中一半元素,当数据元素信息量较大且表较长时,对此不能忽视;
    • 链表中进行插入、删除操作时,主要是比较操作,从该角度考虑,优于顺序表;
  • 基于环境的考虑。
    • 顺序表容易实现,链表的操作基于指针,有一些语言没有指针类型;

相关内容

热门资讯

万亿稳定币市场开启:从概念炒作... 文|恒心来源|博望财经数字货币概念港股又迎来利好消息。据《金融界》等多家媒体报道,中国香港特别行政区...
北交所打新持续火热,新三板公司... 新京报贝壳财经讯(记者黄鑫宇)中签率0.17%、0.04%、0.03%……今年以来北交所新股申购行情...
增长53%!1至4月雄安民营企... 河北日报客户端讯(康晓博、王敏)从雄安海关获悉,今年1至4月,雄安新区进出口总值14.7亿元,同比增...
深圳华大智造科技股份有限公司关... 本公司董事会及全体董事保证本公告内容不存在任何虚假记载、误导性陈述或者重大遗漏,并对其内容的真实性、...
金价狂飙,金表也不好卖了 泡泡玛特、老铺黄金、蜜雪冰城股价屡创新高,被港股市场称为“新消费三姐妹”。与它们有着异曲同工之妙的金...
汉口银行拟吸收业绩下滑资产,谋... 文 | 刘振涛谋求IPO的汉口银行,拟吸收合并村镇银行,谋划异地扩张。近日,汉口银行发布公告,将在6...
大连银行迎新帅 能否走出“消失... “ 过去十年,大连银行业绩可谓是原地踏步,2018年到2023年,净利润更是六连降。去年,大连银行结...
雷军凶猛,兵临董明珠城下 雷军... 家电大战,全面开打。文 | 华商韬略 东木褚小米的大家电业务爆发了,营收首次突破1000亿大关。去年...
财报里的十大跨境巨头:出海增长... 作者 | 唐飞编辑 | 谭格2024年的跨境电商江湖,正经历着前所未有的冰火两重天。一边是安克创新靠...
A股、港股云计算概念双双走强,... 6月5日,三大指数集体高开,盘中创业板指走高,港股方面,恒生科技指数涨幅走阔。A股、港股云计算概念双...
财报解读|蔚来一季度营收增长2... 21世纪经济报道记者 郑植文 上海报道6月3日,蔚来汽车发布了2025年第一季度财报。数据显示,20...
广发基金王明旭:面对市场逆风,... (来源:资事堂)过去五年里,我们对话了数百名基金经理,广发基金王明旭无疑是极具特点且与众不同的。第一...
小商品城股价创历史新高 小商品... 6月5日,小商品城股价振荡上扬,最高涨至18.99元/股,创下历史新高。截至记者发稿,公司市值达10...
张裕A被调出深证成指,总经理孙... 瑞财经 刘治颖 6月3日,深交所发布公告称,将于6月16日对深证成指、创业板指、深证100等指数实施...
信音电子:6月4日融资买入26... 证券之星消息,6月4日,信音电子(301329)融资买入268.8万元,融资偿还146.89万元,融...
李斌:蔚来正在走出最低谷,二季... 成立11年,蔚来遭遇多次起伏。 2019年时,蔚来靠外部融资成功复活;而今年一季度,在外部唱衰之下,...
海科新源:6月4日融资买入38... 证券之星消息,6月4日,海科新源(301292)融资买入381.85万元,融资偿还265.17万元,...
长安、东风重组中止 兵装集团汽... 经观要闻 经过116天的磋商与谈判之后,两大汽车央企东风与长安的重组计划中止。 长安汽车(0006...
“苏超”引爆A股足球概念持续狂... 6月5日早盘,足球概念再度走强,截至发稿,金陵体育、共创草坪涨停,星辉娱乐涨超9%,探路者、康力源、...
人工智能产业迎政策利好,恒生互... 6月5日早盘,港股集体走强。截至发稿,恒生互联网科技业指数涨2.38%,微盟集团涨超6%,快手-W、...
商业航天概念股爆发,超捷股份超... 6月5日,商业航天概念股爆发,超捷股份超逾15%,天银机电、上海瀚讯、航宇微涨逾3%。
「开盘」A股三大股指集体小幅高... A股三大股指6月5日集体小幅高开。其中,沪指涨0.06%报3378.22点,深成指涨0.08%报10...
长安与东风不再合并,兵器装备集... 红星资本局6月5日消息,今日早间,长安汽车(000625.SZ)、中光学(002189.SZ)、湖南...
CRO概念股走低,金凯生科跌逾... 6月5日,CRO概念股走低,金凯生科跌逾8%,鸿博医药、泰格医药、成都先导、昭衍新药跌逾2%。
体育概念再度活跃,共创草坪四连... 6月5日,体育概念再度活跃,共创草坪四连板,冰山冷热、元隆雅图快速拉升涨停,金陵体育冲击涨停。
6月5日投资避雷针:7天5板人... 导读:财联社6月5日投资避雷针,近日,A股及海外市场潜在风险事件如下。国内经济信息方面包括:1)本周...
楼市已经触底,明年会回暖吗?国... 我最近一直在研究房地产市场,发现2024年的数据确实挺吓人的: 全国新建商品房销售面积同比下降12....
百亿市值大族数控冲击IPO:5... 瑞财经 王敏 5月30日,据港交所官网,深圳市大族数控科技股份有限公司(以下简称“大族数控”)向港交...
李在明上台,韩国转机将至? 李... 6月4日上午11时许,韩国新任总统李在明在国会正式宣誓就职,开启五年任期。李在明在就职讲话中表示,将...