数据结构——线性表

本文详细介绍了线性表的定义、抽象数据类型及其两种主要存储结构——顺序存储和链式存储。顺序存储结构适用于快速访问元素,但插入和删除操作效率较低。链式存储结构插入删除效率高,但查找相对较慢。文中还讨论了静态链表、循环链表和双向链表的概念。

1 线性表的定义

  1. 线性表:零个或多个数据元素的有限序列。
  2. 若将线性表记为(a1...ai1,ai,ai+1,...an),则称ai1ai的直接前驱元素,ai1ai1
  3. 线性表元素的个数n定义为线性表的长度,当n=0时,称为空表。
  4. 在较复杂的线性表中,一个数据元素可以由若干个数据项组成,比如人员信息表等。

2 线性表的抽象数据类型

operation:

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

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

ClearList(*L): 将线性表清空。

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

LocateElem(L, e): 在线性表中查找与定值e相等的元素,如查找成 功,返回该元素在表中序号,否则返回0表示失败。

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

ListDelete(*L, i, *e): 删除线性表L中的第i个位置,并用e返回其值。

ListLength(L): 返回线性表L的元素个数。

/*实现两个线性表A和B的并集操作*/

void unionL(list *La, list Lb)
{
  int La_len, Lb_len, i;
  ElemType e;
  La_len = ListLength(*La);
  Lb_len = ListLength(*Lb);
  for(i = 1; i <= Lb_len; i++)
  {
      GetElem(La, i, &e);
      if(!LocateElem(*La, e))
          ListInsert(La, ++La_len, e);
  }
}

3 线性表的顺序存储结构

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

顺序存储结构的结构代码:

# define MAXSIZE 20
typedef int ElemType;
typedef struct
{
    ElemType data[MAXSIZE];
    int length;
}SqList;

顺序存储结构元素地址:LOC(ai) = LOC(a1) + (i-1)*c,其存取的时间性能为O(1)。

3.1 顺序存储结构的插入与删除

插入操作:

Status ListInsert(SqList *L, int i, ElemType e)
{
    int k;
    if(L->length == Maxsize) /*顺序线性表已满*/
        return ERROR;
    if(i<1 || i>L->length + 1)
        return ERROR;
    if(i <= L->length)
        {
            for(k = L->length; k >= i; k--)
                {
                    L->data[k] = L->data[k - 1];
                }
        }
     L->data[i-1] = e;
     L->length++;
     return OK;
}

删除操作:

Status ListDelete(SqList *L, int i, ElemType *e)
{
    int k;
    if(L->length == 0)
        return ERROR;
    if(i<1 || i > L->length)
        return ERROR;
    *e = L->data[i-1];
    if(i < L->length)
    {
        for(k = i; k < L->length; k++)
            L->data[k-1] = L->data[k]; 
    }
    L->length--;
    return OK;
}

顺序存储结构的时间复杂度都是O(n)。

3.2 顺序存储结构的优缺点

优点:
1. 无需为表示表中元素之间的逻辑关系额外增加存储空间。
2. 可实现快速存取表中任意位置的元素。

缺点:
1. 插入和删除操作需要移动大量元素,不适用于需要频繁插入和删除。
2. 难以确定存储空间容量。
3. 造成存储空间“碎片”。

4 线性表的链式存储结构

1 链式存储结构定义
为了表示每个元素与其直接后继元素之间的逻辑关系,对数据元素ai来说,除了存储本身信息之外,还需存储指示其直接后继元素的存储位置的信息,我们将存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域,指针域中存储的信息称为指针或链,把这两部分信息组成数据元素ai的存储映像称为结点

n个节点链结成一个链表,即为线性表(a1,a2……an)的链式存储结构,因为每个节点只包含一个指针域,故称为单链表

链表中第一个节点的位置称为头指针,常用头指针冠以链表的名字,是链表的必要元素。单链表中最后一个节点指针为空,用NULL表示。

