数据结构部分__2、链表的定义及其操作的实现

这篇博客介绍了C++中链表的基本操作,包括单链表的定义、头插法、尾插法、按序号和值查找节点、插入与删除节点、求链表长度,以及双链表和循环链表的创建与操作。还探讨了如何使用快慢指针找到链表的特定位置元素和进行链表的就地逆置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、单链表的定义:

typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;

LNode表示节点,LinkList指针可以理解为链表,data数据域,next指针域。

2、头插法:

LinkList headinsert(LinkList &L)
{
    L=(LinkList)malloc(sizeof(LNode));
    L->next=NULL;
    int x;
    LNode *s;
    scanf("%d",&x);
    while(x!=1)
    {
        s=(LNode*)malloc(sizeof(LNode));
        s->data=x;
        s->next=L->next;
        L->next=s;
        scanf("%d",&x);
    }
    return L;
}

头插法是链表逆置的关键方法,就是在起始端后的第一个位置不断插入新的节点,这里对应语句s->next=L->next;这是新点和后面的链接关系,还要注意头部的链接:L->next=s;
注意头插法的L->next=NULL的操作,不要忘了,在一些逆置问题中是需要拿下头结点的。

3、尾插法:

LinkList Tailinsert(LinkList &L)
{
    LNode *r,*s;
    r=L;
    while(r->next!=NULL)
    {
       s=(LNode*)malloc(sizeof(LNode));
       int a=0;
       //加个循环终止条件,比如:a=999
       cin>>a;
       if(a==999) {r->next=NULL;return L;}
       s->data=a;
       r->next=s;
       r=s;
    }
    return L;
}

尾插法是一种关键方法,它使得链表按照输入的节点的顺序得到定义,注意r是尾指针,最后一步尾指针的指针域为NULL。注意r=s的节点移动,不要忘了。

4、按序号查找

LNode *search(LinkList &L,int i)
{
    if(i==0)
        return L;
    if(i<1)
        return NULL;
    LNode *s=L->next;
    for(int j=1;j<i;j++)
        s=s->next;
    return s;
}

第一步合法性判断,如果第0号,返回一个头指针,如果小于1(等于0不包括)不合法,返回一个NULL。
第二步创个指针s,它即对应头指针指向头结点的指针域,即第一个元素的地址,然后不断查找就可以了。

5、按值查找结点

LNode *search2(LinkList &L,int i)
{
    LNode *s=L->next;
    while(1)
    {
        if(s->data==i) return s;
        else s=s->next;
        if(s==NULL) return s;
    }
    return s;
}

首先s指向第一个元素,然后找数值域就行了,走到尾部发现还是没有,就返回一个NULL。

6、插入结点(插入到第i个位置)

LinkList insert(LinkList L,int i,int x)
{
   LNode *s=search(L,i-1);//按序查出对应结点的前驱结点地址
   LNode *ss;
   ss=(LNode*)malloc(sizeof(LNode));
   ss->data=x;
   ss->next=s->next;
   s->next=ss;
   return L;
}

无论插入还是删除,对于单链表而言,第一步永远是查出待插入位置的前驱结点的地址。

7、删除节点(删除第i个位置)

LinkList delete1(LinkList &L,int i)
{
   LNode *s=search(L,i-1);
   LNode *ss=s->next;
   s->next=ss->next;
   free(ss);
}

找到前驱结点后,令ss保存待删除节点的位置,如果直接用s->next->next,经过一系列操作后会丢失删除节点的位置,仅是建立前驱和后继结点的连接关系,所以ss=s->next是有必要的。

8、求表长

int length(LinkList &L)//求表长
{
    LNode *s=L->next;
    for(int i=1;;i++)
    {
        if(s==NULL) return i;
        else s=s->next;
    }
}

注意s=L->next对应i=1,表示从第一个元素开始,如果不是从第一个元素开始,应该写作s=L;i=0。

9、双链表的定义

typedef struct DLNode {
   ElemType data;
   struct DLNode *next,*prior;
}DLNode,*LinkList

就是多了一个指向前面的指针prior。

10、双链表的创建(尾插法)

