第二章 线性表——链表

目录

1.单链表

(1)定义

(2)链表的创建

(3)链表的初始化

(4)链表的查找

查找位置

查找元素

 (5)链表的插入

 (6)链表的增加

(8)链表的输出

补充:

链表的指针

 (7)链表顺序表的优缺点

(9)小结

2.双向链表

(1)定义

(2)创建

(3)插入

(4)删除


1.单链表

(1)定义

1.采用链式存储结构的线性表称为线性链表。

2.结点包含数据域和指针域两部分。

3.元素节点前通常加头结点,链表名指向头结点(头指针),尾结点指针域值为NULL。

4.头指针标识整个链表, 链表名即头指针;

db7add226d5b47eb80665cdaf499aae9.jpeg

(2)链表的创建

typedef struct LNode{//LNode类似user
ElemType data;//数据域
struct LNode *next;//指针域
}LNode,*Linklist;
//这里的LNode等于struct LNode,结构体名称,类似xiaoming
//命名方式:表名创建LNode T=>T.data   T.next
//这里的*Linklist等于struct LNode*,指向整个结构体的指针
//命名方式:指针创建Linklist T=>T->data  T->next

(3)链表的初始化

Status Listcreate_(Linklist &L,int n)
{
    LNode *rearptr,*curptr;//一个尾指针,一个指向结点的指针
    L=(LNode*)malloc(sizeof(LNode));
    //开辟头结点,其数据域一般不做赋值。
    if(!L)
        exit(OVERFLOW);
    L->next=NULL;//指针域指向空,先建立一个头结点的单链表
    rearptr=L;//初始头结点为尾结点
    for(int i=1;i<n;i++)
    {//每次循环一次开辟一个新节点,并把新节点拼到尾结点
    curptr=(LNode*)malloc(sizeof(LNode));//开辟一个新结点
    if(!curptr)
        exit(OVERFLOW);
    scanf("%d",&curptr->data);//输入元素值
    curptr->next=NULL;
    rearptr->next=curptr;
    rearptr=curptr;
    }
}

  

(4)链表的查找

查找位置

//查找位置
Status ListLocate_L(Linklist &L,ElemType e)
{
    LNode *temp=L->next;//临时指针指向第一个结点
    int pos=0;
    while(temp!=NULL)
    {
        pos++;
        if(temp->data==e)
            return pos;//当指向e时,返回pos并结束循环
        else temp=temp->next;
    }
    return 0;
}

查找元素


Status GetElem_L(Linklist &L,int pos,ElemType &e)
{
    //L为带头结点的单链表的头指针。
//第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
//设遍历指针和相应计数器,从头开始后移至计数器为i停.i异常
    LNode *p=L;
    int j=0;//p指向”第0个”元素结点,j是该结点的位序
    while(p&&j<pos) //顺序查找,只要p不空且未到第i结点就前进
    {
        p=p->next;
        ++j;
    }
    if(!p||pos<1)return ERROR;    //第i个元素不存在 或 i<1,
    e=p->data;                   //取第i个元素
    return OK;
}

 (5)链表的插入


Status ListInsert_L(Linklist &L, int i, ElemType e){
//定位到第i-1个结点,在其后插入一个新开辟的结点.注意i异常
  LNode *p=L;  int j=0;  /*p始终指向第j个结点*/
//找到插入位置
  while(p&&j<i-1)
    {p=p->next; ++j;}//只要不空且未到第i-1个
   if(!p||i<1) return  ERROR;  //第i-1个节点不存在或者i<1
//连接插入结点
   LNode *q;
   q= (LNode *)malloc(sizeof(LNode)); //或q=new LNode;
   if(!q)exit(OVERFLOW);
   q->data=e;
   q->next=p->next;
   p->next=q;
   return(OK);
}

 (6)链表的删除


Status ListDelete_L(Linklist &L, int i, ElemType &e){
//寻找第i-1个结点 ,若其后继结点存在则带回其data并删除
  LNode *p=L,*q;  int j=0;  /*p始终指向第j个结点*/
  while(p&&j<i-1){p=p->next; ++j;}//寻找第i-1个结点
                                             // j 最终为i-1,除非p空
  if(!p||!p->next||i<1) return  ERROR;//第i个或更前结点不存在,
  q=p->next;
  e=q->data;
  p->next=q->next;
  free(q);
  return(OK);
}

