数据结构与算法 ---- 单链表与其代码演示

数组的局限性

我们在编程时,往往都会用到数组,数组是顺序存储结构,其在内存中,都是以相邻地址来做存储的,
例如,定义一个整型的数组,int a[10] ;在内存中,这样表现:
在这里插入图片描述
每个成员占用4个字节,然而,在我们使用数组时,很多情况是这样的:
在这里插入图片描述
从图中可以看出,数组在使用时会产生离散空间,这会造成内存的浪费,并且,数组是需要先分配,在使用的,很多时候我们是不知道我们接收到的数据的,即使使用动态分配,其原理还是离不开先分配后使用,内存需求是不确定的,所以我们引入了链表;

单链表的原理

我们将链表中的每一个成员设置为一个结构体,该结构体包含了数据段(Data)
与指针段(nextNode),数据段用来存储我们想存储的数据,数据是多样的,数据的信息更是多样,例如我们要保存有个学生的学号,姓名和年龄,所以这个数据段,我们任然可以用一个结构体来表示;而指针段则存储了指向下一个结点位置的指针,因为指向链表中的结点,所以指针的类型与该结点的结构体一样;定义结点:

typedef struct 
{
      char key[10];   //该节点的关键字
      char name[20];   //数据内容,假设为学生姓名
      int  age;       //学生年龄
}Data;
      
typedef struct Node
{
      Data nodeData;   //数据段;
      struct Node *nextNode;   //指向下一个结点的指针
}CLType;

      

定义一个链表很简单,只需定义一个CLType 类型的头指针即可,该指针指向链表的第一个结点(头结点),若为空表,则头指针为空;一个完整的链表如图:
在这里插入图片描述

头指针指向头结点,头结点的nextNode指正指向其下一个结点,以此往下,就把各个结点“ 串 ” 了起来,并且,这些结点的位置在物理内存中可以不相邻,只要指针以此指向他们的下一个结点即可;我们又称结点的的上一个结点是该节点的前去,该节点指向的结点为其后继,最后一个结点的指针指向空,这是尾结点;

创建链表

尾插法

既然知道了链表的结构,那么怎么将我们想要的数据插入到链表中呢?
首先来讲尾插法:顾名思义,尾插法就是从链表的尾部进行插入,每来一个新的 结点,就将其安排在链表的尾部;

思路

既然要插入到尾部,则必须让链表当前的尾结点的nextNode指针指向新来的结点,并将新结点的nextNode指针指向空;

1.定义两个 CLType 类型的指针,一个是新节点,一个用来遍历链表(我们并不知道尾结点的指针,只知道头指针,所以我们需要从头结点出发,根据链表的指针顺藤摸瓜一直找到指向NULL的指针为止,第二个指针就用来保存我们遍历的链表结点的指针);
2.为新节点指针申请存储空间;
3.将数据存入新节点;
4.遍历链表,找到指向NULL的指针,即尾结点;
5.插入新节点

图解

在这里插入图片描述

尾插法代码
CLType *CLAddEnd(CLType *head,Data nodeData)   //返回值是CLtype类型的,参数会用到头指针,传入的一个结构体
{
    CLType *node,*htemp;
    if((node = (CLType *)malloc(sizeof(CLType))) == NULL)   //分配存储空间
    {
        perror("malloc failure");   //分配失败
        return NULL;
    }

    else 
    {
        node->nodeData = nodeData;   //将参数的值赋给新结点;
        node->nextNode = NULL;

        if(head == NULL)   //空表,则成为头结点
        {
            head = node;
            return head;
        }

        htemp = head;   //将头指针的值赋给临时变量htemp;

        while(htemp->nextNode != NULL)
        {
            htemp = htemp->nextNode;   //移动临时变量
        }

        htemp->nextNode = node;

        return head;
        
    }
}       

头插法

思路

1.定义一个新结点;
2.malloc() 分配内存;
3.赋值;
4.将头指针所指的结点赋予新结点的 nextNode,将头指针指向新结点;

图解

在这里插入图片描述

头插法代码
CLType *CLAddFirst(CLType *head,Data nodeData)   //头插法
{
    CLType *node;
    if((node = (CLType *)malloc(sizeof(CLType))) == NULL)
    {
        printf("malooc failure");
        return NULL;
    }
    else 
    {
        node->nodeData = nodeData;   //保存数据
        node->nextNode = head;       //指向头结点所指的指针
        head = node;                 //头结点指向新增结点
        return head;
    }
    
}

注意:头插法操作比较简单,但是,在将新结点插入到头指针之后的操作顺序是不能颠倒的,必须先将头指针所保存的地址赋给新结点的nextNode指针,再将头指针指向新结点node,后面结点的插入也是此,只是将头指针换成了某个结点的nextNode指针;

