数据结构(1)线性表,链表,静态链表

线性表

零个或多个数据元素的有限序列

非空表中每个数据元素都有一个确定的位置

在较复杂的线性表中,一个数据元素可以由若干个数据项组成

线性表的抽象数据类型定义如下:

ADT //线性表
Data
	//线性表的数据对象集合,每个元素的类型均为DataType,其中除第一个元素外,每一个元素有且只有一个前驱元素,除了最后一个元素外,每一个元素有且只有一个直接后继元素。数据元素之间是一对一的关系。
Operation
	InitList(*L);	//初始化操作,建立一个空的线性表
	ListEmpty(L);	//若线性表为空,返回true,否则返回false
	ClearList(*L);	//将线性表清空
	GetElem(L,i,*e);	//将线性表L中的第i个元素值返回给e
	LocateElem(L,e);	//在线性表中查找与给定e相等的元素,如果查找成功,返回该元素在表中序号标识成功,否则返回0标识失败
	ListInsert(*L,i,e);	//在线性表中的第i个位置插入新元素e
	ListDelete(*L,i,*e);		//删除线性表L中第i个位置元素,并用e返回其值
	ListLength(L);			//返回线性表L的元素个数
endADT
/*将所有的在线性表Lb中但不在La中的数据元素插入到La中
*/
void union(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(Lb,i.e);	//取Lb中第i个数据元素赋给e
        if(!LocateElem(La,e,equal))		//如果定位不到La中相同的元素
        {
            ListInsert(La,++La_len,e);
        }
    }
}

线性表的顺序存储结构

顺序存储定义:

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

可以用C语言中的一维数组来实现顺序存储结构

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

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

(1)存储空间的起始位置:数组data

(2)线性表的最大存储容量:数组长度MaxSize

(3)线性表的当前长度:length

数组长度是存放线性表的存储空间的长度,分配后这个量一般是不变的:

线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的

任意时刻,线性表的长度应该<=数组的长度
在这里插入图片描述
存储器中的每个存储单元都有自己的编号,这个编号称为地址。

每个数据元素占c个存储单元,LOC:存储位置

LOC(a+1)=LOC(a)+c

所以对于第i个数据元素的存储位置可以由a1推算出

LOC(Ai)=LOC(A1)+(i-1)*c

在这里插入图片描述

存取时间性能为O(1),通常把具有这一特点的存储结构称为随机存取结构

顺序存储结构的插入和删除
  1. 获得元素操作

    #define OK 1
    #define ERROR 0
    #define TRUE 1
    #define FALSE 0
    typedef int status;
    
    status GetElem(SqList L,int i,ElemType *e)
    {
        if(L.length==0 || i<1 || i>L.length)
        {
            return ERROR;
        }
        *e=L.data[i-1];
        return OK;
    }
    
  2. 插入操作

    插入算法思路:

    1.如果插入位置不合理,抛出异常

    2.如果线性表长度>=数组长度,则抛出异常或动态增加容量

    3.从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置

    4.将要插入元素填入位置i

    5.表长度加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-1;k>=i-1;k--)	//将要插入位置后数据元素向后移动一位
            {
                L->data[k+1]=L->data[k];
            }
        }
        L->data[i-1]=e;	//插入新元素
        L->length++;
        return OK;
    }
    
  3. 删除操作

    删除算法思路:

    如果删除位置不合理,抛出异常

    取出删除元素

    从删除元素位置开始遍历到最后一个元素位置,分别将他们都向前移动一个位置表长度-1

    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;
    }
    

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

  1. 优点:

    无须为表示表中元素之间的逻辑关系而增加额外的存储空间

    可以快速地存取表中任一位置的元素

  2. **缺点: **

    插入和删除需要移动大量的元素

    当线性表长度变化较大时,难以确定存储空间的容量

    造成存储空间的碎片

线性表的链式存储结构

为了表示每个元素及其直接后继元素之间的逻辑关系。对数据元素来说,除了要存储其本身的信息之外,还需要存储一个指示其直接后继元素的信息

将存储数据元素信息的域称为数据域,把存储直接后继元素位置的域成为指针域。指针域中存储的信息称为指针或者链。这两部分信息组成数据元素ai的存储映像,称为节点(Node)

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

