个人对数据结构的理解和总结
在很多编程人员的潜意识里总是觉得数据结构知识似乎没什么用,因为工作中似乎从来都没有涉及到数据结构的什么内容。我对这样的认识只能报以呵呵~ 也难怪,其实有这些想法的同行在工作中的大部分都是如此走过来的:掌握几种常用Web框架,比如SSH,然后不停的堆砌已有的API做一些对数据库的增删改查之类的简单代码设计,最后反正功能是实现了,是否设计无误,效率又优,就几乎没有人去管了。也是,这样的工作也基本涉及不到有用到数据结构知识去解决问题的地方。其实,有这样想法人算不上真正的软件开发者,或者层次还不深,因为数据结构是软件开发中最基础最重要的理论基础。
1. 为什么数据结构很重要
首先为什么要开发各种各样的软件,目的只有一个:就是利用计算机来为人们处理各种数据并以一定的形式展现出来供用户使用。随着计算机的应用范围的不断扩大,计算机所需要处理的数据越来越复杂,并且规模越来越大,计算机所处理的数据已不再是单纯的数值数据,而更多的是非数值数据。
另一方面,现实中需要处理的数据并不是杂乱无章的,它们一定有内在的各种联系,但这需要算法设计人员去总结、归纳、建模、然后抽象出一个具体的模型来表示—,我们将这个模型成为数据的逻辑结构。然后聪明的设计师再围绕这个创建的逻辑结构总结设计出一套处理方法,这样,数据有了,模型有了,算法有了,在理论上问题就可以解决了。剩下的就应该交给计算机去做了,但以上都是基于逻辑上的设计,计算机才不懂这些。所以接下来还需要对应的存储结构来将要处理的数据先存储到计算机中,再将那些处理逻辑(算法)用相应的代码实现,计算机才能对数据进行有效的处理。
2. 什么是数据结构
数据结构:即人们抽象出来的描述现实世界实体的数学模型(非数值计算)及其上的操作(运算),在计算机上的表示和实现。
数据结构就是指按一定的逻辑结构组成的一批数据,使用某种存储结构将这批数据存储于计算机中,并在这些数据上定义了一个运算集合。
3. 数据类型
数据类型:定义该类型涵盖的数据的性质、取值范围以及对数据所能进行的各种操作。程序中的每一个数据都属于一种数据类型,决定了数据的类型也就决定了数据的性质以及对数据进行的各种运算和操作,同时数据也受到类型的保护,确保对数据不能进行非法操作。
高级程序设计语言通常预定义一些基本数据类型和构造数据类型。基本数据类型的值是单个的、不可分解的,它可以直接参与该类型所允许的运算。构造数据类型是使用已有的基本数据类型和已定义的构造数据类型按照一定的语法规则组织起来的较复杂的数据类型。构造数据类型的值由若干元素组合而成,这些元素按某种结构组织在一起。Java语言的基本数据类型有整数类型、浮点数类型、字符类型、布尔类型,构造数据类型(称为引用类型)有数组、类和接口。
抽象数据类型(Abstract Data Type ,ADT):是指一个数学模型以及定义在该模型上的一组操作。抽象数据类型和数据类型本质上是一个概念,它们都表现数据的抽象特征。数据抽象是指“定义和实现相分离”,即将一个类型上的数据及操作的逻辑含义与具体的实现分离。程序设计语言提供的数据类型是抽象的,仅描述数据的特征和对数据操作的语法规则,并没有说明这些数据类型是如何实现的。程序设计语言实现了它预定义数据类型的各种操作,编程人员按照语言提供的规则使用数据类型,只考虑对数据执行什么操作(做什么),而不必考虑怎样实现这些操作(怎样做)。对于使用数据类型的用户来说,数据类型实现了信息的隐蔽,即将一切用户不必了解的细节都封装在类型中。例如Java语言的类型-整数类型即是一个抽象数据类型,编程人员在使用整型时,并不需要知道其是如何实现的。
另一方面,抽象数据类型的范畴更广,它不再局限于程序语言已定义并实现的数据类型(也可称这类数据类型为固有数据类型),还包括编程人员在设计软件系统时自己定义的数据类型。
如前所述,抽象数据类型的定义是由一个值域和定义在该值域上的一组操作组成,算法设计时,编程人员通常需要为一些抽象出来的逻辑结构来自定义一个抽象数据类型,比如:为线性表,栈,队列,串,广义表,二叉树,树,图等自定义抽象类型。一种抽象数据类型描述一种数据结构的逻辑特性和操作,与该数据结构在计算机内存储及实现无关。
抽象数据类型的三要素:数据对象,数据关系,基本操作。
在实际应用中,编程人员在使用自定义的抽象数据类型之前,必须实现这些抽象数据类型(在实现之前,还应该为属于数据类型的数据元素定义结点),才能使用它们。而实现抽象数据类型依赖于数据存储结构。例如,线性表可分别采用顺序存储结构或链式存储结构来实现。
在Java语言设计中,通常使用接口描述抽象数据类型,使用类实现借接口,即实现抽象数据类型中描述的操作。
4. 数据的逻辑结构
数据的逻辑结构是指数据元素之间的逻辑关系,用一个数据元素的集合(包括n>=0个数据元素)和定义在此集合上的若干关系来表示,常被简称为数据结构。
数据的逻辑结构是独立于计算机的,它与数据在计算机中的存储无关。数据的逻辑结构指数据元素之间的逻辑关系,分四种:集合,线性结构、树形结构、图状结构。再具体一些:线性表,栈,队列,串,广义表,树,图等都是对现实实体的抽象出来的逻辑结构。
数据元素:是数据的基本单位,在计算机程序中通常做为一个整体进行考虑和处理。数据元素可以是一个不可分割的原子项,也可以由多个数据项组成。数据项是数据元素中有独立含义的、不可分割的最小标示单位。例如,一个整数、一个字符都是原子项;一个学生的数据元素由学号、姓名、性别和出生日期等多个数据项组成。在计算机存储时,我们可以用一个由若干位组合起来形成的一个位串标示一个数据元素(如用一个字长的位串表示一个整数,用8位二进制数表示一个字符等),通常呈这个位串为元素(element)或结点(node)。当数据元素由若干数据项组成时,位串中对应与各个数据项的子位串称为数据域。在编程语言中,比如Java,一个数据元素通常由一个类来描述。我们知道在C语言和C++语言中是用指针来实现链表结构的,由于Java语言不提供指针,所以有人认为在Java语言中不能实现链表,其实不然,Java语言比C和C++更容易实现链表结构。Java语言中的对象引用实际上是一个指针(本文中的指针均为概念上的意义,而非语言提供的数据类型),所以我们可以编写这样的类来实现链表中的结点。
- class Node
- {
- Object data;
- Node next;//指向下一个结点
- }
将数据域定义成Object类是因为Object类是广义超类,任何类对象都可以给其赋值,增加了代码的通用性。为了使链表可以被访问还需要定义一个表头,表头必须包含指向第一个结点的指针和指向当前结点的指针。为了便于在链表尾部增加结点,还可以增加一指向链表尾部的指针,另外还可以用一个域来表示链表的大小,当调用者想得到链表的大小时,不必遍历整个链表。
5. 数据的存储结构
但要对数据进行处理,就必须将数据存储在计算机中。如果将数据在计算机中无规律地存储,那么在处理时是非常糟的,是没有用的。试想一下,如果一本英汉字典中的单词是随意编排的,这本字典谁会用!
数据结构在计算机中的表示(又称映像)成为数据的物理结构,又称存储结构。它包括数据元素在计算机中的表示和关系的表示。
5.1 数据的基本存储结构主要有4种:
(1) 顺序存储:将逻辑上相邻的结点存储在一组连续的内存单元中,使得逻辑相邻的结点一定是物理位置相邻,元素在内存中的存储次序与它们的逻辑次序相同。顺序存储通常用于存储具有线性结构的数据,在高级程序设计语言中通常使用数组来实现。
(2) 链式存储:即使用若干地址分散的存储单元存储数据元素,逻辑上相邻的数据元素在物理位置上不一定相邻,数据元素之间的关系需要采用附加信息特别指定。通常,采用指针变量记载前驱或后继元素的存储地址,由数据域和地址域组成的一个结点表示一个数据元素,通过地址域把相互直接关联的结点链接起来,结点间的链接关系提仔数据元素间的逻辑关系。
(3) 索引存储:在线性结构中,设开始结点的索引号为1,其它结点的索引号等于其前继结点的索引号加1,则每一个结点都有唯一的索引号,索引号就是根据结点的索引号确定该结点的存储地址。
(4) 散列(哈希)存储:散列存储的思想是构造一个从集合K到存储区域M的一个函数h,该函数的定义域为K,值域为M,K中的每个结点ki在计算机中的存储地址由h(ki)确定。
其中,顺序存储结构和链式存储结构是两种最基本、最常用的存储结构。除此之外,将顺序存储结构和链式存储结构进行组合,还可以构造出一些更加复杂的存储结构。
5.2 一些常见的数据结构(即逻辑结构)对应的存储结构
线性表的存储:线性表可以采用顺序存储结构和链式存储结构。采用顺序存储结构的链表称为顺序表,顺序表通常使用采用数组存储其数据元素,将线性表的数据元素顺序存放在数组中,数据元素在数组中的物理顺序与线性表中的元素的顺序关系完全相同。采用链式存储的线性表成为链表。
栈的存储:顺序存储和链式存储两种,分别成为顺序栈和链栈;
队列的存储:队列一般采用链式存储,其中循环队列可采用顺序存储;
数组的存储结构:顺序存储,其中二维数组包括以行序为主序存储和以以列序为主序存储;
数组是顺序存储的随机存储结构,占用一组连续的存储单元,通过下标识别元素,元素地址是下标的线性函数。在程序设计语言中,数组已被实现为一种构造数据类型,程序语言中的整数类型,字符类型等都是基本数据类型,通过一个变量表示一个数据,这种变量称为简单变量。在实际应用中,经常需要处理具有相同性质的一批数据。例如要处理100个学生的考试成绩,如果要使用简单变量,将需要100个不同的变量,极为不便,为此,比如在java中,可以使用数组来存储着一批具有相同性质的数据。数组一旦占用一片存储空间,这片存储空间的地址和长度就是确定的,不能更改。因此,数组只能进行赋值、取值两种随机存取操作,不能进行插入、删除操作。
字符串的存储结构:
1. 定长顺序存储:类似于线性表的顺序存储结构,用一组地址连续的存储单元存储串值的字符序列(比如使用一维数组存储)。
2. 堆分配存储:仍以一组地址连续的存储单元存储串值的字符序列,但它们的存储空间是在程序执行过程中动态分配而得。
3. 串的块链存储:可用链表的方式存储串值。
掌握:数组,字符数组,字符串数组,ArrayList(动态数组)、LinkedList类、String类 、StringBuffer类 、StringBuilder类的区别和使用方法。
矩阵的存储结构:高级语言编程时,通常都是用二位数组来存储矩阵元。矩阵存储时,我们感兴趣的不是矩阵本身,而是如何存储矩阵的元,从而使矩阵的各种运算能够有效的进行。矩阵式很多科学与工程计算问题中经常研究的数学对象。
广义表的存储结构:通常采用链式存储结构。
树的常用存储结构有:双亲表示法存储、孩子链表存储、孩子兄弟表示法(又称二叉链表表示法)存储等,这几种存储方法都属于链式存储的不同方式。
二叉树的常用存储结构:适用于完全二叉树的顺序存储结构、二叉链表存储、三叉链表存储法两种链式存储结构。
图的常用存储结构有:邻接矩阵表示法存储(数组表示法)、邻接表法、十字链表法、邻接多重表;其中邻接表法和十字链表法、邻接多重表都是图的不同链式存储方法。
5.3数据的操作(运算集合)
数据操作指对一种数据结构中的数据元素进行各种运算或处理。每种数据结构都有一组数据操作。对于一批数据,数据的运算是定义在数据的逻辑结构之上的,而运算的具体实现就依赖于数据的存储结构。
数据的运算集合要视情况而定,一般而言,数据的运算包括插入、删除、更新、检索、遍历、排序等。
初始化、判断是否为空状态、统计数据元素个数
插入:在一个结构中增加一个新的结点。
删除:在一个结构删除一个结点。
更新:更新一个结构中制定位置的结点。
检索:在一个结构中查找满足条件的结点。
输出:将一个结构中所有结点的值打印、输出。
排序:将一个结构中所有结点按某种顺序重新排列。
遍历:按某种次序访问所有元素,每个元素只能并且只能被访问一次,称为遍历操作。