《大话数据结构》---- 第三章、线性表

《大话数据结构》---- 第三章、线性表


代码仅供参考,有需要可留言。

前言

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

线性表的抽象数据类型

ADT 线性表(List)
Data
    线性表的数据对象集合为 {a1, a2, ..., an},每个元素的类型均为 DataType。其中,除第一个元素 a1 之外,每一个元素有切只有一个前驱元素,除了最后一个元素 an 之外,其他元素都有且只有一个后继元素。数据元素之间的关系是一对一的关系。
Operation
    initList(*L):初始化操作,创建一个空的线性表 L。
    listEmpty(L):如线性表为空,返回 true,否则返回 false。
    clearList(*L):将线性表清空。
    getElem(L, i, *e):将线性表 L 中的第 i 个位置的元素返回给 e。
    locateElem(L, e):在线性表 L 中查找与给定值 e 相等的元素,如果查找成功,返回该元素在表中的序号。否则,返回 0 表示失败。
    listInsert(*L, i, e):在线性表 L 中的第 i 个位置插入元素 e。
    listDelete(*L, i, *e):在线性表 L 中删除第 i 个位置的元素,并返回给 e。
    listLength(L):返回线性表的元素个数。

一般而言,抽象数据类型中的操作都是最基本的,对于实际问题中的复杂操作,完全可以用如上基本操作组合实现。

【PS:刚开始学的时候,感觉这个抽象数据类型没有什么用,后来才发现,这个真的是个好东西。】

顺序存储结构

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

描述顺序存储结构的三个属性:

  1. 存储空间的起始位置
  2. 线性表的最大存储容量
  3. 线性表的当前长度

来看线性表的顺序存储结构的定义。(对于顺序存储结构我们一般使用的都是数组)

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

地址计算方法

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

一些常用基本操作

  1. 返回元素

    /*
    返回线性表L中的第i个位置的元素
    */
    int GetElem(SqList L,int i, ElemType e)
    {
        if(L.length == 0 || i < 1 || i > L.length)
            return 0;
        e = L.data[i-1];
        return e;
    }
    
  2. 插入

    1. 如果插入位置不合理,抛出异常。【已满/越界】
    2. 从最后一个元素开始向前遍历到第 i 个位置,分别将它们都向后移动一个位置
    3. 将要插入元素填入位置 i
    4. 表长加 1
    /*
    在L中第i个位置之前插入元素e
    第i个元素之后 所有元素后移一个单位长度,从后往前遍历
    */
    int ListInsert(Sqlist *L,int i, ElemType e)
    {
        int k;
        if(L->length == MAXSIZE)
            return false;
        if(i < 1 || i > L->length + 1)
            return false;
        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 true;
    }
    
  3. 删除

    1. 如果删除的位置不合理,抛出异常。【越界】
    2. 取出删除元素。
    3. 从删除位置开始遍历到最后一个元素位置,分别将它们都往前移动一位。
    4. 表长 -1
    /*
    删除L的第 i 个 数据元素
    第i+1个元素其所有元素往前移一个单位长度
    */
    int ListDelete(Sqlist *L, int i, ElemType *e)
    {
        if(L->length == 0 || i < 1 || i > L->length)
            return false;
        e = L->data[i-1];
        if(i < L->length)
        {
            for(int k = i; k < L->length; k++)
                L->data[k-1] = L->data[k];
        }
        L->length--;
        return true;
    }
    
  4. 优缺点

    • 优点:无须为表中元素的逻辑关系增加存储空间;可以快速存取表中元素
    • 缺点:插入和删除 需要移动大量元素;存储空间易浪费;造成存储空间的“碎片”

线性表的链式存储结构

特点:用一组任意的存储单元存储线性表的数据元素【数据域 + 指针域】

头指针 & 头结点 :

​ 头指针:链表中第一个结点的存储位置

​ 头结点:为了更好的操作链表,在第一个结点前附设的结点(其指针域存放指向第一个结点的指针)