DLinkList establish(DLinkList &L)//创建双链表
{
    L=(DLinkList)malloc(sizeof(DNode));
    DNode *s,*r;
    r=L;
    int a=0;
    for(int i=0;;i++)
    {
        if(i==999) {r->next=NULL;break;}
        else{
            cin>>a;
            s=(DNode *)malloc(sizeof(DNode));
            s->data=a;
            r->next=s;
            s->prior=r;
            r=s;
        }
    }
    return L;
}

这里记得双向链接,写了r->next后不要忘了s->prior=r;
不要忘了移动:r=s;

11、双链表的插入(在结点p后插入结点)

DLinkList dinsert(DLinkList &L,DNode *p,int a)//在p后面插入节点
{
    DNode *s;
    s=(DNode *)malloc(sizeof(DNode));
    s->data=a;
    p->next->prior=s;
    s->next=p->next;
    s=p->next;
    s->prior=p;
    return L;
}

就是建立被插入结点和前驱和后继结点的双向链接关系,总共四个链接,对应四个语句,少一个不对。

12、双链表的删除(删除结点p的后继结点)

DLinkList Ddelete1(DLinkList &L,DNode *p)//删除节点p的后继节点
{
    DNode *s=p->next;
    p->next->next->prior=p;
    p->next=p->next->next;
    free(s);
    return L;
}

和单链表的删除一致,需要现将后继元素的位置存储起来,避免操作丢失这个地址,然后建立两个链接关系,对应两个语句即可。

13、双链表的删除(删除节点p的前驱节点)

DLinkList Ddelete2(DLinkList &L,DNode *p)//删除节点p的前驱节点
{
    DNode *s=p->prior;
    p->prior=s->prior;
    s->prior->next=p;
    free(s);
    return L;
}

这个同理,不做过多解释。

14、循环链表的建立

LinkList cycle(LinkList &L)//创建循环链表
{
    LNode *s,*r;
    r=L;
    for(int i=0;;i++)
    {
        if(i==99999) {r->next=L;return L;}
        else{
            s=(LNode*)malloc(sizeof(LNode));
            int a=0;
            cin>>a;
            s->data=a;
            r->next=s;
            r=s;
        }
    }
}

循环跳出条件满足后,令尾指针r的指针域指向头结点。

常规操作部分如上,下面来骚操作部分:

1、带头结点的单链表就地逆置,空间复杂度O(1)

LinkList reverse (LinkList &L)
{
    LNode *s=L->next;
    L->next=NULL;//头插法第一步,头指针指针域置NULL,即取下头结点
    while(s!=NULL)
    {
        L->next=s->next;
        L->next=s;
        s=s->next;
    }
    return L;
}

其实就是头插法。

2、快慢指针找特定位置的元素
快慢指针被用来解决两种问题,第一种是为了减少求倒数第k个元素,为了减少时间复杂度,替代递归和栈的方法。第二种是找中间位置,即链表的中间元素。

第一个问题,找倒数第k个元素,如果找到,输出数据域,return1,找不到return 0;

int searchK (LinkList &L,int k)
{
    LNode *quick=L->next;
    LNode *slow=L->next;
    int j=1;
    while(quick!=NULL)
    {
        quick=quick->next;
        if(j!=k) j++;
        if(j==k)
            slow=slow->next;
    }
    if(slow!=NULL&&j==k) {cout<<slow->data;return 1;}
    return 0;
}

快指针走到正数第k个位置时慢指针开始走,快指针走到尽头的时候慢指针指向的刚好是倒数第k个位置。
注意条件的判断,如果j==k的话,j++其实也没必要做,因为j只有一个使命,就是提醒慢指针开始工作。

第二个问题,找到链表的中间元素

LNode *findmid(LinkList &L)
{
    LNode *quick=L->next;
    LNode *slow=L->next;
    while(quick->next!=NULL&&slow!=NULL)
    {
        slow=slow->next;
        quick=quick->next;
        if(quick->next!=NULL) quick=quick->next;
    }
    return slow;
}

注意条件是quick的next不为空,slow不为空,不是slow的next不为空,因为慢指针是一步一步走的,尾端元素是最后一步操作,而对于两步两步走的快指针,第二步总是要判断一下结点是否在尾端,在尾端的话再走两步就越界了。循环终止条件满足后,slow指针对应的就是中间元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值