数据结构P1.2: 线性表-链表

线性表:概念和基本操作

  • 线性表的逻辑结构
    线性表是具有相同数据类型(每个数据元素所占的空间一样大)的n(n >=0)个数据元素的有限序列
    直白理解:一条线串起了一个序列,上面的数据元素类型是相同的。各个数据元素之间有前后顺序。可以通过位序表示数据元素
    下图是一个长度为5的线性表
    在这里插入图片描述 - 线性表的基本操作创销、增删改查
初始化:构建一个空的线性表,分配内存空间
销毁操作:销毁线性表并释放线性表所占的内存空间
插入操作:在表中的第 i 个位置上插入指定的元素a
删除操作:删除表中的第  i 个位置上元素,并用e返回删除元素的值
按值查找:在表中寻找给定关键值的元素
按位查找:获取表中第  i 个位置上元素的值
  • 线性表的存储/物理结构
    关注定义,如何用代码实现,基本操作如何实现
    分类:顺序表(顺序存储)链表(链式存储)

线性表:单链表

  • 链表概念: 一种数据元素按照链式存储结构进行存储的数据结构,这种存储结构具有在物理上存在非连续的特点。
  • 单链表概念:单链表由一个个结点组成,节点中有一片空间是存放数据元素的,还需要一片空间指向下一个结点的指针
  • 单链表分类:带头结点和不带头结点
  • 单链表优点:不要求大片连续空间,改变容量方便
  • 单链表缺点:不可随机读取,利用指针依次找到数据,要耗费一定空间存放指针
  • 单链表的局限性:无法逆向检索,会造成检索不易
  • 单链表简化图:由一个个结点node串联而成
    在这里插入图片描述

单链表的实现

  • 代码实现定义一个单链表:
/*单链表由一个个结点组成,节点中有一片空间是存放数据元素的,还需要一片空间指向下一个结点的指针*/
/*定义一个单链表,结构体就是结点结构*/
/*方法一*/struct LNode{
    变量类型 data;//数据域,每个节点存放一个数据元素
    struct LNode *next;//指针域,单链表指针指向下一个结点
}//增加一个新的结点:就在内存中申请一个结点 LNode 所需的空间,并用指针p指向这个结点
struct LNode *p=(struct LNode *)malloc(sizeof)(struct LNode);//增加一个结点至链表:内存中申请一个结点所需要的空间,并用指针p指向这个结点

/*方法二*/typedef struct LNode LNode;//类型重命名 typedef <数据类型> <别名>
struct LNode{
    变量类型 data;//数据域,每个节点存放一个数据元素
    struct LNode *next;//指针域,单链表指针指向下一个结点
};

LNode *p = (LNode *)malloc(sizeof)(LNode));//LNode = struct LNode LNode

/*方法三*/struct LNode{
    变量类型 data;//数据域,每个节点存放一个数据元素
    struct LNode *next;//指针域,单链表指针指向下一个结点
}LNode;*LinkList;

/*表示一个单链表时,只需声明一个指向单链表的第一个结点的头指针L*/
LNode *L;			//强调这是一个结点
//或 LinkList L;	//强调这是一个单链表
  • 代码实现初始化一个单链表:
//声明一个不带头结点的单链表
struct LNode{
    变量类型 data;
    struct LNode *next;
}LNode;*LinkList;

bool InitList(LinkList &L){
    L = NULL;   //空表,头指针为NULL即没有任何结点
    return true;
}

bool Empty(LinkList L){
    if(L==NULL)
        return true;
    else
        return false;
}

void test(){
    LinkList L; //声明一个指向单链表的第一个结点的头指针L,防止脏数据
    InitList(L);//初始化一个空表
    Empty(L);//判断单链表是否为空

}
//声明一个带头结点的单链表
struct LNode{
    变量类型 data;
    struct LNode *next;
}LNode;*LinkList;

bool InitList(LinkList &L){
   L = (LNode *)malloc(sizeof)(LNode));//分配一个头结点
   if(L==NULL)
    return false;   //内存不足,分配失败
   L->next = NULL;  //头结点之后暂时还没有结点,并且头结点上不存数据    
}

bool Empty(LinkList L){
    if(L->next==NULL)
        return true;
    else
        return false;
}

void test(){
    LinkList L; //声明一个指向单链表的第一个结点的头指针L,防止脏数据
    InitList(L);//初始化一个空表
    Empty(L);//判断单链表是否为空(带头结点)
}

单链表的插入删除

插入

  • 按位插入
  • 指定结点的后插
  • 指定结点的前插