每个元素存储在结点中,结点由存放数据元素的数据域及存放后继结点地址的指针域组成。

/*
线性表的单链表存储结构
*/
typedef struct node{
    ElemType data;
    struct Node *next;
}Node;
Typedef struct Node *LinkList;
  1. 单链表的读取【工作指针后移】
    1. 声明一个结点p 指向链表的第一个结点
    2. 当j < i 时,就遍历链表,指针后移,计数器加 1
    3. 若到链表末尾,p 为空,则 第 i 个元素不存在
    4. 否则 查找成功,返回结点 p 的数据
/*
返回L中的第 i 个元素的值
*/
int GetElem(LinkList L, int i, ElemType e)
{
    int j = 1;
    LinkList p = L->next;
    while(p && j < i)
    {
        p = p->next;
        j++;
    }
    if(!p || j > i)
        return false;
    e = p->data;
    return true;
}
  1. 单链表的插入
    1. 先查找到第 i 个元素,并生成空结点S
    2. 更新S,插入【s->next = p->next; p->next = s
/*
在L中第i个位置之前插入新的数据元素e,L的长度加 1
*/
int ListInsert(LinkList L, int i, ElemType e)
{
    int j = 1;
    LinkList p = L, s;
    while(p && j < i)
    {
        p = p -> next;
        j++;
    }
    if(!p || j > i)
        return false;
    s = (LinkList)malloc(sizeof(Node));
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}
  1. 单链表的删除
    1. 先查找到第 i 个元素,赋值给q
    2. 删除【p->next = q->nxet
    3. 释放 q
/*
删除L的第 i 个元素,L的长度减1
*/
int ListDelete(LinkList L, int i, ElemType e)
{
    int j = 1;
    LinkList p = l, q;
    while(p->next && j < i)
    {
        p = p->next;
        j++;
    }
    if(!(p->next) || j > i)
        return false;
   	q = p->next;
    p->next = q->next;
    e = q->data;
    free(q);
    return true;
}
  1. 单链表的创建

    • 创建单链表的过程就是一个动态生成链表的过程,即依次建立各元素结点,并逐个插入链表。【头插法 / 尾插法】

    • /*
      头插法,创建链表,带头结点
      */
      void CreateListHead(LinkList l,int n)
      {
          LinkList p;
          L = (LinkList)malloc(sizeof(Node));
          L->next = NULL;
          for(int i = 0; i < n; i++)
          {
              p = (LinkList)malloc(sizeof(Node));
              p->data = rand()*100 + 1;
              p->next = L->next;
              L->next = p;
          }
          return;
      }
      
      /*
      头插法,创建链表,带头结点
      */
      void CreateListTail(LinkList L,int n)
      {
       	LinkList p,r;
       	L = (LinkList)malloc(sizeof(Node));
          r = L;
          for(int i = 0; i< n; i++)
          {
              p = (Node *)malloc(sizeof(Node));
              p->data = rand()*100 + 1;
              r->next = p;
              r = p;
          }
          r->next = NULL;
          return;
      }
      
  2. 单链表的销毁

    /*
    将L重置为空表
    */
    int ClearList(LinkList L)
    {
        LinkList p,q;
        p = L->next;
        while(p)
        {
            q = p->next;
            free(p);
            p = q;
        }
        L->next = Null;
        return true;
    }
    
  3. 单链表相较于顺序存储结构而言:

    • 若线性表需要频繁查找,则宜采用顺序存储结构,若需要频繁插入和删除时,宜采用单链表结构。

静态链表

**用数组代替指针,来描述单链表。**即数组元素由两个数据域组成,data,cur(相当于链表的指针)。

/*
线性表的静态链表的存储结构
*/
#define MAXSIZE 1000
typedef struct{
    ElemType data;
    int cur;
}Component,StaticLinkList[MAXSIZE];

通常,我们对数组的第一个元素及最后一个元素作为特殊元素处理,即下标为0 的元素的cur 用以存放备用链表的第一个结点的下标,最后一个元素的cur则存放第一个有数值的元素的下标。

