数据结构学习之路-第二章:静态单链表

【 声明:版权所有,转载请标明出处,请勿用于商业用途。  联系信箱:[email protected]


前言:

对于指针构造单链表的方法,我们在之前那篇博客里已经学完了,但是我们知道,指针是C/C++特有的神器,那么对于没有指针的其他语言,又该怎么去模拟链表呢?我们就需要使用静态链表的思想了。

接下里我们将要学习的就是如何不用指针来完成单链表的创建与操作。


注:

本文仅代表博主本人的一些浅显的见解,欢迎大家评论学习,共同创造出一个良好的环境

对于一些问题,博主会尽量为大家解答,但是如果有疑问没有及时回答的,也希望其他热心人心帮忙解决,鄙人不胜感激


静态单链表


1.存储结构

首先我们要开一个足够大的结构体数组,单个结构体就表示一个结点,里面要保存两个元素,其中一个是数据data,而另外一个则是代替指针而存在的“游标”cur,这个cur就像指针型的链表的next指针一样,我们要遍历这个链表就得靠它了。

#define MAXSIZE 1000  //链表的最大长度
typedef struct
{
    ElemType data;    //数据
    int cur;          //游标
} component,SLinkList[MAXSIZE];


2.分配与释放

我们知道,在指针型的链表之中,我们可以通过库函数的malloc与free函数来动态分配与释放内存,但是这对于静态链表的cur是不适用的,事实上我们并没有指针那种完全的自由,我们的操作必须还是在指定大小的数组内进行,最笨的方法就是每次人工判断是否为空闲节点,但是这种方法显然是不符合实际的,为了解决这个问题,我们可以将这个链表分为两个部分。一个部分是已经保存了数据的数据链表,另外一个则是还没有数据的备用链表,我们以位置0为备用链表的表头,以MAXSIZE-1为数据链表的表头。那么当我们需要新节点的时候,就把备用链表中的第一个节点删除,作为新结点。当删除数据链表中的结点的时候,就把删除的结点插入备用链表中成为备用链表的第一个结点。

int Malloc(SLinkList L)
{
    //操作结果:若备用链表非空,则返回分配的结点下标(备用链表的第一个结点),否则返回0
    int i = L[0].cur;
    if(L[0].cur)     //备用链表非空
        L[0].cur = L[i].cur;   //备用链表的头结点指向原备用链表的第二个结点,那么第一个结点则脱离了备用链表,返回其位置,作为数据链表将要插入的新结点
    return i;        //返回新开辟结点的坐标
}


void Free(SLinkList L,int k)
{
    //操作结果:将下标为k的空闲结点回收到备用链表(成为备用链表的第一个结点)
    L[k].cur = L[0].cur;   //回收结点的"游标"指向备用链表的第一个结点
    L[0].cur = k;          //备用链表的头结点指向新回收的结点
}

3.创建、插入、删除

在完成了开辟空间与释放空间的基础之上,我们应该已经了解到现在这个静态链表的操作是怎么一回事了,那么接下来完成这三个操作就手到擒来了。

void InitList(SLinkList L)
{
    //操作结果:构造一个空的链表,表头为L的最后一个单元L[MAXSIZE-1],其余单元链成一个备用链表,表头为L的第一个单元L[0],“0”表示空指针
    int i;
    L[MAXSIZE-1].cur = 0;          //L的最后一个单元为空链表的表头
    for(i = 0; i<MAXSIZE-2; i++)   //将其余单元链接成以L[0]为表头的备用链表
        L[i].cur = i+1;
    L[MAXSIZE-2].cur = 0;
}

Status ListInsert(SLinkList L,int i,ElemType e)
{
    //操作结果:在L中第i个元素之前插入新的数据元素e
    int l,j,k = MAXSIZE-1;       //k指向表头
    if(i<1||i>ListLength(L)+1) return ERROR;
    j = Malloc(L);               //申请新单元
    if(j)                        //申请成功
    {
        L[j].data = e;           //赋值给新单元
        for(l = 1; l<i; l++)     //找到第i-1个元素的位置
            k = L[k].cur;
        L[j].cur = L[k].cur;     //改变指针,大家可以回忆一下指针链表的插入过程
        L[k].cur = j;
        return OK;
    }
    return ERROR;
}