//按位插入操作(带头结点):在表L中的第i个位置上插入元素e
//怎么做?1找到第(i-1)个node;2.将新结点插入其后面
bool InsertList(LinkList &L,int i,元素类型 e){
    //按位插入,先判断插入位置的合法性
    if(i<1)
        return false;
    LNode *p;      //强调结点,p指向当前扫描到的结点
    p = L;          //L是链表的头结点,也是第0个结点,此结点不存放数据。借助p从头开始扫描
    int j=0;
    //循环条件:找到第(i-1)个结点,找到了(i-1)个结点时,跳出循环
    while(p!NULL && j<i-1){
        p=p->next;
        j++;
    }
    if(p==NULL)          //判断找到的这个i-1是否合法
        return false;
    //开始进行插入操作
    LNode *s = (LNode *)malloc(sizeof(LNode)); //申请一个新的结点空间  地址为s
    if(s==NULL)                                //内存申请失败        
        return false;
    s->data = e;                               //在这个结点上存放e元素
    s->next = p->next;                         //将新的结点s指向原表第i个结点
    p->next = s;                               //将新结点s连到p(第i-个结点)之后
    return true;
}    
//后插操作:在给定的P结点后插入元素e
bool InsertNextNode(LNode *p,元素类型 e){
    if (p==NULL)
        return false;
    LNode *s=(LNode *)malloc(sizeof(LNode));
    if (s==NULL)
        return false;
    s->data=e;
    s->next=p->next;
    p->next=s;
    return true;
}
//前插操作
//偷天换日,先创建p的后继结点,再传数据
bool InsertPriorNode(LNode *p,元素类型 e){
    if (p==NULL)
        return false;
    LNode *s=(LNode *)malloc(sizeof(LNode));
    if (s==NULL)
        return false;
    s->next=p->next;
    p->next = s;        //s做p的后继节点
    s->data = p->data;  //把结点P的元素复制到结点s中
    p->data = e;        //p中的元素覆盖overwrite为e
    return true;
}

删除

  • 按位序删除
  • 指定结点的删除
//按位删除:删除表L中的第i个结点
//思路:删除第i个结点,找到第i-1个结点,将其指针指向i+1个结点,最后释放第 i 个结点
bool DelList(LinkList &L,int i,元素类型 &e){
    if(i<1)
        return false;
    //找到前驱结点,并判断合法性
    LNode *p;
    p = L;
    int j = 0;
    while(p!=NULL && j<i-1){
        p=p->next;
        j++;
    }
    if(p==NULL)
        return false;
    if (p>next==NULL)           //前驱结点i-1个结点后无其他结点
        return false;
    
    //找到了前驱结点
    LNode *q = p->next          //指针q指向要被删除的第i个结点(p->next)
    e = q->data;                //用e返回元素的值
    p->next=q->next;            //把结点q从链表中断开
    free(q);                    //释放结点q的存储空间
    return true;
}
//删除指定结点 p 
bool DelNode(LNode *p){
    if (p==NULL)
        return false;
    LNode *q = p->next;         //声明一个结点q,令q指向p的后继结点
    p->data=p->next->data;      //p结点后继结点上的数据 复制到 p结点
    p->next=q->nextl            //p->next和q->next指向一个地址,目的是断开q
    free(q);
    return q;
}

单链表的查找

单链表的查找只能依次扫描,不像顺序表那样具有随机性

  • 按位查找:找到单链表L中第i个位置元素
  • 按值查找:查找给定关键字
//按位查找,返回第i个元素
LNode *GetElem(LinkList L,int i){
    if(i<0)
        return NULL;
    LNode *p;               //声明一个指针,指向当前扫描到的结点
    p = L;                  //L指向头结点,L的地址赋值给p,p也指向了头结点
    int j=0;                //代表当前p的位序
    while(p!=NULL && j<i){
        p=p->next;
        j++;
    }
    return p;               //返回指针p
}
//按值查找,找到数据e的结点
LNode *LocateElem(LinkList L,元素类型 e){
    LNode *p=L->next;
    //从第一个结点开始查找
    while(p!==NULL && p->data!=e)
        p=p->next;
    return p;                       //找到后就跳出循环,返回该结点指针,否则反返回NULL
}

单链表的建立

  • 尾插法
  • 头插法
//尾插法建立单链表
//创建表尾指针,在这个结点后依次进行后插操作
LinkList List_TailInsert(LinkList &L){
    int e;                                  //插入的元素位int型
    L = (LinkList)malloc(sizeof(LNode))     //建立头结点
    LNode *s,*r = L;                        //s为新结点指针,r为表尾指针
    scanf("%d",&x);                         //输入需要插入的元素
    while(x!=99999){                        //表示结束
        //在r结点后插元素x
        s = (LNode *)malloc(sizeof(LNode));
        s->data =x;
        r->next=s;                          //表L尾结点指针指向s
        r=s;                                //r指针永远指向新的表尾结点
        scanf("%d",&x);
    }
    r->next=NULL;
    return L;
}
//头尾插法建立单链表
//依次在头结点后进行后插
LinkList List_TailInsert(LinkList &L){
    LNode *s;
    int x;
    L = (LinkList)malloc(sizeof(LNode))     //建立头结点
    L->next = NULL;
    scanf("%d",&x); 
    while(x!=99999){
        //在头结点后插元素x
        s = (LNode *)malloc(sizeof(LNode));
        s->data =x;
        s->next = L->next;
        L->next = s;                        //将新的结点插入表中,L指向该结点
        scanf("%d",&x);
    }
    return L;
}

