【C语言数据结构】线性表和单链表

1. 数据结构的划分

数据结构按逻辑划分为线性结构非线性结构。

1.1 线性结构

有限的序列;序列中的每一个元素都有唯一的前驱和后继,除了开头和结尾两个节点。
线性表主要由顺序表或链表表示。在实际应用中,常以栈、队列、字符串等特殊形式使用。
它们共同的特点就是数据之间的线性关系,除了头结点和尾结点之外,每个结点都有唯一的前驱和唯一的后继,也就是所谓的一对一的关系。

1.11 顺序表

顺序表中每个元素被分配的内存空间是连续的,我们的编程语言中的数组就是顺序表。

优点:无须为元素逻辑关系而增加额外的存储空间,可以快速取其中某个元素。
缺点:

  1. 顺序表的长度相对固定,当定义长度过大,有可能会浪费空间,过小则可能发生溢出。
  2. 插入和删除元素时,如果要保持有序的话,需要移动大量的数据。

1.12 链表

内存是不连续的,各个元素分配的内存不一样,内存和内存之间用指针进行相连,内存空间可以动态分配,解决了顺序表的缺点。

1.2 非线性结构

对应于线性结构,非线性结构也就是每个结点可以有不止一个直接前驱和直接后继。常见的非线性结构包括:树、图等。

2. 单链表

(线性链式表,简称链表,特点是用一组任意的存储单元存储线性表的数据元素。因此为了表示当前元素和逻辑上的下一个元素之间的关系,除了存储本身的数据之外,还要存储一个指示其直接后继的信息。由这两个信息组成的单元叫做结点。)
所以链表需要定义一个数据域和一个指针域,指针域用于存放逻辑上的下一个节点的地址,即指向下一个节点。
在这里插入图片描述
头节点的数据域存储了节点的个数(除去头节点和尾节点),指针域指向链表的第一个节点。

2.1 单链表数据节点类型的定义

typedef struct LinkListNode
{
    ElemType                data; /* 数据域存储数据 */
    struct  LinkListNode    *next; /* 指针域存储下一个节点的地址 */
}LLNode;

其中宏定义ElemType便于修改元素类型,data是数据域,next是指针域。此处我们定义了一个next指针,而没有pre,指向上一个节点的指针,因此是单向链表(单链表),链表最后的节点指向NULL。

2.2 初始化单链表

Status initlist(LLNode *L)
{
    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    }

    L->data = 0;    /* 头节点的数据域记录链表中有多少个节点,现在为0,即一个都没有 */
    L->next = NULL; 

    return SUCCESS;
}

链表的初始化时代表了头节点的初始化。

2.3 头插法

在这里插入图片描述

/* 在链表头插入新的节点 */
Status HeadInsert(LLNode *L,ElemType data)
{
    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    }

    LLNode *newnode = (LLNode *)malloc(sizeof(LLNode));

    newnode->data = data; /* 把数据放入新的节点的数据域中 */
    newnode->next = L->next; /* 新的节点指向头结点的下一个节点(第一个节点) */

    L->next = newnode; /* 头结点指向新插入的节点 */
    L->data ++; /* 头结点的数据域+1,表示有新的一个节点插入 */

    return SUCCESS;
}

在链表的表头插入新的节点,让头节点指向新的节点。

2.4 尾插法

在这里插入图片描述

/* 在链表尾插入新的节点 */
Status TailInsert(LLNode *L,ElemType data)
{
    LLNode  *cur;

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    LLNode *newnode = (LLNode *)malloc(sizeof(LLNode));

    newnode->data = data; /* 把数据放入新的节点的数据域中 */
    newnode->next = NULL; /* 新的节点指向NULL(尾节点) */

    cur = L;
    while(cur->next) /* 遍历到最后一个节点 */
    {
        cur = cur->next;
    }    

    cur->next = newnode; /* 链表尾部插入新节点 */
    L->data ++; /* 头结点的数据域+1,表示有新的一个节点插入 */

    return SUCCESS;
}

