线性表

第二章 线性表

线性表的基本概念

线性表是由 n 个数据元素(结点)组成的 有限序列

数据元素的个数 n 定位为表的长度:

  1. n = 0 时,称为 空表,记作 () 或 ;
  2. 将非空的线性表(n > 0),记作: ;

线性表的 描述

  1. 起始结点, 为 终端结点
  2. 对任意相邻结点 和 , 称为 的 直接前驱, 称为 的 直接后继
  3. 数据元素 只是个抽象符号,其具体含义在不同情况下可以不同;

线性表的 逻辑结构特征

  1. 线性表中结点具有 一对一 的关系;
  2. 有且 仅有一个起始结点 ,没有直接前驱,有且仅有一个直接后继 ;
  3. 有且 仅有一个终端结点 ,没有直接前驱,有且仅有一个直接前驱 ;
  4. 其余的内部结点 ,都有且仅有一个直接前驱 和一个直接后继 ;

线性表的 基本运算

命令 含义 描述
Initiate(L) 初始化 建立 一个空表L
Length(L) 求表长度 返回线性表L的 长度
Get(L,i) 取表元 返回线性表 第 i 个 元素,当 i 不满足 1<=i<=Length(L) 时,返回一个特殊值
Locate(L,x) 定位 查找 元素值等于 x 的结点,存在多个值时返回结点中最小序号的值,找不到则返回 0
Insert(L,x,i) 插入 在线性表 第 i 个 元素前 插入 一个值为 x 的新数据元素
Delete(L,i) 删除 删除 线性表 第 i 个 元素

线性表的顺序存储

线性表的顺序存储的方法是:将表中的结点依次存放在计算机内存中 一组连续的存储单元 中,数据元素在线性表中的邻接关系决定它们在存储中间的存储位置,即逻辑结构中相邻的结点其存储位置也相邻。

用顺序存储实现的线性表称为 顺序表。一般使用 数组 来表示顺序表。

顺序表是用 一维数组 实现的线性表,数组下标可以看成是元素的相对地址。

线性表顺序存储的类型定义

顺序存储线性表时,需要存储:存储单元大小、数据个数

  1. 线性表大小:MaxSize
  2. 线性表长度:Length
  3. 所存放数据的类型:DateType
顺序表的结构体定义.png

顺序存储结构的特点:

  1. 线性表的逻辑结构与存储结构一致;
  2. 可以对数据元素实现随机读取;

线性表的基本运算在顺序表上的实现

顺序表的插入

插入算法的注意点:

  1. 当表空间已满,不可再做插入操作;
  2. 当插入位置为非法位置,不可做正常插入操作;

合法位置:1 <= i <= n+1

顺序表的运算实现-插入.png

插入算法的分析:

假设线性表中含有 个数据元素

在进行插入操作时,有 个位置可插入

在每个位置插入数据的概率是:

在 位置插入时,要移动 个数据

假定在 个位置上插入元素的可能性相等,则平均移动元素的个数为:

平均时间复杂度为:

顺序表的删除

删除算法的注意点:

  1. 当要删除第 i 个元素的位置不在表长范围内时,为非法位置,不能做正常的删除操作;

合法位置:1 <= i <= n

顺序表的运算实现-删除.png

删除算法的分析:

假设线性表中含有 个数据元素

在进行删除操作时,有 个位置可删除

在每个位置删除数据的概率是:

在 位置删除时,要移动 个数据

假定在 个位置上删除元素的可能性相等,则平均移动元素的个数为:

平均时间复杂度为:

顺序表的定位

顺序表的运算实现-定位.png

定位算法 LocateSeqlist(L,X) 的功能是:求L中值等于X的结点的 最小序号,当不存在这种结点时结果为0。

序号:指第几个。

定位算法的分析:

假定在 个位置上定位元素的可能性相等,则平均移动元素的个数为:

平均时间复杂度为:

顺序表实现算法的分析

顺序表的运算实现-分析总结.png

在分析线性表的顺序表实现算法时,一个重要指标就是数据元素的比较和移动的次数。

条件:假设表的长度 length=n

顺序表的 优点

  1. 无需增加额外的存储空间(为表示结点间的逻辑关系);
  2. 可以 方便地随机存取 表中的任一结点;

顺序表的 缺点

  1. 插入和删除运算不方便,必须移动大量的结点;
  2. 顺序表要求占用连续的空间,存储分配只能预先斤进行,因此 当表长度变化较大时,难以确定合适的存储规模

线性表的链接存储

链接方式存储的线性表简称为 链表

链表的具体存储方式:

  1. 用一组 任意 的存储单元来存放;
  2. 链表中结点的逻辑次序和物理次序 不一定相同。还必须存储指示其后继结点的地址信息;

单链表

单链表的结点结构.png

结点结构分析:

  1. data 域:存放结点值的 数据域
  2. next 域:存放结点的直接后继的地址的 指针域(链域)

