线性表----顺序存储、单链表、静态链表

线性表的顺序存储结构

一段地址连续的存储单元依次存储的数据元素

确定3个要素:

 初始化线性表 InitList(*L)

1、线性表清空   ClearList(*L)

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

3、查找线性表中和给定数据元素e相等的元素序号  LocateElem(L,e)

4、在线性表中第i个位置插入元素e   ListInsert(*L,i,e)

5、删除线性表中第i个位置的元素,并返回给e  LitDelete(*L, i,*e)

6、返回线性表中的元素个数  ListLength(L)

顺序存储结构的基本操作:获得第i个位置的元素O(1)、插入O(n)、删除操作O(n)(即是数组的操作使用C++方式,理解思路很重要)

#define MAXSIZE 30 //宏定义 分配空间
typedef int ElemType 
typedef struct
{
   ElemType  data[MAXSIZE];
   int length;

}Sqlist;

#define OK 1
#define ERROR 0
#define TURE 1
#define FALSE 0

typedef int Status;
/*找到第i个元素并返回*/
Status GetElem(Sqlist L,int i, ElemType *e)
{
  if(L.length==0||i<1||i>L.length)//i 表示的是第i个位置 因此最小为1 最大为长度
     rerun ERROR; //这个很关键

    *e=L.data[i-1];
     rerun OK; 
}

/*在第i个位置之前插入元素e*/
Status ListInsert(Sqlist *L,int i, ElemType e)//注意C语言里"."的左操作数为值,"->"的左操作数为指针
{
 int k;
 if(L->length==MAXSIZE)
  rerun ERROR; 
 if(i<1||i>L->length)
  rerun ERROR; 
if(i<=L->length)//判断是否在表尾
 {
  for(k=L->length-1;k>=i-1;k--)
   {
   L->data[K+1]=L->data[k];
  
   }//第i-1个元素位置空闲
 }
L->data[i-1]=e;
L->length++;
return OK;
}
/*删除第i个位置的元素*/
Status ListDelete(Sqlist *L,int i, ElemType *e)
{
  int k;
 if(L->length==0)
   return ERROR;
 if(i<1||i>L->length)
   return ERROR;
 if(i<L->length)//判断是不是表尾
  {
  for(k=i;k<L->length;k++)
    L->data[k-1]=L->data[k]
  }
 L->length--;
 return OK;
}

链式存储结构

一组任意的存储单元存储线性表的数据元素

结点:包括数据域和指针域

 多了头结点、以及最后一个结点指针指向为空

 

存储结构:

typedef struct Node

{

ElemType data;

strcut Node *next;// 改为  Node *next;

} node;

typedef struct Node *LinkList;//改为 Node *LinkList

typedef 用法 https://blog.csdn.net/wangqiulin123456/article/details/8284939

获取链表第i个数据

//输入 链表L 位置i 返回:e
Status GetElem(LinkList L,int i,ElemType *e)
 {
  int j;
 LinkList p;
 p=L->next;
 j=1;
 while(p&&j<i)//指针一直往后移动
  {
  p=p->next;   
  ++j;
  }
 if(!p||j>i)
  {
  return ErROR;
  }
  *e=p->data;
  retun ok;
 }

链表的插入操作:

//在第i个位置之前插入新的数据元素e
Status ListInsert(LinkList *L,int i,ElemType e)
{
 
int j;
LinKList p,s;//s 为暂存结点
p=*L;
j=1;
while(p&&j<i)
 {
 p=p->next;
 ++j;
 }

if(!p||j>i)
{
 return ERROR;
}
s=(LinkList)malloc(sizeof(Node));//生成新的结点
s->data=e;
s->next=p->next;//将p结点指向的下一结点地址给s的指针域
p->next=s;//将s的地址赋值给p的指针域
return ok;
}

单链表的删除

//在第i个位置之前插入新的数据元素e
Status ListInsert(LinkList *L,int i,ElemType e)
{
 
int j;
LinKList p,q;//为要删除的结点
p=*L;
j=1;
while(p&&j<i)
 {
 p=p->next;
 ++j;
 }

if(!p||j>i)
{
 return ERROR;
}
q=p->next;//q为要删除结点的缓存变量 将指针赋值给它
p->next=q->next;//起到断开连接作用
*e=q->data;//数据赋值给指针e
free(q);
return ok;

}

对于插入和删除越频繁的操作,单链表具有优势!

链表的初始化创建

分为:头插法和 尾插法

头插法: 每次都让新结点在前面