在链表的尾部插入新的节点,让新节点指向NULL。

2.5 判空

/* 判断是否为空链表 */
Status JudgeNull(LLNode *L)
{
    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;       
    }

    if(0 == L->data || NULL == L->next) /* 链表头结点的数据域为记录多少个实际节点 */
    {
        printf("LinkList is NULL\n");
        return TRUE;
    }

    return FALSE;
}

判断链表是否为空链表,防止对空指针进行删除等操作,会导致段错误,主要是利用了头结点的特性,头结点记录了链表有多少个实际节点。

2.6 打印链表的全部数据

/* 遍历打印链表中所有的数据 */
Status PrintfList(LLNode *L)
{
    LLNode  *cur;
    Status  rv;

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    }

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;

    cur = L->next;
    while(cur)
    {
        printf("%d->",cur->data);
        cur = cur->next;
    }
    printf("NULL\n");

    return SUCCESS;
}

打印前先判空,链表不为空的情况下,打印数据。

2.7 头删法

/* 从链表的头部删除一个节点 */
Status HeadDelete(LLNode *L)
{
    Status      rv;

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;

    LLNode *cur = L->next;
    L->next = cur->next; /* 让头结点指向原来第一个节点的后一个节点,即删除了原来第一个节点 */
    L->data --; /* 头结点的数据域-1,表示有新的一个节点删除 */

    free(cur); /* 删除后释放内存 */

    return SUCCESS;
}

让头结点指向原来第一个节点的后一个节点,并且释放掉第一个节点的内存空间,即删除了原来第一个节点。

2.8 尾删法

/* 从链表的尾部删除一个节点 */
Status TailDelete(LLNode *L)
{
    Status      rv;
    LLNode      *cur; /* 表示当前节点 */
    LLNode      *pre; /* 表示前一个节点 */

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;

    pre = L;
    cur = pre->next;
    while(cur->next) /* 遍历到最后一个节点 */
    {
        pre = cur;
        cur = pre->next;
    }   

    pre->next = NULL;
    free(cur); /* 释放内存空间 */

    L->data --;

    return SUCCESS;
}

把链表最后一个实际节点删除,前一个节点指向NULL,并且释放掉最后一个实际节点的内存空间。

2.9 删除链表中数据域 data 的节点

在这里插入图片描述

/* 删除链表中数据域 data 的节点 */
Status ApoDelete(LLNode *L,ElemType data)
{
    Status      rv;
    LLNode      *cur; /* 表示当前节点 */
    LLNode      *pre; /* 表示前一个节点 */
    int         i = 0;

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;
    
    pre = L;
    cur = pre->next;
    while(cur) /* 遍历到最后一个节点 */
    {
        if(data == cur->data)
        {
            pre->next = cur->next;
            free(cur);
            i++;
            cur = pre->next; /* 让cur指向删除节点的下一个节点,继续判断是否还有满足条件的节点 */
            continue;
        }

        pre = cur;
        cur = pre->next;
    }   

    L->data -= i;

    return SUCCESS;
}

删除链表中任意一个或多个数据域为data的节点。

2.10 清空链表

/* 清除链表 */
Status ClearList(LLNode *L)
{
    Status      rv;
    LLNode      *cur; /* 表示当前节点 */
    LLNode      *temp; /* 表示当前节点 */

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;  

    cur = L->next;
    while(cur)
    {
        temp = cur;
        cur = cur->next;
        free(temp);
    }

    L->data = 0; 
    L->next = NULL;

    return SUCCESS;
}

清除链表中的全部实际节点。

整体代码实现

/*********************************************************************************
 *      Copyright:  (C) 2022 Li Rongquan<2962837290@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  LinkList.c
 *    Description:  This file is LinkList ctl. 
 *                 
 *        Version:  1.0.0(08/15/2022)
 *         Author:  Li Rongquan <2962837290@qq.com>
 *      ChangeLog:  1, Release initial version on "08/15/2022 01:21:16 AM"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <stdlib.h>

#define SUCCESS 0
#define ERROR -1
#define TRUE 1
#define FALSE 0

typedef int Status; /* Status 是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType; /* ElemType类型根据实际情况而定,这里取int整型 */

