【数据结构】线性表

一、线性表及其逻辑

定义:线性表是具有相同特性的数据元素的一个有限序列,一般分为顺序表、链表、循环链表

数据类型描述:

ADT List{
D={ai|1<=i<=n,n>=0,ai为ELement类型}
R={<ai,ai+1>|ai~ai+1∈D,i=1,...,n-1}
9个运算}

二、顺序表

结构体定义

typedef struct{
Element data[Maxsize];
int length;
}SqList;

建立顺序表

void CreateList(Sqlist*L,ELemType a[],int n)
{
    int i,k;
    SqList*L=(Sqlist*)malloc(sizeof(SqList))
    while(i<n){
    L->data[k]=a[i];
    k++;i++;
    }
    L->length=k;
    return L;
}

初始化线性表(构造一个空的线性表)

SqList*InitList()
{SqList*=(SqList*)malloc(sizeof(SqList));
L->length=0;
return L;
}

销毁线性表

void DestoryList(SqList *L)
{
    if(L!=NULL)
    free(L);
]

判断顺序表是否为空

void ListEmpty(SqList*L)
{
return(L->length==0)
}

计算顺序表长度

int ListLength(SqList*L)
{
    return L->length;
}

打印顺序表

void DispList(SqList*L){
    int i;
    for(i=0;i<L->length;i++)
    {
       printf("%d",L->data[i];}
    printf{"/n"};
}

求第i个元素

int GetElem(SqList*L, int i,Element *e)
{
    if(i<1||i>L-length)
    return 0;
    *e=L->data[i-1];
    return 1;
}

求值为e的元素位次

int LocateELem(SqList*L,ELemType*e)
{
    int i=0;
    while(i<L->length&&L->data[i]!=e)
        i++;
    if(i=L->length)
        return false;
    else
        return i+1;
}

插入

插入元素要先找到插入位置,然后把插入位置当前的元素及之后的元素全部后移以将插入位置空出来,然后把待插入元素赋值给空出的插入位置 

int ListInsert(SqList*L,int i,ElemType e)
{
    int j;
    if(i<1||i>L->length+1||L->length==Maxsize)
        return 0;
    i--;
    for(j=L->length;j>i;j--)
        L->data[j]=L->data[j-1];
    L->data[i]=e;
    L->length++;
    return 1;
}

删除

删除元素就是将被删除元素之后的所有元素直接前移覆盖 

int ListDelete(SqList*L,int i,ElemType *e)
{
    int j;
    if(i<1||i>L->length)
        return 0;
    i--;
    for(j=L->length;j>i;j++)
        L->data[j]=L->data[j+1];
    L->length--;
    return 1;
}

三、链表

链表包含一系列结点,每个结点包含两部分:数据部分和指向下一个结点的指针(或引用)。

特点:链表的每个逻辑元素用一个结点单独存储,为表示逻辑关系,增加指针域。

链表可以有两种主要类型:带头结点的链表和不带头结点的链表。

  1. 不带头结点的链表:在这种链表中,第一个节点(称为首节点或起始节点)直接存储数据,并且有一个指向下一个节点的指针。这个首节点并不特别,它只是链表中众多节点中的一个,只是它位于链表的开始位置。
  2. 带头结点的链表:在这种链表中,有一个额外的节点,称为头结点(或哑节点、虚拟节点),它位于链表的开始位置。头结点通常不存储实际的数据(尽管有些实现中也可能存储一些信息,如链表长度),但它有一个指针,指向链表中第一个实际存储数据的节点(即首节点)。头结点的主要目的是简化链表的某些操作,如插入和删除节点。

在带头结点的链表中,头结点通常不存储实际的数据,而是作为一个“哨兵”或“占位符”,使得链表的某些操作(如插入和删除)更加统一和简单。例如,当在链表头部插入一个节点时,不需要特殊处理链表的第一个节点(因为它已经被头结点“保护”了),只需要简单地更新头结点的指针即可。同样,当删除链表中的最后一个节点时,也不需要担心链表会变为空(因为头结点始终存在)。

 

头结点的意义

头结点是在链表的第一个有效元素结点之前附设的一个结点,它不是链表的有效数据结点,因此其数据域通常不存储有效数据,但可以用来存放一些附加信息,如链表的长度等。头结点的主要意义在于:

  1. 简化操作:头结点的存在使得对链表的插入、删除等操作更加统一和方便,特别是在链表为空或操作涉及第一个有效元素时。
  2. 避免空指针操作:当链表为空时,头指针仍然指向头结点,避免了空指针的使用,从而简化了代码逻辑并减少了出现错误的机会。
  3. 作为统一接口:头结点可以作为链表操作的统一接口,无论是空链表还是非空链表,头指针始终指向头结点,使得链表操作更加一致。

首结点的意义

首结点是指链表中存储第一个有效数据元素的结点。在链栈中,首结点通常是栈顶元素所在的结点。首结点的意义在于:

  1. 存储有效数据:首结点是链表中实际存储数据的第一个结点,对于链栈来说,它通常表示栈顶元素。
  2. 操作起点:在进行链表或链栈的操作时,首结点是操作的起点之一,特别是在访问或修改链表中的有效数据时。

头结点与首结点的区别

头结点首结点
定义在链表的第一个有效元素结点之前附设的结点链表中存储第一个有效数据元素的结点
数据域通常不存储有效数据,但可以存放附加信息(如链表长度)存储链表中的有效数据
存在性在链表中可有可无,但通常为了操作方便而设置链表中必须存在,除非链表为空
作用简化链表操作,避免空指针操作,作为统一接口存储有效数据,作为链表或链栈操作的起点之一
关系头结点的指针域指向首结点(如果链表非空)首结点是链表或链栈中第一个被操作的有效数据结点

1、单链表

 单链表:每个物理结点增加一个指向后继结点的指针域

当访问过一个结点后,只能接着访问它的后继结点,而无法向前访问

结构类型声明
typedef struct LNode//结点类型
{
    ElemType data;
    struct LNode *next;
}LinkNode;//链表类型
 插入结点操作

插入操作:将值为x的新结点s插入到p结点之后。    

操作语句描述如下:

① s->next = p->next;

②p->next = s;

 删除操作:删除p结点之后的一个结点。

删除操作语句描述如下:

  p->next = p->next->next;

为了释放被删除结点,操作语句描述如下:

  q=p->next;

   p->next = q->next;

   free(q);

整体建立单链表
1、头插法
从一个空表开始,创建一个头结点。
依次读取字符数组a 中的元素,生成新结点
将新结点插入到当前链表的表头上,直到结束为止。

注意:链表的结点顺序与逻辑次序相反。  

LinkNode *CreateList(ElemType a[],int n)
{  LinkNode *s;
   int i;
   L=(LinkNode *)malloc(sizeof(LinkNode));
   L->next=NULL;		//创建头结点,其next域置为NULL
   for (i=0;i<n;i++)		//循环建立数据结点
   {  s=(LinkNode *)malloc(sizeof(LinkNode));
      s->data=a[i];		//创建数据结点s
      s->next=L->next;		//将s插在原开始结点之前,头结点之后
      L->next=s;
   }
   return L;
}
2、尾插法
从一个空表开始,创建一个头结点。
依次读取字符数组a 中的元素,生成新结点
将新结点插入到当前链表的表尾上,直到结束为止。

 注意:链表的结点顺序与逻辑次序相同。

LinkNode * CreateListR(ElemType a[],int n)
{  LinkNode *s,*r;
   int i;
   L=(LinkNode *)malloc(sizeof(LinkNode));  //创建头结点
   r=L;			//r始终指向尾结点,开始时指向头结点
   for (i=0;i<n;i++)		//循环建立数据结点
   {	s=(LinkNode *)malloc(sizeof(LinkNode));
	s->data=a[i];		//创建数据结点s
	r->next=s;		//将s插入r之后
	r=s;
   }
   r->next=NULL;			//尾结点next域置为NULL
   Return L
}   

基本运算

初始化单链表
 LinkNode *InitList()
 {
    LinkNode *L=(LinkNode *)malloc(sizeof(LinkNode));   //创建头结点
    L->next=NULL;
    return L;
 }
销毁单链表
void DestroyList(LinkNode *L)
{   
   LinkNode *pre=L, *p=L->next;    //pre指向p的前驱结点
   while (p!=NULL)	//扫描单链表L
   {  free(pre);		//释放pre结点
      pre=p;		//pre、p同步后移一个结点
      p=pre->next;
   }
   free(pre);     //循环结束时,p为NULL,pre指向尾结点,释放它
}    
判断是否为空
int ListEmpty(LinkNode *L)
{
  return(L->next==NULL);
}
计算长度
int ListLength(LinkNode *L)
{
   int n=0;
   LinkNode *p=L;	//p指向头结点,n置为0(即头结点的序号为0)
    while (p->next!=NULL)
    {	n++;
	p=p->next;
    }
    return(n);	//循环结束,p指向尾结点,其序号n为结点个数
}      
输出链表
void DispList(LinkNode *L)
{
   LinkNode *p=L->next;		//p指向开始结点
   while (p!=NULL)		//p不为NULL,输出p结点的data域
   {  printf("%d ",p->data);
      p=p->next;			//p移向下一个结点
   }
   printf("\n");
}
求链表中位置为i的元素值
int GetElem(LinkNode *L,int i,ElemType *e)
{  
   int j=0;
   LinkNode *p=L;	 //p指向头结点,j置为0(j和p要同步)

   while (j<i && p!=NULL)  //找到i节点
   {	j++;
	p=p->next;
   }

   if (p==NULL)		//不存在第i个数据结点,返回false
      return false;
   else			//存在第i个数据结点,返回true
   {  *e=p->data;
      return true;
   }
}
求链表中值为e的元素位置i
int LocateElem(LinkNode *L,ElemType e)
{
   int i=1;
   LinkNode *p=L->next;		//p指向开始结点,i置为1  

   while (p!=NULL && p->data!=e) 
   {  p=p->next;  		//查找data值为e的结点,其序号为i
      i++;
   }
   
   if (p==NULL)		//不存在元素值为e的结点,返回0
      return(0);
   else			//存在元素值为e的结点,返回其逻辑序号i
      return(i);
}     
插入元素
int ListInsert(LinkNode *L,int i,ElemType e)
{  int j=0;  //j表示当前访问元素的下标
   LinkNode *p=L,*s;          	//p指向头结点,j置为0,保持同步
   while (j<i-1 && p!=NULL)
   {	j++;
	p=p->next;
   }
   if (p==NULL) return false;     //未找到第i-1个结点,返回false
   else			//找到第i-1个结点p,插入新结点并返回true
   {
	s=(LinkNode *)malloc(sizeof(LinkNode));
	s->data=e;		//创建新结点s,其data域置为e
	s->next=p->next;	//将s插入到p之后
	p->next=s;
	return true;
   }
}
删除元素
int ListDelete(LinkNode *L,int i,ElemType *e)
{  int j=0;
   LinkNode *p=L,*q;		//p指向头结点,j置为0

   while (j<i-1 && p!=NULL)	//查找第i-1个结点
   {	j++;
	p=p->next;
   }
   if (p==NULL)			//未找到第i-1个结点,返回false
	return false;
   else				//找到第i-1个结点p
   {  q=p->next;			//q指向第i个结点
      if (q==NULL)		//若不存在第i个结点,返回false
	 return false;
      *e=q->data;
      p->next=q->next;		//从单链表中删除q结点
      free(q);			//释放q结点
      return true;		//返回true表示成功删除第i个结点
   }
}

2、双链表

 双链表:每个物理结点增加一个指向前驱结点和一个指向后继结点的指针域

 类型声明

typedef struct DNode       	//双链表结点类型
{  ElemType data;
   struct DNode *prior;    	//指向前驱结点
   struct DNode *next;     	//指向后继结点
} DLinkNode;

插入与删除操作

 p结点之后插入结点s(先连后断)

操作语句:

① s->next = p->next

②p->next->prior = s

③s->prior = p

④ p->next = s

删除p结点之后的一个结点

 

操作语句:

①p->next->next->prior = p

②p->next = p->next->next

四、循环链表

循环链表结点类型与非循环链表的相同

循环单链表:将表中尾结点的指针域改为指向表头结点,整个链表形成一个环。由此从表中任一结点出发均可找到链表中其他结点。

循环双链表:形成两个环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值