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

下表显示了基于数组和基于栈的编辑器实现的编辑器操作的计算复杂度。
1

哪个实现更好呢? 没有一些usage pattern的知识,就不可能回答这个问题。 然而,通过了解一些人们使用编辑器的方式,表明基于堆栈的策略可能会更有效率,因为实现(插入和删除)的操作比使用编辑器的其他操作更频繁。
鉴于相关操作的相对频率,这种权衡似乎是合理的,所以栈实现有可能做得更好。毕竟,现在六个基本编辑操作中的每一个在两个编辑器实现中的至少一个中始终运行。在使用数组方法时,插入速度慢, 相比之下,移动到缓冲区的前端在数组情况下是快速的,但在栈情况下缓慢。 然而,没有一个操作似乎基本上很慢,因为总是有一些实现使操作更快。 那么是否有可能开发一个所有操作都很快的实现? 这个问题的答案可能是“有的”,但是发现这个难题的关键将需要你学习一种在数据结构中表示排序关系的新方法。

基于linked - list的实现

作为为编辑器buffer找到更有效的表示的初始步骤,有必要了解为什么以前的方法未能为某些操作提供有效的服务。在数组实现的情况下,答案是显而易见的:问题来自于当你需要在缓冲区开头附近插入一些新文本时,必须移动大量字符。例如,假设你尝试补全插入字母表:
2

当你发现你没有留下字母B的时候,你必须将下一个24个字符的每一个字符向右移动一个位置,以便为丢失的字母留出空间。只要buffer没有太长,现代电脑就可以相对较快地处理这种移动操作;即使如此,如果缓冲器中的字符数足够大,则延迟最终将变得明显。
然而,假设你在发明现代电脑之前正在写作。如果你是使用打字机并已经输入完整的行,会发生什么?为了避免给人的印象是你已经忽略了字母表中的一个字母,你可以简单地拿出一支笔,并使用以下符号:
3

结果可能是微不足道的,但在这种绝望的情况下仍然可以接受。
这种人类编辑符号的一个优点是,它允许你暂停规则,说明所有字母按照它们在打印页面上出现的形式按顺序排列。线下方的克拉符号告诉你的眼睛,阅读A后,你必须向上移动,读取B,回来,读取C,然后继续按顺序。注意使用此插入策略的另一个优点也很重要。不管行是多长,你要绘制的是新的字符和克拉符号。只是使用铅笔和纸张而已,插入时间不变。

linked list的概念

你可以采用类似的方法来设计编辑buffer的表示。实际上,你甚至可以推广这个想法,这样,而不是将缓冲区主要表示为数组,而是与正常序列有偏差,你只需将缓冲区中每个字母的箭头绘制到跟随它的字母上即可。 原始缓冲区内容可能表示如下:
4
如果你需要在字符A之后添加字符B,你需要做的就是:(1)将B写到某处,(2)从B绘制一个箭头到A指向的字母(当前是 C),以便你不会失去对其他字符串的跟踪,(3)更改从A指向的箭头,以便现在指向B,如下所示:
5

给定用于绘制这些图表的符号,发现用于在C ++中实现新策略的主要工具是指针可能并不奇怪。指针的一大优点在于,它们使一个数据对象能够包含指向第二个对象的指针。可以使用此指针来表示排序关系,这与上图中箭头所暗示的顺序关系非常相似。以这种方式使用的指针通常被称为链接(links)。当这样的链接用于创建线性排序的数据结构,其中每个元素指向其后继者时,该结构称为链表(linked list)。

设计linked-list的数据结构

如果要将此基于指针的策略应用于编辑器缓冲区的设计,你需要的是一个链接的字符列表。所以你必须将缓冲区中的每个字符与指示列表中下一个字符的指针相关联。 然而,该指针不能是简单的字符指针; 你需要的是指向下一个字符/链接组合的指针。 要使linked-list工作,你必须创建一个单一的结构,其中包含与应用程序相关的数据(在本例中为字符)和指向同一类型的另一个结构的指针,然后用于指示内部排序。应用数据和指针的这种组合成为链表的基本构建块,其通常被称为单元(cell)。
为了使单元格的想法更容易可视化,我们可以从结构图开始。 在编辑器buffer中,单元格有两个组件:一个字符和链接到以下的单元格。 因此,单个单元格可以如下图所示:
6