线性表:双链表

  • 单链表:只有一个后驱指针,因此无法逆向检索,有时会带来不便
  • 双链表:同时拥有前驱和后继指针,指向前驱和后继结点。可前向检索也可后向检索,存储密度更低一点
  • 双链表定义
typedef struct DNode{           //定义双链表指针
    元素变量类型 data;
    struct DNode *prior,*next;  //前驱和后继指针
}DNode,*Dlinklist;

双链表的实现

  • 对于链表中的某个结点p,它的后继的前驱是它自己,同样,它的前驱的后继也是它自己
p->next->prior = p; 
p->prior->next = p;
//初始化双链表
bool InitDlinklist(Dlinklist &L){
    L = (DNode*)malloc(sizeof(DNode));  //分配一个头结点给
    if (L==NULL)
        return false;
    L->prior = NULL;                    //头结点的前驱指针prior永远指向NULL
    L->next = NULL;                     //头结点之后暂时没有节点
    return true; 
}
//判断双链表是否为空
bool Empty(Dlinklist L){
    if(L->next==NULL)
        return true;
    else
        return false;
}

void test(){
    Dlinklist L;                 //声明指向头结点的指针L   
    InitDlinklist(L);
    Empty(L);
}

双链表的插入

//双链表的后插.在p结点后插入新的s结点
bool InsetNextDNode(DNode *p,DNode *s){
    if(p==NULL || s==NULL)
        return FALSE;
    s->next = p->next;          //结点后继指针指向的下一个结点是当前结点p后继指针指向的下一个结点
    if(p->next!=NULL)          //如果结点p存在后继结点
        p->next->prior=s;       //当前结点p指针指向的下一个结点的前驱指针指向新插入的结点s
    s->prior = p;               //新插入的结点s的前驱指针指向p
    p->next = s;                //当前结点p后继指针指向的下一个结点指向新插入的结点s
    return true;    
}

双链表的删除

//双链表的删除,删除p的后继结点q
bool DeleteNextDNode(DNode *p){
    DNode *q=p->next;           //找到了p的后继结点q
    if(p==NULL|| q==NULL)
        return false;
    p->next=q->next;
    if(q->next!=NULL//结点q不是最后一个结点
        q->next->prior=p;
    free(q);                    //释放结点q空间
}

双链表的销毁

//销毁双链表
void DestoryList(Dlinklist &L){
    //循环释放头结点L的后继结点
    while(L->next!=NULL)
        DeleteNextDNode(L);     //删除头结点L的后继结点
    free(L);                    //释放头结点L空间
    L=NULL;                     //头结点L指向NULL
}

线性表:循环链表

  • 循环单链表:单链表最后一个结点p->next=NULL;循环单链表最后一个结点指向头结点即 p->next=L
  • 循环双链表:双链表表头结点L->prior=NULL,表尾结点p->next=NULL;循环双链表表头结点L->prior=p,表尾结点p->prior=L

循环单链表的实现

//初始化循环单链表
bool InitDlinklist(Linklist &L){
    L = (DNode*)malloc(sizeof(DNode));  //分配一个头结点
    if (L==NULL)
        return false;
    L->next = L;                     //头结点为当前的最后一个结点,L->next指向的是头结点
    return true; 
}

//判断循环单链表是否为空
bool Empty(Linklist L){
    if(L->next==L)
        return true;
    else
        return false;
}

//判断结点p是否为循环单链表的表尾结点
bool isTail(Linklist L,LNode *p){
    if(p->next==L)
        return true;
    else
        return false;        
}

循环双链表的实现

//初始化循环双链表
bool InitDlinklist(Dlinklist &L){
    L = (DNode*)malloc(sizeof(DNode));  //分配一个头结点给
    if (L==NULL)
        return false;
    L->prior = L;                    //头结点的前驱指针prior指向头结点L
    L->next = L;                     //头结点后驱结点指针next指向指向头结点L
    return true; 
}
//判断循环双链表是否为空
bool Empty(Dlinklist L){
    if(L->next==L)
        return true;
    else
        return false;
}

//判断结点p是否为循环双链表的表尾结点
bool isTail(Dlinklist L,DNode *p){
    if(p->next==L)
        return true;
    else
        return false;        
}

线性表:静态链表

  • 单链表:各个结点在内存中是离散的。一个结点由数据元素指向下一个结点的指针(地址)组成
  • 静态链表:各个结点集中安置在内存中连续的空间内一个结点由数据元素指向下一个结点的数组下标(游标)组成。内存的物理结构和链表的逻辑结构如图所示

在这里插入图片描述
在这里插入图片描述

  • 静态链表优点:增、删不需要移动大量元素
  • 静态链表缺点:不能随机读取,只能从头结点开始遍历;容量是固定不变的

静态链表的实现

#define MaxSize                 //静态链表的长度
//静态链表结构体类型的定义
struct Node{
    元素变量类型 data;
    int next;                   //下一个结点元素的数组下标,也就是地址
}

void test(){
    struct Node a[MaxSize];     //数组a为静态链表,数组中的每一个元素类型都是结构体Node
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kxwang_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值