链表基本操作

查找节点

思路

通常我们需要在已创建的链表中查找某个节点,在这,我们可以用到结点数据段中的某得成员进行查找,例如使用关键字进行查找,我们可以输入想要查找的关键字,然后从头结点依次往下查找,查找到后返回该节点的指针;

图解

在这里插入图片描述
定义一个临时指针。用来遍历这个链表。若查找不到就下移,超找到了就返回这个指针当前所指,若到表尾任然未找到则返回空;

查找结点代码

CLType *CLFindNode(CLType *head,char *key)   //按关键字查找结点
{
    CLType *htemp;
    htemp = head;                    //保存头结点的指针 
    while(htemp)
    {
        if(strcmp(htemp->nodeData.key,key) == 0) //查找关键字
        {
            return htemp;
        }
        htemp = htemp->nextNode;     //处理下一个结点
    }
    return NULL;

}

插入节点

思路

回想头插法,在插入一个结点时,先将要插入位置前的那一个结点的指针所指向的地址赋给新结点的nextNode指针,再将要插入位置前的哪一个结点的指针指向新结点,具体如何找到在哪开插入,我们可以使用关键字进行循环查找;这就会用到我们的前一个功能查找,定义一个临时的变量 nodetemp,这个变量用来保存插入位置的前一个结点,即在该节点后插入;

图解

在这里插入图片描述
顺序:
nodetemp->nextNode = node->nextNode;
nodetemp->nextNode = node;

插入成功:
在这里插入图片描述

删除结点

思路

删除结点很简单,只需将要删除结点的上一个结点指向要删除结点的下一个结点,再将删除结点的内存释放即可,所以我们要用到两个指针,分别代表要删除的结点和该结点的上一个结点;

图解

在这里插入图片描述
node->nextNode = htemp->nextNode;
free(nodetemp);

在这里插入图片描述

删除结点代码
int CLDeleteNode(CLType *head,char *key)
{
    CLType *node,*htemp;                  //node用来保存要删除结点的前一个结点
    htemp = head;
    while(htemp)
    {
        if(strcmp(htemp->nodeData.key,key) == 0)
        {
            node->nextNode = htemp->nextNode;
            free(htemp);
            return 1;
        }
        else 
        {
            node = htemp;
            htemp = htemp->nextNode;   //node指向htemp所指,htemp后移;

        }
    }
    return 0;      //未删除
}

计算链表长度

只需遍历链表,找到nextNode为空的结点,每后移一个结点,length+1;

代码
int CLLength(CLType *head)
{
    CLType *htemp;
    int Len = 0;
    htemp = head;
    while(htemp)
    {
        Len++;
        htemp = htemp->nextNode;
    }

    return Len;
}

列出链表所有内容

定义一个结点类型(CLType)的结构体Data,和一个指针node,指针用来遍历链表,而Data用来保存该指针当前所指的结点信息,打印完该信息后,指针后移,再用后移后的指针给结构体赋值,循环一直到表尾;

代码
void CLAllNode(CLType *head)
{
    CLType *htemp;
    Data nodeData;
    htemp = head;
    printf("链表当前共有%d个结点。链表如下:\n",CLLength(head));
    while(htemp)
    {
        nodeData = htemp->nodeData;
        printf("结点(%s,%s,%d)\n",nodeData.key,nodeData.name,nodeData.age);
        htemp = htemp->nextNode;
    }
}

单链表程序总览与代码演示

我将所有的操作函数全部写到一个c文件里,写一个主函数,通过通过标准输入获取到信息,再使用尾插法将信息存入链表,然后依次演示代码功能;

单链表程序总览

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    char key[10];
    int age;
    char name[20];
}Data;

typedef struct Node
{
    Data nodeData;
    struct Node *nextNode;
}CLType;

CLType *CLAddEnd(CLType *head,Data nodeData)   //尾插法
{
    CLType *node,*htemp;
    if((node = (CLType *)malloc(sizeof(CLType))) == NULL)
    {
        perror("malloc failure");
        return NULL;
    }

    else 
    {
        node->nodeData = nodeData;
        node->nextNode = NULL;

        if(head == NULL)
        {
            head = node;
            return head;
        }

        htemp = head;

        while(htemp->nextNode != NULL)
        {
            htemp = htemp->nextNode;
        }

        htemp->nextNode = node;

        return head;
        
    }
}               
    
CLType *CLAddFirst(CLType *head,Data nodeData)   //头插法
{
    CLType *node;
    if((node = (CLType *)malloc(sizeof(CLType))) == NULL)
    {
        printf("malooc failure");
        return NULL;
    }
    else 
    {
        node->nodeData = nodeData;   //保存数据
        node->nextNode = head;       //指向头结点所指的指针
        head = node;                 //头结点指向新增结点
        return head;
    }

    
}