Status ListDelete(SLinkList L,int i,ElemType *e)
{
    //操作结果:删除在L中第i个数据元素e,并返回其值
    int j,k = MAXSIZE-1;       //k指向表头
    if(i<1||i>ListLength(L)) return ERROR;
    for(j = 1; j<i; j++)       //找到第i-1个元素的位置
        k = L[k].cur;
    j = L[k].cur;              //改变指针,大家可以回忆一下指针链表的删除过程
    L[k].cur = L[j].cur;
    *e = L[j].data;
    Free(L,j);
    return OK;
}


4.其他操作

其他的操作我也就不用再多做解释了,一切都是在指针形式的单链表中修改过来的,只要理解了静态链表的原理,要做出相应的修改也不是很难,具体解释已经都在注释中了,大家可以试着自己去理解,如果还有不懂的也欢迎提问。

Status ClearList(SLinkList L)
{
    //初始条件:线性表L已存在。
    //操作结果:将L重置为空表,把原本的备用链表接到原本的数据链表后面,作为一整条新的备用链表
    int i,j,k;
    i = L[MAXSIZE-1].cur;   //链表第一个结点的位置
    L[MAXSIZE-1].cur = 0;   //链表置为空表
    k = L[0].cur;           //备用链表第一个结点的位置
    L[0].cur = i;           //把链表的结点连到备用链表的表头
    while(i)                //没到链表尾
    {
        j = i;
        i = L[i].cur;       //指向下一个元素
    }
    L[j].cur = k;           //备用链表的第一个结点接到链表的尾部
    return OK;
}

Status ListEmpty(SLinkList L)
{
    //操作结果:若L是空表,返回TRUE;否则返回FALSE
    return L[MAXSIZE-1].cur==0;
}

int ListLength(SLinkList L)
{
    //操作结果:返回L中数据元素个数
    int j = 0,i = L[MAXSIZE-1].cur;  //i指向第一个元素
    while(i)                         //没到静态链表尾
    {
        i = L[i].cur;                //指向下一个元素
        j++;
    }
    return j;
}

Status GetElem(SLinkList L,int i,ElemType *e)
{
    //操作结果:用e返回L中第i个元素的值
    int l,k = MAXSIZE-1;          //k指向表头序号
    if(i<1||i>ListLength(L)) return ERROR;
    for(l = 1; l<=i; l++)   //移动到第i个元素处
        k = L[k].cur;
    *e = L[k].data;
    return OK;
}

int LocateElem(SLinkList L,ElemType e)
{
    //操作结果:在静态单链线性表L中查找第1个值为e的元素。若找到,则返回它在L中的位序,否则返回0。
    int i = L[MAXSIZE-1].cur;  //i指示表中第一个结点
    while(i&&L[i].data!=e)     //在表中顺链查找
        i = L[i].cur;
    return i;
}

Status PriorElem(SLinkList L,ElemType cur_e,ElemType *pre_e)
{
    //初始条件:线性表L已存在
    //操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义
    int j,i = L[MAXSIZE-1].cur;   //i指示链表第一个结点的位置
    do
    {
        j = i;
        i = L[i].cur;             //向后移动结点
    }
    while(i&&L[i].data!=cur_e);
    if(i)                         //找到该元素
    {
        *pre_e = L[j].data;
        return OK;
    }
    return ERROR;
}

Status NextElem(SLinkList L,ElemType cur_e,ElemType *next_e)
{
    //初始条件:线性表L已存在
    //操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义
    int j,i = LocateElem(L,cur_e);//在L中查找第一个值为cur_e的元素的位置
    if(i)                         //L中存在元素cur_e
    {
        j = L[i].cur;             //cur_e的后继的位置
        if(j)                     //cur_e有后继
        {
            *next_e = L[j].data;
            return OK;            //cur_e元素有后继
        }
    }
    return ERROR;                 //L不存在cur_e元素,cur_e元素无后继
}

Status ListTraverse(SLinkList L,void (*vi)(ElemType))
{
    //初始条件:线性表L已存在
    //操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败
    int i = L[MAXSIZE-1].cur;   //指向第一个元素
    while(i)                    //没到静态链表尾
    {
        vi(L[i].data);          //调用vi()
        i = L[i].cur;           //指向下一个元素
    }
    printf("\n");
    return OK;
}

5.具体测试

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <math.h>
#include <bitset>
#include <algorithm>
#include <climits>
#include <ctype.h>
#include <malloc.h>
#include <limits.h>
#include <stdlib.h>
#include <io.h>
#include <process.h>
using namespace std;

#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1

typedef int ElemType;
typedef int Status;

#define MAXSIZE 1000  //链表的最大长度

