【数据结构】线性表——链式表示:单链表的基本操作

目录

1、单链表的初始化

2、求表长操作

3、按序号查找结点

4、按值查找表结点

5、插入结点操作

6、删除结点操作

7、采用头插法建立单链表

8、采用尾插法建立单链表


1、单链表的初始化

以下两种定义方式结果相同但内涵不同:

  • LinkList L :强调这是一个单链表
  • LNode * :强调这是一个结点

定义一个结构体来表示结点

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

初始化一个空的单链表

分为带头结点和不带头结点两种,区别在于,带头结点的单链表,头结点中不存储数据。

// 初始化一个空的单链表(不带头结点)
bool InitList(LinkList &L){
    L=NULL;         //空表,此时还没有任何结点(防止脏数据)
    return true;
}

// 初始化一个单链表——带头结点(头结点不存储数据)
bool InitList1(LinkList &L){
    L=(LNode *)malloc(sizeof(LNode));  // 分配一个头结点
    if(L==NULL)                        // 内存不足,分配失败
        return false;
    L->next=NULL;                      // 头节点之后暂时还没有结点
    return true;
}

判断单链表是否为空

/ 判断单链表是否为空
bool Empty(LinkList L){
    // if(L==NULL)
    //     return true;
    // else
    //     return false;
    return(L==NULL);
}

// 判断单链表是否为空--带头结点
bool Empty1(LinkList L){
    return(L->next==NULL);
}

2、求表长操作

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

3、按序号查找结点

// 按位查找——带头结点
LNode * GetElem(LinkList L, int i){
    if(i<0)
        return NULL;
    LNode *p;     // 指针p指向当前扫描到的结点
    int j=0;      // 当前p指向的是第几个结点
    p=L;          // L指向头结点,头结点是第0个结点(不存数据)
    while (p!=NULL&&j<i){  // 循环找到第i个结点
        p=p->next;
        j++;
    }
   return p;
}

4、按值查找表结点

// 按值查找
LNode * LocateElem(LinkList L, int e){
    LNode *p =L->next;
    // 从第一个结点开始查找数据域为e的结点
    while (p!=NULL&& p->data != e)
        p=p->next;
    return p;     // 找到后返回该结点指针,否则返回NULL   
}

5、插入结点操作

按位序插入:

不带头结点的插入操作,第一个结点的操作与其他结点有所不同。

因为没有头结点,所以插入时,先将next指针指向原来的头指针L指向的区域,然后再将头指针指向自己(第一个结点)。

代码如下:

// 按位序插入(不带头结点)
bool ListInsert0(LinkList &L, int i, int e){
    if(i<1)
        return false;
    //---插入第一个结点的操作与其他结点操作不同---
    if(i==1){
        LNode *s=(LNode *)malloc(sizeof(LNode));
        s->data=e;
        s->next=L;
        L=s;
        return true;
    }
    //------------------------------------------
    LNode *p;    // 指针p指向当前扫描到的结点
    int j=1;     //当前P指向的是第几个结点
    p=L;         //L指向第一个结点
    while (p!=NULL && j<i-1){   //循环找到第i-1个结点
        p=p->next;
        j++;
    }
    if(p==NULL)  // i值不合法
        return false;
    LNode *s =(LNode *)malloc(sizeof(LNode));
    s->data=e;
    s->next=p->next;
    p->next=s;         // 将结点s连接到p之后
    return true;       // 插入成功
    
}

带头结点的插入:

// 按位序插入(带头结点)
bool ListInsert1(LinkList &L, int i, int e){
    if(i<1)
        return false;
    LNode *p;    // 指针p指向当前扫描到的结点
    int j=0;     // 当前P指向的是第几个结点
    p=L;         // L指向头结点,不存放数据
    while (p!=NULL && j<i-1){   // 循环找到第i-1个结点
        p=p->next;
        j++;
    }
    // -----------------------------
    if(p==NULL)  
        return false;
    LNode *s =(LNode *)malloc(sizeof(LNode));
    s->data=e;
    s->next=p->next;
    p->next=s;         // 将结点s连接到p之后
    return true;       // 插入成功
    // ------------------------------
    //return InsertNextNode(p,e);
}

