数据结构之链表

2019-8-30链表

1、表的链式存储—链表
链表:线性表采用链式方式将结点链接起来的存储结构称为链表,修改结点链接适合动态变化
单链表:链表中的每个结点只有一个指针域
①单链表的结点结构

值域(数据域)链域 (指针域)
datanext
存储结点的数据值存储数据元素的直接后继的地址

在这里插入图片描述

②链式存储结构图

  • 首指针(表头指针):指向链表的第一个结点的指针变量,其值为首结点的存储地址
  • 表尾结点(最后一个结点):由于最后一个数据元素没有直接后继,链域值为空(NULL)
  • 链表就是表头指针和一串相继链接的结点的总称
    用一组任意的存储单元(地址可以不连续的单元)存放线性表的结点
    每个结点的唯一后继依靠一个结点指针维持

    在这里插入图片描述
    H ----头指针,指向第一个结点
    ^ ----最后一个结点的指针域为"空"(NULL)

③带头结点的单链表结构
(a)带头结点的空单链表
在这里插入图片描述
(b)带头结点的非空单链表
在这里插入图片描述
附设头结点的目的:为方便统一空表或非空表的运算处理
头结点:数据域无要求,其指针域指向第一个结点
首结点:链表中第一个元素结点
Q:链表中头指针、头结点、首结点的关系
链表头指针指向单链表开始(H)
带头结点的链表中,头指针(H)指向头结点,头结点指向首结点
无头结点的链表中,头指针(H)指向首结点
④单向链表结构
结构示例:线性表(A,B,C,D,E,F,G,H)的单链表存储结构,头指针H为31

存储地址数据域指针域
1D43
7B13
13C1
19HNULL
25F37
31A7
37G19

解析:头指针H为31,指向表中地址为31的存储地址,其中31的存储地址数据元素为A,其指针域为7,指向其后继结点存储地址为7的地址,以此循环下去。这个表很好地反映了存放线性表的结点是用一组任意的存储单元(地址可以不连续的单元)。
在这里插入图片描述
⑤单链表类型C语言定义

typedef struct Node  //结点类型定义
{
  ElemType data;//定义数据域
  struct Node *next;//定义指针域
}Node,*LinkList; //LinkList为结构指针类型
LinkList  L; //L为单链表的头指针,也称为单链表L

LinkList与Node* 同为结构指针类型
常用LinkList类型定义指针变量
常用Node* 来定义单链表中的结点类型

⑥单链表的基本使用方式
在这里插入图片描述

  1. 头指针指向单链表
  2. 单链表的操作必须从头指针开始依次访问表中的结点
  3. 访问带头结点单链表L的首结点p,其语句为:p=L->next
  4. 首结点的data部分(a1):头指针指向单链表(L->next)->data

2、单链表的基本操作以及运算

结点的引用:
p->data:结点的值域,等价于(*p).data
p->next:结点的链域,等价于(*p).next

①单链表的建立

  • 空表
Status InitList(LinkList &L)
{
   L=(LinkList)malloc(sizeof(LNode)); //申请结构体类型的内存
   if(!L)exit(OVERFLOW); //若申请内存失败,退出
   L->next=NULL;//将头结点的next域置空,变为空指针
 return OK;
}
  • 头插法建立非空单链表
Status InitList(LinkList &L)
{
   L=(LinkList)malloc(sizeof(LNode));//申请内存
   if(!L)exit(OVERFLOW);
   L->next=NULL; //置空头结点的next域
   cin>>n;  //n代表n个结点
   for(i=1;i<=n;i++)
   {
     p=(LinkList)malloc(sizeof(LNode)); //每一次循环,申请新结点的内存空间
     cin>>p->data;//每一次循环,输入结点的data值
     p->next=L->next;//将头结点的next域赋给新结点的next域,使新结点的next域指向头结点后面的结点
     L->next=p;//将指向新结点的指针赋给头结点的next域,使头指针指向头结点
   }
   return OK;
}
  • 尾插法建立非空单链表
Status InitList(LinkList &L)
{
   L=(LinkList)malloc(sizeof(LNode));
   if(!L)exit(OVERFLOW);
   L->next=NULL;
   q=L;
   cin>>n;//输入要插入的结点个数
   for(i=1;i<=n;i++)
   {
     p=(LinkList)malloc(sizeof(LNode)); //申请新结点的内存空间,并将地址赋给指针p
     cin>>p->data;//存入新结点数据域的值
     q->next=p; //将指向新结点的指针赋给尾结点q的next域,使尾结点的next域指针指向新结点的地址
     q=p; //将指向新结点的指针赋给指向尾结点的指针
   }
   return OK;
}

①单链表的插入
插入条件:要在带头结点的单链表L中的第i个数据元素之前插入一个数据元素e
算法描述:
a)在单链表中找到第i-1个结点并由指针pre指示
b)然后申请一个新结点并由指针s指示,其数据域的值为e
c)修改第i-1个结点的指针使其指向s
d)使s结点的指针域指向第i个结点
在这里插入图片描述

//插入关键步骤
① s->next=pre->next;
② pre->next=s;
//注意:这两个的操作步骤顺序不能改变
//表中插入
//单链表插入新结点的完整代码
Status ListInsert_L(LinkList &L,int i,ElemType e){
  //在带头结点的单链表L中的第i个位置之前插入元素e
  p=L;
  j=0;
  while(p&&j<i-1){
  //寻找第i-1个结点
     p=p->next;
     ++j;
  }
  if(!p||j>i-1) //
  {
     return ERROR;
  }
  s=(LinkList)malloc(sizeof(LNode));//申请一个新的存储结点
  s->data=e;//置结点的值域,将读入的元素值存入新结点的值域
  s->next=p->next;//将指向第i-1个结点的指针赋给s的指针域,使指向新结点e的指针域指向后继结点i
  p->next=s;//将指向新结点的指针s赋给第i-1个结点的指针域,使其指向新结点e
  return OK; 
}
//插在表头
//head为头指针
p->next=head;//将头指针head赋给新结点的指针域,使新结点的指针域指向其后继结点
head=p;//将指向新结点的指针赋给头指针,使头指针指向新结点的地址

