链表总结

链表总结

(链表的具体相关操作请参考本博客文章中的链表一类)

链表(Linked list是一种常见的基础数据结构,是一种线性表,是一种物理存储单元上非连续、非顺序的存储结构。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括存储数据元素的数据域和存储下一个结点地址的指针域两个部分。 相比于线性表顺序结构,操作复杂。数据元素的逻辑顺序也是通过链表中的指针链接次序实现的。使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。在计算机科学中,链表作为一种基础的数据结构可以用来生成其它类型的数据结构。链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接("links")。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。而链表是一种自我指示数据类型,因为它包含指向另一个相同类型的数据的指针(链接)。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的存取和操作。

    

特点

线性表的链式存储表示的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素与其直接后继数据元素 之间的逻辑关系,对数据元素 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。由这两部分信息组成一个"结点"(如概述旁的图所示),表示线性表中一个数据元素。线性表的链式存储表示,有一个缺点就是要找一个数,必须要从头开始找起,十分麻烦。

图7-3是单链表的结构:

 
                                              

基本操作:

1)建立:

顺序建链表:

 

复制代码
struct node *head;
struct node *creat(int n)
{
    int i;
    struct node *tail,*p;
    head=(struct node *)malloc(sizeof(struct node));
    head->next=NULL;
    tail=head;
    for(i=0;i<n;i++)
    {
        p=(struct node *)malloc(sizeof(struct node));
        scanf("%d",&p->data);
        p->next=NULL;
        tail->next=p;
        tail=p;
    }
    return(head);
};
复制代码

 

逆序建链表:

复制代码
struct node *head;
struct node *creat(int n)
{
    int i;
    struct node *p;
    head=(struct node *)malloc(sizeof(struct node));
    head->next=NULL;
    for(i=0;i<n;i++)
    {
        p=(struct node *)malloc(sizeof(struct node));
        scanf("%d",&p->data);
        p->next=head->next;
        head->next=p;
    }
    return(head);
};
复制代码


2)查找:

从链表中删除一个节点有三种情况,即删除链表头节点、删除链表的中
间节点、删除链表的尾节点。题目给出的是学生姓名,则应在链表中从头到尾依此查找各节
点,并与各节点的学生姓名比较,若相同,则查找成功,否则,找不到节点。由于删除的节
点可能在链表的头,会对链表的头指针造成丢失,所以定义删除节点的函数的返回值定义为
返回结构体类型的指针。

复制代码
struct node *delet(head,pstr)//以he a d 为头指针,删除pstr所在节点
struct node *head;
char *pstr;
{
    struct node *temp,*p;
    temp = head;// 链表的头指针
    if (head==NULL) //链表为空
        printf("\nList is null!\n");
    else //非空表
    {
        temp = head ;
        while (strcmp(temp->str,pstr)!=0&&temp->next!=NULL)// 若节点的字符串与输入字符串不同,并且未到链表尾
        {
            p=temp;
            temp = temp->next; //  跟踪链表的增长,即指针后移
        }
        if(strcmp(temp->str,pstr)==0 ) //找到字符串
        {
            if(temp==head)
            {
                //表头节点
                printf("delete string :%s\n",temp->str);
                head = head->next;
                free (temp) ; //释放被删节点
            }
            else
            {
                p->next=temp->next; //表中节点
                printf("delete string :%s\n",temp->str);
                free( temp ) ;
            }
        }
        else printf("\nno find string!\n");//没找 到要删除的字符串
    }
    return(head) ;
//返回表头指针*
}
复制代码