插入操作从下图可以更好的理解。

需要注意的是:步骤2和步骤3不可以颠倒。

如果先将p->next指向结点s,那么链表的剩余部分就再也无法找到。


以下均探讨带头结点的情况:

指定结点的后插操作:

// 指定结点的后插操作
bool InsertNextNode(LNode *p, int 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的值复制到插入的结点中,然后将结点p的值更改为要插入的结点的值。简单理解就是将要前插的结点后插,再交换位置,即可实现。代码如下:

// 指定结点的前插操作:
// 1、由于结点p的前一个结点不可知,传入头指针L循环遍历
// 2、后插一个结点,再将结点p的值复制到插入的结点中,再将结点p的值更改为要插入的结点的值
bool InsertPriorNode(LNode *p, int 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->data=p->data;
    p->data=e;

    return true;
}
// 另一种写法

bool InsertPriorNode1(LNode *p, LNode *s)
{
    if(p==NULL)
        return false;
    s->next=p->next;
    p->next=s;

    int temp=p->data;
    p->data=s->data;
    s->data=temp;

    return true;
}

6、删除结点操作

按位序删除(带头结点):

// 按位序删除(带头结点)
bool ListDelete(LinkList &L, int i, int &e){
    if(i<1)
        return false;
    LNode *p;    // 指针p指向当前扫描到的结点
    int j=0;     //当前P指向的是第几个结点
    p=L;         //L指向头结点,不存放数据
    while (p!=NULL && j<i-1){   //循环找到第i-1个结点
        p=p->next;
        j++;
    }
    if(p==NULL)  
        return false;
    if(p->next==NULL)      // 第i个结点不存在
        return false;
    LNode *q=p->next;
    e=q->data;
    p->next=q->next;
    free(q);
    return true;
}

指定结点的删除:

类似于前插结点的操作,先将要删除的结点p的值设为p->next结点的值,再删除p->next结点。

// 指定结点的删除
bool DeleteNode(LNode *p){
    if(p==NULL)  
        return false;
    LNode *q=p->next;
    p->data=p->next->data;
    p->next=q->next;
    free(q);
    return true;
}

下图表示,删除最后一个结点的情况:

7、采用头插法建立单链表

需要注意的是:头插法需要将L->next=NULL

头插法的操作步骤由下图所示:

可以看出,最终得到的链表和我们输入元素的数据顺序相反,借助这一特性,我们可以将其用于链表的逆置:顺序读出链表元素依次用头插法存入新链表中。

// 头插 可以用于链表的逆置
LinkList List_HeadInsert(LinkList &L){ // 逆向建立单链表
    LNode *s;
    int x;
    L=(LinkList)malloc(sizeof(LNode)); // 建立头结点
    L->next=NULL;                      // 初始为空链表
    scanf("%d",&x);                    // 输入结点的值
    while (x!=9999)                    // 输入一个特殊值表示结束,此处为9999
    {
        s=(LNode *)malloc(sizeof(LNode));
        s->data=x;
        s->next=L->next;
        L->next=s;                     // 将新结点插入表中,L为头指针
        scanf("%d",&x);
    }
    return L;
}

8、采用尾插法建立单链表

// 单链表的建立---带头结点
// 尾插
LinkList List_TailInsert(LinkList &L){ // 正向建立单链表
    int x;                             // 设ElemType为整型
    L=(LinkList)malloc(sizeof(LNode)); // 建立头结点
    LNode *s,*r=L;                     // r为表尾指针
    scanf("%d",&x);                    // 输入结点的值
    while (x!=9999)                    // 输入一个特殊值表示结束,此处为9999
    {
        s=(LNode *)malloc(sizeof(LNode));
        s->data=x;
        r->next=s;
        r=s;                           // r指向新的表尾结点
        scanf("%d",&x);
    }
    r->next=NULL;                      // 尾结点指针置空
    return L;
}

以上就是单链表的各种基本操作,欢迎提问和补充!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值