数据结构是相互之间存在一种或多种特定关系的数据元素的集合(数据元素可以由多个数据项构成,这种数据元素一般叫记录,但数据元素必须属于同一数据对象)。也可以说:数据结构指同一类数据元素中,各元素之间的相互关系。数据结构由工业界诞生,具体的数据结构由具体的业务场景决定,人们从商业应用中某一类/某个数据对象的数据表现出来的逻辑关系来定义数据结构。
根据数据元素之间关系的不同特性,通常有下列四种基本机构:1:集合,结构中的元素除了同属一个数据对象外没有其他关系。2:线性结构(线性表),结构中的元素同属一个数据对象,一个对一个首尾相连,有唯一的“第一个”数据元素和“最后一个”数据元素,相邻元素存在顺序不可打乱的关系。3:树形结构(二叉树),结构中的元素同属一个数据对象,结构中的元素存在一个对多个的关系。4:图状结构/网状结构,结构中的元素同属一个数据对象,结构中的元素存在多个对多个的关系。数据结构描述的关系并不是真正的逻辑关系,因为数据元素之间在数据内容上没有具体逻辑可言,这种关系只是特定业务场景下对应的数据元素集合内数据元素间的存在关系。
数据结构的形式定义为:数据结构是一个二元组
Data_Structure=(D,S);
D是数据元素的有限集,S是数据元素间关系的有限集。
数据类型和数据结构的概念密切相关,数据类型是一个值的集合和定义在这个值集上的一组操作的总称(包含了“集合”这种数据结构和一组操作)。
数据类型的三元组表示形式为:
(D,S,P)
D是数据元素的有限集,S是数据元素间关系的有限集,P是对数据元素的操作集。
数据结构在计算机中的表示/映像称为数据元素的物理结构,又称存储结构,包括数据元素的表示和存在关系的表示。
数据的存在关系结构和物理结构是密切相关的,任何一个算法的设计取决于存在关系结构,算法的实现依赖于采取的存储结构。
数据元素之间的存在关系在计算机中有两种不同的表示方法:顺序映像和非顺序映像,并由此得到两种不同的存储结构:顺序存储结构和链式存储结构。(四种数据结构(存在关系)可以用两种物理结构的任意一种来表示)
顺序映像的特点是:借助元素在存储器中的相对位置来表示数据元素之间的存在关系。比如用两个字长的位串来表示一个实数,则可以用地址相邻的四个字长的位串表示一个复数。
非顺序映像的特点是:借助指示存储元素地址的指针来表示数据元素之间的存在关系。见下图(b)
线性表/线性结构
线性结构(线性表),结构中的元素同属一个数据对象,一个对一个首尾相连,有唯一的“第一个”数据元素和“最后一个”数据元素,相邻元素存在顺序不可打乱的关系。第一个数据元素的地址常称为起始位置或基地址。含有大量记录(含有多个数据项的数据元素)的线性表又称为文件。
顺序存储结构和链式存储结构表示线性表/线性结构:
顺序存储结构
插入或删除元素成本大:顺序存储结构中,在某个位置上插入或删除一个数据元素时,时间主要消耗在移动元素上,移动元素的个数取决于插入或删除元素的位置。
可随机存(set赋值)取(get读值):顺序存储结构中,每个元素的存储位置和物理结构表示的线性表的起始位置相差一个和数据元素在线性表中位置成正比的常数,只要确定了起始位置便可存取任一元素。
(高级程序语言中常常用数组来描述顺序存储结构。)
链式存储结构
用一组任意的存储单元存储线性表的数据元素,因此为了表示每个数据元素和其直接后继元素的逻辑关系,元素a的存储映像除了保存元素的数据信息外还需存储一个指示其直接后继元素位置的信息,元素的这个存储映像也称为结点,包括数据域和指针域。
C语言中的指针和java中的对象引用是一个意思。
每个结点中只包含一个指针域,这样的链式存储结构也叫线性链表或单链表。最后一个元素没有后继元素,因此线性链表的最后一个结点的指针为null。
整个链表的存取(set、get)必须从头指针开始进行。
整个链表的插入或删除仅需要修改最多两个指针不需要移动元素。
任何一种数据结构:起始位置是系统确定的,插入的数据一定可以是有序的,虽然set没有实现。
关于双向链表和循环链表:
循环链表即收尾相连的链表,最后一个元素的指针部分指出第一个元素的存储位置。
双向链表则是指元素的指针部分有两块,一块指向前趋元素,一块指向后继元素。双向链表也可以是循环的(肯定是可以的,工业界各种结构可见,而数据结构课程是对工业界数据结构的总结)。
线性表/线性结构之数组
数组是受限的线性结构
数组一旦被定义其维数和维界不再改变,因此除了结构的初始化和销毁外,数组只有存取元素和修改元素值的操作,不能插入和删除。因此,采用顺序存储结构表示数组是自然的事了(也可以用链式结构)。在java语言中数组类型数据的确是不可变长的,但用数组实现的arrayLis和Vector均可以在指定位置删除和插入元素,本质是建立一个新的数组然后进行复制。
线性表/线性结构之栈和队列
栈和队列也是受限的线性表结构,栈和队列一样可以用链式结构和顺序结构来表示。顺序栈(顺序存储结构的栈)设立一个top指针来指示栈顶元素,出栈时top指针减1,入栈则加1.
队列和栈类似,指示需要在队头和队尾设立两个指针。
树和二叉树
树是n个节点的有限集,任何非空树中有且仅有一个根节点。n>1时,其他节点可分为若干个互不相交的有限集,每个集合本身是一个树,称为根的子树。
树的节点包含一个数据元素和若干指向其子树的分支,节点拥有的子树的数量称为子树的度。度为0的节点称为叶子或终端节点。节点A的子树的根节点B称为节点A的孩子,反过来节点A是节点B的双亲。同一双亲的孩子称为兄弟。
树中节点的最大层次为数的深度(第一层为根节点,最后一层为叶子)。
如果将树种各子树看成是有序的则为有序树,否则为无序树,有序树中左边第一个子树为第一个孩子,右边第一个子树为最后一个孩子。任意一个节点至多只有两个分支并且子树有左右之分的树称为二叉树(二叉树中不存在度大于2的节点),(任何二叉树都是有序树)。
一个深度为k且有(2的k次方)-1个节点的二叉树为满二叉树。
将一个满二叉树编号,从上至下,从左到右。如果一个普通二叉树上的节点跟满二叉树上对应位置的节点的编号一样,则该二叉树为完全二叉树。
二叉树的存储结构:
顺序存储结构只适用于完全二叉树(非完全二叉树的空节点随机分布且编号不确定)
链式存储结构:由二叉树的定义知一个结点至少包括数据,左右指针,有时还包括指向双亲的指针,根据这两种结构得到二叉树的物理存储结构为二叉链表和三叉链表。二叉链表查找某节点只能从根节点出发。
二叉树的遍历和线索二叉树:
在实际应用中对二叉树遍历是少不了的操作,为了方便对二叉树进行遍历,必须在第一次复杂的遍历的动态过程中对二叉树进行线索化(第一次遍历按照树上所有根节点统一被遍历的先后顺序分为先序遍历,中序遍历和后序遍历),将线性遍历链路上的每个节点可找到其前趋和后继元素,以便以后实现简单的线性遍历。这个过程叫二叉树的线索化,线索化后的二叉树叫线索二叉树。当然线索二叉树上有左右子树的节点是没有后继/前趋元素的,线索二叉树在遍历到这类节点的时候按照先序/中序/后序原则找到其后继元素继续进行遍历。
二叉树和二叉排序树的区别:二叉树是只区分左右子树的有序树,而二叉排序树则分清了左右子树的值和节点的值相比较的大小。
查找
查找和存取的区别?
根据相对于起始元素的相对位置进行的搜寻叫存取,根据内容(关键字)进行的搜寻叫查找。顺序存储结构的存取可以根据相对于起始元素的相对位置算出目标元素的地址进行直接存取,链式存储结构的存取只能从特殊位置出发一个一个找(单链表只能从头,双向链表只能头/尾,双向循环链表可以任何一个元素,线索二叉树如果用二叉链表只能从根节点,三叉链表是根节点或最后一个遍历的节点)。java语言没有提供对非顺序存储结构的集合中单个元素的存取(只能add添加一个元素和遍历取得所有元素)。
实际应用中有一种大量使用的集合性质的集合元素间没有存在关系(除了同属一个数据对象),完全松散的数据结构——"查找表"。
对查找表常进行的操作有:1,查询某数据元素是否在查找表中2,检索某数据元素的各种属性3,在查找表中插入元素4,在查找表中删除元素。只对查找表进行前两种操作时称该查找表为静态查找表,对查找表的操作包括插入和删除元素时称该查找表为动态查找表。
关键字(key):关键字是数据元素(或记录)中某个数据项的值,它可以标识一个数据元素或记录,若关键字可以唯一得标识一个记录则该关键字为主关键字,否则为次关键字。关键字的概念不仅用于集合,在四种逻辑结构中也用于线性结构(如线性表)和树形结构。关键字只是数据的一部分,仅仅和数据有关系,和数据元素的位置没有任何关系。给出关键字找到对应数据只能通过让所有数据元素的关键字和给定的关键字依次比较来查找,查找的快慢取决于比较的次数。。。
查找:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素或记录的过程即查找。
静态查找表查找方法一般有顺序表查找(实现顺序查找意味着必须将查找表进行线性化遍历,用线性结构实现或以二叉链表或三叉链表来实现再进行线索化,然后实现顺序查找,但这样更麻烦。说白了顺序表查找就是一个一个找。)和有序表的折半查找。折半查找只适用于顺序存储结构(存储位置需要折半),并且元素也是按大小顺序排列的才行。静态查找表中查找效率几乎最高构建成本又不高的结构是次优二叉查找树。次优二叉查找树和后面所说的二叉排序树唯一的区别是静态的,不能删除或添加元素,根据固定的元素一次性生成。并且次优二叉查找树取的根节点不是按数据大小的中间值取的,而是取最接近给定值的元素为根节点。
从静态查找表的查找方法可以看出最适合实现查找的物理存储结构是树结构,根据范围边界来排查查找无疑提高了查找效率。还有一种更高效率的查找方式是哈希表(但哈希表的构建成本应该高,索引都不用哈希表去实现可想而知。。。)
二叉排序树:
对于任何一个结点,左子树的值均小于根节点的值,右子树的值均大于根节点的值。并且二叉排序树是动态生成的,一个元素一个元素查找并添加且不添加重复元素。
二叉排序树的查找:
先比较根节点的值,再依次从左子树/右子树进行查找。
二叉排序树的插入或删除:
插入:当树中不存在关键字等于给定值的节点时再进行插入。
删除P节点时:用P节点的左子树替代P,并将P节点的右子树变成P节点左子树的最右节点的右子树。
平衡二叉树:
任何一个节点的左子树深度减去右子树的深度的绝对值小于等于1.平衡二叉树不一定是二叉排序树,讨论是否是平衡二叉树仅关注深度。
B-树和B+树:
B-树几乎和二叉排序树一模一样,仅仅是分叉多了而已。查找过程也一模一样。
B+树是B-树的变形,差异仅在于:1.每个节点上的关键字和其子树数量相等(B-树中关键字为子树个数-1)。2.叶子节点包含全部关键字的信息并依关键字大小从小到大顺序链接(比B-树多了一种查找方式)3.结点中的关键字是子树中的最大/最小关键字。
哈希表
在线性表和树中,记录在结构中的相对位置是随机的,和记录的关键字之间不存在确定的关系,因此在结构中查找记录时需进行一系列和关键字的比较,查找的效率取决于查找过程中所进行的比较次数。理想的情况是希望不进行任何比较,一次得到所查记录,那就必须在记录的存储位置和它的关键字之间建立一个确定的对应关系,是每个关键字和结构中一个唯一的存储位置相对应。我们称这个对应关系f为哈希函数,哈希函数是关键字集合到地址集合的映像。按这个思想建立的表为哈希表。
哈希表只能表明查找方式,不能说明底层物理存储结构是以什么实现的。
由此可知:hashset和treeset的本质区别是查找数据元素时的效率:前者直接根据哈希code定位到数据元素的地址,后者是根据节点关键字确定范围排除查找关键字。
插入数据方面的区别似乎没有:因为hashset也需要遍历集合来保证其中没有相同的hashcode。treeset暂不详,只知道排序二叉树树和hashset的插入是一样的,只是遍历的不是hashcode而是关键字而已。
算法
算法是对特定问题的求解步骤的描述,是指令的有限序列。由控制结构(顺序,分支,循环)和原子操作构成。通常以原操作重复执行的次数为算法的时间度量。比如:
{++x;s=0}
算法复杂度为O(1)
for(i=0;i<n;i++){++x;s+=x;}
算法复杂度为O(n)
排序类算法关键:区分出嵌套循环,独立循环和交叉循环(这一步映像做不到,算法基础再好都扯淡)。交叉循环的自增通常放在交叉结果后。
冒泡排序:不断比较取最大/小的值,便能把最大/小的值排到末尾。
构造哈希函数:对于关键字中的任何一个关键字,经哈希函数映像到地址集合中任何一个地址的概率是相等的,则成此类哈希函数是均匀的哈希函数。也就是让关键字经过哈希函数得到一个“随机的地址”,以便使一组关键字的哈希地址均匀分布在整个地址区间中,从而减少冲突。
1.直接定址法:一次线性关系
2.随机数法:用一个随机函数,取关键字的随机函数值为地址。
哈希冲突处理方法:
1:开放定址法:
2:再哈希法:产生哈希冲突时计算另一个哈希函数地址,直到冲突不再发生。
3:链地址法:
4:公共溢出区:建立一个公共溢出区,将产生冲突的哈希地址填入溢出表。
算法的复杂度:
内部排序
按排序工作量的分:1.简单排序方法O(n²), 2.先进排序方法O(nlogn) 3.基数排序时间复杂度为O(d▪n)