然后,你可以通过将几个这些单元格链接在一起来表示一系列的字符。例如,字符序列ABC可以被表示为包含以下小单元集合的链表:
这里写图片描述
如果C是序列中的最后一个字符,则需要通过在该单元格的链接字段中添加特殊值来指示该事实,以指示列表中没有其他字符。当用C ++编程时,为此目的通常使用特殊的指针值NULL。然而,在列表结构图中,通常在框中指定带有对角线的NULL值,如前面的示例所示。
为了在C ++中表示这些单元格结构图,你需要定义一个结构类型来保存数据。单元格结构必须包含字符的ch字段和指向下一个单元格的link成员。 这个定义有点不寻常,因为Cell类型是根据自身定义的,因此是一种递归类型(* recursive type*)。 以下类型定义正确表示单元格的结构:

struct Cell {
    char ch;
    Cell *link;
};

用链表来表示buffer

我们现在可以考虑如何使用链表来表示编辑器缓冲区。一种可能性是将链表中的初始指针设置为buffer,但这种方法忽略了你还必须表示光标的事实。指示当前缓冲位置的需要表明,EditorBuffer类的数据成员应该包含两个指向Cell对象的指针:一个指针,指示缓冲区启动的位置。另一个指针,指示当前光标位置。
这个设计似乎是合理的,直到你试图弄清楚光标指针的工作原理。 如果有一个包含三个字符的buffer,那么你的第一个想法是使用具有三个单元格的链表来表示该buffer。在此设计中,光标成员也表示为指向单元格的指针,这可能根据光标的位置而改变。不幸的是,有一个问题,给定一个包含三个字符的缓冲区,光标有四个可能的位置,如下所示:
这里写图片描述
如果光标字段只能指向三个单元格,则不能够表示每个可能的光标位置。
有许多方法来解决这个问题,但通常情况最好的是分配一个额外的单元格,以便列表包含每个可能的插入点的一个单元格。通常,该单元格在list的开始处,称为虚拟单元格(dummy cell.)。 虚拟单元格中的ch字段的值是不相关的,并且通过用灰色背景填充值字段在图中指示。 当使用虚拟单元格方法时,光标字段指向逻辑插入点之前的单元格。例如,包含ABC,缓冲区开头的游标的缓冲区将如下所示:
这里写图片描述
start和cursor指向虚拟单元格,插入将在该单元格之后立即出现。 如果光标字段指示缓冲区的结尾,则该图如下所示:
这里写图片描述
私有部分中出现的唯一实例变量是起始和光标指针。 即使这个结构的其余部分不是正式的对象的一部分,它将帮助后来必须使用这个结构的程序员。我们在bufferpriv.h文件中记录数据结构设计,如下:

/*
 *说明:buffer 数据结构的实现
 *--------------------------
 *在缓冲区的链表实现中,缓冲区中的字符存储在单元结构中,
 *每个单元格结构都包含一个字符和指向链中下一个单元格的指针 。
 *为了简化用于维护光标的代码,此实现在列表的开头添加了一个额外的“dummy”单元格。 
 *该单元格中的字符不被使用,但是在数据结构中提供了一个单元格,
 *当游标位于缓冲区的开头时,该值指向游标。
 */ 
private:
    /*
     *结构类型:Cell 
     *-------------------------- 
     *该结构类型在实现中本地使用以将每个单元格存储在链表表示中。 
     *每个单元格包含一个字符和指向链中下一个单元格的指针 
     */ 
     struct Cell{
        char ch;
        Cell *link;
     }
     /*实例化变量*/
     Cell *start;   //指向虚拟的单元格 
     Cell *cursor; // 指向光标之前的单元格 
    /* 使得对象复制不合法 */
    EditorBuffer(const EditorBuffer & value) { }
    const EditorBuffer & operator=(const EditorBuffer & rhs) { return *this; }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值