大话数据结构学习笔记之(3) - 线性表、顺序存储、单链表及静态链表

大话数据结构学习笔记之(3) - 线性表、顺序存储、单链表及静态链表

定义

线性表(List): 零个或多个数据元素的有限序列

数学定义: 若将线性表记为 ( a 1 , . . . , a i 1 , a i , a i + 1 , . . . , a n ) , 则表中 a i 1 领先于 a i a i 领先于 a i + 1 , 称 a i 1 a i 的直接前驱元素, a i + 1 a i 的直接后继元素。 当有 i = 1 , 2 , . . . , n 1 时, a i 仅有一个直接后继, 当 i = 2 , 3 , . . . , n 时, a i 有且仅有一个直接前驱

线性表的抽象数据类型

ADT 线性表(List)
Data

​ 线性表的数据对象集合为 { a 1 , a 2 , . . . , a n } , 每个元素的类型均为DataType。 其中, 除第一个元素 a 1 外,每一个元素有且只有一个直接前驱元素,除了最后一个元素 a n 外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系

Operation

​ InitList(*L); 初始化操作, 建立一个空的线性表 L。

​ ListEmpty(L); 若线性表为空,返回 true, 否则返回 false

​ ClearList(*L); 将线性表清空

​ GetElem(L, i, *e); 将线性表L中的第i个位置元素值返回给e

​ LocateElem(L, e); 在线性表L中查找与给定值e相等的元素,如果查找成功,返回钙元素在表中序号表

​ 示成功;否则返回0表示失败

扫描二维码关注公众号,回复: 921164 查看本文章

​ ListInsert(*L, i, e); 在线性表L中的第i个位置插入新元素e

​ ListDelete(*L, e, *e); 删除线性表L中第i的位置元素, 并用e返回其值

​ ListLength(L); 返回线性表L的元素个数

endADT

线性表的顺序存储结构

即用一段地址连续的存储单元依次存储线性表的数据元素

#define MAXSIZE 20  // 存储空间初始分配量
typedef int ElemType;  // ElemType 类型根据实际情况而定, 这是设为 int
typedef struct
{
    ElemType data[MAXSIZE];  // 数组存储数据元素, 最大值为 MAXSIZE
    int length;  // 线性表当前长度
} SqList;

由上可知,顺序存储结构需要三个属性

  • 存储空间的起始位置:数组data, 它的存储位置就是存储空间的存储位置
  • 线性表的最大存储容量:数组长度MaxSize
  • 线性表的当前长度:length

顺序存储结构的插入操作

插入算法的思路:

  • 如果插入位置不合理,抛出异常
  • 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量
  • 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置
  • 将要插入元素填入位置i
  • 表长加1
// 操作结果:在 L 中第 i 个位置插入新的数据元素 e, L 的长度加 1
Status ListInsert(SqList *L, int i, ElemType e)
{
    if(L->length == MAXSIZE)  // 顺序线性表已满
        return ERROR;
    if(i < 1 || i > L->length + 1)  // 当插入位置 i 不在范围内时
        return ERROR;
    if(i <= L->length)  // 若插入位置不在表尾
    {
        for(int k = L->length - 1; k >= i - 1; k--)  // 将要插入位置后数据元素向后移动一位
            L->data[k + 1] = L->data[k];
    }
    L->data[i - 1] = e;  // 插入新元素
    L->length++;
    return OK;
}

顺序存储结构的删除操作

删除算法的思路:

  • 如果删除位置不合理,抛出异常
  • 取出删除元素
  • 从删除元素位置开始遍历到最后一个位置,分别将它们都向前移动一个位置
  • 表长减1
Status ListDelete(SqList *L, int i, ElemType *e)
{
    if(L->length == 0)
        return ERROR;
    if(i < 1 || i > L->length)
        return ERROR;
    *e = L->data[i - 1];
  if(i < L->length)
  {
    for(int j = i - 1; j < L->length - 1; j++)
        L->data[j] = L->data[j + 1];
  }
    L->length--;
}

线性表顺序存储结构的优缺点

linear_list_sequential_storage_structure_advantages_and_disadvantages

线性表的链式存储结构

定义

为了表示每个数据元素 a i 与其直接后继数据元素 a i + 1 之间的逻辑关系,对数据元素 a i 来说, 除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域成为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素 a i 的存储映像,称为结点(Node)

n个结点( a i 的存储映像)链结成一个链表,即为线性表( a 1 , a 2 , . . . , a n )的链式存储结构, 因为此链表的每个节点中只包含一个指针域,所以叫单链表

链表中第一个结点的存储位置叫做头指针。单链表的第一个结点前附设一个结点,称为头结点

头指针与头结点的异同

head_pointer_and_head_node_same_and_diff

单链表

  • 无头结点的单链表

    single_link_list_without_head_node

  • 有头结点的单链表

    single_link_list_with_head_point

单链表结构

结点由存放数据元素的数据域以及存放后继结点地址的指针域组成

typedef struct Node
{
    ElemType data;
    struct Node *next;
}Node;
typedef struct Node *LinkList;

单链表的插入

