什么是链表

文章讨论了数组的存储特点,强调其地址连续性以及在“增”、“删”操作上的不便。链表作为替代方案,通过结构体指针实现数据间的连接,允许更灵活的“增删”操作。文章提供了链表增加和删除数据的示例,并展示了如何通过链表头遍历数据。最后,文章提到了封装遍历链表的API设计思路。
摘要由CSDN通过智能技术生成

1.数组的存储方式和缺点

1、数组的存储特点:在一个连续的内存空间里面,存放着某些特点的数据,把这些数据集合在某个内存空间中。

  1. 数组非常明确的特点是:数组是一个整体,每一个元素的地址是连续的。
  2. 结构体其实也类似,结构体也是一个整体,抛开结构体内存中那部分空闲的内存不讲,各个成员变量也是有序存放的。
  3. 结构体数组也很明显,每个结构体是数组的元素,它们的地址也是连续的。

2、连续存储的缺点:

  1. 知识点:对于数据,我们希望能对它们做四种操作:“增”、“删”、“改”、“查”。显然,对于数组这种数据结构,“增”、“删”这两个操作很难实现,有时会涉及到整个数组的挪动。如图:
  2. 缺点1:不灵活,增删数据时运算量变大。
  3. 缺点2:不管是静态分配内存还是动态分配内存,数组都是一口气申请了大部分空间。 

2.链表是普通结构体的应用,为我们开发带来便利

1、链表举例:链表的每一个数据是结构体变量,每个数据之间用结构体指针的方式将数据串起来。

  1. 示例:链表中的数据中都存放着下一个数据的地址 。
    #include <stdio.h>
    struct A
    {
      int idata;
      struct A* p;  
    };
    int main(int argc, char const *argv[])
    {
        struct A a = {1};    
        struct A b = {2};    
        struct A c = {3};
        struct A d = {4};
        a.p = &b;				//数据a指向数据b
        b.p = &c;    			//数据b指向数据c
        c.p = &d;				//数据c指向数据d
        return 0;
    }
         

2、链表的“增、删数据”操作方法:

  1. 删去其中一个数据:以删去数据b为例,只需要将数据a的指向从b改为c。后面的数据不需要动。
    #include <stdio.h>
    struct A
    {
      int idata;
      struct A* p;  
    };
    int main(int argc, char const *argv[])
    {
        struct A a = {1};    
        struct A b = {2};    
        struct A c = {3};
        struct A d = {4};
        a.p = &b;
        b.p = &c;
        c.p = &d;
        /* 删去链表中的数据b */
        a.p = &c;               //只需要将数据a的指向从b改为c
        return 0;
    }

  2. 增加一个数据:以在数据a和数据b之间增加数据e为例,只需要将数据a的指向从b改为e,将数据e指向b。后面的数据不需要动。   
    #include <stdio.h>
    struct A
    {
      int idata;
      struct A* p;  
    };
    int main(int argc, char const *argv[])
    {
        struct A a = {1};    
        struct A b = {2};    
        struct A c = {3};
        struct A d = {4};
        a.p = &b;
        b.p = &c;
        c.p = &d;
        /* 在数据a和数据b之间增加链表数据e */
        struct A e = {5};
        a.p = &e;                   //只需要将数据a的指向从b改为e
        e.p = &b;                   //再将数据e指向数据b
        return 0;
    }

     

3.实现链表的第一个代码——与数组进行比较

代码心得

  1. gcc编译时,我们有加-o,如果没有写-o,那么会默认生成一个a.out文件。
  2. 目前单纯只讲结构体,还没讲链表。所以只能一个一个定义数据,定义结构体变量时,可以把里面的结构体指针初始化成NULL。
  3. 链表数据内部定义结构体指针变量的时候,可以形象的给这个指针起个名字,叫做next。
  4. 在用链表前一个数据访问下一个数据时,我们先通过点运算符取到下一个结构体的地址,再用剑号运算符取出这个结构体中的成员变量的值。其中,可以不加小括号,因为点运算符和剑号运算符的结合方向都是从左向右,比如:t1.next->idata。
  5. 在Ubuntu中,在用VI工具编辑代码时,用 :set nu 指令,显示行号。