(8)链表的输出


void printfLinklist(Linklist &L){
    LNode *p=L->next;
while (p!= NULL)
	{
		printf("%d ",p->data) ;
		p=p->next;
	}
	printf("%d",p->data) ;
}
//库函数头文件包含
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>

//函数状态码定义
#define TRUE        1
#define FALSE       0
#define OK          1
#define ERROR       0
#define INFEASIBLE -1
#define OVERFLOW   -2
typedef int  ElemType;
typedef int  Status;
#define LIST_INIT_SIZE  100
#define LISTINCREMENT   10
typedef struct LNode //LNode类似user
{
    ElemType data;//数据域
    struct LNode *next;//指针域
    //数据域用来存放数据元素,指针域用来存放下一个数据结点的地址
} LNode,*Linklist;
//这里的LNode等于struct LNode,结构体名称,类似xiaoming
//命名方式:表名创建LNode T=>T.data
//这里的*Linklist等于struct LNode*,指向整个结构体的指针
//命名方式:指针创建Linklist T=>LinkList->data
Status Listcreate_L(Linklist &L,int n)
{
    LNode *rearptr,*curptr;//一个尾指针,一个指向结点的指针
    L=(LNode*)malloc(sizeof(LNode));
    //开辟头结点,其数据域一般不做赋值。
    if(!L)
        exit(OVERFLOW);
    L->next=NULL;//指针域指向空,先建立一个头结点的单链表
    rearptr=L;//初始头结点为尾结点
    for(int i=1; i<=n; i++)
    {
        //每次循环一次开辟一个新节点,并把新节点拼到尾结点
        curptr=(LNode*)malloc(sizeof(LNode));//开辟一个新结点
        if(!curptr)
            exit(OVERFLOW);
        scanf("%d",&curptr->data);//输入元素值
        curptr->next=NULL;
        rearptr->next=curptr;
        rearptr=curptr;
    }
    return OK;
}
//查找位置
Status ListLocate_L(Linklist &L,ElemType e)
{
    LNode *temp=L->next;//临时指针指向第一个结点
    int pos=0;
    while(temp!=NULL)
    {
        pos++;
        if(temp->data==e)
            return pos;//当指向e时,返回pos并结束循环
        else temp=temp->next;
    }
    return 0;
}
Status GetElem_L(Linklist &L,int pos,ElemType &e)
{
    //L为带头结点的单链表的头指针。
//第i个元素存在时,其值赋给e并返回OK,否则返回ERROR
//设遍历指针和相应计数器,从头开始后移至计数器为i停.i异常
    LNode *p=L;
    int j=0;//p指向”第0个”元素结点,j是该结点的位序
    while(p&&j<pos) //顺序查找,只要p不空且未到第i结点就前进
    {
        p=p->next;
        ++j;
    }
    if(!p||pos<1)return ERROR;    //第i个元素不存在 或 i<1,
    e=p->data;                   //取第i个元素
    return OK;
}
Status ListInsert_L(Linklist &L, int i, ElemType e){
//定位到第i-1个结点,在其后插入一个新开辟的结点.注意i异常
  LNode *p=L;  int j=0;  /*p始终指向第j个结点*/
  while(p&&j<i-1)
    {p=p->next; ++j;}//只要不空且未到第i-1个
   if(!p||i<1) return  ERROR;  //第i-1个节点不存在或者i<1
   LNode *q;
   q= (LNode *)malloc(sizeof(LNode)); //或q=new LNode;
   if(!q)exit(OVERFLOW);
   q->data=e;
   q->next=p->next;
   p->next=q;
   return(OK);
}
Status ListDelete_L(Linklist &L, int i, ElemType &e){
//寻找第i-1个结点 ,若其后继结点存在则带回其data并删除
  LNode *p=L,*q;  int j=0;  /*p始终指向第j个结点*/
  while(p&&j<i-1){p=p->next; ++j;}//寻找第i-1个结点
                                             // j 最终为i-1,除非p空
  if(!p||!p->next||i<1) return  ERROR;//第i个或更前结点不存在,
  q=p->next;
  e=q->data;
  p->next=q->next;
  free(q);
  return(OK);
}
void printfLinklist(Linklist &L){
    LNode *p=L->next;
while (p!= NULL)
	{
		printf("%d ",p->data) ;
		p=p->next;
	}
	printf("%d",p->data) ;
}
int main()
{
    Linklist L;
    int n;
    scanf("%d",&n);
   Listcreate_L(L,n);
   // printfLinklist(L);
    int pos;
    scanf("%d",&pos);
     //ListInsert_L(L,pos,e);
      ElemType e;
     GetElem_L(L,pos,e);
     printf("%d",e);
      //printfLinklist(L);
}

