链表的基本操作(C)

1,为什么要用到链表

数组作为存放同类数据的集合,给我们在程序设计时带来很多的方便,增加了灵活性。但数组也同样存在一些弊病。如数组的大小在定义时要事先规定,不能在程序中进行调整,这样一来,在程序设计中针对不同问题有时需要3 0个大小的数组,有时需要5 0个数组的大小,难于统一。我们只能够根据可能的最大需求来定义数组,常常会造成一定存储空间的浪费。

我们希望构造动态的数组,随时可以调整数组的大小,以满足不同问题的需要。链表就是我们需要的动态数组。它是在程序的执行过程中根据需要有数据存储就向系统要求申请存储空间,决不构成对存储区的浪费。

链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链表、双向链表,下面将逐一介绍。

2,单向链表

单链表有一个头节点head,指向链表在内存的首地址。链表中的每一个节点的数据类型为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组)。链表按此结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为NULL。

如图所示

上图还给出这样一层含义,链表中的各节点在内存的存储地址不是连续的,其各节点的地址是在需要时向系统申请分配的,系统根据内存的当前情况,既可以连续分配地址,也可以跳跃式分配地址。

3,单向链表程序的实现
(1),链表节点的数据结构定义

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片

struct node
{
int num;
struct node *p;
} ;

在链表节点的定义中,除一个整型的成员外,成员p是指向与节点类型完全相同的指针。

在链表节点的数据结构中,非常特殊的一点就是结构体内的指针域的数据类型使用了未定义成功的数据类型。这是在C中唯一规定可以先使用后定义的数据结构。

(2),链表的创建、输出步骤
单链表的创建过程有以下几步:

1 ) 定义链表的数据结构;

2 ) 创建一个空表;

3 ) 利用malloc ( )函数向系统申请分配一个节点;

4 ) 将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新

节点接到表尾;

5 ) 判断一下是否有后续节点要接入链表,若有转到3 ),否则结束;

单链表的输出过程有以下几步

  1. 找到表头;

  2. 若是非空表,输出节点的值成员,是空表则退出;

3 ) 跟踪链表的增长,即找到下一个节点的地址;

  1. 转到2 ).

(3),程序代码例子:

创建一个存放正整数单链表,输入0或小于0的数,结束创建链表,并打印出链表中的值,程序如下:

//
//  main.cpp
//  List链表
//
//  Created by 于磊 on 16/8/19.
//  Copyright ? 2016年 于磊. All rights reserved.
//
 
# include <stdio.h>
//# include <malloc.h>
# include <stdlib.h>
 
typedef struct Node
{
    int data;
    struct Node * pNext;
} * PNODE, NODE;
 
PNODE establish_list (void);
void traverse_list (PNODE pHead);
bool is_empty(PNODE pHead);
int length_list(PNODE pHead);
void sort_list(PNODE pHead);
void insert_list(PNODE pHead, int pos, int val);
int delete_list(PNODE pHead, int pos, int val);
void freeer(PNODE pHead);
 
int main(void)
{
    PNODE pHead;
    int len, i, j, val;
    pHead = establish_list();
    traverse_list(pHead);
 
    if(is_empty(pHead))
        printf("链表为空\n");
    else
        printf("链表不空\n");
 
    len = length_list(pHead);
    printf("链表的长度为: %d\n", len);
 
    sort_list(pHead);
    traverse_list(pHead);
 
    printf("请输入您要在第几个节点插入\n");
    scanf("%d", &i);
    printf("请输入您要在第%d个节点插入的值\n", i);
    scanf("%d", &j);
    insert_list(pHead, i, j);
    traverse_list(pHead);
    printf("请输入您要第几个删除的节点\n");
    scanf("%d", &i);
    val = delete_list(pHead, i, val);
    printf("您删除的节点值为: %d\n", val);
    traverse_list(pHead);
    freeer(pHead);
 
    return 0;
}
 
PNODE establish_list(void)//初始化链表,返回头结点地址
{
    int val, len;
    PNODE Tem;
    PNODE pNew;
    PNODE pHead;
 
    pHead = (PNODE)malloc(sizeof(NODE));
    Tem = pHead;
    if(NULL == pHead)
    {
        printf("分配失败");
        exit(-1);
    }
    Tem->pNext = NULL;
    printf("请输入您要定义节点的长度: ");
    scanf("%d", &len);
 
    for (int i=0;i<len;++i) :="" null="=" pnew="(PNODE)malloc(sizeof(NODE));" pnew-="">data = val;//首先把本次创建的新节点的值付给新节点的数据域
        Tem->pNext = pNew;//然后使用临时的节点变量的指针域保存了新节点的地址,也就是指向了新节点
        pNew->pNext = NULL;//如何再不循环,新节点成为最后一个节点
        Tem = pNew;//把本次分配的新节点完全的赋给Tem,Tem就成为了这次新节点的影子,那么下次分配新节点时可以使用上个新节点的数据
    }
    return pHead;
}
 