①求带头结点单链表长度
算法思路:沿带头结点单链表L的首元结点计数统计单链表长度
算法描述:
a)顺链头开始,计数器j的初值为0,当前指针p指向链表L的首元结点 p=L->next
b)p依次往后(计数j++)直到表尾(p=NULL)

//求带头结点的单链表L的单链表的长度,next部分是指针域
int ListLength(LinkList L)
{
   Node *p;
   p=L->next; //使当前p指针指向链表L的首元结点,L是头指针
   j=0;  //用来存放单链表的长度
   while(p!=NULL)
   { 
      p=p->next;//使指针p指向下一个结点的存储位置
      j++;//从首结点开始计数到尾结点
   }
   return j;
}

②建立单链表

//建立空表L
InitList(LinkList *L)
{
  L=(LinkList)malloc(sizeof(Node));//建立头结点
  L->next=NULL;//建立空的单链表
}

注:L是指向单链表头结点的指针,用来接收单链表头指针变量的地址
③头插法建表
算法描述:已知空链表L,依次读入结点数据头插入,直到读入结束标志为止。
每插入一个结点到表头的步骤:
1、生成新结点s
2、将结点s插入到首元结点之前,即表头结点之后

Linklist CreateFromHead(LinkList L)
{
  Node *s;
  char c;
  int flag=1;//设置一个标志,初值为1,当输入"$"时,flag为0,建表结束
  while(flag)
  {
     c=getchar();//输入字符
     if(c!='$')
      {  s=(Node*)malloc(sizeof(Node));//申请新结点s
          s->data=c;//赋值给新结点的数据域
          s->next=L->next;//头结点的指针域赋给新结点的指针域
          L->next=s;//将结点s插入链表L
      }
    else flag=0;
  }
}

④尾插法建表
算法描述:已知空链表,设置尾指针r指向当前表尾,依次读入结点数据尾插入,直到读到结束标志时将表尾结点链域置空
1、生成新结点s
2、将结点s插入到表尾r之后,该结点作为当前表尾结点

Linklist CreateFromTail(LinkList L)
{ 
  LinkList L;
  Node *r,*s;//定义指针变量r,s
  int flag=1;//设置一个标志,初值为1,当输入“$”时,flag=0,建表结束
  r=L;//r指针始终动态指向链表的当前表尾,便于做尾插入,其初值指向头结点
  while(flag)
  { 
   c=getchar();
   if(c!='$')
   { 
     s=(Node*)malloc(sizeof(Node));
     s->data=c;
     r->next=s;
     r=s;
   }
   else{
   flag=0;
   r->next=NULL;//将最后一个结点的next链域置为空,表示链表的结束
        }
   }
}

3、指定位置的删除
①删除表头结点

q=head;
head=head->next;
free(q);

②删除表中结点

p=q->next;
q->next=p->next;
free(p);

4、链表的特点

  • 结点地址不连续
  • 插入/删除不移动结点,耗时为O(1)
  • 用于动态管理
  • 使用指向结点(结构类型)的指针
  • 执行期间,调用动态存储管理函数产生结点、回收结点

5、单向链表的构造
构造链表的通用算法
步骤1)构造“空链表”
步骤2)读入第一个元素值
步骤3)当读入的元素值不是“输入结束标记”时,循环执行步骤4~7
步骤4)申请一个新结点
步骤5)将读入的元素值存入新结点的值域
步骤6)将新结点“插在”链表中
步骤7)读入“下一个”元素值,转步骤3
步骤8)构造完毕,返回首指针
①向前插入法造表

ptr creatlinkedA()
{  
    ptr head,p;//定义局部变量,head,p为指针变量
    int x;
    head=NULL;//将表头指针置空,表示链表的初始状态
    scanf("%d",&x);//读入第一个元素
    while(x!=End_elm)//当读入的不是结束标记时循环,这里的End_elm是抽象定义,具体实现自己定义
    {
         p=(ptr)malloc(sizeof(snode));//申请一个存储结点
         p->data=x;//置结点的值域,将读入的元素值存入新结点的值域
         p->next=head;//插在表头处
         head=p;//表头指针指向新结点
         scanf("%d",&x);
    }
    return(head);//返回表头指针
 }//ptr在C语言中没有特别的含义,既不是关键字也不是库函数的函数名。是自定义的一个变量名或函数名。

②向后插入法造表
构造链表的通用算法
步骤1)构造“空链表”
步骤2)读入第一个元素值
步骤3)当读入的元素值不是“输入结束标记”时,循环执行步骤4~7
步骤4)申请一个新结点
步骤5)将读入的元素值存入新结点的值域
步骤6)将新结点“插在”链表的表尾处
步骤7)读入“下一个”元素值,转步骤3
步骤8)构造完毕,返回首指针

ptr creatlinked_B()
{
     ptr head,last,p;
     int x;
     head=NULL;
     scanf("%d",&x);
     while(x!=End_elm)
     {
        p=(ptr)malloc(sizeof(snode));//申请一个存储结点
        p->data=x;
        if(head=NULL)//空表,修改头指针和尾指针
         {
            p->next=head;
            head=p;
            last=p;
         }
         else//非空表,插在表尾,修改尾指针
         {
            last->next=p;
            p->next=NULL;
            last=p;
         }
         scanf("%d",&x);//读入下一个元素
     }
     return head;
}
  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值