补充:

链表的指针

83fbea2f27f941649db93eb84717e861.jpg

#include<stdio.h>
typedef struct LNode
{
      int data;
      LNode *p;
} Node;
int main()
{
    Node a,b;
    a.data=123;
    b.data=124;
    a.p=&b;//指向b的头结点
    a.p->data=1212;
    b.p=&a;//地址=地址
    b.p->data=807;//等于a.data
    printf("%d\n",b.data);
    printf("%d",a.data);
}

 (7)链表顺序表的优缺点

优点:空间利用好,插入删除不需移动数据,表头操作快。

缺点:顺序访问,位序概念淡化, 表尾操作、求前驱、求表长慢,存储密度低。例如,为方便求前驱可引入双向链表,为方便表尾操作可引入尾指针和循环链表,为实用改进结构定义如添加表长成员和尾指针的改进单链表等

(9)小结

219b6f63281e41ada358d6a4bad27605.png

2.双向链表

(1)定义

单向链表特点

      1.我们可以轻松的到达下一个节点, 但是回到前一个节点是很难的.
  2.只能从头遍历到尾或者从尾遍历到头(一般从头到尾)
双向链表特点
  1.每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个. 实现起来要困难一些
  2.相对于单向链表, 必然占用内存空间更大一些.
  3.既可以从头遍历到尾, 又可以从尾遍历到头
双向链表的定义:
  双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。下图为双向链表的结构图。b82542c6826c426da114fa990f31fe51.png

  从上中可以看到,双向链表中各节点包含以下 3 部分信息:
  指针域:用于指向当前节点的直接前驱节点;
  数据域:用于存储数据元素。
  指针域:用于指向当前节点的直接后继节点;

63a63985407745f599991071f52da4b1.png

双向循环链表的定义:
  双向链表也可以进行首尾连接,构成双向循环链表,如下图所示

da6dfb501dba4a2ab7561c460697a1a5.png
在创建链表时,只需要在最后将收尾相连即可(创建链表代码中已经标出)。其他代码稍加改动即可。

(2)创建

//---线性表的双向(循环)链表存储结构---
typedef struct DuLNode{
   ElemType     data;
   struct DuLNode *prior;
   struct DuLNode *next;
}DuLNode,*DuLinkList;
//双向链表有利于访问前驱结点,复杂度多少?

(3)插入

p=GetElemP_DuL(L,i-1); //确定插入位置

s=new DuLNode;

 s->data=e;

s->prior=p;  

s->next=p->next;

p->next->prior=s;  

p->next=s;

  Status ListInsert_DuL(DuLinkList &L,int i,ElemType &e){
  DuLNode *p=L; int j=0;
  while(p&&j<i-1){p=p->next;++j;}
  if(!p||i<1) return ERROR;
  s=new  DuLNode;   if(!s)exit(OVERFLOW);
  s->data=e;
  s->prior=p; s->next=p->next;   //注意分析指针改变次序
  p->next->prior =s;  p->next=s; 
  return OK;
}//ListDelete_DuL

(4)删除

(1)p->prior->next=p->next; (2)p->next->prior=p->prior; (3)  free(p); p=NULL

Status ListDelete_DuL(DuLinkList &L,int i,ElemType &e){
//删除带头结点的双向链表L的第i个元素,1≤i≤ListLength(L)
 if(!(p=GetElemP_Dul(L,i))) return ERROR;
  e=p->data;
  p->prior->next=p->next;
  p->next->prior=p->prior;
  free(p);p=NULL;  //教材未写p=NULL,容易引起内存泄露;
  return OK;
}//ListDelete_DuL

单链表的反转

988f1b696d0947aba856367cb0139d78.jpeg

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值