void traverse_list(PNODE pHead)
{
    PNODE p = pHead;//使用P是为了不改写头结点里保存的地址
    p = pHead->pNext;//使P指向首节点
 
    while(p != NULL)//P本来就是头结点的指针域,也就是首节点的地址,既然是地址就可以直接判断p是否等于NULL
    {
        printf("%d ", p->data);
        p = p->pNext;//使P每循环一次就变成P的下一个节点
    }
}
 
bool is_empty(PNODE pHead)
{
    if(NULL == pHead->pNext)
        return true;
    else
        return false;
}
 
int length_list(PNODE pHead)
{
    PNODE p = pHead->pNext;
    int len = 0;
 
    while(p != NULL)
    {
        len++;
        p = p->pNext;
    }
    return len;
}
 
void sort_list(PNODE pHead)
{
    int i, j, t, len;
    PNODE p, q;
    len = length_list(pHead);
 
    for(i=0,p=pHead->pNext;i<len;i++,p=p->pNext)//逗号后只是为了找到下一个节点,因为不是数组,所以不能使用下标来++
    {
        for(j=0,q=pHead->pNext;j<len;j++,q=q->pNext)
            if(q->data > p->data)//这里的大小与号可以决定是升序还是降序,如果是大于号就是升序,反之小于号就是降序
            {
                t = q->data;
                q->data = p->data;
                p->data = t;
            }
    }
 
    return;
}
 
void insert_list(PNODE pHead, int pos, int val)
{
    int i;
    PNODE q = pHead;
    PNODE p = pHead;
    if(pos > 0 && pos <= length_list(pHead))
    {
        for(i=0;i<pos;i++) q="q-">pNext;//q就是要插入的连接点
        }
        for(i=1;i<pos;i++) p="p-">pNext;//p就是要插入连接点的前一个节点
        }
        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        p->pNext = pNew;
        pNew->data = val;
        pNew->pNext = q;
    }
    else if(pos > length_list(pHead))//追加
    {
        PNODE t;
        t = pHead;
        PNODE PN;
        PN = (PNODE)malloc(sizeof(NODE));
        if(PN == NULL)
            printf("分配失败");
        else
            while(t->pNext != NULL)
            {
                t = t->pNext;//使T->pNext成为尾结点
            }
        PN->data = val;//给新节点赋予有效数据
        t->pNext = PN;//使尾结点的指针域指向了新的结点
        PN->pNext = NULL;//新节点成为尾结点
    }
    else
        printf("error\n");
    return;
}
 
int delete_list(PNODE pHead, int pos, int val)
{
    int i, j;
    PNODE q, p;
    q = pHead;
    p = pHead;
 
    if(pos > 0 && pos <= length_list(pHead))//保证删除的是节点的有效数
    {
        for(i=0;i<pos;i++) p="p-">pNext;
        }
        for(j=1;j<pos;j++) else="" pos="=" q="pHead;">pNext;
        }
        q->pNext = p->pNext;
        val = p->data;
 
        free(p);
        return val;
    }
    else
        printf("error");
    return 0;
}
 
void freeer(PNODE pHead)
{
    PNODE pT = pHead;
    while(NULL != pHead->pNext)
    {
        free(pT);
        pT = pT->pNext;
    }
 
    return;
}
</pos;j++)></pos;i++)></pos;i++)></pos;i++)></len;j++,q=q-></len;i++,p=p-></len;++i)></stdlib.h></malloc.h></stdio.h>

在这里插入图片描述

