Day4 —— 静态链表的概念及基本操作的实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_41221623/article/details/82812249

静态链表的概念及基本操作的实现


首先,先回顾一下静态链表的概念


静态链表是对于一些早期的编程高级语言没有指针而出来的一种描述单链表的结构,它以数组来代替指针,那么它是怎么构成的呢?接下来一一道来。


首先,对于静态链表中的每一个元素,都是由两个数据域组成的,一个是data、一个是cur,也就是说数组中的每一个下标都对应一个data和一个cur。其中data为元素的数据域,用来存放数据元素;cur为游标,也就是相当于单链表中的next指针,用于存放该元素的后继(也就是存放后继元素在数组中的下标)。通常我们把这种用数组描述的链表叫做静态链表(游标实现法)。所以,我们一般以以下代码来实现静态链表的结构:

#define MAX_SIZE 1000

/****** 统一的数据类型(这里以角色ID和名称为例)******/
typedef struct
{
    int id;
    char *name;
}ElementType;

/****** 定义静态链表的结构 ******/
typedef struct
{
    ElementType data;   //数据域
    int next;           //int cursor; 游标:如果为0表示无指向
}StaticLinkList[MAX_SIZE];

  • 另外,对于数组第一个和最后一个元素要特殊处理,不存数据!

  • 并且通常把未被使用的数组元素称为备用链表。

  • 对于数组的第一个元素,即下标为0的元素的cur就存放备用链表的第一个结点的下标;对于数组的最后一个元素的cur则存放第一个有数值的元素的下标,相当于单链表中的头结点!当整个链表为空表时,它的cur为0,如下图:

image

  • 即静态链表初始化时的状态,代码实现如下:

/** 初始化链表 */
Status InitStaticLinkList(StaticLinkList slList)
{
    for(int i = 0; i < MAX_SIZE; i++)
    {
        //给元素数据域赋不可能的初值
        slList[i].data.id = -1;
        slList[i].data.name = NULL;
        
        //初始化各个元素的游标
        slList[i].next = i + 1;
    }

    //将最后一个结点置空(空表时最后一个元素的游标为0)
    slList[MAX_SIZE - 1].next = 0;

    return OK;
}

  • 若静态链表中已存入数据(这里以1000为表最大长度),则它将处于下图所示这种状态:

    image

  • 此时D1这里的游标域就存有下一个元素的下标2,D2则存有D3的下标3,以此类推,到最后一个有值元素D999时,由于它后面已无元素,所以它的游标为0。而最后一个元素的游标则存放第一个有值元素D1的下标1,第一个元素则因表已存满,所以游标为1000。


  • 初始化完成后继而到插入操作

  • 对于静态链表,我们需要注意的是如何用静态模拟动态链表结构的存储空间的分配,需要时申请,不用时释放。

  • 所以需要实现两个函数用来分配空间以及释放空间。

  • 对于分配空间,可以将所有未被使用过的以及已被删除的元素用游标链结成一个备用链表,每当进行插入时,便可以从备用链表取得第一个结点分配给插入的元素。

  • 分配空间的代码实现如下:

/** 为静态链表分配一个空间的内存,返回ERROR表示分配失败 */
Status mallocSSL(StaticLinkList slList)
{
    //判断是否满表,若满表返回ERROR
    if(slList[0].next == MAX_SIZE - 1)
    {
        return ERROR;
    }

    //拿到第一个空闲结点的下标(备用链表第一个结点)
    int cursor = slList[0].next;

    //取出备用链表第一个结点
    if(cursor)
    {
        //让新的空闲结点的下标为取出结点的next,也就是它的下一个结点变成了备用链表中的新的第一个结点
        slList[0].next = slList[cursor].next;
        
        //返回取出结点的下标
        return cursor;
    }

    //若cursor为0则分配失败,返回ERROR
    return ERROR;
}
  • 从以上代码可以看到当取得备用链表第一个结点后,就将它的next值(即它的下一结点)赋值给第一个元素的next,也就是把它的下一结点变成了新的空闲结点,接下来可以继续分配。


  • 接下来就可以实现插入操作了,对于在表中第pos个位置插入元素:先让它先待在分配出来的结点里,然后通过循环找到插入位置的前一个位置,再将该位置的next赋值给插入元素的next,即插入元素变成了刚刚找到的位置的下一结点的前缀结点,最后再将插入元素的下标赋值给找到的位置的next,这样插入的元素就插入到表中第pos个位置了,过程如下图:

    image
  • 代码实现如下:

/** 向指定位置插入元素 */
Status InsertStaticLinkList(StaticLinkList slList, int pos, ElementType element)
{
    //判断插入位置是否越界,若越界则返回ERROR
    if(pos < 1 || pos > GetStaticLinkList(slList) + 1)
    {
        return ERROR;
    }

    int cursor = MAX_SIZE - 1;  //取得最后一个元素的下标,可用于拿到第一个元素的下标

    //分配内存
    int newIndex = mallocSSL(slList);

    if(newIndex)
    {
        slList[newIndex].data = element;    //将插入元素赋给插入结点的数据域
        
        //找到插入位置的前缀结点
        for(int i = 1; i <= pos - 1; i++)
        {
            cursor = slList[cursor].next;
        }
        
        //将该位置的next赋值给插入元素的next
        slList[newIndex].next = slList[cursor].next;
        
        //将插入元素的下标赋值给找到的位置的next
        slList[cursor].next = newIndex;
        
        return OK;
    }

    return ERROR;
}

  • 最后就到了静态链表的删除操作了,和插入一样,我们需要先实现释放(回收)结点的函数。

  • 对于回收结点,十分简单明了,只需将指定位置的结点回收到备用链表第一个位置即可。首先,先令该位置的next为第一个元素的next,这样这个结点就链结上了备用链表了,然后再将第一个元素的next变为该位置的下标,这样这个位置的结点就回收到了备用链表的第一个位置了。

  • 实现代码如下:

/** 回收原始数组中指定下标的空间 */
Status FreeStaticLinkList(StaticLinkList slList, int index)
{
    //将下标为index的空闲结点回收到备用链表
    slList[index].next = slList[0].next;
    
    //0号元素的next结点指向备用链表的第一个结点,表示index结点空闲
    slList[0].next = index;

    return OK;
}

  • 接下来就可以进行删除操作了,对于删除,先通过循环找到要删除位置的前缀结点,再生成一个整型变量delIndex用于保存这个前缀结点的next,即要删除的位置,再令前缀结点的next为delIndex的next,这样就将要删除位置的结点从表中断开了,最后再用回收函数将该位置结点回收即可。

  • 实现代码如下:

/** 删除链表中指定位置的元素 */
Status DeleteStaticLinkList(StaticLinkList slList, int pos)
{
    //判断删除位置是否越界
    if(pos < 1 || pos > GetStaticLinkList(slList))
    {
        return ERROR;
    }

    int cursor = MAX_SIZE - 1;  //取得最后一个元素的下标,可用于拿到第一个元素的下标

    //通过循环找到要删除位置的前缀结点
    for(int i = 1; i <= pos - 1; i++)
    {
        cursor = slList[cursor].next;
    }

    int delIndex = slList[cursor].next;             //要删除的位置
    slList[cursor].next = slList[delIndex].next;    //将要删除位置的结点从表中断开

    //释放空间
    FreeStaticLinkList(slList, delIndex);

    return OK;
}

  • 对于以上代码使用的获取链表长度的函数GetStaticLinkList的实现代码如下:

/** 获得静态链表的长度 */
int GetStaticLinkList(StaticLinkList slList)
{
    int count = 0;  //计数器
    int cursor = slList[MAX_SIZE - 1].next; //获得第一个元素的下标
    while(cursor)
    {
        //形如链表中的p = p->next
        cursor = slList[cursor].next;
        count++;
    }

    return count;
}

  • 到了这里,静态链表的基本操作就基本实现了,接下来进行一些测试。(测试用的MAX_SIZE的值为10)

  • 测试代码:

//测试函数
void Test_Fun();

int main()
{
    Test_Fun();
    return 0;
}

//测试函数
void Test_Fun()
{
   StaticLinkList slList;       //要操作的静态链表

   InitStaticLinkList(slList);  //静态链表的初始化

   printf("初始化后:\n");
   PrintStaticLinkList(slList); //打印链表

   ElementType element1;
   element1.id = 1;
   element1.name = "蝙蝠侠";
   InsertStaticLinkList(slList, 1, element1);

   ElementType element2;
   element2.id = 2;
   element2.name = "闪电侠";
   InsertStaticLinkList(slList, 1, element2);

   ElementType element3;
   element3.id = 3;
   element3.name = "蜘蛛侠";
   InsertStaticLinkList(slList, 1, element3);

   ElementType element4;
   element4.id = 4;
   element4.name = "AA侠";
   InsertStaticLinkList(slList, 1, element4);


   printf("\n\n插入元素后:\n");
   PrintStaticLinkList(slList);


   printf("\n删除2号元素:\n");
   DeleteStaticLinkList(slList, 2);
   printf("\n删除后:\n");
   PrintStaticLinkList(slList);

}

  • 测试结果:

image


  • 小结

  • 静态链表的优缺点

    • 优点

      • 在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点
    • 缺点

      1. 没有解决连续存储分配带来的表长难以确定的问题。
      2. 失去了顺序存储结构随机存取的特性。
  • 总的来说,静态链表其实就是为了给没有指针的高级语言设计的一种能实现单链表能力的方法。


---------------------------------本文结束---------------------------------


猜你喜欢

转载自blog.csdn.net/qq_41221623/article/details/82812249