//创建一个n个结点的链表L
void CreateListHead(LinkList *L,int n)
 {
   LinkList p;
   int i;
   srand(time(0));
   *L=(LinList)malloc(sizeof(Node));
   (*L)->next=NULL;//只有头结点
   for(i=0;i<n;i++)
    {

    p=(LinkList)malloc(sizeof(Node));
    p->data=rand()%100+1;
    p->next=(*L)->next;
    (*L)->next=p;//在表头插入
    }
 }

尾插法

定义一个结点的变量让它代表链表的尾结点

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

对比链表和顺序存储结构的特点:

 从存储分配方式、时间性能(对于查找、插入、删除)、空间性能

单链表判断结束标志是结点的指向是否为空;顺序存储的判断是依据长度;

后面将要讲到的静态链表大小是预先分配好的,但是有元素的个数需要判断第一个备用结点的指向。

循环链表的判断结束标志是指针的指向是否是头结点!

10-16及静态链表+循环链表+双向链表+例子实现从头到尾打印一个链表

静态链表:在其它的一些语言中例如fortan没有指针的操作,为此使用数组充当指针,

#define MAXSIZE 1000
typedef struct
{
  ElemType data;
  int cur;//游标
}StaticLinkList;

静态链表的初始化:

//初始化的过程
/*
1、定义一个空间
2、将每个游标连接
*/
Status InitList(StaticLinkList space)
{

 int i;
 for(i=0;i<MAXSIZE;i++)
 {
  space[i].cur=i+1;
 }
sapce[MAXSIZE-1].cur=0;
return ok;
}

 静态链表相比于单链表,结点的插入和删除需要自己实现一个malloc和free函数。

试着想一个问题?数组是连续若要删除一个结点,它是不能释放的,因此做法是把此结点清空并留作备用结点。

演示申请一个空闲空间,每一次都将第一个元素cur指向的空闲空间使用,再把其它空闲的空间地址给为第一个结点。

int Malloc_SLL(StaticLinList sapce)
{
 int i=sapce[0].cur;
 if(space[0].cur)
  space[0].cur=space[i].cur;//申请完空闲空间后,为了下一次依然能继续分配,则将另一个空闲空间的位置给第一个位置的游标cur

  return i;//返回的是申请得到的空闲空间
}

静态链表的插入

/*静态链表L的第i位置插入e*/
StatusListInsert(StaticLinList L,int i,ElemType e)
 {
  int j,k,l;//l为暂存的计数变量
  k=MAX_SIZE-1;//k 指向最后一个值
  if(i<1||i>ListLength(L)+1)
    return ERROR;
  j=Malloc_SSL(L);//获得空闲分量的下标 j 同时第0个元素的cur继续指向新的备用链表
  if(j)
  {
   L[j].data=e;
   for(l=1;l<=i-1;l++)//循环的次数取决于插入的位置
    { 
     k=L[k].cur; //如果在乙和丁之间插入丙,丁位置为3.执行2次操作
    }//得到k=2 
    //关键的两句替换
    L[j].cur=L[k].cur;//把i之前的cur赋值给 新元素j的cur
    L[k].cur=j;//同时将新元素的位置(也即是下标给i之前的cur)
    return ok;
  }
 return ERROR;
 }

最终的结果:

静态链表的删除:

首先实现释放结点的函数

void Free_SSL(StaticLinkList space,int k)
 {

  space[k].cur=space[0].cur;//相当于把此变量当做备用的空闲链表
  space[0].cur=k;
 }
Status ListDelete(StaticLinkList L,int i)
{

 int j,k;
 if(i<1||i>ListLength(L))
    return ERROR;
 k=MAX_SIZE-1;
 for(j=1;j<=i-1;j++)
   {
    k=L[k].cur;
   }
  j=L[k].cur;//将要删除元素的前一个位置的下一个指向的位置给j
  L[k].cur=L[j].cur;//j所指向的下一个位置给要删除元素的前一个位置的cur
  Free_SSL(L,j);
 return ok;
}

总结:对比单链表和静态链表的插入和删除都是类似的。

例如插入是:在A和B之间插入,则将A指向的地址给X的指向地址,然后将X的地址给A的指向地址。而且插入需要申请结点

 删除是:ABC,需要删除B,则将A指向的地址(B)的地址(C)给现在的A将要指向的地址(C)。删除需要释放结点

单链表可以使用free 和mallo函数,但是静态链表需要自己写,此过程的插入操作是将备用空间的序号给要插入的结点,然后再连接,最后还原新的备用链表空间。此过程的删除操作是将要删除的结点充当新的备用空间。

猜你喜欢

转载自blog.csdn.net/heda3/article/details/81437052