<think>好的,我现在需要帮助用户学习C语言中链表基本操作。首先,我需要整理用户提供的引用内容,看看有哪些相关的信息可以利用。 用户提供了四个引用,都是关于C语言链表的操作。引用1和4提到了链表的创建方法,特别是尾插法。引用3是关于双链表的插入操作,引用2解释了链表的基本概念。所以,我需要从链表的基础开始,涵盖结构定义、创建、插入、删除、遍历等基本操作,并结合用户提供的代码示例进行说明。 接下来,我需要确保回答符合系统级指令的要求。所有行内数学表达式要用$...$,但这里可能用不上数学公式,但要注意代码块的使用。独立公式可能需要用$$...$$,但同样在链表操作中可能不涉及。需要检查LaTeX语法是否正确,不过主要的内容是代码和步骤说明。回答需要用中文,并且生成相关问题。 用户的需求是学习链表基本操作,所以需要结构清晰,分步骤讲解。可能需要包括以下几个部分: 1. 链表结构定义 2. 创建链表(尾插法) 3. 插入节点 4. 删除节点 5. 遍历链表 6. 释放内存 每个部分都需要结合用户提供的代码示例,比如引用1中的尾插法,引用4的结构定义,引用3的双链表插入操作。需要注意单链表和双链表的区别,但用户主要问的是基本操作,可能先集中在单链表上,再提及双链表。 需要确保代码示例正确,并且解释清楚每一步的作用。例如,在尾插法中,头节点的作用,tail指针如何移动。在插入节点时,如何处理指针的指向,避免内存泄漏或者断链。 另外,用户提供的引用中有一些代码可能需要修正或解释。例如,引用3中的双链表插入代码里,函数名是InserList,可能存在拼写错误,但用户可能希望看到正确的示例。不过,直接使用用户提供的代码时,需要指出可能的问题,或者给出更标准的实现。 在回答中,还需要自然地添加引用标识,比如在提到尾插法时引用[^1],链表结构定义引用[^4]等。要确保引用标记的位置合理,不影响阅读。 最后,生成相关问题,可能涉及链表与其他数据结构的比较、常见错误处理、高级操作如反转链表、应用场景等。 现在需要组织这些内容,确保逻辑连贯,步骤清晰,代码示例正确,并且符合用户的要求。同时,要避免使用Markdown格式,但根据用户提供的示例,代码块需要用三个反引号包裹,并指定语言为python或c。在回答中,用户的问题是关于C语言链表,所以代码块应该用c语言。 需要检查是否有遗漏的基本操作,比如查找节点、更新节点等,但可能用户更关注插入、删除、创建和遍历。释放内存也是一个重要部分,避免内存泄漏。 总结,回答的结构大致如下: - 链表概念和结构定义 - 创建链表(尾插法) - 插入节点(头插、中间插入、尾插) - 删除节点 - 遍历链表 - 释放内存 - 相关问题 每个部分结合引用中的代码,并添加解释。例如,结构体定义引用,尾插法引用,插入操作引用[^3]等。</think>### C语言链表基本操作实现教程 #### 一、链表基本概念 链表是一种**动态存储结构**,通过指针将多个节点连接成逻辑连续的数据集合。每个节点包含**数据域**和**指针域**,指针域存储下一个节点的地址[^2]。例如单链表的结构定义: ```c typedef struct Node { int data; // 数据域 struct Node* next; // 指针域 } Node; ``` --- #### 二、基本操作实现 ##### 1. 创建链表(尾插法) 通过逐步添加节点到链表尾部实现,时间复杂度为$O(n)$。需要维护`head`和`tail`两个指针: ```c Node* create_tail(int n) { Node *head = (Node*)malloc(sizeof(Node)); // 头节点 Node *tail = head; // 初始时头尾重合 for(int i=0; i<n; i++) { Node *pt = (Node*)malloc(sizeof(Node)); scanf("%d", &pt->data); pt->next = NULL; tail->next = pt; // 新节点接在尾部 tail = pt; // tail指针后移 } return head; } ``` > 特点:生成的链表顺序与输入顺序一致[^1] ##### 2. 插入节点 分为**头插法**、**中间插入**、**尾插法**三种场景。以在位置$i$插入为例: ```c void insert_node(Node* head, int i, int value) { Node *p = head; int count = 0; // 找到插入位置的前驱节点 while(p && count < i-1) { p = p->next; count++; } if(!p) return; // 位置无效 Node *newNode = (Node*)malloc(sizeof(Node)); newNode->data = value; newNode->next = p->next; p->next = newNode; } ``` > 双链表插入需要同时修改前驱和后继节点的指针 ##### 3. 删除节点 需要定位目标节点的前驱节点: ```c void delete_node(Node* head, int target) { Node *p = head; while(p->next && p->next->data != target) { p = p->next; } if(p->next) { Node *temp = p->next; p->next = temp->next; free(temp); // 释放内存 } } ``` ##### 4. 遍历链表 ```c void traverse(Node* head) { Node *p = head->next; // 跳过头节点 while(p) { printf("%d ", p->data); p = p->next; } } ``` ##### 5. 释放内存 ```c void destroy_list(Node* head) { Node *p = head; while(p) { Node *temp = p; p = p->next; free(temp); // 逐个释放节点 } } ``` --- #### 三、关键注意事项 1. **头节点作用**:简化插入/删除操作,避免处理首节点特例 2. **指针修改顺序**:插入操作中先连接新节点再断开原链接 3. **内存管理**:动态分配的内存必须显式释放
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值