typedef struct LinkListNode
{
    ElemType                data; /* 数据域存储数据 */
    struct  LinkListNode    *next; /* 指针域存储下一个节点的地址 */
}LLNode;

//typedef struct LinkListNode *LinkList; /* 定义一个别名Linklist(指针) 

Status initlist(LLNode *L)
{
    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    }

    L->data = 0;    /* 头节点的数据域记录链表中有多少个节点,现在为0,即一个都没有 */
    L->next = NULL; 

    return SUCCESS;
}

/* 在链表头插入新的节点 */
Status HeadInsert(LLNode *L,ElemType data)
{
    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    }

    LLNode *newnode = (LLNode *)malloc(sizeof(LLNode));

    newnode->data = data; /* 把数据放入新的节点的数据域中 */
    newnode->next = L->next; /* 新的节点指向头结点的下一个节点(第一个节点) */

    L->next = newnode; /* 头结点指向新插入的节点 */
    L->data ++; /* 头结点的数据域+1,表示有新的一个节点插入 */

    return SUCCESS;
}

/* 判断是否为空链表 */
Status JudgeNull(LLNode *L)
{
    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;       
    }

    if(0 == L->data || NULL == L->next) /* 链表头结点的数据域为记录多少个实际节点 */
    {
        printf("LinkList is NULL\n");
        return TRUE;
    }

    return FALSE;
}

/* 遍历打印链表中所有的数据 */
Status PrintfList(LLNode *L)
{
    LLNode  *cur;
    Status  rv;

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    }

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;

    cur = L->next;
    while(cur)
    {
        printf("%d->",cur->data);
        cur = cur->next;
    }
    printf("NULL\n");

    return SUCCESS;
}

/* 在链表尾插入新的节点 */
Status TailInsert(LLNode *L,ElemType data)
{
    LLNode  *cur;

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    LLNode *newnode = (LLNode *)malloc(sizeof(LLNode));

    newnode->data = data; /* 把数据放入新的节点的数据域中 */
    newnode->next = NULL; /* 新的节点指向NULL(尾节点) */

    cur = L;
    while(cur->next) /* 遍历到最后一个节点 */
    {
        cur = cur->next;
    }    

    cur->next = newnode; /* 链表尾部插入新节点 */
    L->data ++; /* 头结点的数据域+1,表示有新的一个节点插入 */

    return SUCCESS;
}

/* 从链表的头部删除一个节点 */
Status HeadDelete(LLNode *L)
{
    Status      rv;

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;

    LLNode *cur = L->next;
    L->next = cur->next; /* 让头结点指向原来第一个节点的后一个节点,即删除了原来第一个节点 */
    L->data --; /* 头结点的数据域-1,表示有新的一个节点删除 */

    free(cur); /* 删除后释放内存 */

    return SUCCESS;
}

/* 从链表的尾部删除一个节点 */
Status TailDelete(LLNode *L)
{
    Status      rv;
    LLNode      *cur; /* 表示当前节点 */
    LLNode      *pre; /* 表示前一个节点 */

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;

    pre = L;
    cur = pre->next;
    while(cur->next) /* 遍历到最后一个节点 */
    {
        pre = cur;
        cur = pre->next;
    }   

    pre->next = NULL;
    free(cur); /* 释放内存空间 */

    L->data --;

    return SUCCESS;
}

/* 删除链表中数据域 data 的节点 */
Status ApoDelete(LLNode *L,ElemType data)
{
    Status      rv;
    LLNode      *cur; /* 表示当前节点 */
    LLNode      *pre; /* 表示前一个节点 */
    int         i = 0;

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;
    
    pre = L;
    cur = pre->next;
    while(cur) /* 遍历到最后一个节点 */
    {
        if(data == cur->data)
        {
            pre->next = cur->next;
            free(cur);
            i++;
            cur = pre->next; /* 让cur指向删除节点的下一个节点,继续判断是否还有满足条件的节点 */
            continue;
        }

        pre = cur;
        cur = pre->next;
    }   

    L->data -= i;

    return SUCCESS;
}

