数据结构简单概述

前言:

数据结构我们首先学习的是线性结构中最基本的线性表,分为顺序表和链表。课本后面的栈,队列不过是在最基本的线性表的基础上限定了对表的操作范围。例如,最基本的线性表中的顺序表可以在任意指定位置进行删除或插入,而栈的定义规定“先进后出”,或者说“后进先出”,即只能对最后进入栈的元素进行删除。

在写代码时,它们的各个函数模块(比如实现插入的函数、删除的函数、初始化的函数等等)的具体代码实现部分都非常类似。

最基本的线性表有顺序表,链表。

同样,栈有顺序栈,链栈;队列有顺序队列,链队。

那么,

到底为什么要分“顺序”和“链”?

什么是 “顺序”的,什么是“链”的?

我们写代码不过是对数据进行①获取,②存储,③操作(也可以理解为计算:算术运算和逻辑运算)和④输出(将结果信息传达给程序员)。关于①获取和④输出不必赘述。

在②存储数据时,计算机首先要给数据分配存储空间。那么计算机在选择如何分配存储空间时,或者说我们在编写代码时,会遇到这样一个问题:是输入一个数据元素,就分配相应大小的存储空间,使得正好用来存储该数据元素,还是要分配一个尽可能大的集中起来存储空间,来为即将输入的数据元素的存储做准备呢?抛开这个问题,我们存储时怎么插入存储记录也有问题:假如我们只想把类似班级的花名册之类的信息存储到计算机中,因为这些记录都是一条接一条的,所以存储时也可以一条接一条的存储。一条接一条,这是线性结构。假设我们按照“一条接一条”这样的逻辑在获取一条数据记录后就去接着上一条数据记录去存储,在遇到这个情况时将无法处理:我们想把家族的遗传图存储到计算机内,我们在读取数据时,我们把小明的记录读取了,上一条记录是小红的妈妈,我们当然不能将此条信息插在小红的妈妈下面,小明的记录应该插在小明的父母下。像这种类似的,不是“一条接一条”的,是按照层次关系去存储的,是非线性结构。

图来自网络。

现在我们回到②存储数据上来,只讨论线性结构:一条接一条存储信息时,我们在现实中,通过眼睛可以看到,班级的花名册的第一个学生信息下面紧挨着就是第二个学生的信息:

图来自网络。

我们在存储数据时,把数据“一条接一条”地接起来,有两种方法:第一种,直接在内存里把数据一条接一条地存放。比如我们存储1,2,3,4,5这五个数字,在C语言中,一个整型数据占4个字节。由于计算机按字节编址,假设上一条记录的地址是0000000,那下一条记录我们就可以选择地址0000004。以此类推。第二种,换一种思路,我们在存储第一条学生记录时,同时在这条记录里插入一个能访问下一条记录的途径。在C语言中,我们叫它指针。我们声明一个指针,并且使指针指向下一条记录,并把这个指针存储在上一条记录里,我们通过上一个记录就能访问下一条记录。记录们间接地“一条接一条”地存储了起来。

第一种方法,我们一般预先分配一个合适的存储空间,能存储若干记录;第二种方法,我们利用其灵活的特点,每读入一条记录,就分配一次存储空间。

假设一年级有六个班,四个班有60个学生,两个班只有10个学生,我们选择第一种方法存储记录时,用相同的表去存储这些学生信息,建表六个,每个表一次性分配能容纳70条记录的存储空间。很明显,60个人的四个班的记录几乎占满了对应表的存储空间,10个人的两个班却浪费了存储空间。在实际应用时,有很多时候无法估计记录的大概数量,我们这个时候就可以用第二种方法去存储记录。但是,第二种方法也要在原始记录后插入一个指针或途径去访问下一条记录,假设记录量很大,会造成相当多的存储空间的占用,并且计算机分配地址是随机分配的,量相当多的话,这样子搞,内存会很乱。

我们回到最开始的问题上来:什么是“顺序”的,什么是“链”的?“顺序的”就是我所说的“第一种”方法:在底层存储时,预先分配足够的存储空间,并且记录的地址是有规律的,直接地一条接一条地存储;“链”的就是我所说的第二种方法:每条记录的地址随机分配,存储空间按需分配,每条记录都存储有访问下一条记录的途径或指针。

这样,我们只需要用一个指针,指向第一条记录,所有的记录就串起来了。这个指针叫做表头指针。

这是为什么要分“顺序”的和“链”的的原因。

基本线性表,对插入删除操作不做限制;栈,在基本线性表的基础上,只允许数据元素在栈内“先进后出”,即最先进去的数据元素一定是最后一个被删除的;队列,“先进先出”,类似于“先到先得”。

总结一下,顺序和链是按照存储方式的不同来分别的,基本线性表,栈,队列等是按照操作不同去分别的。

接下来,用C语言来介绍实现基本线性表的顺序表和链表。

首先,我们知道,一条记录中可能有多个数据,有整型,有字符型,有字符串,甚至有我们自定义的类型(结构体类型)。我们就假设要模拟一个班级花名册记录的存储,每条记录就都是一条学生的记录信息,那我们就这样定义一个类型:

typedef struct student{

int sno;//学号

char *name;//姓名

char *sex;//性别,’男’,’女’

int age;//年龄

}student;

如果我们要设计一个顺序表,那类型就这样定义就可以。但是链表的记录我们要再插入一个指针用来指向下一条记录。所以应该这样定义:

