目录
十一、简单数据结构
11.1 记录(结构体)
结构体的一些其他说明在如下的文档中:
11.2 数组
11.3 链表
11.4 数组与链表比较
最后附上线性表ADT的数组和链表C语言实现:
本章讨论两种最简单的数据结构,数组和链表。数据结构是一组具有特殊关系的数据的集合,它可以作为一个整体被处理。
记录是一组相关元素的集合,这些元素可以是相同类型或是不同类型,整个记录有一个名称。记录中的每个元素称为记录的域,这些域是记录中真正存储数据的内存空间的名称。这些域与变量类似,可以被赋值,也可以被选择和操作。但与变量不同的是,域是记录的一部分,而不是独立的变量,我们通常希望记录作为一个整体去存储某些信息。
记录中的元素一般是与同一个对象关联的,因为我们需要记录来存储简单数据类型无法存储的信息,例如,我们要记录一个学生的姓名、学号等信息,简单数据类型是无法实现的,所以我们需要一个名称为学生的记录,里面有字符型、整型等数据类型用于保存这个学生的一些信息。但是记录中的元素之间也可以没有关系,这是语法允许的,但这样的记录没有意义。
记录有两种标识符:记录名和记录中每个域的名字。记录名是整个结构的名字,即我们想要保存信息主体的名字,记录中的域的名字即是记录中的数据的内存空间的名字。如图,记录的名字为student,记录中域的名字为student.id、student.name等,约定用(.)来分隔记录名和域的名字。
结构体、枚举、联合体的定义及一些说明-CSDN博客
假设我们需要处理100个整数,如果没有数组,那么我们需要定义100个名字不同的整型变量,再对这100个数分别进行相同的处理,如果我们只需要输入和输出两种操作,那么也需要200行的代码来处理这100个数。但如果使用数组,那么只需要一个简单的循环结构就能处理这个问题。
数组是一组类型相同的元素的顺序集合,数组中的元素可以是简单数据类型或复杂数据类型(如记录等)。 数组可以看成一种复杂数据类型也可以看作一种数据结构,而记录是一种数据类型(注意:是复杂数据类型而不是抽象数据类型),在将数组看成复杂数据类型时,数组和记录是相似的,只是其中的元素有区别。数组有两种标识符:数组名和元素名。数组名是整个结构的名字,但在C语言中数组名不止表示整个结构的名字,实际上它还是数组首元素的地址。元素名是数组中元素存储的内存单元的名字,通过元素名我们可以很容易的找到这个元素,并对它进行操作。例如,一个数组名是array,那么array[0]则表示该数组中第一个元素的名字,中括号内的数字称为索引(下标),通过索引可以找到数组中的某个元素。在大部分语言中,索引都从0开始。数组在内存中是顺序存储的,它使用内存中连续的内存单元来存储元素,并且数组的大小需要在定义时指定而且不能在程序中改变,但现在有些语言允许大小可变的数组。
多维数组:前面我们介绍的数组的元素是顺序存储的,但有时候一维数组不能满足需求,我们可以使用array[5][5]这样的形式在C语言中定义一个5行5列的二维数组。二维数组就像生活中的表,它既有行也有列。我们可以使用二维数组来存储学生的成绩,列的每一项表示科目,行表示不同的学生。但内存并没有像二维数组这样的结构,所以二维数组在内存中还是线性存储的,有两种存储方法:行主序存储和列主序存储。行主序存储指在内存中按行来存储数组,列主序存储指按列来存储数组。多于二位的数组也是可以的,但是基本不会使用。

