常见数据结构

一、数据结构

1.线性(数组)

按顺序存储在内存中 每一个节点都有下标(查询快)

        数组一般用来存储相同类型的数据,可通过数组名和下标进行数据的访问和更新。数组中元素的存储是按照先后顺序进行的,同时在内存中也是按照这个顺序进行连续存放。数组相邻元素之间的内存地址的间隔一般就是数组数据类型的大小。

 

2.链表

每个节点有两个区域 数据区域和指针区域 i节点得指针区域指向 > i+1节点得数据区域 由于是通过指针进行下一个数据元素的查找和访问,使得链表的自由度更高(增删快

       这表现在对节点进行增加和删除时,只需要对上一节点的指针地址进行修改而无需变动其它的节点。不过事物皆有两极,指针带来高自由度的同时自然会牺牲数据查找的效率和多余空间的使用。一般常见的是有头有尾的单链表对指针域进行反向链接,还可以形成双向链表或者循环链表。

 

2.1双向链表

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针分别指向直接前驱和直接后继

3.跳表

跳表通过增加的多级索引能够实现高效的动态插入和删除,其效率和红黑树和平衡二叉树不相上下。索引级的指针域除了指向下一个索引位置的指针,还有一个down指针指向低一级的链表位置,这样才能实现跳跃查询的目的。

       链表虽然通过增加指针域提升了自由度,但是却导致数据的查询效率恶化。特别是当链表长度很长的时候对数据的查询还得从头依次查询,这样的效率会更低。跳表的产生就是为了解决链表过长的问题,通过增加链表的多级索引来加快原始链表的查询效率。这样可以让查询的时间复杂度从O(n)提升至O(logn),目前redis和levelDB都有用到跳表。

 

4.栈

一种特殊的线性表其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。   入栈 push:栈的插入操作叫做进栈/压栈/入栈,入栈数据在栈顶。                                                出栈 pop:栈的删除操作叫做出栈。出栈数据也在栈顶。

 

5.队列

只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列是一种先进先出FIFO(First In First Out) 的数据结构,入队列:进行插入操作的一端称为队尾(Tail/Rear) 出队列:进行删除操作的一端称为队头 (Head/Front)

       队列是栈的兄弟结构,与栈的后进先出相对应。顾名思义队列的数据存储是如同排队一般,先存入的数据先被压出。常与栈一同配合,可发挥最大的实力。

 

5.1循环队列

环形队列逻辑上成环,物理上还是线性表 通常使用数组实现。

 

5.2双端队列

双端队列又名double ended queue,简称deque,双端队列没有队列和栈这样的限制级,它允许两端进行入队和出队操作,也就是说元素可以从队头出队和入队,也可以从队尾出队和入队。

        在基于NLNode类实现双向链表的时候,通常我们都要在最前端和最后端各设置一个哑元节点(Dummy node)。这两个节点分别称作头节点(Header node)和尾节点(Trailer node)㈠,起哨兵(Sentinel)的作用。也就是说,它们并不存储任何实质的数据对象,头(尾)节点的next(prev)引用指向首(末)节点,而prev(next)引用为空。如此构成的列表如图所示,

6.树

树作为一种树状的数据结构,其数据节点之间的关系也如大树一样,将有限个节点根据不同层次关系进行排列,从而形成数据与数据之间的父子关系。常见的数的表示形式更接近“倒挂的树”,因为它将根朝上,叶朝下。树的数据存储在结点中,每个结点有零个或者多个子结点。没有父结点的结点在最顶端,成为根节点;没有非根结点有且只有一个父节点;每个非根节点又可以分为多个不相交的子树这意味着树是具备层次关系的,父子关系清晰,家庭血缘关系明朗;别看树好像很高级,其实可看作是链表的高配版。树的实现就是对链表的指针域进行了扩充,增加了多个地址指向子结点。同时将“链表”竖起来,从而凸显了结点之间的层次关系,更便于分析和理解。树可以衍生出许多的结构,若将指针域设置为双指针,那么即可形成最常见的二叉树,即每个节点最多含有两个子节点的树称为二叉树。。二叉树根据结点的排列和数量还可进一度划分为完全二叉树、满二叉树、平衡二叉树、红黑树等

         每一棵树都能唯一得转换到他所对应得二叉树(左孩子,右兄弟) 二叉树i层上最多有2得(i-1)次方节点 深度为k最多为2得(k-1)次方节点 满了称为满二叉树

6.1满二叉树

除了最后一层,其它层的结点都有两个子结点

 

6.2完全二叉树

除了最后一层结点,其它层的结点数都达到了最大值;同时最后一层的结点都是按照从左到右依次排布

6.3二叉排序树

二叉排序树是一棵空树,或者:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也分别为二叉排序树。

二叉排序树本身为有序,当插入一个有序程度十分高的序列时,生成的二叉排序树会持续在某个方向的字数上插入数据,导致最终的二叉排序树会退化为链表,从而使得二叉树的查询和插入效率恶化。

6.4平衡二叉树

平衡二叉树又被称为AVL树,它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

        平衡二叉树的产生是为了解决二叉排序树在插入时发生线性排列的现象,但是在构造平衡二叉树时,却需要采用不同的调整方式,使得二叉树在插入数据后保持平衡。主要的四种调整方式有LL(左旋)、RR(右旋)、LR(先左旋再右旋)、RL(先右旋再左旋)。

        在插入一个结点后应该沿搜索路径将路径上的结点平衡因子进行修改,当平衡因子大于1时,就需要进行平衡化处理。从发生不平衡的结点起,沿刚才回溯的路径取直接下两层的结点,如果这三个结点在一条直线上,则采用单旋转进行平衡化,如果这三个结点位于一条折线上,则采用双旋转进行平衡化

左旋操作:将当前结点S的左孩子旋转为当前结点父结点E的右孩子,同时将父结点E旋转为当前结点S的左孩子。

右旋操作:将当前结点S的左孩子E的右孩子旋转为当前结点S的左孩子,同时将当前结点S旋转为左孩子E的右孩子。

 6.5红黑树

高度平衡带来的好处是能够提供更高的搜索效率,其最坏的查找时间复杂度都是O(logN)。但是由于需要维持这份高度平衡,所付出的代价就是当对树种结点进行插入和删除时,需要经过多次旋转实现复衡。这导致AVL的插入和删除效率并不高。红黑树通过将结点进行红黑着色,使得原本高度平衡的树结构被稍微打乱,平衡程度降低。红黑树不追求完全平衡,只要求达到部分平衡。这是一种折中的方案,大大提高了结点删除和插入的效率。

红黑树具有五个特性

  1. 每个结点要么是红的要么是黑的。

  2. 根结点是黑的。

  3. 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。

  4. 如果一个结点是红的,那么它的子节点都是黑的。

  5. 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

7.堆

堆就是将一个集合的数据按照完全二叉树的顺序结构存储在一个一维数组中,堆在逻辑上是一棵完全二叉树,在物理结构上是一个一维数组.对于任意一个父节点的序号n来说(这里n从0算),它的子节点的序号一定是2n+1,2n+2,因此可以直接用数组来表示一个堆。堆还有一个性质:堆中某个节点的值总是不大于或不小于其父节点的值。将根节点最大的堆叫做最大堆大根堆,根节点最小的堆叫做最小堆小根堆

        堆也被称为优先队列。队列中允许的操作是先进先出(FIFO),在队尾插入元素,在队头元素。堆遵循同样的原理,在堆顶取出元素,但是堆中元素的排列,不是按照到来的先后顺序,而是按照一定的优先顺序排列的,排列的优先顺序可以是元素大小或者其他规则。由于堆的根节点是序列中最大或者最小值,因而可以在建堆以及重建堆的过程中,筛选出数据序列中的极值,从而达到排序(堆排序)或者挑选topK值的目的。

查找数组中某个数的父结点和左右孩子结点

例如已知索引为 i 的数,那么
1.父结点索引:( i -1 )/2 计算机省略计算的小数部分
2.左孩子索引:2i +1
3.右孩子索引:2
i +2

8.散列表(哈希表)

散列表也叫哈希表,是一种通过键值对直接访问数据的机构。将一个x值通过一个函数获得对应的一个y值的操作叫做映射。散列表的实现原理正是映射的原理,通过设定的一个关键字和一个映射函数,就可以直接获得访问数据的地址,实现O(1)的数据访问效率。在映射的过程中,事先设定的函数就是一个映射表,也可以称作散列函数或者哈希函数。

哈希函数构造方法

1.直接寻址法:取关键字或关键字的某个线性函数值作为散列地址,即 H(key) = key 或 H(key) = a * key + b,其中 a 和 b 为常数(这种散列函数叫自身函数)。

2.数字分析法:分析一组数据,比如某班学生的出生年月日时发现出生年月日的前几位数字大体相同,这样的话,冲突的几率会很大,但是发现年月日的后几位表示月份和具体日期的数字差别较大,如果用后几位构成散列地址,则冲突的几率会明显降低。因此数字分析法是找出数字的规律,尽可能利用这些数字构造冲突几率低的散列地址。

3.平方取中法:先通过求关键字的平方值扩大相近的差别,然后根据表长度取中间的几位数作为散列函数值。又因为一个乘积的中间几位数和乘数的每一位都相关,所以由此产生的散列地址较为均匀。

4.除余法:该方法是最为简单常用的一种方法。它是以表长 m 来除关键字,取其余数作为散列地址,即 H(key) = key % m。该方法的关键是选取 m。选取的 m 应使得散列函数尽可能与关键字的各位相关。m 最好为素数。

5.相乘取整法:该方法包括两个步骤,首先用关键字 key 乘上某个常数 A(0 < A < 1),并抽取出 key.A 的小数部分;然后用 m 乘以该小数后取整

6.开放地址法(也叫开放寻址法):实际上就是当需要存储值时,对Key哈希之后,发现这个地址已经有值了,这时该怎么办?不能放在这个地址,不然之前的映射会被覆盖。这时对计算出来的地址进行一个探测再哈希,比如往后移动一个地址,如果没人占用,就用这个地址。如果超过最大长度,则可以对总长度取余。这里移动的地址是产生冲突时的增列序量。

7.再哈希法:在产生冲突之后,使用关键字的其他部分继续计算地址,如果还是有冲突,则继续使用其他部分再计算地址。这种方式的缺点是时间增加了。

8.链地址法:链地址法其实就是对Key通过哈希之后落在同一个地址上的值,做一个链表。其实在很多高级语言的实现当中,也是使用这种方式处理冲突的。

9.公共溢出区:这种方式是建立一个公共溢出区,当地址存在冲突时,把新的地址放在公共溢出区里。

目前常用的冲突解决方法是链地址法,可以通过数组和链表的结合达到冲突数据缓存的目的。 

        左侧数组的每个成员包括一个指针指向一个链表的头。每发生一个冲突的数据,就将该数据作为链表的节点链接到链表尾部。这样一来,就可以保证冲突的数据能够区分并顺利访问。考虑到链表过长造成的问题,还可以使用红黑树替换链表进行冲突数据的处理操作,来提高散列表的查询稳定性。

9.图

图(Graph)是由顶点和连接顶点的边构成的离散结构,是一个顶点有穷得非空集合。图是最灵活的数据结构之一,很多问题都可以使用图模型进行建模求解。例如:生态环境中不同物种的相互竞争、人与人之间的社交与关系网络、化学上用图区分结构不同但分子式相同的同分异构体、分析计算机网络的拓扑结构确定两台计算机是否可以通信、找到两个城市之间的最短路径等等。图结构一般包括顶点和边,顶点通常用圆圈来表示,边就是这些圆圈之间的连线。边还可以根据顶点之间的关系设置不同的权重,默认权重相同皆为1。此外根据边的方向性,还可将图分为有向图和无向图。

无向图:有点集 V = { 1 , 2 , 3 , 4 , 5 , 6 } 边集E = { (1,2), (1,5), (2,3), (2,5), (3,4) , (4,5), (4,6) } 。在无向图中,边( u , v )和边( v , u ) 是一样的,因此只要记录一个就行了简而言之,对称

有向图:加上了方向性,顶点(u, v)之间的关系和顶点(v, u)之间的关系不同,后者或许不存在。例如,地图应用中必须存储单行道的信息,避免给出错误的方向

二、对比关系

链表和数组对比

红黑树VS平衡二叉树

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值