单链表第i个数据插入节点的算法思路:

  • 声明一个结点p指向链表第一个结点, 初始化j1开始
  • j < i时,就遍历链表, 让p的指针向后移动, 不断指向下一结点, j累加1
  • 若到链表末尾p为空,则说明第i个元素不存在
  • 否则查找成功,在系统生成一个空结点s
  • 将数据元素e赋值给s->data
  • 单链表的插入标准语句s->next = p->next; p->next = s
  • 返回成功
// 操作结果:在 L 中第 i 个位置之前插入新的数据元素 e, L 的长度加 1
Status ListInsert(LinkList *L, int i, ElemType e)
{
    LinkList p, s;
    p = *L;
    int j = 1;
    while(p && j < i)  // 寻找第 i 个结点
    {
        p = p->next;
        ++j;
    }
    if(!p || j > i)  // 第 i 个元素不存在
        return ERROR;
    s = (LinkList)malloc(sizeof(Node));  // 生成新结点
    s->data = e;
    s->next = p->next;  // 将 p 的后继结点赋值给 s 的后继
    p->next = s;  // 将 s 赋值给 p 的后继
    return OK;
}

单链表的删除

单链表第i个数据删除结点的算法思路:

  • 声明一个结点p指向链表第一个结点,初始化j1开始
  • j < i时,遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
  • 若到链表末尾p为空,则说明第i个元素不存在
  • 否则查找成功,将与删除的结点p->next赋值给q, 即p为欲删除结点的上一结点,q为欲删除结点
  • 单链表的删除标准语句p->next = q->next
  • q结点中的数据赋值给e,返回
  • 释放q结点
  • 返回成功
Status ListDelete(LinkList *L, int i, ElemType *e)
{
    LinkList p, q;
    p = (*L);
    int j = 1;
    while(p->next && j < i)
    {
        p = p->next;
        ++j;
    }
    if(!(p->next) || j > i)  // 第 i 个元素不存在
        return ERROR;
    q = p->next;
    p->next = q->next;  // 将 q 的后继赋值给 p 的后继
    *e = q->data;  // 将 q 结点中的数据赋值给 *e
    free(q);  // 让系统回收此节点,释放内存
    return OK;
}

单链表结构与顺序存储结构优缺点

静态链表

用数组描述的链表叫做静态链表,也叫作游标实现法,是为了给某些没有实现指针的编程语言设计的类似单链表

// 线性表的静态链表存储结构
#define MAXSIZE 1000
typedef struct
{
    ElemType data;
    int cur;  // 游标,为 0 时表示无指向
}Component, StaticLinkList[MAXSIZE];

另外数组第一个和最后一个元素作为特殊元素处理,不存数据。我们通常把未被使用的数组元素称为备用链表。而数组第一个元素,即下标为0的元素的cur就存放备用链表的第一个结点的下标;而数组的最后一个元素的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点作用,当整个链表为空时,则为0。下图所示相当于初始化的数组状态。

static_linked_list_abstract

静态链表的插入

由于静态链表中操作的是数组,而不是动态链表中的结点申请和释放问题。故需实现申请结点和释放结点的功能,方法是将所有未被使用的及被删除的分量用游标链接成备用链表,插入时,从备用链表取得第一个结点作为待插入的新节点

// 若备用链表非空,则返回分配的结点下标,否则返回 0
int Malloc_SLL(StaticLinkList space)
{
    int i = space[0].cur;  // 获取备用链表的第一个结点,即存放在数组第一个元素中的 cur
    if(space[0].cur)
        space[0].cur = space[i].cur;  // 备用链表第一个结点被申请使用,故后移一个
    return i;
}

// 在 L 中第 i 个元素之前插入新的数据元素 e
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
    int j, k, l;
    k = MAXSIZE - 1;
    if(i < 1 || i > ListLength(L) + 1)
        return ERROR;
    j = Malloc_SLL(L);
    if(j)
    {
        L[j].data = e;
        for(l = 1; l < i; l++)
        {
            k = space[k].cur;
        }
        L[j].cur = L[k].cur;
        L[k].cur = j;
        return OK;
    }
    return ERROR;
}

静态链表的删除

// 将下标为 k 的空闲结点回收到备用链表
void Free_SSL(StaticLinkList L, int k)
{
    L[k].cur = L[0].cur;  // 把备用链表的第一个元素 cur 值赋给要删除的分量 cur
    L[0].cur = k;  // 把要删除的分量下标赋给第一个元素的 cur
}

// 删除在 L 中第 i 个数据元素 e
Status ListDelete(StaticLinkList L, int i)
{
    int j, k;
    k = MAXSIZE - 1;
    if(i < 1 || i > ListLength(L))
        return ERROR;
    for(j = 1; j < i; j++)
        k = L[k].cur;
    j = L[k].cur;
    L[k].cur = L[i].cur;
    Free_SSL(L, j);
    return OK;
}

静态链表优缺点

static_link_list_disadvantages_and_advantages

Code

github

结语

关于链表的面试题后续整理, fighting

猜你喜欢

转载自blog.csdn.net/u011221820/article/details/80343528