数组操作: 查找、插入、删除、检索、遍历,例如,对于一个有10个元素,大小为11的数组进行操作:
查找:数组的查找使用顺序查找,当数组有序时,可以使用折半查找。
插入:
在开始或中间插入:需要将插入位置之后的所有元素都向后移动一位(包括插入位置的元素),这是一个很低效的动作,例如,如果要插入到索引为7的地方,那么7-9的元素都需要向后移动一位。
在尾部插入:如果数组中的元素没有满,直接在尾部插入;如果数组大小为10,如果数组大小可变,则数组大小增加1,并完成插入。
删除:也可以分为在尾部删除或其他地方删除,实际上在尾部删除(插入)可以看成后面元素数量为零(即移动零个元素)的删除(插入)。如果要删除6索引处的元素,那么7-9的元素全部需要向前移动一位。
检索:查找是找到对应元素的位置,而检索是得到某个位置的值。
遍历:就是将数组中的元素从头到尾过一遍并对它们进行相应的操作。
通过对操作的定义,可以很清楚的得知:在数组中进行插入和删除并不是高效的,因为有许多其他的元素需要移动;但检索是高效的,因为数组是一个随机存取的结构,所以我们可以直接拿到对应位置的值(通过索引)。
字符串:字符春是字符的集合,在C语言中,一个字符串是由字符组成的数组,并且数组的末尾元素(最后一个有效字符后面的元素)为0.但在C++中,字符串可以是数组,也可以是名叫string的数据类型。在Java中,字符串是string数据类型。
链表同数组一样,是一组元素的集合,但链表只能由记录构成,而数组中的元素可以是整型、字符型或是记录。在链表中,每个记录都需要一个额外的域,这个域用来存放下一个记录的地址,用于将多个记录连接成一个线性结构。
链表中的每个记录(元素)称为节点,最开始的那个节点称为头节点。每个节点的名称是隐含的,由前一个节点中的地址域可以找到这个节点,例如,如果指向当前节点的指针变量是p,那么这个节点的名字就可以看成是(*P),而节点中元素的名称与记录中元素的名称一致。
指针变量是存放地址的变量,在声明指针变量时对其进行初始化是一个好习惯。当指针变量未初始化或是越界访问等情况下,这个指针称为野指针,它指向的是不可用的内存空间;我们通常将没有分配地址的指针变量初始化为NULL(空指针),空指针的值为0,但它不代表0地址,它表示不指向任何地址或对象。
在链表中,我们通常将链表的最后一个节点的指针域设置为空指针,这样不仅可以避免错误,还能标记链表的结尾(当节点的指针域为NULL时,这个节点为尾节点)。有两种定义链表的方式:使用一个指针域指向链表中第一个元素的额外头节点;链表的第一个元素作为头节点。
链表操作:查找、插入、删除、检索、遍历。
查找:链表只能使用顺序查找,如果数据量相同的数组不是有序的,那么查找所花费的时间其实差不多,但链表花费的空间多些。有序链表也不能使用二分法。当链表是有序的,查找就可以在特定位置停止,例如,链表升序,当前位置的元素如果大于要查找元素,并且该元素还没找到,那么就可以直接返回找不到该元素的信息(而不是遍历链表在尾部停止)。
插入:如果不使用额外头节点,那么在插入和删除链表第一个元素时,我们需要改变指向链表第一个元素的变量的值,使它指向新的头节点。如果添加一个额外的节点,这个节点的指针域永远指向链表的第一个元素,这个时候不论从哪个位置插入或删除需要的操作都是相同的了。在插入时,记录要插入位置的前一个节点的地址,接下来只需要将要插入节点的指针域指向前一个节点的指针域指向的位置,让前一个节点的指针域指向新节点即可。
删除:与插入相似,如果使用额外的头节点,只需要将要删除节点前一个位置的指针域指向要删除节点后一个节点即可,然后将删除节点的空间释放掉。
检索:如果检索是返回第几个元素的值,那么链表需要按顺序找到该位置的元素。
遍历:按顺序访问链表的每个元素并执行相应操作。
1. 数组在声明时需要指定大小(C语言是这样),而链表可以动态开辟空间(大小可变);
2. 数组占用内存中连续的空间,而链表可以使用不连续的空间;
3. 链表的插入和删除操作时间复杂度要小于数组;
4. 数组的检索操作时间复杂度要小于链表;
5. 对于相同的数据量,链表占用的空间比数组大。
表ADT(抽象数据类型)_thdwx的博客-CSDN博客