1、用链表头访问链表:

  1. 知识点:数组里的数据是连续的,只需要知道数组头,就能输出整串数据;而对于分散的数据,通过链表才能用一个数据访问其他不连续存放的数据。
  2. 类似于数组头是数组的首地址,链表的第一个数据的首地址称为链表头。拿到链表头,通过next的方式就能遍历整个链表。 

代码心得

  1. 通常我们用head代表链表头。
  2. 你会发现在上面使用链表头t1并通过next访问链表数据时,有个特点,从t1到t1.next增加了一个next,从t1.next到t1.next->next增加了一个next,因此肯定能够通过循环的方式通过链表头访问链表数据。   
    #include <stdio.h>
    struct Test
    {
        int idata;
        struct Test *next;
    };
    int main(int argc, char const *argv[])
    {
        /* 用数组的方式存放1、2、3 */
        int i;
        int arr[] = {1,2,3};
        int len = sizeof(arr) / sizeof(arr[0]);
        for(i=0; i<len; i++){           //只需要知道数组头,就能访问数组中所有数据
            printf("%d ", arr[i]);
        }
        putchar('\n');
        /* 用链表的方式存放1、2、3 */
        struct Test t1 = {1,NULL};
        struct Test t2 = {2,NULL};
        struct Test t3 = {3,NULL};
        t1.next = &t2;                  //通过链表,才能通过一个数据访问其他不连续存放的数据
        t2.next = &t3; 
        puts("use t1 print three numbers");
        printf("%d %d %d\n", t1.idata, t1.next->idata, t1.next->next->idata);
        return 0;
    }

     

2、链表的完善(如何让链表头往后走):

  1. 知识点
    1. 链表中只有链表尾巴那个数据里面的指针是NULL。
    2. 对于数组来说,我们不知道遍历到什么时候结束(防止越界),所以遍历数组的时候需要两个参数(数组头和数组长度)。
    3. 对于链表来说,我们知道遍历什么时候结束(链表尾巴里的指针是NULL),所以遍历链表的时候只需要一个参数(链表头)。
    4. 对于数组arr来说,可以用数组名偏移的方式访问数组元素,也就是说每次让数组名这个“指针”往后走(arr+1)。而对于链表头head来说,它的后面指的是head->next(就是后一个链表数据的地址)。

      

  2. 封装遍历链表的API:
    思路:
    f1. 封装遍历链表的API: void printLink(struct Test *head); 形参head用来接收链表头的地址
    	f1.1 while死循环
        	f1.1.1 判断结构体指针head是否不等于NULL
            	f1.1.1.1 如果是,说明没有访问到链表尾巴
                	f1.1.1.1.1 打印链表数据: 
    				f1.1.1.1.2 修改代表链表数据地址的循环变量head: head = head->next;
    			f1.1.1.2 否则,说明已经访问到链表尾巴
                			那么,用break提前退出循环
    代码:
    #include <stdio.h>
    struct Test
    {
        int idata;
        struct Test *next;
    };
    void printLink(struct Test *head);
    int main(int argc, char const *argv[])
    {
        struct Test t1 = {1,NULL};
        struct Test t2 = {2,NULL}; 
        struct Test t3 = {3,NULL};
        struct Test t4 = {4,NULL};
        t1.next = &t2;                 
        t2.next = &t3; 
        t3.next = &t4; 
        puts("use t1 print three numbers");
        //printf("%d %d %d\n", t1.idata, t1.next->idata, t1.next->next->idata);
        printLink(&t1);
        return 0;
    }
    void printLink(struct Test *head) 
    {
        while(head != NULL){
            printf("%d ", head->idata);
            head = head->next;
        }
        putchar('\n');
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值