首页 > 其他分享 >数据结构(单向链表)

数据结构(单向链表)

时间:2024-11-16 17:43:29浏览次数:3  
标签:单向 list next 链表 link 数据结构 data 节点

链式存储的优缺点:

优点:

1、动态分配内存:

链式存储不需要在数据插入之前分配固定大小的数组或内存块,因此它更适合存储动态变化的数据

2、高效的插入和删除操作:

在链表中插入或删除元素只需要调整相邻节点的指针,不需要移动大量数据,因此时间复杂度通常为O(1)(在已知位置时)或O(n)(在未知位置需要遍历链表时)。

3、节省内存:

链表中的每个节点只需要存储数据和指向下一个节点的指针,没有额外的空间浪费(如数组中的空闲空间)。

4、灵活性

链表可以以多种方式实现,如单向链表、双向链表、循环链表等,可以根据具体需求选择合适的结构。

缺点:

1、访问速度慢:

链表不支持像数组那样的随机访问,访问某个元素需要从头节点开始遍历,时间复杂度为O(n)。

2、额外的空间开销:

每个节点除了存储数据以外,还需要存储指针,增加了内存的开销。

3、内存碎片化:

链表中的节点可能在内存的不同位置分配内存,可能导致内存碎片化,影响性能。

4、管理复杂:

链表的管理(如内存分配和释放)相对复杂,容易出现内存泄露或指针错误等问题。

单向链表

单向链表(Singly Linked List)是一种链式存储结构的数据结构,它包含一系列节点(Node),每个节点都包含两个部分:数据域(存储节点的数据)和指针域(存储指向下一个节点的引用或指针)。单向链表的特点是链表中的节点只能单向遍历,即只能从链表的头部开始,依次访问每个节点,直到链表的尾部。

在我的链表中,我在定义时就给它一个规定:头节点不存数据,数据从头节点的下一个节点开始存储。

#ifndef _LINKLIST_H
#define _LINKLIST_H

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

#define OUT(A) {printf("%c\n",A);}
typedef char Type;       //为了方便修改存储数据的类型

//约定头节点不存数据
typedef struct node{
    Type data;         //节点中用于存储值
    struct node *next; //下一个节点的地址
}list;                 //声明了节点结构体类型

//创建
list *create_link();
//判空
int null_link(list *l);
//求长度
int length_link(list *l);
//首插
void head_insert_link(list *l,Type data);
//尾插
void tail_insert_link(list *l,Type data);
//查找
list *search_link(list *l,Type data);
//更新
void updata_link(list *l,Type oldData,Type newData);
//删除
void delete_link(list *l,Type data);
//遍历
void traverse_link(list *l);
//初始化
void init_link(list *l);
//回收
void free_link(list **l);

#endif

       还是跟顺序表一样,使用多文件封装的形式来存储,在.h文件中定义结构体,结构体包括存数据的变量data和指向下一个结构体的指针next;在这里定义结构体的时候node不能省略,因为在结构体里面需要定义一个指向这个结构体类型的指针,因此需要用到这个结构体类型来定义指针。

#include "linklist.h"

