线性表的链式表示(单链表)c++

这篇博客详细介绍了如何使用C语言实现单链表的各种基本操作,包括头插法和尾插法建立链表、初始化、判空、求表长、插入元素、删除元素、按位查找、按值查找以及打印链表。此外,还讨论了链表结点的销毁和内存管理,特别强调了在销毁链表时需要逐个释放结点的重要性。
摘要由CSDN通过智能技术生成

注意销毁链表时,要一个一个地free ( p )
下面各个函数均测试过可以正常运行
LinkList HeadInsert(LinkList &L);//头插法建立单链表
LinkList TailInsert(LinkList &L);//尾插法建立单链表
bool InitList(LinkList &L);//初始化单链表
bool Empty(LinkList L);//判空
int Length(LinkList L);//求表长
bool ListInsert(LinkList &L,int i, int e);在位序i处插入e
bool InsertNextNode(LNode p, int e);//后插操作 在p结点之后插入元素e
bool InsertPrior(LNode *p, int e);//前插操作, 在结点p之前插入元素e
bool ListDelete(LinkList &L, int i, int &e);//按位序删除(带头结点) 删除L的第i个元素,并用e返回被删除的值
bool DeleteNode(LNode *p);删除指定结点p, O(1) “偷天换日”
bool IndexLeagal(LinkList L,int i);//位序i的合法性检查,线性表的i从1开始
LNode GetElem(LinkList L,int i);//按位查找,返回第i个元素(带头结点)
LNode
LocateElem(LinkList L,int e);//按值查找,找到数据域等于e的结点 O(n)
void PrintList(LinkList L);//打印链表

//线性表的链式表示  单链表 带头结点
#include<stdio.h>
#include<stdlib.h>//malloc free函数的头文件
typedef struct LNode{
    int data;//数据域
    struct LNode *next;//指针域
}LNode,*LinkList;
//************************    函数    **************************
LinkList HeadInsert(LinkList &L);//头插法建立单链表
LinkList TailInsert(LinkList &L);//尾插法建立单链表
bool InitList(LinkList &L);//初始化单链表
bool Empty(LinkList L);//判空
int Length(LinkList L);//求表长
bool ListInsert(LinkList &L,int i, int e);//在位序i处插入e
bool InsertNextNode(LNode *p, int e);//后插操作   在*p结点之后插入元素e
bool InsertPrior(LNode *p, int e);//前插操作, 在结点p之前插入元素e
bool ListDelete(LinkList &L, int i, int &e);//按位序删除(带头结点)  删除L的第i个元素,并用e返回被删除的值
bool DeleteNode(LNode *p);//删除指定结点p, O(1) "偷天换日"
bool IndexLeagal(LinkList L,int i);//位序i的合法性检查,线性表的i从1开始
LNode *GetElem(LinkList L,int i);//按位查找,返回第i个元素(带头结点)
LNode* LocateElem(LinkList L,int e);//按值查找,找到数据域等于e的结点 O(n)
void PrintList(LinkList L);//打印链表

/*注意:
头插法时要malloc新结点
*/

//带头结点
//头插法建立单链表
LinkList HeadInsert(LinkList &L){//@@@
    LNode *s;
    int x;
    L=(LinkList)malloc(sizeof(LNode));

    //养成习惯,初始为空链表,否则此指针会指向某未知区域,可能会造成不知名错误
    L->next=NULL;
    printf("input node value(input 9999 to end):");
    scanf("%d",&x);
    while(x!=9999){
        s=(LNode*)malloc(sizeof(LNode));//@@@
        s->data=x;
        s->next=L->next;
        L->next=s;
        printf("input next node value:");
        scanf("%d",&x);
    }
    return L;
}
//头插法尾插法的核心:初始化操作,指定结点的后插操作

//尾插法建立单链表
//尾插法建立单链表
/*while{
    每次取一个数据元素e;
    ListInsert(L,length+1,e);//但这样的时间复杂度为n^2
    //因为每第i次插入,ListInsert中的while循环都要执行i-1次
    length++;
}
*/
LinkList TailInsert(LinkList &L){
    int x;
    L=(LinkList)malloc(sizeof(LNode));
    //L->next=NULL;
    LNode *s,*r=L;//r为表尾指针,s用来申请新结点
    printf("input node value(input 9999 to end):");
    scanf("%d",&x);
    while(x!=9999){
//注意这里的s不能少,因为s用来申请新的结点,
//装入新的值,而后再接到r后面,然后再将r指向s,即尾结点
        s=(LNode *)malloc(sizeof(LNode));
        s->data=x;
       // s->next=r->next;
        r->next=s;//在结点r之后插入节点s
        r=s;//r指向新的表尾结点,永远保持r指向最后一个节点
        printf("input next node value:");
        scanf("%d",&x);
    }
    r->next=NULL;//尾结点指针置空
    return L;

}

