数据结构之单向有头链表的操作(纯代码及代码解释)

一,头文件

    在了解了链表的定义等基础知识之后,我们需要将定义转化为程序;

    首先我们需要将概念的思想转化为程序的思路:

        ①单向有头链表,存在一个空的头节点;

        ②链表中节点逻辑上按顺序排列,存储时节点之间通过指针连接,存储空间上不一定连续;

        ③节点需要存放数据,还需要存放下一个节点的地址,由此我们可知链表结构体中的成员变量包括一个数据域"datatype data;",和一个指针域"结构体类型 *p;"

        ④头节点的数据域为空,没有数据,指针域指向链表中的第一个有数据的节点;当链表为空时,头节点的指针域指向空;

    了解了上述内容后,就容易理解程序;

    程序主要掌握空链表的创建,数据的插入和删除,以及链表的转置。

    头文件程序代码如下:

#ifndef _LINKLIST_H_
#define _LINKLIST_H_
#include <stdio.h>
#include <stdlib.h>

typedef int datatype;
typedef struct link_node
{
    datatype data;//数据域
    struct link_node *next;//指针域
}link_t;

//1.创建一个有头单向链表
link_t *CreateEpLinklist(void);
//2.向指定位置插入数据
int InsertIntoLinklist(link_t *p,int post,datatype data);
//3.计算链表长度
int LengthLinklist(link_t *p);
//4.在指定位置删除数据
int DeleteLinklist(link_t *p,int post);
//5.遍历链表
void ShowLinklist(link_t *p);
//6.判断链表是否为空
int isEpLinklist(link_t *p);
//7.查询链表指定位置的数据
int SearchPostLinklist(link_t *p,int post);
//8.查询指定数据在链表的位置
int SearchDataLinklist(link_t *p,datatype data);
//9.修改链表中指定的数据
int ChangeDataLinklist(link_t *p,datatype old,datatype new);
//10.修改链表中指定位置的数据
int ChangePostLinklist(link_t *p,int post,datatype new);
//11.删除链表中指定的数据
void DeleteDataLinklist(link_t *p,datatype data);
//12.链表的转置
void ReserveLinklist(link_t *p);
//13.清空链表
void ClearLinklist(link_t *p);
//14.销毁链表
void DestoryLinklist(link_t **p);
#endif

二,函数代码

1.创建一个空的单向有头链表

     先使用malloc开辟一个结构体空间,然后将指针域指向空,数据域不赋值,即无数据,就创建好了一个单向链表的空的头结点。

//创建一个有头单向链表
link_t *CreateEpLinklist(void)
{
    link_t *p = (link_t *)malloc(sizeof(link_t));
    if(NULL == p)
    {
        printf("CreateEpLinklist fail\n");
        return NULL;
    }
    p->next = NULL;//数据域无效,指针域有效
    return p;
}

2.向链表中指定位置插入数据

插入数据时,需要判断插入的位置是否合理;

将指向空的头节点的指针指向要插入位置的前一个节点;

开辟一个新的结构体空间;

给结构体中的数据域赋值,指针域指向该位置的后一位的节点,也就是头节点指向的当前节点的下一个节点(因为新节点还没插入,新节点要插入到两个节点之间);

将头节点当前指向的节点,其指针域指向新节点,新节点才算插入成功;

//向指定位置插入数据
int InsertIntoLinklist(link_t *p,int post,datatype data)
{
    int i;
    link_t *pnew = NULL;
    //容错判断
    if(post < 0 || post > LengthLinklist(p))//插入数据时,可能会插入最后一位的后一位
    {
        printf("InsertIntoLinklist fail\n");
        return -1;
    }
    //将头指针指向post-1位置的节点
    for(i = 0;i < post;i++)
    {
        p = p->next;
    }
    //创建新节点
    pnew = (link_t *)malloc(sizeof(link_t));
    if(NULL == pnew)
    {
        printf("pnew create fail\n");
        return -1;
    }
    pnew->data = data;
    pnew->next = NULL;
    //新节点先指向post-1指向的节点,post-1再指向新节点
    pnew->next = p->next;
    p->next = pnew;
    return 0;
}

3.在指定位置删除数据

 删除数据时也需要判断删除位置的合理性;

头节点也需要指向要删除节点的前一个节点;

然后用一个临时指针指向要删除的节点;

让头节点当前指向的节点,其指针域指向要删除结点的下一个节点;

将要删除的节点释放,并指向NULL。

