C++算法学习——数据结构——链表(2)

##链表中的插入
无论光标位于何处,链表的插入操作都包括以下步骤:

  1. 为新单元格分配空间,并将指针存储在此单元格中的临时变量cp中的。
  2. 将要插入的字符复制到新单元格的 ch 成员中。
  3. 转到缓冲区光标字段指示的单元格,并将其link成员复制到新单元格的link成员。 此操作确保不会丢失超出当前光标位置的字符。
  4. 更改光标所在单元格中的link成员,使其指向新单元格。
  5. 更改缓冲区中的光标字段,以便它也指向新的单元格。此操作确保在重复插入操作之后插入下一个字符。 为了说明这个过程,假设你想将字母B插入到当前包含的缓冲区中

为了说明这个过程,假设你想将字母B插入到当前包含的buffer中
这里写图片描述
光标在A和C之间,如图所示。 插入之前的情况如下所示:
这里写图片描述
第一步,分配一个新单元格,并在变量cp中存储一个指针,如下所示:
这里写图片描述
第二步,将字符B存储到新单元格的ch中,这将留下以下配置:
这里写图片描述
第三步,将出现在cursor中的link地址复制到新单元格的link中。该link字段指向包含C的单元格,因此生成的图形如下所示:
这里写图片描述
第四步,更改当前单元格中由cusor的link成员,使其指向新分配的单元格的ch值,如下所示:
这里写图片描述
注意,buffer现在具有正确的内容。如果你从缓冲区开始处的dummy单元格中按箭头,则沿路径顺序遇到包含A,B,C和D的单元格。
最后一步,包括更改缓冲区结构中的cursor字段,以便它也指向新的单元格,这将导致以下配置:
这里写图片描述
当程序从insertCharacter方法返回时,临时变量cp被释放,这导致以下最终状态:
这里写图片描述
这就代表了下列的结果:
这里写图片描述

将上图所示的几幅图,转换为我们所学的C++代码,倒是显得简单了很多

void EditorBuffer::insertCharacter(char ch) {
    Cell *cp = new Cell;
    cp->ch = ch;
    cp->link = cursor->link;
    cursor->link = cp;
    cursor = cp;
}

因为此方法中没有循环,所以insertCharacter方法是在固定的时间内运行的。
##链表的删除
要删除链接列表中的单元格,您只需将其从指针链中删除即可。 我们假设缓冲区的当前内容是:
这里写图片描述
用图形表示为:
这里写图片描述
删除光标后的字符意味着你需要通过更改包含A的单元格的link来消除包含B的单元格,以便进一步指向下一个字符。要找到该字符,你需要遵循沿着单元格中的link,并继续找到下一个link。 因此,必要的声明:

cursor->link = cursor->link->link;

执行此语句将使缓冲区处于以下状态:
这里写图片描述
因为包含B的单元格不再能通过链表结构访问了(因为此时它已经不在链表中了),所以通过调用delete来释放其存储是一个很好的策略,如下面的deleteCharacter实现所示:

void EditorBuffer::deleteCharacter() {
    if (cursor->link != NULL) {
        Cell *oldCell = cursor->link;
        cursor->link = cursor->link->link;
        delete oldCell;
    }
}

请注意,你需要一个像oldCell这样的变量,以便在调整link指针时保存指向要释放的单元格的指针的副本。 如果你不保存此值,则在调用delete时将无法引用该单元格。
##链表中的cursor的运动
EditorBuffer类中的剩余操作只需移动光标即可。你将如何在链表缓冲区中实现这些操作? 其中的两个操作(moveCursorForward和moveCursorToStart)在链表模型中很容易执行。例如,要向前移动光标,你只需要从当前单元格中取出link,并将该指针存储在缓冲区的cursor字段中,使其成为新的当前单元格。完成此操作所需的声明很简单。

cursor = cursor->link;

假设,buffer的光标位置如下
这里写图片描述
此时对应的模式图如下:
这里写图片描述
执行moveCursorForward之后,变为:
这里写图片描述
当然,当您到达缓冲区的末尾时,您将无法向前移动。 moveCursorForward的实现必须检查这种情况,所以完整的方法定义如下所示:

void EditorBuffer::moveCursorForward() {
    if (cursor->link != NULL) {
        cursor = cursor->link;
    }
}

将光标移动到缓冲区的开头同样容易。无论光标在何处,你都可以将start中的内容复制到cursor中,始终将其还原到缓冲区的开头。 因此,moveCursorToStart的实现是简单的:

void EditorBuffer::moveCursorToStart() {
    cursor = start;
}

然而,操作moveCursorBackward和moveCursorToEnd更复杂。例如,假设cursor位于包含字符ABC的缓冲区的末尾,并且你要移回一个位置。 在其图形表示中,缓冲区如下所示:
这里写图片描述
给定EditorBuffer的结构,指针允许你从指向其所指向的对象的指针中跟踪,但无法反转方向。只给出一个单元格的地址,不可能找出什么单元格指向它。对于指针图,这种限制的效果是你可以从箭头底部的点移动到箭头指向的单元格,但是你永远不能从箭头返回到其上一个。
在缓冲区的列表结构表示中,必须根据缓冲区结构本身可以看到的数据实现每个操作,其中包含start和cursor指针。只看到cursor字段,并且跟随可从该位置访问的链接看起来不太有希望,因为该链上唯一可访问的单元格是缓冲区中最后一个单元格。然而,start指针可以访问整个链表。同时,你显然需要考虑cursor的值,因为你需要从该位置开始备份。
**通过链接列表的值移动,通过跟随链接指针,一次移动单元格是一个非常常见的操作,通常称为遍历(traversing)或行走列表。**要遍历表示缓冲区的列表,首先声明一个指针变量并将其初始化为列表的开头。 因此,在这种情况下,你可能会写;

Cell *cp = start;

要找到光标前面的字符,只要cp的link与cursor的link不匹配,你就继续遍历链表,按照每个link字段将cp从单元移动到单元格。 因此,可以通过在循环中添加以下代码继续执行代码:

Cell *cp = start;
    while (cp->link != cursor) {
    cp = cp->link;
}

当while循环退出时,cp被设置为光标之前的单元格。 与向前移动一样,你需要保护此循环,避免超过缓冲区的限制,因此moveCursorBackward的完整代码将为:

void EditorBuffer::moveCursorBackward() {
    Cell *cp = start;
    if (cursor != start) {
        while (cp->link != cursor) {
        cp = cp->link;
    }
    cursor = cp;
    }
}

出于同样的原因,你可以通过遍历整个链表来实现移动光标到末尾,直到检测到NULL指针,如以下代码所示:

void EditorBuffer::moveCursorToEnd() {
    while (cursor->link != NULL) {
        cursor = cursor->link;
    }
}

下面是上述各种算法的复杂度:
在这里插入图片描述

之前写过的一个有趣的链表的例子,有兴趣可以看看:Light the fires

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值