所有结点通过指针链接而组成单链表。其中:

  1. NULL:称为 空指针
  2. Head:称为 头指针变量,存放链表中第一个结点的地址;
单链表的一般图示法.png

单链表的一般图示法:

使用 箭头 来表示链域中的指针,单链表可以表示为下图形式。

单链表中 第一个 结点内一般 不存数据,称为 头结点,利用头指针存放该结点地址。

头结点的结点结构:

  1. data 域,不存数据;
  2. next 域,存放 首结点 的地址,或者是空指针;

为什么加设头结点?:为了方便运算

通过指针删除直接后继结点.png

单链表的类型定义:

typedef struct node
{
  DataType data;
  struct node *next;
} Node, *LinkList;

线性表的基本运算在单链表上的实现

单链表的初始化

空表由一个头指针和一个头结点组成,算法描述如下:

LinkList InitiateLinkList()
{
  LinkList head;
  head = malloc(sizeof(Node)); // 动态创建一个结点,它是头结点
  head -> next = NULL;
  return head;
};

// malloc(size):开辟空间

在算法中,变量 head 是链表的头指针,它指向新创建的结点,即头结点。一个空单链表仅有一个头结点,它的指针域为NULL。

单链表的求表长

int lengthLinkList(LinkList head)
{
  Node *p = head;
  int j = 0;
  while(p -> next != NULL)
  {
    p = p -> next;
    j ++;
  };
  return j;
};

单链表的读表元素

Node *GetLinkList(LinkList head, int i)
{
  Node *p = head -> next;
  int c = 1;
  while((c < i) && (p != NULL))
  {
    p = p -> next;
    c ++;
  };
  if(i == c) return p;
  else return NULL;
};

单链表的定位

在定位运算中,找到需要的结点,则返回其对应的 序号。若为找到,则返回 0。

int locateLinkList(LinkList head, DateType x)
{
  Node *p = head -> next;
  int i = 1;
  while(p != NULL && p -> data != x)
  {
    p = p -> next;
    i ++;
  };
  if (p != NULL) return i;
  else retrun 0;
};

单链表的插入

单链表的插入运算实现.png
void InsertLinkList(LinkList head, DataType x, int i)
{
  Node *p, *q;
  
  if (i == 1) q = head;
  else q = GetLinkList(head, i - 1);

  if (q == NULL) exit("找不到插入的位置");
  else
  {
    p = malloc(sizeof(Node));
    p -> data = x;
    p -> next = q -> next;
    q -> next = p;
  };
};

单链表的删除

单链表的删除运算实现.png
void DeleteLinkList(LinkList head, int i)
{
  Node *p, *q;
  
  if (i == 1) q = head;
  else q = GetLinkList(head, i - 1);

  if (q != NULL && q -> next != NULL)
  {
    p = q -> next;
    q -> next = p -> next;
    free(p);
  }
  else exit("找不到删除的结点");
}

// free(),释放内存空间

其他链表

循环链表

循环链表示意图.png

普通链表的 终端结点 的 next 值为 NULL

循环链表的 终端结点 的 next 指向 头结点。在循环链表中,从任一结点出发都能够扫描整个链表。

在循环链表中附设一个 rear 指针指向尾结点。

双向循环链表

双向循环链表示意图.png

在链表中设置 两个 指针域,一个指向后继结点,一个指向前驱结点。这样的链表叫做 双向循环链表

双向循环链表的 结构定义

typedef struct dbnode {
  DataType data;
  struct dbnode *prior, *next;
}DlinkList, *dbpointer;

双向循环链表适合应用在 需要经常查找结点的前驱和后继的场合。找前驱和后继的复杂度均为: 。

假设双向循环链表中 p 指向某结点,则有:p -> next -> prior 与 p -> prior -> next 相等

双向循环链表的结点删除

双向循环链接的结点删除.png
  1. p -> next -> prior = p -> prior;
  2. p -> prior -> next = p -> next;
  3. free(p);

双向循环链表的结点插入

双向循环链接的结点插入.png

在 p 所指向的结点的后面插入一个新结点 *t,需要修改四个指针:

  1. t -> prior = p;
  2. t -> next = p -> next;
  3. p -> next -> prior = t;
  4. p -> next = t;

顺序实现和链式实现的比较

单链表的每个结点包括 数据域指针域,指针域需要占用额外空间。

顺序表 预先分配内存空间,如果预先分配得过大,将造成浪费,若分配得过小,又将发生上溢。

单链表 不需要 预先分配内存空间,只要内容空概念没有耗尽,单链表中的结点个数就没有限制。

时间性能的比较

顺序表 链表
读表元 O(1) O(n)
定位 O(n) O(n)
插入 O(n) O(n)
删除 O(n) O(n)

你可能感兴趣的:(线性表)