3. 链表的插入
首先定义链表的结构:
struct
{
int num; /*学生学号* /
char str[20]; /*姓名* /
struct node *next;
} ;
在建立的单链表中,插入节点有三种情况,如图7 - 5所示。

插入的节点可以在表头、表中或表尾。假定我们按照以学号为顺序建立链表,则插入的节点依次与表中节点相比较,找到插入位置。由于插入的节点可能在链表的头,会对链表的头指针造成修改,所以定义插入节点的函数的返回值定义为返回结构体类型的指针。节点的
插入函数如下:

复制代码
struct node *insert(head,pstr,n) //插入学号为n、姓名为p s t r 的节点
struct node *head; //链表的头指针
char *pstr;
int n;
{
    struct node *p1,*p2,*p3;
    p1=(struct node*)malloc(sizeof(struct node));
    分配//一个新节点
    strcpy(p1->str,pstr); // 写入节点的姓名字串
    p1->num=n; // 学号
    p2=head;
    if (head==NULL) // 空表
    {
        head=p1;
        p1->next=NULL;//新节点插入表头
    }
    else
    {
        //非空表
        while(n>p2->num&&p2->next!=NULL)
//输入的学号小于节点的学号,并且未到表尾
        {
            p3=p2;
            p2=p2->next; //跟踪链表增长
        }
        if (n<=p2->num) //找到插入位置
            if (head==p2) // 插入位置在表头
            {
                head=p1;
                p1->next=p2;
            }
            else
            {
                //插入位置在表中
                p3->next=p1;
                p1->next=p2;
            }
        else
        {
            //插入位置在表尾
            p2->next=p1;
            p1->next=NULL;
        }
    }
    return ( head ) ; // 返回链表的头指针
}
复制代码

循环链表是与单链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。

循环链表的运算与单链表的运算基本一致。所不同的有以下几点:

1、在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是象单链表那样置为NULL。此种情况还使用于在最后一个结点后插入一个新的结点。

2、在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域值等于表头指针时,说明已到表尾。而非象单链表那样判断链域值是否为NULL。

双向链表

双向链表其实是单链表的改进。

当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表

在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域;一个存储直接前驱结点地址,一般称之为左链域。

应用举例概述

约瑟夫环问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。例如:n = 9,k = 1,m = 5。

参考代码

复制代码
#include<stdio.h>
#include<malloc.h>
#defineN41
#defineM5

typedef struct node*link;
struct node
{
    int item;
    link next;
};
link NODE(intitem,linknext)
{
    linkt=malloc(sizeof*t);
    t->item=item;
    t->next=next;
    return t;
}
int main(void)
{
    int i;
    link t=NODE(1,NULL);
    t->next=t;
    for(i=2; i<=N; i++)
t=t->next=NODE(i,t->next);
while(t!=t->next)
{
    for(i=1; i<M; i++)
            t=t->next;
        t->next=t->next->next;
    }
    printf("%d\n",t->item);
    return0;
}
复制代码

 

其他相关结语与个人总结

C语言是学习数据结构的很好的学习工具。理解了C中用结构体描述数据结构,那么对于理解其C++描述,Java描述都就轻而易举了!链表的提出主要在于顺序存储中的插入和删除的时间复杂度是线性时间的,而链表的操作则可以是常数时间的复杂度。对于链表的插入与删除操作,个人做了一点总结,适用于各种链表如下:

插入操作处理顺序:中间节点的逻辑,后节点逻辑,前节点逻辑。按照这个顺序处理可以完成任何链表的插入操作。

删除操作的处理顺序:前节点逻辑,后节点逻辑,中间节点逻辑。

按照此顺序可以处理任何链表的删除操作。

如果不存在其中的某个节点略过即可。

上面的总结,大家可以看到一个现象,就是插入的顺序和删除的顺序恰好是相反的,很有意思!

<think>好的,我需要帮助用户完成一个链表实验的总结,并提供完整的C语言代码示例,包括输入输出。首先,用户提到了之前的对话,其中他们扩展了一个分割链表的C代码,并希望参考站内的两个引用。现在用户的需求是获取链表实验的总结,可能涉及输入输出,并且需要完整的代码。 首先,我需要回顾之前的回答。之前用户提供了一个分割链表的函数,然后我扩展成了一个完整的程序,包括创建链表、分割、打印和释放内存。用户现在可能希望类似的总结,但针对另一个链表实验,可能涉及创建、搜索、删除节点,并按照特定格式输出,如引用[1]中的问题描述。 用户提到的引用[1]中的实验题目要求输入一系列整数建立链表,直到-1结束,然后输入一个整数n进行搜索和删除。输出包括链表、是否找到以及删除后的链表。引用[2]中的代码展示了链表的创建和顺序输入输出,使用do-while循环读取输入,直到输入0结束,然后输出并释放内存。 结合这些,用户需要的是一个链表实验的总结,包含完整的C语言代码,实现以下功能: 1. 输入整数建立链表,直到输入-1。 2. 输入一个整数n,搜索链表中是否存在该节点。 3. 删除所有值为n的节点。 4. 按照要求的格式输出:第一行链表以逗号分隔,第二行搜索结果YES/NO,第三行删除后的链表。 我需要确保代码结构清晰,包含必要的注释,正确处理内存管理,避免内存泄漏。同时,输入输出要符合用户给出的示例,比如输入用空格分隔,输出用逗号分隔,最后一个没有逗号。 接下来,我需要考虑如何构建链表。使用头插法还是尾插法?引用[1]的示例输入是按顺序10到100,但输出是逆序的,如100,90,...,这可能意味着链表是逆序建立的,或者输出时是反向遍历。但根据Sample Input的输入顺序是10 20 30 ... 100 -1,而Sample Output是逆序输出,可能是在创建链表时使用了头插法,每次新节点插入头部,这样链表顺序与输入顺序相反。或者,可能是在输出时逆序打印。需要确认用户的需求。 查看引用[1]的Sample Input和Output,输入是按10到100顺序,输出是逆序,所以可能链表是头插法建立的。例如,输入10,20,30,建立的链表是30->20->10,这样输出时顺序遍历就是30,20,10。但Sample Output的示例输出是100,90,...10,而输入是10 20 ...100,所以应该是头插法,每个新节点插入头部,导致链表顺序与输入相反。因此,在创建链表时需要使用头插法。 然后,删除操作需要遍历链表,删除所有值为n的节点,并保持剩余节点的顺序。需要注意处理头节点和中间节点的情况,以及内存释放。 另外,输出格式要求每个整数以逗号分隔,最后一个没有逗号。这需要在打印时判断是否是最后一个节点,或者在循环中处理逗号的添加。 现在,开始构建代码: 1. 定义链表结构体:struct Node { int data; struct Node* next; }; 2. 创建链表函数:使用头插法,每次读取一个整数,直到-1。新节点插入链表头部。 3. 搜索函数:遍历链表,检查是否存在值为n的节点。 4. 删除函数:遍历链表,删除所有值为n的节点,注意处理头节点和其他节点的情况,可能需要使用双指针(前驱节点和当前节点)。 5. 打印函数:遍历链表,输出节点值,用逗号分隔,最后一个没有逗号。这可以通过判断当前节点的下一个是否为空来实现。 6. 释放链表内存的函数。 然后,主函数中处理输入输出,按照用户提供的示例格式。 测试案例需要符合Sample Input和Output,例如输入10到100,然后输入60,输出逆序链表,搜索结果为YES,删除后的链表不包含60。 在实现过程中,需要注意内存管理,确保删除节点后正确释放内存,避免内存泄漏。 可能的难点在于删除节点时的指针操作,特别是在处理头节点时。使用双指针法可以简化这一过程,即维护一个prev指针和current指针,当current节点的值等于n时,prev->next = current->next,然后释放current节点。 另外,输出时的逗号处理需要小心。可以在打印每个节点时,先打印数据,然后如果不是最后一个节点,打印逗号。或者,先构建一个字符串,再处理末尾的逗号。但为了简单起见,可以在遍历时判断下一个节点是否存在,从而决定是否打印逗号。 现在,将这些步骤整合到代码中,确保每个部分正确,并进行测试。</think>### 链表实验总结(C语言实现) 本实验实现链表创建、搜索、删除等核心操作,满足以下功能要求: - **输入处理**:第一行输入整数建立链表(-1结束),第二行输入目标值n - **输出要求**:格式化输出链表元素、搜索结果、删除后的链表 - **内存管理**:动态内存分配与释放 #### 完整代码实现 ```c #include <stdio.h> #include <stdlib.h> #include <assert.h> // 链表节点结构 typedef struct Node { int data; struct Node* next; } Node; // 创建链表(头插法) Node* create_list() { Node* head = NULL; int val; while (1) { scanf("%d", &val); if (val == -1) break; Node* new_node = (Node*)malloc(sizeof(Node)); new_node->data = val; new_node->next = head; // 头插法建立逆序链表 head = new_node; } return head; } // 搜索节点 int search_node(Node* head, int target) { Node* cur = head; while (cur) { if (cur->data == target) return 1; cur = cur->next; } return 0; } // 删除节点(删除所有匹配项) Node* delete_node(Node* head, int target) { Node dummy = {0, head}; // 哨兵节点简化头节点删除操作 Node* prev = &dummy; Node* cur = head; while (cur) { if (cur->data == target) { prev->next = cur->next; free(cur); cur = prev->next; } else { prev = cur; cur = cur->next; } } return dummy.next; } // 格式化打印链表 void print_list(Node* head) { Node* cur = head; while (cur) { printf("%d", cur->data); if (cur->next != NULL) printf(","); cur = cur->next; } printf("\n"); } // 释放链表内存 void free_list(Node* head) { Node* cur = head; while (cur) { Node* temp = cur; cur = cur->next; free(temp); } } int main() { // 输入链表 printf("输入链表元素(-1结束): "); Node* head = create_list(); // 输入目标值 int target; printf("输入目标值: "); scanf("%d", &target); // 输出原始链表 printf("原始链表: "); print_list(head); // 搜索并输出结果 printf("搜索结果: %s\n", search_node(head, target) ? "YES" : "NO"); // 删除节点并输出新链表 Node* new_head = delete_node(head, target); printf("删除后链表: "); print_list(new_head); // 释放内存 free_list(new_head); return 0; } ``` #### 代码特性 1. **头插法建表**:实现输入顺序与存储顺序相反(如输入`10 20 30 -1`生成链表`30->20->10`)[^1] 2. **哨兵节点优化**:使用虚拟头节点`dummy`统一处理删除头节点的情况[^2] 3. **时间复杂度**: - 创建链表:$O(n)$ - 搜索/删除:$O(n)$ 4. **输入输出示例**: ```text 输入链表元素(-1结束): 10 20 30 40 50 60 70 80 90 100 -1 输入目标值: 60 原始链表: 100,90,80,70,60,50,40,30,20,10 搜索结果: YES 删除后链表: 100,90,80,70,50,40,30,20,10 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值