typedef struct
{
    ElemType data;    //数据
    int cur;          //游标
} component,SLinkList[MAXSIZE];

int Malloc(SLinkList L)
{
    //操作结果:若备用链表非空,则返回分配的结点下标(备用链表的第一个结点),否则返回0
    int i = L[0].cur;
    if(L[0].cur)     //备用链表非空
        L[0].cur = L[i].cur;   //备用链表的头结点指向原备用链表的第二个结点,那么第一个结点则脱离了备用链表,返回其位置,作为数据链表将要插入的新结点
    return i;        //返回新开辟结点的坐标
}


void Free(SLinkList L,int k)
{
    //操作结果:将下标为k的空闲结点回收到备用链表(成为备用链表的第一个结点)
    L[k].cur = L[0].cur;   //回收结点的"游标"指向备用链表的第一个结点
    L[0].cur = k;          //备用链表的头结点指向新回收的结点
}

void InitList(SLinkList L)
{
    //操作结果:构造一个空的链表,表头为L的最后一个单元L[MAXSIZE-1],其余单元链成一个备用链表,表头为L的第一个单元L[0],“0”表示空指针
    int i;
    L[MAXSIZE-1].cur = 0;          //L的最后一个单元为空链表的表头
    for(i = 0; i<MAXSIZE-2; i++)   //将其余单元链接成以L[0]为表头的备用链表
        L[i].cur = i+1;
    L[MAXSIZE-2].cur = 0;
}

Status ClearList(SLinkList L)
{
    //初始条件:线性表L已存在。
    //操作结果:将L重置为空表,把原本的备用链表接到原本的数据链表后面,作为一整条新的备用链表
    int i,j,k;
    i = L[MAXSIZE-1].cur;   //链表第一个结点的位置
    L[MAXSIZE-1].cur = 0;   //链表置为空表
    k = L[0].cur;           //备用链表第一个结点的位置
    L[0].cur = i;           //把链表的结点连到备用链表的表头
    while(i)                //没到链表尾
    {
        j = i;
        i = L[i].cur;       //指向下一个元素
    }
    L[j].cur = k;           //备用链表的第一个结点接到链表的尾部
    return OK;
}

Status ListEmpty(SLinkList L)
{
    //操作结果:若L是空表,返回TRUE;否则返回FALSE
    return L[MAXSIZE-1].cur==0;
}

int ListLength(SLinkList L)
{
    //操作结果:返回L中数据元素个数
    int j = 0,i = L[MAXSIZE-1].cur;  //i指向第一个元素
    while(i)                         //没到静态链表尾
    {
        i = L[i].cur;                //指向下一个元素
        j++;
    }
    return j;
}

Status GetElem(SLinkList L,int i,ElemType *e)
{
    //操作结果:用e返回L中第i个元素的值
    int l,k = MAXSIZE-1;          //k指向表头序号
    if(i<1||i>ListLength(L)) return ERROR;
    for(l = 1; l<=i; l++)   //移动到第i个元素处
        k = L[k].cur;
    *e = L[k].data;
    return OK;
}

int LocateElem(SLinkList L,ElemType e)
{
    //操作结果:在静态单链线性表L中查找第1个值为e的元素。若找到,则返回它在L中的位序,否则返回0。
    int i = L[MAXSIZE-1].cur;  //i指示表中第一个结点
    while(i&&L[i].data!=e)     //在表中顺链查找
        i = L[i].cur;
    return i;
}

Status PriorElem(SLinkList L,ElemType cur_e,ElemType *pre_e)
{
    //初始条件:线性表L已存在
    //操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义
    int j,i = L[MAXSIZE-1].cur;   //i指示链表第一个结点的位置
    do
    {
        j = i;
        i = L[i].cur;             //向后移动结点
    }
    while(i&&L[i].data!=cur_e);
    if(i)                         //找到该元素
    {
        *pre_e = L[j].data;
        return OK;
    }
    return ERROR;
}

Status NextElem(SLinkList L,ElemType cur_e,ElemType *next_e)
{
    //初始条件:线性表L已存在
    //操作结果:若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义
    int j,i = LocateElem(L,cur_e);//在L中查找第一个值为cur_e的元素的位置
    if(i)                         //L中存在元素cur_e
    {
        j = L[i].cur;             //cur_e的后继的位置
        if(j)                     //cur_e有后继
        {
            *next_e = L[j].data;
            return OK;            //cur_e元素有后继
        }
    }
    return ERROR;                 //L不存在cur_e元素,cur_e元素无后继
}