规定:线性链表的最后一个结点指针为空“NULL”或“^”

为了方便,会在单链表的第一个结点前附设一个结点,称为头结点,头结点的数据域可以不存储信息,也可以存储例如线性表的长度等附件信息,头结点的指针域存储指向第一个结点的指针

在这里插入图片描述

在这里插入图片描述

定义线性表的单链表存储结构

typedef struct Node
{
    ElemType data;
    struct Node *next;
}Node;
typedef struct Node *LinkList;		//定义LinkList
  1. 单链表的读取
    1. 获得链表第i个数据的算法思路:
      1. 声明一个结点p指向链表的第一个结点,初始化j从1开始
      2. 当j<i时,就遍历链表,让p的指针像后移动,不断指向下一结点,j累加1
      3. 若到链表末尾p为空,则说明第i个元素不存在
      4. 否则查找成功,返回结点p的数据
Status GetElem(LinkList L,int i,ElemType *e)
{
    int j;
    LinkList p;		//声明一个结点
    p = L->next;		//让p指向链表L的第一个结点
    j = 1;				//j为计数器
    while(p && j<i)		//p不为空或者计数器j还没等于i时,循环继续	
    {
        p=p->next;		//让p指向下一个结点
        ++j;
    }
    if(!p || j>i)		//第i个结点不存在
    {
        return ERROR;
    }
    *e = p->data;		//取第i个元素的数据
    return OK;
}

核心思想:工作指针后移

  1. 单链表的插入

    • 思路

      声明一结点p指向链表第一个结点,初始化j从1开始

      当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1

      若到链表末尾p为空,则说明第i个元素不存在

      否则查找成功,在系统中生成一个空结点s

      将数据元素e赋值给s->data

      单链表的插入标准语句s->next=p->next; p->next=s;

      返回成功

      Status ListInsert(LinkList *L,int i,ElemType e)
      {
          int j;
          LinkList p,s;
          p = *L;
          j = 1;
          while(p && j <1)	//寻找第i个结点
          {
              p=p->next;
              ++j;
          }
          if(!p || j>1)
          {
              return ERROR;		//第i个结点不存在
          }
          s = (LinkList)malloc(sizeof(Node));	//生成新结点
          s->data=e;
          s->next=p->next;			//将p的后继节点赋值给s的后继
          p-next=s;					//将s赋值给p的后继
          return OK;
      }
      
  2. 单链表第i个数据删除结点的算法思路

    1. 声明一结点p指向链表第一个结点,初始化j从1开始

    2. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1

    3. 若到链表末尾p为空,则说明第i个元素不存在

    4. 否则查找成功,将欲删除的结点p->next赋值给q

    5. 单链表的标准删除语句:p->next=q->next

    6. 将q结点中的数据赋值给e,作为返回

    7. 释放q结点

    8. 返回成功

      status ListDelete(Linklist *L,int i,ElemType *e)
      {
          int j;
          LinkList p,q;
          p=*L;		//将L给p,之后遍历的时候只用p就可以,防止修改原指针
          j=1;
          while(p->next && j<i)		
          {
              p=p->next;
              ++j;
          }
          if(!(p->next) || j>i)
          {
              return ERROR;
          }
          q=p->next;		/*将q的后继赋值给p的后继 */
          p->next=q->next;	
          *e=q->data;			//将q结点中的数据给e
          free(q);			//回收结点,释放内存
          return OK;
      }
      

      显然,对于插入或删除数据越频繁的操作,单链表的效率优势就越是明显

  3. 单链表的整表创建

    1. 创建单链表的过程就是一个动态生成链表的过程
    2. 算法思路
      1. 声明一结点p和计数器变量i
      2. 初始化一空链表L
      3. 让L的头结点的指针指向NULL,即建立一个带头结点的单链表
      4. 循环
        1. *生成一新结点赋值给p
        2. *随机生成一数字赋值给p的数据域p–>data
        3. *将p插入到头结点与前一个新结点之间
    /*随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)*/
    void CreateListHead(LinkList *L,int n)
    {
        LinkList p;
        int i;
        srand(time(NULL));		//初始化随机数种子
        *L = (LinkList)malloc(sizeof(Node));
        L->next =NULL;
        for(i=0;i<n;i++)
        {
            p=(Node)malloc(sizeof(Node));
            p->data=rand()%100+1;
            p->next=L->next;		
            L->next=p;
        }
    }
    
    /*随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法)*/
    void CreateListTail(LinkList *L,int n)
    {
        LinkList p,r;
        int i;
        srand(time(NULL));		//初始化随机数种子
        *L = (LinkList)malloc(sizeof(Node));		//	L为整个线性表
        r=*L;		//r为指向尾部的结点
        for(i=0;i<n;i++)
        {
            p=(Node)malloc(sizeof(Node));
            p->data=rand()%100+1;
            r->next=p;		
            r=p;
        }
        r->next=NULL;
    }
    
    
  4. 单链表的整表删除

    实现思路

    1.声明一结点p和q

    2.将第一个结点赋值给p

    3.循环

    ​ *将下一结点赋值q

    ​ *释放p

    ​ *将q赋值给p

    status ClearList(LinkList *L)
    {
        LinkList p,q;
        p=(*L)->next;
        while(p)
        {
            q=p->next;
            free(p);
            p=q;
        }
        (*L)->next = NULL;
        return OK;
    }
    

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