//在指定位置删除数据
int DeleteLinklist(link_t *p,int post)
{
    link_t *pdel = NULL;
    //容错判断
    //后面的所有相关指定位置post的容错判断,因为都表示的是下标,指定的post不能超出下标,所以需要减1
    if(post < 0 || post > LengthLinklist(p) - 1 || isEpLinklist(p))
    {
        printf("DeleteLinklist fail\n");
        return -1;
    }
    //将节点指向要删除的节点的前一个节点post-1
    for(int i = 0;i < post;i++)
    {
        p = p->next;
    }
    //新建一个临时指针指向要删除的post节点
    pdel = p->next;
    //要删除的节点的前一个节点post-1指向要删除的节点的后一个节点post+1
    p->next = pdel->next;
    //释放要删除的节点
    free(pdel);
    pdel = NULL;
    return 0;
}

4.链表的转置

转置,对于链表来说,可以理解为除去空的头节点,将其他的原来的节点倒过来排序;

先将空的头节点和第一个有数据的节点断开,生成一个空的头节点和一个没有无头链表;

然后将无头链表中的所有节点循环插入到空的头节点的后面,而且每次插入节点都要插入到空的头节点的后一位;

void ReserveLinklist(link_t *p)
{
    link_t *temp = NULL;//接收无头节点的插入节点的下一个节点
    link_t *q = NULL;//接收无头节点的第一个节点
    q = p->next; //让q接收断开的无头节点的第一个节点
    p->next = NULL;//让原本的头节点后面指向空,也就是断开链表
    while(q != NULL)//所有的节点都要插入,所以应该是对q判断,不应该是对q->next判断;q所在的节点为空了,才停止插入
    {
        temp = q->next;//先保存下来无头节点中要插入节点的下一个节点
        q->next = p->next;//插入的节点后面要链接之前插入的节点;一开始后面链接的是空,下一次插入后面链接的就是无头节点的第一个节点
        p->next = q;//循环插入空的头节点后面
        q = temp;//让q移动到无头节点的下一个节点
    }
}

//或
void ReserveLinklist(link_t *p)
{
    link_t *temp = NULL;
    link_t *pa = p->next;
    p->next = NULL;

    while(pa != NULL)
    {
        temp = pa;  //保存下来要插入的节点
        pa = pa->next; //无头链表向后移动
        temp->next = NULL; //要插入的节点后面指向空,也就是把它和无头链表断开

        temp->next = p->next; //插入的节点后面接上已经插入的节点;
        p->next = temp;  //节点插入;
    }
}

三,详细代码

#include "linklist.h"

