• 链结构


      1 #include<stdio.h>
      2 
      3 /*
      4 数据结构:
      5     在大一上学期,我们学过了C语言程序设计,可以说,所有C语言的语法规则都讲清楚了,如果学好了,给你一段C语言的
      6 代码,至少可以看懂了。而下学期要学的,很重要的一门课:数据结构,则是在C语言的基础上,灵活应用,来实现一些功能
      7 或者使得代码更加简洁。
      8     数据结构最主要的框架就是C程序设计中的结构体,我们来讲解一下结构体,结构体就是通过将不同的变量封装在一起,
      9 用来描述一个特定的事物,目的是使得代码更加条理清楚,比如,我们要描述一个空间中的点,我们可以用,x、y、z来表示
     10 三维坐标,那么,使用时,我们要定义int x,y,z;如果是数组,我们还需要int x[10],y[10],z[10],然后使用时一一对应,
     11 x[1]、y[1]、z[1]描述的是第一个点的三维坐标,这样子,代码就会显得很零散,如果这个事物的属性更多时,我们便难以判
     12 断使用,所以,我们可以采用结构体来定义,比如三维空间的点可以定义成:
     13     typedef struct point  //typedef 起到的是重新给point这个结构体起名字的作用。
     14     {
     15       int x,y,z;          //将x、y、z封装在一起,共同来描述点这个事物
     16     }Point;               //这样子我们便可以用Point a; 来申请一个点所需的空间,用起来更加的方便。
     17     而数据结构里就讲解了一些前人定义好的,一些常见的结构体,通过这些结构体,我们可以更加方便地编程。
     18 
     19 
     20 链结构:
     21     定义:数据存储或逻辑上是链式的
     22     基本实现方式:数组和链表
     23 
     24 ==============================================================================================================
     25     数组:
     26         数组大家都接触得比较多,就不细讲了,不过在结构化中,我们可以将数组的大小和数组封装在一起,如下:
     27         typedef struct
     28         {
     29           int data[1000];
     30           int num;
     31         }Node;
     32         这样子,我们便可以用Node a;定义一个数组,并且a.num是这个数组中存放数的数量。
     33 ==============================================================================================================
     34     链表:
     35     我们可以通过指针或者数组下标将一些元素链接在一起,和数组相似又不完全一样。
     36     一. 指针链接的链表:
     37     指针初步了解:
     38     我们先来了解一下指针,比如说int *a; 我定义了一个指向整型数据的指针变量a,我们可以用这个指针指向另外一个整型
     39 变量,比如说int b=3; a=&b;我们把b的地址给a,然后我们便可以通过a来访问b所在的空间来查看或修改b这个变量空间里面的
     40 内容,比如说printf("%d
    ",*a);我们可以输出b空间里的内容,比如*a=4;我们修改b这个变量的值为4;
     41     基本上,指针有两种用法,一个比较简单,就像是前面那样,我们将这个指针指向某一个已经定义好的变量来访问这个变
     42 量,或者,我们为这个指针申请一个空间,a=(int *)malloc(sizeof(int)); 这段代码就叫做动态申请,我们通过这段代码手
     43 动地向系统申请到了一个整型数据大小的空间,现在,这个指针就是通往这个空间的梯子,通过这个指针我们可以访问到这个
     44 空间,当然,如果我们现在将指针a指向b,也是可以的,但你申请到的这个空间就再也找不到了,因为你把梯子给撤掉了,这
     45 个空间又没有别的标记,自然就再也找不到了,这就是所谓的内存泄漏,我们写程序的时候要避免出现这种情况。
     46     链表实现:
     47     指针的用途基本看到了,我们现在用指针来链接几个元素,来构建一个链表。
     48     我们可以封装一个结构体如下:
     49     typedef struct node
     50     {
     51       int data; //这个链表所存放的元素,这里用int来定义,也可以是自己定义的其他结构体。
     52       struct node *next; //这是一个struct node类型的指针,用来指向下一个这种类型的变量
     53     }Node;
     54     现在我们先定义两个变量:Node a,b;
     55     接下来,我们赋值:a.data=1; b.data=2; 然后要将这两个变量链接在一起:
     56     a.next=&b; //将b的地址赋值给a的next属性,这样,a.next就指向了b,自然,a、b就连在一起了,如果b的后面没有别的
     57 元素,我们可以让b的next指向NULL,表明b是这个链表的最后一个元素:b.next=NULL;
     58     这样子,一个最基本的链表就构建完成了,表头是a,表尾是b,甚至,我们接下来都用不到b这个变量名了,a.next就表示
     59 b。不管链表的元素有多少,我们都可以用表头来表示任意一个元素,差别只不过是.next的数量而已,比如有五个元素,那么
     60 a.next->next->next->next就代表着第五个元素。当然,实际应用的时候,肯定不是这样用的。
     61     我们平时写链表的时候都是动态申请空间的,也就是说链表里面的每一个元素都是通过malloc来获取空间的,如下:
     62     Node *CreatNode()   //写一个单独函数来重复调用,每一次调用获得一个Node变量大小的空间
     63     {
     64       Node *s;
     65       s=(Node *)malloc(sizeof(Node));
     66       s->next=NULL;     //如果后面不接东西的话,必须置为NULL,这样遍历链表的时候遇到NULL就表示链表结束
     67       return s;         //创建完这个空间,要将这个空间的地址返回回去
     68     }
     69     接下来,要创建链表,我们可以先调用一次CreatNode来获取表头。
     70     Node *p; //我们设立的表头p
     71     p=CreatNode();  //一般链表有两种模式:有表头和无表头,有表头的意思就是在正式数据前多弄一个空的变量,这样做
     72                     //的好处,用过就知道!
     73     比如现在我们不断添加元素,从键盘读取整数,遇到-9999时链表创建结束,如下:
     74     while(1)
     75     {
     76       scanf("%d",&x);        //输入
     77       if(x==-9999) break;    //如果是-9999就退出循环
     78       Node *s;
     79       s=CreatNode();         //定义了一个指针,用过不指向一个可用的空间的话,被称作是野指针,直接用程序会崩的
     80       s->data=x;             //赋值
     81       s->next=p->next;       //这种插入方式叫做头插法,也就是说每创建一个变量就插在表头的后面
     82       p->next=s;             //将表头的next指针指向s,那么p->next就等于s,这就是将这些元素链接在一起的链。
     83     }
     84     这样子,链表就创建完毕。那么如何通过一个表头来遍历整个链表呢。如下:
     85     比如我要输出这个链表:
     86     void Dis(Node *p) //将你要输出的链表的表头当作参数传递给函数
     87     {
     88       p=p->next;      //表头是空的
     89       while(p)        //当p不是NULL的时候,说明p->data是有值得,当等于NULL的时候,就是链表的结尾了
     90       {
     91         printf("-> %d ",p->data); //输出
     92         p=p->next;                //p移动到他链接的下一个变量上。
     93       }
     94       printf("
    ");
     95       //指针和函数参数深入:
     96       //不知道大家会不会疑惑,我没有新生成一个指针来遍历数组,直接用表头来遍历,会不会出现遍历一遍,链表就找不
     97       //到了,因为老师肯定讲过swap(int *a,int *b)的例子,当传递的是指针进来的时候,好像可以实现a、b的互换,为
     98       //什么这里可以直接用表头遍历呢。其实和函数的形参一个道理,函数在接收外面传进来的参数的时候,其实是自己重
     99       //新弄了一个变量来接收,所以才会出现函数内部a、b交换了而外部不变的现象,用指针之所以可以交换是因为,虽然
    100       //指针也是新创的,但由于值一样,所以指向的空间一样,通过地址来改变里面的元素,所以可以实现交换,但是如果
    101       //函数内部是直接改变指针的值,也就是指针指向的地址的话,他改变的只是函数内部的这个变量值,外面的实参是不
    102       //会有变化的。
    103     }
    104     遍历链表的方法很重要,很多操作都是通过这个方法来实现的。
    105     比如说,我要所有data值为5的结点,如下:
    106     void Del(Node *p,int x) //表头和删除的值传进来
    107     {
    108       Node *q;              //重新弄一个指针来遍历链表
    109       q=p->next;            //表头里面是不存值的
    110       while(q)              //当q不为空,也就是链表还没结束的时候
    111       {
    112         if(q->data==x)      //如果q指向的结点的data值等于x,那么就要删除它
    113         {
    114           p->next=q->next;  //p一直是q的前一项,现在删除q就是将p的next指针指向q的下一项,自然就跳过了q
    115           free(q);          //因为空间是我们动态申请的,现在也要手动收回,不然会内存泄漏
    116           q=p->next;        //q重新指向p的下一项
    117         }
    118         else
    119         {
    120           p=p->next;        //如果不等于x,p就跳一格,q也跳一格,始终保持q在p的下一项
    121           q=q->next;
    122         }
    123       }
    124     }
    125     插入与删除类似。
    126     值得注意的是:严谨的代码中,动态申请到的空间,必须在不用时释放掉,不然会造成内存泄漏,如下:
    127     void Des(Node *p)    //递归程序
    128     {
    129       if(p)              //如果还没到达链表尾部,那么就要先释放掉该结点后面的结点,再释放本结点
    130       {
    131         Des(p->next);
    132         free(p);
    133       }
    134     }
    135     以上就是用指针链接的链表,建议大家都自己手写一份,实现插入、删除等操作。。。
    136     现在大家有没有发现数组和链表的优缺点,明明数组更加简便,为什么要创造链表这种东西呢?
    137     实际上,对于插入、删除操作来说,链表比起数组是有巨大优势的,数组想要插入一个数,插入以后还得移动n个数来维持
    138 顺序,但对于链表,你只需要找到这个数的位置,然后修改上一个结点的指针指向就可以了。不需要移动大量的数据,如果这
    139 个表非常长的话,那么优势是巨大的。当然,如果你想知道某一个位置的数的话,那么数组可以直接输出,链表却需要O(n)
    140 的时间,因此,数组和链表是各有优缺点的。
    141     二. 数组下标链接的链表:
    142     比如我们有一串数3 4 7
    143     那么我们可以构成一个数组表
    144     地址下标  存放的数   下一个数存放的位置,如果为-1就代表这个数就是表尾
    145     0        | 3        | 1
    146     1        | 4        | 2
    147     2        | 7        | -1
    148     我们可以用一个二维数组来实现这个功能,比如:a[100][2]; 总共有100个空间,a[i][0]代表存放的数、a[i][1]代表下
    149 一个数存在的位置,这样子,我们便构建了一个链表,只不过这个链表不是由指针链接,而是由下标链接在一起的。那么,他
    150 和前面讲的链表一样,实现方法也差不多,你们可以自己手写一份。
    151     链表的用法多种多样,而且可以根据实际用途来自己决定他的结构,比如我们可以将最后一个结点的next指针指向第一个
    152 结点,形成循环链表,比如我们可以在每个结点内多包含一个struct node *pre; 用来指向上一个结点,称作双向链表。还可
    153 结合到栈和队列中去用,总之,只要能实现目标,一起都好说。。。。
    154 
    155 注:
    156     我们来看一下有表头的链表和没有表头的链表的差别:(指针实现)
    157     如果有表头,删除第一个结点的时候,直接将表头的next指向第二个结点就可以了,如果没有表头,你的头指针指向了第
    158 二个结点,但你必须将这个头指针返回回去,或者通过双重指针来实现,不然就会出错,原因和上面的形参实参关系一样。
    159 */
  • 相关阅读:
    ubuntu12.04 LTS 安装vmware 错误解决方法
    Linux 下的Bluetooth 架构 分类: Android驱动 20120316 11:07 492人阅读 评论(0) 收藏 举报 实战Linux Bluetooth编程(一) 协议栈概述
    通过DEFINE 生存config.h 配置
    Git的初次使用 ; Git常用命令查询 ; Git push ; Git pull 20111216 17:32 在介绍安装和简单使用前,先看一下百度百科中的简介吧: ———————————
    Android BCM4330 蓝牙BT驱动调试记录
    Linux的cpufreq(动态变频)技术
    高通平台android开发总结
    ssh 客户端配置文件
    Jprofile使用随笔_1_安装与监控linux
    服务器cpu占用100%,如何排查(java进程&sql)
  • 原文地址:https://www.cnblogs.com/hchlqlz-oj-mrj/p/4959581.html
Copyright © 2020-2023  润新知