1554119745328

图示相当于初始化的数组状态。

/*
将一维数组space 中各分量链成一备用链表
*/
int InitList(StaticLinkList space)
{
    for(int i = 0; i < MAXSIZE - 1; i++)
    {
        space[i].cur = i + 1;
    }
    space[MAXSIZE - 1].cur = 0;
    return true;
}

说明:为了辨明数组中哪些分量未被使用,我们需要将所有未被使用过的及已被删除的分量用游标链成一个备用的链表,每当进行插入时,便可以从备用链表上取得第一个结点作为待插入的新结点。

  1. 静态链表的插入:

    /*
    在L中第i个元素之前插入新的数据元素e
    */
    bool LinkInsert(StaticLinkList L, int i, ElemType e)
    {
     	int k = MAX_SIZE - 1;
        if(i < 1 || i > ListLength(L) + 1)
            return false;
        int j = Malloc_SSl(L); // 获得空闲分量的下标
        if(j)
        {
            L[j].data = e;
            for(int l = 1; l <= i - 1; l++)
            {
                k = L[k].cur;
            }
            L[j].cur = L[k].cur;
            L[k].cur = j;
            return true;
        }
        return false;
    }
    
  2. 静态链表的删除:

    /*
    删除在L中第i个数据元素e
    */
    bool ListDelete(StaticLinkList L, int i)
    {
        if(i < 1 || i > Listlength(L))
            return false;
        int k = MAX_SIZE - 1;
        for(int j = 1; j <= i - 1; j++)
        {
            k = L[k].cur;
        }
        int j = L[k].cur;
        L[k].cur = L[j].cur;
        Free_SSL(L,j);//将下标为j的空间重新加入到备用链表中
        return true;
    }
    

其实,静态链表不是很好,既失去了顺序结构存储随机存取的特点,也没有解决连续存储分配带来的表长难以确定的问题。

单向循环链表

单循环链表:头尾相接的单链表。

循环链表解决了,如何从任一结点出发,访问到链表全部结点的问题。

为了使空链表和非空链表处理一致,我们通常设置一个头结点。

单向循环链表的插入和删除操作基本同单链表相同,这里不做赘述。

双向链表

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

/*
线性表的双向链表存储结构
*/
typedef struct DulNode{
    ElemType data;
    struct DulNode *prior;
    struct DulNode *next;
}DulNode,*DuLinkList;
  1. 双向链表的插入

    /*
    在双链表L的第i个位置前 插入 元素 x
    */
    bool InsertList(DuLinkList L, int i, ElemType x)
    {
        if(i < 1 || i > LengthLink(L))
            return false;
        DuLinkList p = L, q = NULL;
        int j  = 1;
        while(p && j < i)
        {
            p = p->next;
            j++;
        }
        DulNode s = (DulNode *) malloc(sizeof(DulNode));
        /*先搞定S的前驱和后继,在搞定后结点的前驱,最后解决前结点的后继*/
        s->data = x;
        s->prior = p;
        s->next = p->next;
        p->next->prior = s;
        p->next = s;
        return true;
    }
    
  2. 双向链表的删除

    /*
    将双链表L的第i个位置元素删除
    */
    bool DeleteDulList(DulLinkList L, int i, ElemType x)
    {
        if(i < 1 || i > length(L) - 1)
            return false;
        DulLinkList p = L, q = NULL;
        int j = 1;
        while(p || j < i)
        {
            p = p->next;
            j++;
        }
        p = p -> next;
        q = p;
        x = p->data;
        p->prior->next = p->next;
        p->prior = p->next->prior;
        free(q);
        return true;
    }
    

总结

总的来说,线性表是数据结构中非常基础的了,可能刚开始学,觉得没有什么用,一点都不酷炫。但是,回过头来看,顺序存储,链式存储的思想一直贯穿在整个数据结构的学习当中,共勉。

《大话数据结构》---- 第三章、线性表_第1张图片

你可能感兴趣的:(数据结构)