CLType *CLFindNode(CLType *head,char *key)   //按关键字查找结点
{
    CLType *htemp;
    htemp = head;                    //保存头结点的指针 
    while(htemp)
    {
        if(strcmp(htemp->nodeData.key,key) == 0) //查找关键字
        {
            return htemp;
        }
        htemp = htemp->nextNode;     //处理下一个结点
    }
    return NULL;

}

CLType *CLInsertNode(CLType *head,char *findkey,Data nodeData)
{
    CLType *node,*nodetemp;
    if((node = (CLType *)malloc(sizeof(CLType))) == NULL)
    {
        printf("malooc failure");
        return 0;
    }
    node->nodeData = nodeData;
    nodetemp = CLFindNode(head,findkey);
    if(nodetemp)
    {
        node->nextNode = nodetemp->nextNode;
        nodetemp->nextNode = node;
    }
    else
    {
        printf("未找到关键字\n");
        free(node);
    }

    return head;
            
}

int CLDeleteNode(CLType *head,char *key)
{
    CLType *node,*htemp;                  //node用来保存要删除结点的前一个结点
    htemp = head;
    while(htemp)
    {
        if(strcmp(htemp->nodeData.key,key) == 0)
        {
            node->nextNode = htemp->nextNode;
            free(htemp);
            return 1;
        }
        else 
        {
            node = htemp;
            htemp = htemp->nextNode;

        }
    }
    return 0;      //未删除
}
    
int CLLength(CLType *head)
{
    CLType *htemp;
    int Len = 0;
    htemp = head;
    while(htemp)
    {
        Len++;
        htemp = htemp->nextNode;
    }

    return Len;
}

void CLAllNode(CLType *head)
{
    CLType *htemp;
    Data nodeData;
    htemp = head;
    printf("链表当前共有%d个结点。链表如下:\n",CLLength(head));
    while(htemp)
    {
        nodeData = htemp->nodeData;
        printf("结点(%s,%s,%d)\n",nodeData.key,nodeData.name,nodeData.age);
        htemp = htemp->nextNode;
    }
}


int main(int argc,char **argv)
{
    CLType *node,*head = NULL;
    Data nodeData;
    char key[10],findkey[10];

    printf("链表测试,先输入链表中的数据,格式为:关键字 姓名 年龄\n");
    do 
    {
        fflush(stdin);   //清空输入缓冲区;
        scanf("%s",nodeData.key);
        if(strcmp(nodeData.key,"0") == 0)   //若关键字为0则表示输入完成
        {
            break;
        }
        else 
        {
            scanf("%s%d",nodeData.name,&nodeData.age);
            head = CLAddEnd(head,nodeData);
        }
    }while(1);
    CLAllNode(head);

    printf("\n演示插入结点,输入插入位置的关键字:");
    scanf("%s",findkey);
    printf("输入插入结点的数据(关键字,姓名,年龄):");
    scanf("%s%s%d",nodeData.key,nodeData.name,&nodeData.age);
    head = CLInsertNode(head,findkey,nodeData);
    CLAllNode(head);

    printf("\n演示删除结点,输入删除结点的关键字:");
    fflush(stdin);    //清空输入缓冲区
    scanf("%s",key);
    CLDeleteNode(head,key);
    CLAllNode(head);

    printf("演示按关键字查找,输入查找的关键字:");
    fflush(stdin);    //清空输入缓冲区
    scanf("%s",key);
    node = CLFindNode(head,key);
    if(node)
    {
        nodeData = node -> nodeData;
        printf("关键字为%s的结点为(%s,%s,%d)\n",key,nodeData.key,nodeData.name,nodeData.age);
    }
    else 
    {
        printf("在链表中未找到关键字为%s的结点!\n",key);

    }

}

   
    

    

代码演示

编译运行程序
在这里插入图片描述
按照要求,输入信息;
在这里插入图片描述
输入0按下enter表示输入完成;
在这里插入图片描述
输入完成后,程序会打印链表,并提示开始演示插入操作,要求我们输入插入的关键字,我们想在余小c后面插入数据,那就输入他的关键字2003,之后程序提示我们要插入的数据信息,按照格式书写即可,按下回车:

在这里插入图片描述程序打印了插入蛇哥后的链表,现在演示删除结点,输入要删除的关键字,若果我们想删除霸哥,则输入霸哥的关键字2004,按下enter:
在这里插入图片描述
霸哥被成功删除,现在提示输入查找,输入2006,按下回车:

在这里插入图片描述
程序打印了这一个结点的信息后退出;

这就是链表的一些基本操作了。

发布了18 篇原创文章 · 获赞 37 · 访问量 4982

猜你喜欢

转载自blog.csdn.net/weixin_45121946/article/details/105060638