//初始化单链表
bool InitList(LinkList &L){
    L=(LNode*)malloc(sizeof(LNode));//分配一个头结点
    //LNode*   等价于LinkList
    //只是前者强调这是个结点,后者强调这是个链表
    if(L==NULL){
        printf("malloc失败\n");
        return false;
    }
    L->next=NULL;
    //这里的L为一个指向链表的指针,所以引用结构体成员时用->而非.
    return true;
}

bool Empty(LinkList L){
    return L->next==NULL;//与不带头结点的判空操作不一样
}

//求表长
int Length(LinkList L){
    int len=0;//@@@
    LNode *p=L->next;
    while(p){
        p=p->next;
        len++;
    }
    return len;
}

//在位序i处插入e
bool ListInsert(LinkList &L,int i, int e){
    int len = Length(L);
    if(i<1||i>len+1){
        printf("非法位序\n");
        return false;
    }
/*
    LNode *p;//指针p指向当前扫描到的结点
    int j=0;//当前p指向的是第几个结点
    p=L;//L指向头结点,头结点算第0个结点,不存数据

    //@@@ p->next != NULL
    while(p->next != NULL && j < i-1){//循环找到第i-1个结点
        p=p->next;
        j++;
    }
*/

    //则GetElem的要求是位序不能<0,即返回的p可能是头结点
    LNode *p=GetElem(L, i-1);//找到第i-1个结点,体会封装的好处

    /*
    LNode *s=(int *)malloc(sizeof(int));
    s->data=e;
    s->next=p->next;
    p->next=s;
    return true;//插入成功
    */
    //可以调用函数InsertNextNode来实现上述代码功能
    return InsertNextNode(p,e);

}

//后插操作   在*p结点之后插入元素e
bool InsertNextNode(LNode *p, int e){
    if(p==NULL){
        printf(" p == NULL !!!\n");
        return false;
    //在ListInsert中会调用,而ListInsert中的p可能返回的是NULL,
        //则调用此函数的时候就需要用到这段代码
    }
    LNode *s=(LNode *)malloc(sizeof(LNode));
    if(s == NULL){
        printf("malloc failuer\n");
        return false;//内存已满,空间分配失败
    }
    s->data=e;
    s->next=p->next;
    p->next=s;//将结点s连接到p之后
    return true;
}

//前插操作, 在结点p之前插入元素e
//如果不给出头结点,则无法找到p的前驱结点   O(n)
//但是可以用偷天换日的方法实现前插          O(1)
bool InsertPrior(LNode *p, int e){
    if(p==NULL){
        printf("p==NULL\n");
        return false;
    }
    LNode *s=(LNode*)malloc(sizeof(LNode));
    if(s==NULL){
        printf("malloc failure\n");
        return false;
    }
    s->data=p->data;//新结点s插入到p之后,把p中的元素复制到s中,
    s->next=p->next;
    p->next=s;
    //用e覆盖p中的元素,相当于前插了
    p->data=e;//偷天换日,用后插法来实现前插法
    return true;
}

//按位序删除(带头结点)  删除L的第i个元素,并用e返回被删除的值
bool ListDelete(LinkList &L, int i, int &e){
    int len=Length(L);
    if(i<1||i>len){
        printf("非法位序\n");
        return false;
    }
    /*
    int j=0;//当前p指向的是第几个结点
    LNode *p=L;//指针p指向当前扫描到的结点//L指向头结点,头结点是第0个结点(不存数据)
    while(p->next!=NULL && j<i-1){
        p=p->next;
        j++;
    }*/
     LNode *p = GetElem(L, i-1);//找到第i-1个结点,
    //体会封装的好处,避免重复代码,简洁,易维护


    LNode *q=p->next;//令q指向被删除结点
    e=q->data;//用e返回被删除元素的值
    p->next=q->next;//将q结点从链中断开
    free(q);//释放结点的存储空间
    q=NULL;//避免野指针
    return true;//删除成功
}

