数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及他们之间的关系的操作等的学科。
根据元素之间的关系,数据结构有4种:1集合、2线性结构(头节点无前驱,尾节点无后继)、3树形结构(每个节点只能有一个前驱)、4图形或网状结构。
根据数据元素在计算机中的表示方式,数据结构有4种:1顺序存储结构、2链式存储结构、3索引存储、4散列(哈希)存储。第1种是顺序映像,后3种是非顺序映像。顺序映像是借助元素在存储器中的相对位置来表示。非顺序映像是借助元素存储地址的指针表示数据元素之间的逻辑关系。
*)线性表
**)介绍
L=(a1,…,ai-1,ai,ai+1,…,an),a1线性起点,an线性终点,中间的元素有一个前驱,一个后继。可以对线性表的元素进行“插入”(在L的某个位置i之前插入元素,同时L的长度+1)、“删除”(删除第i个元素,同时L的长度-1)、“访问”(返回L中第i个元素的值)。
LinkedList,ArrayList就是这种数据结构。LinkedList和ArrayList的区别:相同之处:都继承了List;不同之处:①LinkedList是链表形式存储的,链表适合增删操作,通过修改指针很快就能实现增删;ArrayList是数组形式存储的,增删操作会影响整个数组元素的存储位置,还可能要重新分配更大的空间,因此数组形式不适合增删操作②链表的长度可以扩展,只要有空间,指针指向新的元素即可;数组的长度是预先分配的,在ArrayList的长度的基础上预留一些备用空间③LinkedList的链表形式,访问(查找)元素时,需要根据从头根据指针一个一个的查;ArrayList因为数组有顺序,直接可以访问到某个元素。④ArrayList在平时更常用,因为创建好之后取用方便。⑤空间耗费:LinkedList链表存储指针花费空间,ArrayList分配时要预留一部分空间。 综上:当查找比较多时,用ArrayList;当查找访问不多,但增删操作多时,用LinkedList更好。
**)线性表的顺序表示和实现
用一组地址连续的存储单元依次存储线性表的数据元素。
l(L的小写)是线性表中每个元素需占用的存储单元。那么线性表中第i+1个数据元素的存储位置LOC(ai+1)和第i个数据元素的存储位置LOC(ai)之间的关系:LOC(ai+1)=LOC(ai)+l。以a1为基准,第i个数据元素ai的存储位置:LOC(ai)=LOC(a1)+l。
顺序存储的优缺点:优:①逻辑相邻,物理也相邻②可随机存取任一元素③存储空间使用紧凑。缺:①插入、删除操作需要移动大量元素②需事先分配一定大小的连续存储空间。
**)链式表示和实现
链式表示:是用一组任意的存储单元存线性表的数据元素(这一组单元可以是连续的,也可以是不连续的)。一个存储结点,包括数据域和指针域。
单链表:一个结点里只有一个指针域。指针域指向下一个结点的首地址。单链表通常在首结点前增加一个“头结点”,头结点的数据域可以存列表长度等附加信息,也可以不存任何信息;指针域存储首结点的地址。
多链表:一个结点有>1个指针域。比如一个结点有2个指针的,第一个指针指向前驱结点,第二个指针指向后继结点。这样的链表查找前驱的各个结点更方便。
循环单链表:单链表的最后一个结点的指针,指向首节点的地址。这个链表的指针指向从头到尾,尾再连到头,形成一个循环。
循环多链表:在“循环单链表”的基础上,每个结点都是>1个指针的。比如一个结点有2个指针,一个指向前驱,一个指向后继。链表的首尾相连。
free(p)方法释放存储空间;
单链表常见算法:1清空一个链表(循环链表中的每个结点p,头结点的指针指向p→next;free(p);循环到最后,释放掉了所有的元素,头结点指针指向了最后一个结点的指针“空”);2链表中插入元素(插入元素的指针域=前驱结点的指针域,前驱结点的指针指向自己)、3删除数据元素(删除的元素的前驱结点指针=自己的指针,free(删除元素)。);4两个有序单链表L1和L2合并成一个有序单链表L3(L1的指针p1,L2的指针p2,L3的指针p3。p1,p2指向首结点,每次循环比较后向后移动一个。p3指向L3首节点,每次循环的时候,把p1,p2的节点拼接到L3上,然后p3指向L3上最后的那个节点。每次循环的时候,比较p1,p2节点值,小的先放到L3上,大的再放上。当L1循环结束后,把L2的所有节点挂到L3上,反之如果L2先循环完,也是类似操作);综上,这些算法不难理解,都是改变指针指向,然后将不用的结点释放空间。
链式表示的优缺点:优:1是一种动态结构,整个存储空间为多个链表共用;2不需要预先分配空间;3插入、删除操作方便。缺:指针占用额外的存储空间。
链表的一维数组表示:叫做“静态链表”。结构:数组的每个元素存一个数据元素;数组的一个元素存储“数据域”+“下一个数据的数组下标”;数组下标从0开始。
*)串(字符串)
串的存储:
1.顺序定长存储:按照预设长度分配连续的空间存储。缺点:字符串的长度是预先定的,可能不够用。
2.在堆中存储:顺序不定长存储,根据程序运行动态分配串的长度,串有多长就分配多长。分配连续的一片空间。堆用free()和malloc()管理空间。优:字符串长度根据实际长度分配。
3.链式存储:一个串以链表形式存,链表里的一个结点可以存1~N个单个字符。缺点:链表形式还要存指针,浪费空间。优:①当一个结点只存单个字符时,增删操作很方便②字符串长度够用。
**)java中的字符串:String a1="abc";这样隐式创建字符串时,存储到“字符串池”中。String a2=new String("def");创建字符串时,就是上面说的,存储到堆中,同时栈里有一个指针指向这个堆。
*)栈和队列
**)栈:后进先出(Last In First Out,LIFO)结构;top栈顶指针,base栈底指针。
应用实例:1数值转换:十进制数字N和其他d进制的转换:N除以d,取余,余数倒过来写。应用:用栈存储余数;2.递归:直接调用自己或者间接调用自己的函数,叫递归函数;实例:Hanoi塔(3个柱子x,y,z,把x柱子上的盘子借助y柱子移到z上,每次只能移动一个圆盘,任何时刻都不能将一个较大的圆盘压在较小的圆盘上。)
**)队列:先进后出(First In First Out,FIFO)结构;
链表表示的队列简称链队列,链队列不会满,因为链表可以在其他的空间链接添加。队列里的每个元素,像链表里的结点,有数据域和指针域。在队列前边加一个“头结点”,链队列有头指针,尾指针。链列表的操作即为单链表的插入和删除的特殊情况,只需要修改尾指针或头指针,因为队列的插入是在队尾,队列的删除是在队首,不会在队列中间操作,所以插入和删除操作比较简单。
*)广义表
广义表是一个集合,元素可以是单个数据元素,也可以是一个广义表。广义表的定义可以说一个递归的概念。如:LS=(a1,a2,……,an,B),通常小写字母表示单个元素,大写字母表示集合。
*)树和二叉树
二叉树的每个结点最多只能有2个子节点。二叉树的遍历:应用递归,D(root结点)L(Left)R(Right),它们仨的6种组合排列:DLR,LDR,LRD,DRL,RDL,RLD。
赫夫曼树(最优二叉树):树的叶子结点(没有子结点的结点)上有权重,组成一颗二叉树,到这每个叶子结点的步数*叶子结点权重,相加,得出的值最小的树结构。
二叉树的存储:
1.顺序存储:一组连续的存储空间,自上而下,自左至右的顺序存进去。对完全二叉树(非叶子结点都有2个子节点),每个存储空间都有值。对一般二叉树,和完全二叉树比,空缺的结点,会存上默认值(0或null),这挺浪费空间的。
2.链式存储:链表形式存它。①每个结点可以有2个指针域,一个存lchild(左孩子),一个存rchild(右孩子),把一个个结点,先连lchild再练rchild,能把整棵树还原出来。②每个结点可以有3个指针域,存lchild,rchild和parent(父结点),能把整棵树还原出来。查找起来会更方便,但是3指针占用的空间也越大。
*)图
图的权重是在路径上,赫夫曼树的权重是在叶子结点上。带权重图的应用例子:各个城市之间交通路线图,连成一个网状图。每个路径上有权重,表示距离数。从一个结点(城市)到一个较远的城市,怎么规划路线,走的距离最短。
*)索引存储
举例:顺序存储的一组数据,分成3部分,第二部分的所有元素值>第一部分的最大值,同理,第三部分的所有元素值>第二部分的最大值(即:分块有序)。专门有一个索引表,存这3部分的最大值,和每部分第一个元素的地址。查找的时候,和索引表里的3个值对比,定位查找的值在哪个部分,然后去这个部分里查找。
**)B-,B+树
B-树是多路树,一个结点可以有多个子结点。表达式:(n,A0,K1,A1,K2,......An-1,Kn)
B-树的特点:1.每个结点最多有m个子结点2.除根以外,每个非终端结点至少有m/2个子结点3.最底一层的叶子结点都在同一层上,不存储值信息,实际上这层结点不存在,是虚拟的4.符合以下规律:K1是A0集合中的最大值(关键字),Kn是An-1集合中的最大值,A2中的所有值>K1,An中的所有值>An-1中的最大值(这个规律和索引存储很像,B-树和B+树是几乎所有关系型数据库都有的索引结构)。根节点的关键字是最小的,越往下关键字越大。
B-树的应用:比如要查找的值是b,从树的根节点开始查找,和关键字对比,确定b在哪个子树上,递归查找,直到查找到或者没有节点可查找(即:没找到)。
B+树:是B-树的变种,B+树的最底层叶子结点存的有元素信息。
*)哈希存储(散列)
直接根据元素值找到存储位置,不需要像其他数据结构一样,要通过比较查找。
需要有一个算地址的函数f,每个关键字通过f计算后,对应一个地址。
算法的特点:在一块存储区域,通过算法,尽量均匀的分布存储区域(均匀可以减少冲突的几率,也可以充分利用这块存储空间)。
算出来的地址可能冲突,多个元素算出的地址一样
通常还要有一个解决冲突的方案。
**)java中的HashSet就是这个存储结构,这也是HashSet为什么无序、不能存重复元素的原理。HashSet存储元素判断是否重复,步骤1.先经过HashCode(),判断元素生成的哈希码是否重复。如果重复,要么是这个元素已经存在于HashSet了,要么是2个不同的元素,恰好生成了相同的哈希码。不管怎样,当哈希码重复了,就执行步骤2。步骤2.执行equals(),HashSet的equals()重写了Object的,判断2个元素的值是否相同。如果相同,就覆盖。如果2个元素不相同,说明只是哈希码恰好相同了,这时候就给第二个元素一个偏移值,让它存储到一个新的位置(这个过程就是HashSet的“解决冲突的方案”)。
*)查找
顺序查找:一个元素一个元素挨着查找。效率比较低
有序链表的查找:折半查找:1先排序;2.值和列表中间值(向下取整的整数值)对比,如果值<中间值,则在前一部分找。3.递归步骤2,直到找到或者最后只剩下一个值为止。