Status ListTraverse(SLinkList L,void (*vi)(ElemType))
{
    //初始条件:线性表L已存在
    //操作结果:依次对L的每个数据元素调用函数vi()。一旦vi()失败,则操作失败
    int i = L[MAXSIZE-1].cur;   //指向第一个元素
    while(i)                    //没到静态链表尾
    {
        vi(L[i].data);          //调用vi()
        i = L[i].cur;           //指向下一个元素
    }
    printf("\n");
    return OK;
}

Status ListInsert(SLinkList L,int i,ElemType e)
{
    //操作结果:在L中第i个元素之前插入新的数据元素e
    int l,j,k = MAXSIZE-1;       //k指向表头
    if(i<1||i>ListLength(L)+1) return ERROR;
    j = Malloc(L);               //申请新单元
    if(j)                        //申请成功
    {
        L[j].data = e;           //赋值给新单元
        for(l = 1; l<i; l++)     //找到第i-1个元素的位置
            k = L[k].cur;
        L[j].cur = L[k].cur;     //改变指针,大家可以回忆一下指针链表的插入过程
        L[k].cur = j;
        return OK;
    }
    return ERROR;
}

Status ListDelete(SLinkList L,int i,ElemType *e)
{
    //操作结果:删除在L中第i个数据元素e,并返回其值
    int j,k = MAXSIZE-1;       //k指向表头
    if(i<1||i>ListLength(L)) return ERROR;
    for(j = 1; j<i; j++)       //找到第i-1个元素的位置
        k = L[k].cur;
    j = L[k].cur;              //改变指针,大家可以回忆一下指针链表的删除过程
    L[k].cur = L[j].cur;
    *e = L[j].data;
    Free(L,j);
    return OK;
}

void visit(ElemType c)
{
    printf("%d ",c);
}

int main()
{
    int j,k;
    Status i;
    ElemType e,e0;
    SLinkList L;
    InitList(L);
    for(j=1; j<=5; j++)
        i=ListInsert(L,1,j);
    printf("在L的表头依次插入1~5后:L=");
    ListTraverse(L,visit);
    i=ListEmpty(L);
    printf("L是否空:i=%d(1:是 0:否)表L的长度=%d\n",i,ListLength(L));
    i=ClearList(L);
    printf("清空L后:L=");
    ListTraverse(L,visit);
    i=ListEmpty(L);
    printf("L是否空:i=%d(1:是 0:否)表L的长度=%d\n",i,ListLength(L));
    for(j=1; j<=10; j++)
        ListInsert(L,j,j);
    printf("在L的表尾依次插入1~10后:L=");
    ListTraverse(L,visit);
    GetElem(L,5,&e);
    printf("第5个元素的值为:%d\n",e);
    for(j=0; j<=1; j++)
    {
        k=LocateElem(L,j);
        if(k)
            printf("值为%d的元素在静态链表中的位序为%d\n",j,k);
        else
            printf("没有值为%d的元素\n",j);
    }
    for(j=1; j<=2; j++)         //测试头两个数据
    {
        GetElem(L,j,&e0);       //把第j个数据赋给e0
        i=PriorElem(L,e0,&e);   //求e0的前驱
        if(!i)
            printf("元素%d无前驱\n",e0);
        else
            printf("元素%d的前驱为:%d\n",e0,e);
    }
    for(j=ListLength(L)-1; j<=ListLength(L); j++) //最后两个数据
    {
        GetElem(L,j,&e0);       //把第j个数据赋给e0
        i=NextElem(L,e0,&e);    //求e0的后继
        if(!i)
            printf("元素%d无后继\n",e0);
        else
            printf("元素%d的后继为:%d\n",e0,e);
    }
    k=ListLength(L);           //k为表长
    for(j=k+1; j>=k; j--)
    {
        i=ListDelete(L,j,&e);  //删除第j个数据
        if(i)
            printf("删除的元素为:%d\n",e);
        else
            printf("删除第%d个数据失败\n",j);
    }
    printf("依次输出L的元素:");
    ListTraverse(L,visit);      //依次对元素调用visit(),输出元素的值
    return 0;
}


总结:

静态链表的学习也就这样告一段落吧,不知道大家是否学会了呢?

学习的过程最重要的是自己动手,将所学的知识思考与总结,这样才能把别人的转化为自己的。

那么这次就到此为止,下次我们将会进入循环链表的学习。

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