typedef struct student{

int sno;//学号

char *name;//姓名

char *sex;//性别,’男’,’女’

int age;//年龄

struct student *next;//指向下一条记录的指针

}student;

这样,我们定义了一种类型,通过操作符’.’或’->’就可以访问成员变量。

以下针对顺序表:

记录的类型我们准备好了,接下来准备分配存储空间。

我们分配存储空间用malloc函数。

首先,我们声明一个指向student类型的指针。因为malloc函数会返回给一个指向第一条记录空间的地址。

student *head;//声明指针

head = (student*)malloc(sizeof(student) * 100);

//申请一个存储一百条学生类型的存储空间

^(student*),这是强制转换类型,因为指针是指向student类型的指针,malloc返回一个无类型的地址,用强制转换类型(student*)把地址转换为指向student类的指针类。

malloc()里的sizeof()是另一个函数,会返回括号里的类型的一个数据所占的字节大小。比如sizeof(int) = 4。

这样,head[0]就表示第一条记录。

head[1]是第二条记录,head[2]也是第三条记录,以此类推。

printf(“%d”,head[0]->sno);

这样,我们就访问了第一个学生的学号。

按照如上步骤编写代码:

#include <stdio.h>

#include  <stdlib.h>//malloc函数包含在stdlib.h的头文件中

typedef struct student{

int sno;//学号

char *name;//姓名

char *sex;//性别,’男’,’女’

int age;//年龄

}student;

int main()

{

student *p;

p = (student*)malloc(sizeof(student) * 100);

p[0].sno = 0001;//p[0]表示这个空间内的第一块元素,元素的数据类型是student,一个student类型的变量有成员变量sno,是学号。

p[0].name = "小明";//成员变量name,名字

p[0].sex = "男";//成员变量sex,性别

p[0].age = 10;//成员变量age,年龄

printf("学号:%d,姓名:%s,性别:%s,年龄:%d",p[0].sno,p[0].name,p[0].sex,p[0].age);

}

可以直接复制运行。

在我们实际应用时,我们或许想知道表内一共有多少个元素,或者设一个指针指向最后一条记录,我们可以选择包装一个表类型,包类型里有成员变量student *s,我们只需要声明一个表类型的变量,再通过这样的操作来赋予存储空间:

表类型变量->s = (student*)malloc(sizeof(student)*100)。

同样,我们把p[0].sno = 1此类对表内元素的成员进行赋值的操作,包装在一个函数内,尽可能使得主函数里的代码简洁易懂。

这是修改过的代码:

#include <stdio.h>

#include  <stdlib.h>//malloc函数包含在stdlib.h的头文件中

typedef struct student{

    int sno;//学号

    char *name;//姓名

    char *sex;//性别,’男’,’女’

    int age;//年龄

}student;

typedef struct slist{//取名slist的原因是,顺序的英文是sequence,表是list,简写为slist,或者Slist

    student *list;//实际表,用来赋给空间

    int len;//每增加一条记录,len加一,初值为0,记录表内的实际记录个数

}slist;

void init(slist *l);//函数的声明,写在主函数前,结构体定义的下面,init,初始化的英文

void add(int sno,char *name,char *sex,int age,slist *l);//把赋值操作包装为add函数。

int main()

{

    slist *l;

    l = (slist*)malloc(sizeof(slist));//声明一个表类型变量,并且赋给存储空间。

    //这时实际的表:l->list只是是一个指针,还没有被赋给空间

    init(l);//给实际的表赋给空间的代码包装成init()函数。

    add(1,"小明","男",10,l);//参数一共有五个,分别是:学号,姓名,性别,年龄,和slist类型的变量l,l->list是实际表

    add(2,"小红","女",10,l);

    printf("学号:%d,姓名:%s,性别:%s,年龄:%d",l->list[0].sno,l->list[0].name,l->list[0].sex,l->list[0].age);

}

void init(slist *l)

{

    l->list = (student*)malloc(sizeof(student) * 100);//赋给实际的表以存储空间,一共是能装下100个student变量的空间

    l.len = 0;//此时表内无元素,长度为0;

}

void add(int Sno,char *Name,char *Sex,int Age,slist *l)

{

    /* slist的成员len的值正好可以用来选择表内位置 */

    l->list[l->len].sno = Sno;

    //l是表类型,l->list[l->len],[]操作符优先级大,先看[]内的操作:l->len,len是l变量的一个成员,list[l->len]意思是list表里第l->len个位置。

    //l->list[l->len]->sno的意思就是,表里第l->len个位置里的成员sno。“= Sno”里的sno是函数声明里的变量,它的值是传过来的参数的值。

    l->list[l->len].name = Name;

    l->list[l->len].sex = Sex;

    l->list[l->len].age = Age;

    l->len++;//添加了一条记录,长度加一。

}

->和.是同一种操作,涉及到指针时必须用->,不涉及到指针时必须用“.”,比如l->list[0]就涉及到了slist*类型的指针l,list[0].sno没有涉及指针,list[0]是student类型的变量,sno是它的整型变量成员。同样,l->len中l是指针,所以不可写成l.len。

到此为止,我们完成了对顺序表的初始化和增加的操作。

对表的操作一共有增删查改四种操作,“增”是插入,上面的代码的add函数只是在上一条记录下面插入,同样我们也可以选择在第i个位置插入信息。实现这样一个完整的功能,才是一个完整的“增”的操作。

以上代码,就是顺序表的雏形。现在我们来给它增加几个函数,多写几个函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值