//1.创建一个有头单向链表
link_t *CreateEpLinklist(void)
{
    link_t *p = (link_t *)malloc(sizeof(link_t));
    if(NULL == p)
    {
        printf("CreateEpLinklist fail\n");
        return NULL;
    }
    p->next = NULL;//数据域无效,指针域有效
    return p;
}
//2.向指定位置插入数据
int InsertIntoLinklist(link_t *p,int post,datatype data)
{
    int i;
    link_t *pnew = NULL;
    //容错判断
    if(post < 0 || post > LengthLinklist(p))//插入数据时,可能会插入最后一位的后一位
    {
        printf("InsertIntoLinklist fail\n");
        return -1;
    }
    //将头指针指向post-1位置的节点
    for(i = 0;i < post;i++)
    {
        p = p->next;
    }
    //创建新节点
    pnew = (link_t *)malloc(sizeof(link_t));
    if(NULL == pnew)
    {
        printf("pnew create fail\n");
        return -1;
    }
    pnew->data = data;
    pnew->next = NULL;
    //新节点先指向post-1指向的节点,post-1再指向新节点
    pnew->next = p->next;
    p->next = pnew;
    return 0;
}
//3.计算链表长度,len是长度,从1开始;post是下标,从0开始;
int LengthLinklist(link_t *p)
{
    int len = 0;
    while(p->next != NULL)
    {
        p = p->next;
        len++;
    }
    return len;
}
//4.在指定位置删除数据
int DeleteLinklist(link_t *p,int post)
{
    link_t *pdel = NULL;
    //容错判断
    //后面的所有相关指定位置post的容错判断,因为都表示的是下标,指定的post不能超出下标,所以需要减1
    if(post < 0 || post > LengthLinklist(p) - 1 || isEpLinklist(p))
    {
        printf("DeleteLinklist fail\n");
        return -1;
    }
    //将节点指向要删除的节点的前一个节点post-1
    for(int i = 0;i < post;i++)
    {
        p = p->next;
    }
    //新建一个临时指针指向要删除的post节点
    pdel = p->next;
    //要删除的节点的前一个节点post-1指向要删除的节点的后一个节点post+1
    p->next = pdel->next;
    //释放要删除的节点
    free(pdel);
    pdel = NULL;
    return 0;
}
//5.遍历链表
void ShowLinklist(link_t *p)
{
    while(p->next != NULL)
    {
        p = p->next;
        printf("%d ",p->data);
    }
    putchar(10);
    printf("---------------------\n");
}
//6.判断链表是否为空
int isEpLinklist(link_t *p)
{
    return p->next == NULL;
}
//7.查询链表指定位置的数据
datatype SearchPostLinklist(link_t *p,int post)
{
    //容错判断
    if(post < 0 || post > LengthLinklist(p) - 1 || isEpLinklist(p))
    {
        printf("SearchPostLinklist fail\n");
        return -1;
    }
    int i;
    for(i = 0;i <= post;i++)
    {
        p = p->next;
    }
    return p->data;
}
//8.查询指定数据在链表的位置
int SearchDataLinklist(link_t *p,datatype data)
{
    if(isEpLinklist(p))
    {
        printf("isEmptyLinklist\n");
        return -1;
    }
    int post = 0;
    while(p->next != NULL)
    {
        p = p->next;
        if(p->data == data)
        {
            return post; //return之后函数结束
        }
        post++;
    }
    return -1;
}
//9.修改链表中指定的数据
int ChangeDataLinklist(link_t *p,datatype old,datatype new)
{
    while(p->next != NULL)
    {
        p = p->next;
        if(p->data == old)
        {
            p->data = new;
        }
    }
    return 0;
}
//10.修改链表中指定位置的数据
int ChangePostLinklist(link_t *p,int post,datatype new)
{
    //容错判断
    if(post < 0 || post > LengthLinklist(p) - 1 || isEpLinklist(p))
    {
        printf("ChangePostLinklist fail\n");
        return -1;
    }
    int i;
    for(i = 0;i <= post;i++)
    {
        p = p->next;
    }
    p->data = new;
    return 0;
}
//11.删除链表中指定的数据
void DeleteDataLinklist(link_t *p,datatype data)
{
    
    while(p->next != NULL)
    {
        if(p->next->data == data)
        {
            link_t *pdel = p->next; //必须放这,每次free后,pdel就被释放了
            p->next = pdel->next;
            free(pdel);
            pdel = NULL;
        }
        else
        {
            p = p->next;
        }
        
    }
}
#if 0
//12.链表的转置
void ReserveLinklist(link_t *p)
{
    link_t *temp = NULL;//接收无头节点的插入节点的下一个节点
    link_t *q = NULL;//接收无头节点的第一个节点
    q = p->next; //让q接收断开的无头节点的第一个节点
    p->next = NULL;//让原本的头节点后面指向空,也就是断开链表
    while(q != NULL)//所有的节点都要插入,所以应该是对q判断,不应该是对q->next判断;q所在的节点为空了,才停止插入
    {
        temp = q->next;//先保存下来无头节点中要插入节点的下一个节点
        q->next = p->next;//插入的节点后面要链接之前插入的节点;一开始后面链接的是空,下一次插入后面链接的就是无头节点的第一个节点
        p->next = q;//循环插入空的头节点后面
        q = temp;//让q移动到无头节点的下一个节点
    }
}
#endif
#if 1
//12.链表的转置
void ReserveLinklist(link_t *p)
{
    link_t *temp = NULL;
    link_t *pa = p->next;
    p->next = NULL;

    while(pa != NULL)
    {
        temp = pa;  //保存下来要插入的节点
        pa = pa->next; //无头链表向后移动
        temp->next = NULL; //要插入的节点后面指向空,也就是把它和无头链表断开

        temp->next = p->next; //插入的节点后面接上已经插入的节点;
        p->next = temp;  //节点插入;
    }
}
#endif
//13.清空链表
void ClearLinklist(link_t *p)
{
    link_t *pdel = NULL;
    while(p->next != NULL)
    {
        pdel = p->next;
        p->next = pdel->next;
        free(pdel);
        pdel = NULL;
    }
}
//14.销毁链表
void DestoryLinklist(link_t **p) //要把main函数中创建的指针也销毁,也就是更改它的值,所以需要地址传递
{
    if(!isEpLinklist(*p))
    {
        ClearLinklist(*p);
    }
    free(*p);
    *p = NULL;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值