可在单链表的第一个节点前附设一个头结点,头结点的数据域可不存储任何信息,头结点指针域存储指向第一节点的头指针,不一定必须。

2 链式存储结构代码描述

/*单链表存储结构*/
typedef struct Node
{
    ElemType data;
    struct Node *next;
} Node;
typedef struct Node *LinkList;

节点ai的数据域用p->data表示,指针域用p->next表示。

3 单链表的读取

/*用e返回L中第i个数据元素的值*/
Status GetElem(LinkList L, int i, ElemType *e)
{
    int j;
    LinkList p;  /* 生成一指针p */
    p = L->next; /* 让p指向链表L的第一个节点 */
    j = 1;       /* j为计数器 */
    while(p && j < i)
    {
        p = p->next;
        j ++;
    }
    if(!p)
        return ERROR; /*第i个节点不存在*/
    *e = p->data;
    return OK;
}

单链表查找的时间复杂度为O(n)。

4 单链表的插入与删除

Status ListInsert(LinkList *L, int i, ElemType e)
{
    int j;
    LinkList p, s;
    p = *L;  /*p指向链表头结点*/
    j = 1;
    while(p && j < i) /*寻找第i-1个节点*/
    {
        p = p->next;
        j++;
    }
    if(!p)
        return ERROR;
    s = (LinkList) malloc (sizeof(Node));/*生成新节点(c标准函数)*/
    s->data = e;
    s->next = p->next;
    p->next = s;
    return OK;
}

第一次插入的时间复杂度是O(n),后面都是O(1)。

5 单链表的整表创建与删除

/*随机产生n个元素的值,建立带表头节点的单链线性表L(尾插法)*/
void CreateListTail(LinkList *L, int n)
{
    LinkList p, r;
    int i;
    srand(time(0));
    *L = (LinkList)malloc(sizeof(Node));
    r = *L;
    for(i = 0; i<n; i++)
    {
        p = (Node*) malloc (sizeof(Node)); /*生成新结点*/
        p->data = rand()%100 + 1;
        r->next = p; /*表尾终端结点的指针指向新结点*/
        r = p;   /*新结点定义为表尾终端结点*/
    }
    r->next = NULL;  /*当前链表结束*/
}
/*将单链表重置为空表*/
Status ClearList(LinkList L)
{
    LinkList p, q;
    p = (*L)->next;
    while(p)
    {
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;
    return OK;
}

6 单链表结构与顺序存储结构的优缺点
(1) 存储分配方式
顺序存储用一段连续的存储单元依次存储线性表的数据元素;单链表用一组任意的存储单元存放线性表元素。

(2) 时间性能
查找:顺序存储结构O(1);单链表O(n)。
插入和删除:顺序存储为O(n);单链表在找出某位置的指针后,仅为O(1)。

(3) 空间性能
顺序存储需要预分配存储空间,大了浪费,小了溢出。
单链表不需分配,元素个数不受限制。

5 静态链表

用数组描述的链表叫做静态链表。(主要用于没有指针的语言如Basic等)

6 循环链表

将单链表中终端结点的指针端由空指针改为指向头结点,这种头尾相接的单链表称为单循环列表。

/*将两个循环链表合并为一个表,尾指针分别为rearA,rearB,则*/
p = rearA->next;
/*将B表的第一个结点赋值给rearA->next*/
rearA->next = rearB->next->next; 
q = rearB->next;
rearB->next = p;
free(q);

7 双向链表

双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。

/*线性表的双向链表存储结构*/
typedef struct DulNode
{
    ElemType data;
    struct DulNode *prior;  /*直接前驱指针*/
    struct DulNode *next;   /*直接后驱指针*/
} DulNode, *DuLinkList;
/*将存储元素e的结点s插入到p和p->next之间*/
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;

/*删除结点p*/
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);

参考:
程杰. 大话数据结构[M]. 清华大学出版社, 2011.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值