//创建
list *create_link()
{
    list *l = (list *)malloc(sizeof(list));
    if(NULL == l)
    {
        perror("create malloc");
        return NULL;
    }
    l->next = NULL;
    return l;
}
//判空
int null_link(list *l)
{
    if(NULL == l)
    {
        puts("listlink is NULL");
        return -1;
    }
    return l->next == NULL?0:1;
}
//求长度
int length_link(list *l)
{
    if(NULL == l)
    {
        puts("listlink is NULL");
        return -1;
    }
    int n = 0;
    while(l->next)
    {
        n++;
        l = l->next;
    }
    return n;
}
//尾插
void tail_insert_link(list *l,Type data)
{
    if(NULL == l)
    {
        puts("listlink is NULL");
        return;
    }
    //找表尾
    while(l->next) //l->next != NULL
    {
        l = l->next;
    }
    //创建新节点
    list *p=(list *)malloc(sizeof(list));
    if(NULL == l)
    {
        perror("tail_insert malloc");
    }
    p->data = data; 
    p->next = NULL;
    //把节点连接到链表中
    l->next = p;
}
//首插
void head_insert_link(list *l,Type data)
{
    if(NULL == l)
    {
        puts("listlink is NULL");
        return;
    }
    list *p = (list *)malloc(sizeof(list));
    if(NULL == l)
    {
        perror("head-insert malloc");
        return;
    }
    p->data = data;
    p->next = l->next;
    l->next = p;

}
//查找
list *search_link(list *l,Type data)
{
    if(NULL == l)
    {
        puts("listlink is NULL");
        return NULL;
    }
    while(l->next)
    {
        /*if(l->next->data == data)
          {
          return l->data;
          l = l->next;
          }*/
        l = l->next;
        if(data == l->data)
            return l;
    }
    return NULL;
}
//更新
void updata_link(list *l,Type oldData,Type newData)
{
    if(NULL == l)
    {
        puts("listlink is NULL");
        return;
    }
    while(l->next)
    {
        l = l->next;
        if(oldData == l->data)
        {
            l->data = newData;
        }
    }  
}
//删除
void delete_link(list *l,Type data)
{
    if(NULL == l)
    {
        puts("listlink is NULL");
        return;
    }
#if 1
    while(l->next)
    {
        if(l->next->data == data)
        {
            list *p = l->next;
            l->next = p->next;
            free(p);
            p = NULL;
            //continue;   //用else不用continue
        }
        else      
            l = l->next;
    }
#elif 0
    list *p = l;
    while(l->next)
    {
        if(data != l->data)
            p = l;
        l = l->next;
        if(data == l->data)
        {
            list *q = l;
            l = l->next;
            p->next = l;
            free(q);
            break;
        }
    }
    /*if(data == l->data) //注释break,加上最后的if为全删
      {
      p->next = NULL;
      free(l);
      }*/
#endif
}
//遍历
void traverse_link(list *l)
{
#if 0
    printf("head->");
    while(l->next)
    {
        printf("%c->",l->next->data);
        l = l->next;
    }
    puts("NULL");
#elif 1
    list *p = l->next;
    while(p)
    {
        printf("%c ",p->data);
        p = p->next;
    }
    puts("");
#endif
}
//初始化
void init_link(list *l)
{
    if(NULL == l)
    {
        puts("listlink is NULL");
        return;
    }
#if 1
    while(l->next)
    {
        list *p = l->next;
        l->next = p->next;
        free(p);
    }
#else
    list *p = l->next;
    while(p)
    {
        list *n = p;
        p = p->next;
        free(n);
    }
    l->next = NULL;
#endif
}
//回收
void free_link(list **l)
{
    if(NULL == *l)
    {
        puts("listlink is NULL");
        return;
    }
    while((*l)->next)
    {
        list *p = (*l)->next;
        (*l) = p->next;
        free(p);
    }
    *l = NULL;
}

单向链表的创建:list *create_link()

       创建之后我们需要拿到头节点的地址,因此函数返回值为结构体指针类型,同样是在堆区开辟空间,因为约定了头节点不存数据,因此这里的data不用管,但是指向下一个节点的指针需要把它赋值为NULL,防止野指针出现。

单向链表的判空:int null_link(list *l)

       因为链表中有指向下一个节点的指针,但是要是链表为空,那么就只有一个头节点,头节点里面的指针指向的是NULL,因此我们可以通过判断头节点里面的指针指向的下一个节点是否为空来判断链表是否为空;空函数返回0,非空返回1。

单向链表求长度:int length_link(list *l)

       单向链表的节点是通过指针连接在一起的,因此求长度只需要从头节点开始遍历到最后一个节点,遍历时使用一个变量计数,最后函数返回这个计数变量即可;循环条件为l->next,当循环到最后一个节点时,下一个为NULL,就会结束循环;在循环里每次让指针L往后移动一个节点。