/* 清除链表 */
Status ClearList(LLNode *L)
{
    Status      rv;
    LLNode      *cur; /* 表示当前节点 */
    LLNode      *temp; /* 表示当前节点 */

    if(!L)
    {
        printf("%s Invalid input argument\n",__func__);
        return  ERROR;   
    } 

    rv = JudgeNull(L);
    if(rv == TRUE || rv == ERROR) /* 栈空或者出错 */
        return ERROR;  

    cur = L->next;
    while(cur)
    {
        temp = cur;
        cur = cur->next;
        free(temp);
    }

    L->data = 0; 
    L->next = NULL;

    return SUCCESS;
}


int main(int argc, char **argv)
{
    LLNode          L;
    Status          rv = 0;
    int             i;
    ElemType        insert_data = 0;
    ElemType        del_data = 0;

    rv = initlist(&L);
    if(rv != SUCCESS)
    {
        printf("Init list failure\n");
        return -1;
    }
    printf("after Init list length is: %d\n",L.data);

    for(i=1; i<=10; i++)
    {
        insert_data = i;
        if(HeadInsert(&L,insert_data) != SUCCESS)
        {
            printf("HeadInsert  error\n");
            return -2;            
        }
    }

    printf("插入数据后的链表为:\n");
    if(PrintfList(&L) != SUCCESS)
    {
        printf("Printf List faliure\n");
        return -3;
    }
    printf("after HeadInsert list length is: %d\n",L.data);

    for(i=1; i<=10; i++)
    {
        insert_data = i;
        if(TailInsert(&L,insert_data) != SUCCESS)
        {
            printf("TailInsert  failure\n");
            return -2;            
        }
    }

    printf("插入数据后的链表为:\n");
    if(PrintfList(&L) != SUCCESS)
    {
        printf("Printf List faliure\n");
        return -3;
    }
    printf("after TailInsert list length is: %d\n",L.data);

    if(HeadDelete(&L) != SUCCESS)
    {
        printf("HeadDelete List faliure\n");
        return -4;
    }

    printf("从链表头部删除数据后的链表为:\n");
    if(PrintfList(&L) != SUCCESS)
    {
        printf("Printf List faliure\n");
        return -3;
    }
    printf("after HeadDelete list length is: %d\n",L.data);

    if(TailDelete(&L) != SUCCESS)
    {
        printf("TailDelete List faliure\n");
        return -5;
    } 

    printf("从链表尾部删除数据后的链表为:\n");
    if(PrintfList(&L) != SUCCESS)
    {
        printf("Printf List faliure\n");
        return -6;
    }
    printf("after TailDelete list length is: %d\n",L.data);

    del_data = 1;
    if(ApoDelete(&L,del_data) != SUCCESS)
    {
        printf("OneDelete List faliure\n");
        return 6;
    }

    printf("从链删除某一个数据后的链表为:\n");
    if(PrintfList(&L) != SUCCESS)
    {
        printf("Printf List faliure\n");
        return -6;
    }
    printf("after OneDelete list length is: %d\n",L.data);  

    del_data = 2;
    if(ApoDelete(&L,del_data) != SUCCESS)
    {
        printf("OneDelete List faliure\n");
        return -6;
    }

    printf("从链删除某一个数据后的链表为:\n");
    if(PrintfList(&L) != SUCCESS)
    {
        printf("Printf List faliure\n");
        return -7;
    }
    printf("after OneDelete list length is: %d\n",L.data); 

    if(ClearList(&L) != SUCCESS)
    {
        printf("ClearList List faliure\n");
        return -8;
    } 

    printf("清除链表后的链表为:\n");
    if(PrintfList(&L) != SUCCESS)
    {
        printf("Printf List faliure\n");
        return -7;
    }
    printf("after ClearList list length is: %d\n",L.data); 

    return 0;
}

    ```

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值