在这里插入图片描述

用数组描述的链表叫做静态链表(游标实现法)

#define MAXSIZE 1000
typedef struct
{
    ElemType data;
    int cur;	//游标,为0时表示无指向
}Component,StaticLinkList[MAXSIZE];

静态链表的结构

数组的每一个元素由数据域data,和游标cur(相当于单链表的next指针)**

*数组第一个元素,即下标为0的元素的cur就存放备用链表的第一个结点的下标

*数组的最后一个元素的cur则存放第一个有数值的元素的下标

  1. 初始化静态链表

status InitList(StaticLinkList space)
{
    int i;
    for(i=0;i<MAXSIZE-1;i++)
    {
        space[i].cur=i+1;
    }
    space[MAXSIZE-1].cur=0;
    return OK;
}

在这里插入图片描述

  1. 静态链表的插入

    1. 实现操作

      1. 获取空置元素位置函数

        int Malloc_SLL(StaticLinkList space)
        {
            int i=space[0].cur;
            if(space[0].cur)
            {
                space[0].cur=space[i].cur;
            }
            return i;
        }
        
      2. 插入操作

        status ListInsert(StaticLinkList L,int i,ElemType e)
        {
            int j,k,l;
            k=MAXSIZE-1;		//K是最后一个元素的下标
            if(i < 1 || i>ListLength(L) +1)
            {
                return ERROR;
            }
            j=Malloc_SSL(L);	//获取空闲分量的下标
            if(j)
            {
                L(j).data=e;	//给数据
                for(l=1;l<=i-1;l++)			//找到第i个元素之前的位置
                {
                    k=L[k].cur;
                }
                L[j].cur=L[k].cur;			//把第i个元素之前的cur赋值给新元素的cur
                L[k].cur=j;					//把新元素的下标赋值给第i个元素之前的cur
                return OK;
            }
            return ERROR;
        }
        
      3. 静态链表的删除操作

        status ListDelete(StaticLinkList L,int i)
        {
            int j,k;
            if(i<1 || i>ListLength(L))
            {
                return ERROR;
            }
            k=MAXSIZE-1;
            for(j=1;j<i-1;j++)
            {
                k=L[k].cur;
            }			//找到第i个元素之前的元素
            j=L[k].cur;		
            L[k].cur=L[j].cur;
            Free_SSL(L,j);
            return OK;
        }
        
        void Free_SSL(StaticLinkList space,int k)
        {
            space[k].cur = space[0].cur;	//把第一个元素的cur值赋给要删除的分量cur
            space[0].cur = k;				//把要删除的分量下标赋值给第一个元素的cur
        }
        
        int ListLength(StaticLinkList L)
        {
            int j=0;
            int i=L[MAXSIZE-1].cur;
            while(i)
            {
                i=L[i].cur;
                j++;
            }
            return j;
        }
        

静态链表的优缺点

在这里插入图片描述

循环链表

将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表

双向链表

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

存储结构

typedef struct DulNode
{
    ElemType data;
    struct DulNode *prior;
    struct DulNode *next;
}DulNode *DuLinkList;

参考:《大话数据结构》.程杰著

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值