//删除指定结点p, O(1) "偷天换日"
/*
    但是若p本身为最后一个节点,即p->data为NULL
    则下面的代码有bug, (但是考试的时候可以这样写)
    则需要老老实实地从头指针以此找到对于的前驱结点
*/
bool DeleteNode(LNode *p){
    if(p==NULL){
        printf("p==NULL\n");
        return false;
    }
     if(p->next==NULL){//p是最后一个节点
        printf("需要从头遍历找到p的前驱结点\n");
        return false;
    }
    LNode *q=p->next;//令q指向p的后继结点
    p->data=q->data;//和后继结点交换数据域
    p->next=q->next;//将q结点从链中断开
    free(q);//释放q结点的存储空间
    q=NULL;
    return true;

}
//DeleteNode的补充
bool DeleteNode2(LinkList &L,LNode *p){
    if(p==NULL){
        printf("p==NULL\n");
        return false;
    }
    if(p->next == NULL){//说明p是最后一个结点,则需要从头开始遍历
        LNode* q=L;
        while(q->next != p){
            q=q->next;
        }
        q->next=p->next;
        free(p);
        p=NULL;
        return true;
    }
    else{//p是不是最后一个结点
        //可用偷天换日,进行数据交换,删除后面的一个结点
        LNode *q = p->next;
        p->data=q->data;
        p->next=q->next;
        free(q);
        q=NULL;
        return true;
    }

}


//位序i的合法性检查,线性表的i从1开始
bool IndexLeagal(LinkList L,int i){
    int len=Length(L);
    if(i<1 || i>len){
        printf("i<1 || i>L.length\n");
        return false;
    }
    return true;
}


//按位查找,返回第i个元素(带头结点)  可以查找第0个元素
LNode *GetElem(LinkList L,int i){
    /*if(!IndexLeagal(L,i+1)){
        return NULL;//把头结点看做第0个结点
    }*/
    int len=Length(L);
    if(i<0 || i>len){
        printf("i<0 || i>L.length\n");
        return NULL;
    }
    int j=0;//当前p指向的是第几个结点
    LNode *p=L;//指针p指向当前扫描到的结点
    //L指向头结点,头结点是第0个结点,不存数据
    while(p->next!=NULL && j<i){
        p=p->next;
        j++;
    }
    return p;
    //当i值非法时,返回NULL
}


//按值查找,找到数据域等于e的结点 O(n)
LNode* LocateElem(LinkList L,int e){
    LNode *p=L->next;//从第1个接地那开始查找数据域为e的结点

    //这里注意如果e 的类型是结构体类型,应将里面的元素一一比较,不能直接用结构体名进行比较
    while(p && (p->data != e)){
        p=p->next;
    }
    return p;//找到后返回指向此结点的指针,否则返回NULL
}

//打印链表
void PrintList(LinkList L){
    LNode *p=L->next;
    int len=Length(L);
    printf("(length:%d)List is:",len);
    while(p){
        printf("%d ",p->data);
        p=p->next;
    }
}

//销毁线性表,并释放内存空间
void DestroyList(LinkList &L){
/*
    LinkList p=L;
    while(p){//这里销毁线性表,要一个结点一个结点地free,不能认为p指向头结点了所以free(p)就好,这种是在malloc的时候
        L=L->next;
        free(p);
        p=L->next; 这里应该是p=L;
    }*/
    LinkList p=L;
    L=L->next;
    printf("\nfree head\n");
    free(p);//删除头结点
    p=L;
    while(p){//这里销毁线性表,要一个结点一个结点地free,不能认为p指向头结点了所以free(p)就好,这种是在malloc的时候
        L=L->next;
        printf("free %d \n",p->data);
        free(p);
        p=L;
    }
}

void test(){
    LinkList L;//声明一个指向单链表的指针,注意此处并没有创建一个链表
    InitList(L);
    PrintList(L);
    printf("\n调用ListInsert\n");
    for(int i=0;i<5;i++){
        ListInsert(L,i+1, i+1);
    }
    PrintList(L);
    LNode *p=GetElem(L,4);
    printf("\np->data=%d\n",p->data);
    printf("\n调用InsertNextNode\n");
    InsertPrior(p,30);
    PrintList(L);
    p=LocateElem(L, 5);
    if(p){
        printf("\n找到了:p->data=%d\n",p->data);
    }
    else{
        printf("\n不存在此数\n");
    }


    /*
    printf("\n头插法:\n");
    HeadInsert(L);
    PrintList(L);

    printf("\n销毁线性表\n");
    DestroyList(L);
    PrintList(L);

    printf("\n尾插法:\n");
    TailInsert(L);
    PrintList(L);
    printf("\n销毁线性表\n");
    DestroyList(L);
    PrintList(L);
    */


}


int main(){
    test();
}


在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值