【算法笔记】实现单链表有关功能的基本思路

        实现单链表的各种功能的代码中往往绕不开三个要考虑的因素:指针位置循环内的算法对头结点的特殊处理。本文将围绕这三个点,从宏观视角上探讨实现单链表各种功能的通用的思路——单链表程序的处理模型。

一、指针位置

        链表的操作单位是结点,而往往解决问题时,我们指针(以下简写为p)指向的结点与将要操作的结点(假设改结点指针为o)可能不是同一个结点,这便导致了两个指针——p和o有位置上的相对差异。

         举个例子,现在我要删除链表中元素为2的结点,那么我会使用指针p从头指针head处开始往后遍历,直到我遇到要删除的结点,然后对该结点进行删除操作,我将被操作的结点的指针命名为o,那在这个程序中,p和o这两个指针的位置就是相同的。

 图1.1 当前结点p与操作结点o同位置

        再如,我现在要删除元素为2的结点之后的一个结点,那同样使p从head开始遍历,直到遇见元素为2的结点,此时p在结点2处。但我要删除的结点是2之后的结点3,所以o在结点3处,就产生了指针p与o的位置差异。

图1.2 当前结点p在操作结点o之前

        同时,指针o还有可能在p之前。例如我需要删除元素为2的结点之前的一个结点,此时p指向结点2但o指向结点1,即指针o在p之前。

图1.3 当前结点p在操作结点o后

        分类这些指针的相对位置有何作用到后面再来讨论,现在再来看几个更复杂的模型。

        以上模型都只关注单个结点,而很多时候我们不止要关注一个结点。例如,我要找到链表中元素相同的结点并删除,使链表无重复元素。那还是老方法,让p从head处开始遍历,不同的是,在遍历的同时我们还要判断当前结点元素与下一结点元素是否相同,相同则删除当前结点。此时我们关注了两个结点,当前结点和下一结点。我将要操作的结点(即删除)的指针仍命名为o,对另一个关注的结点命名为c。那么几个指针的相对位置就如图1.4所示。

图1.4 c在p和o之后

        还是刚才的例子,是这次找到相同元素时我们选择删除下一结点。

图1.5 o在p和c之后

        最后还有两种情况,不再赘述其对应的例子。

图1.6 c在p和o之前

图1.7 o在p和c之前

        以上是单链表基础的指针相对位置模型,我将从前文提到的三因素中剩余的两个——“循环内的算法”,“对头结点的特殊处理”来分析以上七个模型孰优孰劣,给出实现单链表各种功能的基本思路。

二、循环内的算法

        循环内的算法是多变的,在此我们只考虑一个指标,就是当我需要操作一个结点时,是否要重新遍历来找到他的位置。

        如图1.1的情形,p和o在同一结点上,那么当我要删除o指向的结点时,我需要将其前置结点的指针域修改,但此时我没有前置结点的位置,因此只能重新从头遍历找到前置结点。无疑,这将是一个非常耗时的工作,因此从这个角度上来看,当我们的操作是删除,改动之类,并且涉及前置结点指针域改动之时,模型1不是一个好选择。当然,不涉及指针域的改动,只涉及元素的改动时,模型1还是不错的选择。

        从遍历的高效性上来看,以上模型被分为了三类:

1.修改前置结点指针域时不用重新遍历:模型2,模型5

2.不修改前置结点指针域时不用重新遍历:模型1,模型4

3.不修改前置结点指针域时也要重新遍历:模型3,模型6,模型7

        无疑,从遍历效率上来看,模型2和模型5是最优模型。在不可避免的情况下,模型1和模型4是次优模型。而模型3,模型6和模型7是我们应当避免的低效模型。

        从图形上我们可以总结出最优模型的特征就是指针p一定在o之前,即当前遍历的结点在操作结点之前,那么即使要修改前置结点的指针域,也可以通过p来修改。在实际应用中我们不会写指针o,而是用p->next来代替。(以C++代码为例)

        次优模型的特点是指针p和o一定在同一结点上,在实际应用中o是不存在的,都是用指针p来进行操作。

        低效模型的特点是指针o在p之前。

三、对头结点的特殊处理

        链表中最特殊的结点是头结点,往往我们要对头结点特殊考虑特殊处理。刚才提到,有的时候我们只能被迫选择次优模型。这是因为对于最优模型来说,他永远不会操作头结点。因此当不需要考虑头结点是否会改动时,我们可以直接选择最优模型。

        而必须要考虑头结点时我们只能选择次优模型,因为相对于最优模型来说,次优模型可以扫描到头结点。但前文也说过,次优模型在修改前置结点的指针域时有很大阻碍。那么有没有办法在必须选择次优模型使还能应对需要改变前置结点指针域的情况呢?答案就在“【算法笔记】关于链表头结点是否需要储存数据的思考”这篇文章中。我们可以在链表头插入一个新的空头结点,使指针p从新头结点出发,o从旧头结点出发,这就将次优模型改造成了一种最优模型,同时还能保证旧头结点也能被扫描到。

图3.1 模型1改

图3.2 模型4改

        总结:第一,不改动前置结点指针域,不考虑头结点的操作:最优模型,次优模型。

                   第二,改动前置结点指针域,不考虑头结点的操作:最优模型。

                   第三,不改动前置结点指针域,考虑头结点的操作,次优模型。

                   第四,改动前置结点指针域,考虑头结点的操作,次优模型改。

        即:改动前置结点指针域就排除次优模型,考虑头结点操作就排除最优模型。都排除就改装次优模型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值