单向链表尾插:void tail_insert_link(list *l,Type data)

       尾插也就是在链表最后插入一个新的节点,首先我们传参传的是链表的头节点,因此需要先遍历找到链表的最后一个节点,然后用一个结构体指针来接收新创建节点的地址,这里也需要判断一下节点创建是否成功,创建成功之后让节点的data等于我们要添加的值,然后让新节点里面的指针指向NULL,最后还需要更改插入新节点之前链表最后一个节点中指针的指向,让这个指针指向我们新加入的节点。

单向链表的头部插入(首插):void head_insert_link(list *l,Type data)

       头插相比尾插要复杂一点,但是这里我们就不需要遍历链表了,因为我们拿到的参数就是头节点,首先也是需要创建新节点,然后给节点里的data赋值,然后让新节点里的指针next指向头节点的下一个,再让头节点的next指向我们新插入的节点;在这里头插函数中最后两条语句的顺序能更改,更改之后我们就无法正确将新节点的next指向头节点的下一个节点了。

单向链表的查找:list *search_link(list *l,Type data)

        查找只需要循环遍历链表,然后将需要查找的数据与节点中的data比较即可,只要找到这个data,就将该data所在节点的地址返回就OK啦。

单向链表的更新:void updata_link(list *l,Type oldData,Type newData)

       更新也就是将链表中某一个值修改为我们想改的值,传入的参数就有三个,表,原数据和更新的数据,也是通过循环遍历的方式来寻找我们需要更改的数据,找到之后直接修改即可,在代码中我的更新是将所有的要修改的那个原数据都改变,例如原来链表中有3个a,我要将a修改为b,那么就会将这三个a都修改为b,如果只想修改找到的第一个a的话就只需要在l->data = newData;语句后面加一个break,直接结束循环即可。

单向链表的删除:void delete_link(list *l,Type data)

       删除和插入是类似的操作,只不过一个是增加节点,另一个是删除节点,首先是将要删除的数据当做参数传入函数,然后通过遍历的方式来查找需要删除的节点,找到节点之后用一个指针来指向这个节点,然后就是断链,将与它相连的前面一条链断开;在这里使用一个指针来指向这个节点是因为要把这个节点的空间释放掉,我们在找到这个节点之后就可以让它的前一个节点的next去指向要删除节点的下一个节点,再把要删除的节点空间释放掉,最后再让指针p指向NULL即可。在这里删除有删一个和删全部的区别,在代码中如果只想删除找到的第一个对应的字母,那就在p=NULL后语句之后加一个break结束循环即可。

单向链表的遍历:void traverse_link(list *l)

        链表的遍历也就是通过循环来让L从头节点往后移动一直到最后一个节点,然后将每一个节点的data输出即可。 

单向链表的初始化:void init_link(list *l)

       链表的初始化我们需要把除了头结点以外的节点都释放掉,同样还是使用循环遍历的方式,初始化跟删除是一样的操作,但是初始化是将所有节点都删除,跟删除差不多的我就不多说啦。

单向链表的回收:void free_link(list **l)

       链表的回收就是先将链表初始化然后再把头节点也释放,最后再让指向头节点的指针指向空即可。

#include "linklist.h"
int main(int agrc,char *agrv[])
{
   list *p = create_link(); 
   tail_insert_link(p,'a');
   tail_insert_link(p,'b');
   tail_insert_link(p,'b');
   tail_insert_link(p,'f');
   printf("尾插后链表内容:");
   traverse_link(p);

   head_insert_link(p,'b');
   head_insert_link(p,'c');
   head_insert_link(p,'d');
   printf("头插后链表内容:");
   traverse_link(p);

   printf("此时链表长度为:%d\n",length_link(p));
   list *q = search_link(p,'d');
   OUT(q->data);
   q->data = 'a';
   printf("将查找到的d改为a后:");
   traverse_link(p);

   updata_link(p,'a','b');
   printf("将第一个a更新为b:");
   traverse_link(p);

   delete_link(p,'b');
   printf("删除第一个b后:");
   traverse_link(p);

   init_link(p);
   printf("此时链表长度为:%d\n",length_link(p));

   free_link(&p);
   printf("回收后p的指向:%p\n",p);
    return 0;
}

