《大话数据结构》之线性表

2019-03-06  本文已影响1人  我才是臭吉吉

1. 线性表的主要类型

线性表在存储方式上划分,可分为:

2. 顺序存储结构

所谓顺序存储结构,即使用一段地址连续的存储单依次存储线性表的数据元素。

我们可以使用数组来描述线性表的顺序存储结构。

2.1 地址计算方法(读取数据)

通俗地讲,与数据下标访问的方式类似,后一个数据的地址是前一个地址加上数据大小。对于第i个数据的储存位置,即可使用以下方式得出:

LOC(a(i + 1)) = LOC(a(i)) + (i - 1) * c

由于任意地址的数据均可以由以上公式一步得出,故顺序存储结构的存储时间为O(1),即时间复杂度为常数阶

2.2 插入数据

主要步骤为:

  1. 查找到要插入的位置i
  2. 将i之后的数据依次后移一个位置
  3. 在i的位置上插入数据e
  4. 表长加1

总的执行次数为 1 + n + 1 + 1,故插入操作的时间复杂度为O(n)

2.3 删除数据

主要步骤为:

  1. 查找到要删除的位置i
  2. 在i的位置上取出数据e
  3. 将i之后的数据依次前移一个位置
  4. 表长减1

总的执行次数为 1 + 1 + n + 1,故删除操作的时间复杂度为O(n)

2.4 顺序存储结构的优缺点

优点:

缺点:

3. 链式存储结构(以单链表为例)

链式存储结构的特点,是使用一组任意的存储单元存储线性表的数据元素。这些存储单元可以是不连续的,任意位置均可。

链式存储结构中的数据结点(Node),除了包含自身数据(数据域),还需要存储一个后继结点的地址(指针域)。

当n个数据结点组成一个链表,其中每一个结点都只包含一个指针域时,这种链式结构称为单链表

我们这里使用单链表来描述线性表的链式存储结构。

3.1 单链表的基本描述

3.2 头指针与头结点的异同

头指针:

头结点:

3.3 单链表的读取

由于每一个结点的存储位置都在前一个结点的指针域中保存,故获取指定位置的结点数据,需要从单链表的头结点开始,依次查找。故其查找的时间复杂度为O(n)

查找方式,即循环”指针后移“。

3.4 单链表的插入

单链表的插入过程,如下图所示:

单链表的插入.jpg

其中,待插入的结点s(数据e),插入到结点p和p->next之间(数据a(i)和数据a(i+1))之间。其关键操作为:

// 将插入结点s的指针域指向原后结点p->next
s->next = p->next;
// 原结点p的指针域指向插入结点s
p->next = s;

故此插入行为的复杂度为O(1)。

不过,由于查找插入位置结点的复杂度为O(n),故最终单链表插入操作的时间复杂度为O(n)

3.5 单链表的删除

单链表的删除过程,如下图所示:

单链表的删除.jpg

其中,a(i)为待删除元素,即p->next结点。删除后即将结点p的指针域指向a(i+1)元素的结点。

// 待删除结点的前一个结点的指针域 指向 待删除结点的后一个结点
p->next = p->next->next;

// 或

q = p->next;
p->next = q->next;

// 最后,释放删除结点的内存
free(q);

故此删除行为的复杂度为O(1)。

不过,由于查找删除位置结点的复杂度为O(n),故最终单链表删除操作的时间复杂度为O(n)

3.6 单链表的整表创建

// 创建单链表L(空表)
*L = (LinkList)malloc(sizeOf(Node));
(*L->next) = NULL;

for (int i = 0; i < n; i++) {
    // L为单链表的头结点,p为待插入结点
    p->next = (*L)->next;
    (*L)->next = p;
}
// 创建单链表L(空表)
*L = (LinkList)malloc(sizeOf(Node));
(*L->next) = NULL;

// r为指向尾部的结点
r = *L;

for (int i = 0; i < n; i++) {
    // p为待插入结点,插入到尾结点后面
    r->next = p;
    // 新加入结点作为新的尾结点
    r = p;
}

// 尾结点指针域赋值
r->next = NULL;

3.7 单链表的整表删除

做法:依次边遍历边删除

// L为待删除链表,p和q分别指向当前结点

// p指向第一个结点
p = (*L)->next;

while(p) {
    // q指向后继结点
    q = p->next;
    // 删除当前结点
    free(p);
    // p指向后继结点
    p = q;
}

// 循环结束后,p结点已经不存在
// 置为空表,让头结点的头指针为空
(*L)->next = NULL;

3.8 单链表与顺序存储结构实用性对比

4. 其他类型链表结构

4.1 静态链表

静态链表是在没有指针的情况下实现的“模拟”单链表,本质上是使用数组进行描述并实现。

其结构如下所示:

静态链表.jpg

其中,数组分为两部分结构:

  1. 以数组的末尾元素为头结点,根据游标(cur)索引,直到元素的游标为0为止,连接而成的结构,也就是真正的单链表
  2. 以数组第一个元素为头结点,根据游标索引,直到元素的游标为末尾元素的下标为止,作为备用链表,用于动态分配数据(插入、移除数据使用)。

主要结构:

静态链表的优缺点

优点:
避免了插入和删除数据时对大量数据的移动,只要修改游标即可实现。

缺点:

  • 由于使用数组进行实现,依然无法避免对内存空间进行考虑。
  • 失去了顺序存储结构随机存取的特性。

备注:

静态链表的创建、插入和删除等操作的Objective-C版本实现:项目地址

4.2 循环链表

循环链表作为单链表的扩展,在链表尾部定义了尾指针,指向最后一个结点rear。其指针域next指向链表的头结点。

同时,判定链表结束的条件变为了rear->next == 头指针p。

4.3 双向链表

双向链表作为单链表的扩展,在结点的头部和尾部均设有指针域(prior和next),分别指向前驱结点和后继结点。可以双向访问链表的元素。

上一篇 下一篇

猜你喜欢

热点阅读