标签:单向,list,next,链表,link,数据结构,data,节点
From: https://blog.csdn.net/yyw_17/article/details/143812562

相关文章

  • 江苏科技大学大二《数据结构》课内实验报告模板答案
    江苏科技大学《数据结构》实验报告(2024/2025学年第1学期)学生姓名:学生学号:院系:计算机学院专业:考核得分:2024年12月实验一线性表的操作一、实验目的掌握线性表的基本操作在存储结构上的实现,其中以单链表的操作作为重点。二、实验题目1.以单......
  • 批量检测微信单向好友,超实用!
    ​随着时间推移,大家免不了加好友。而微信的“删除好友”功能一直被广大网友诟病:无法查看单向删除好友。其实最近(2024-10-23)微信上线了一项新功能,当微信好友量到达1万上限后可查单删好友(安卓,iOS均可)。但对于大多数人来说,门槛太高了吧......
  • 数据结构/第二章 线性表/数据结构习题/线性表的习题/考研/期末复习
    一、选择题1.在线性表中,表尾元素(    )。A.有且仅有一个直接前驱        B.有且仅有一个直接后继C.没有直接前驱               D.有多个直接前驱2.在顺序表上按位查找一个元素的时间复杂度是(    )。A.O......
  • C 语言 【单链表】
         单链表是一种基本的数据结构,由一系列节点组成,每个节点包含数据域和指针域。‌数据域用于存储实际的数据,而指针域则存储指向下一个节点的地址。单链表的特点包括动态存储、非连续存储、易于插入和删除。    节点可以定义成一个结构体,每个节点中包含一个数......
  • 数据结构——AVL树
    目录一.AVL树的概念二.AVL树的实现1.AVL树结点的定义2.AVL树的插入3.AVL树的删除4.AVL树的查和改5.AVL树的遍历 6.验证AVL树是否平衡7.AVL树的性能三.整体代码1.AVLTree.h2.AVLTree.cpp一.AVL树的概念二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有......
  • 数据结构(初阶5)---堆与堆排序(详解)
    堆与堆排序一.二叉树初探1).基本概念2).满二叉树和完全二叉树3.)二叉树的存储方式二.堆与堆排序1.堆(完全二叉树的特例)1).建堆(向下调整法)2).堆排序再将堆排序之前,我们先引入二叉树概念一.二叉树初探1).基本概念二叉树是一种数据结构,二叉树形如:1.其中A节......
  • 数据结构——栈和队列的模拟实现
    文章目录前言一、栈1.1概念与结构1.2栈的实现二、队列2.1概念与结构2.2队列的实现总结前言继上篇博客,已经把链表的有关习题完成,链表也已经收尾啦。今天来学习新的数据结构——栈和队列,fellowme一、栈1.1概念与结构栈:⼀种特殊的线性表,其只允许在固定......
  • 114. 二叉树展开为链表
    题目链接解题思路:对于这类递归问题,采用「宏观」思考模式。对于任意一个节点A,左子树先「展开为链表」,右子树再「展开为链表」,然后A节点,将左子树的结果,和右子树的结果,「串在一起」即可。左子树展开为链表,所以要返回两个节点,一个是链表的头,然后是链表的尾。代码/***......
  • 初级数据结构——栈题库(c++)
    目录前言1.杭电oj——Bitset2.杭电oj——进制转换[3.力扣——LCR123.图书整理I](https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/description/)[4.力扣——LCR027.回文链表](https://leetcode.cn/problems/aMhZSa/)[5.力扣——1614.括号的......
  • 数据结构程序设计(C语言)校园导游系统
    使用队列以及深度搜索算法,加上dos命令调用图片的校园导游系统#define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<Windows.h>structgraph{ intnode_num;//顶点数 intedge_num;//边数 charnode_name[20][50......