常见索引概念 索引按照物理实现方式,索引可以分为 2 种:聚簇(聚集)和非聚簇(非聚集)索引。我们也把非聚集 索引称为二级索引或者辅助索引。 1. 聚簇索引 特点: 1. 也就 是根据c2列的值查询一条完整的用户记录需要使用到 2 棵B+树! 问题:为什么我们还需要一次 回表 操作呢?直接把完整的用户记录放到叶子节点不OK吗? 3. 在记录的c2列相同的情况下,采用c3列进行排序 注意一点,以c2和c3列的大小为排序规则建立的B+树称为 联合索引 ,本质上也是一个二级索引。 为c2和c3列分别建立索引会分别以c2和c3列的大小为排序规则建立2棵B+树。 3.4 InnoDB的B+树索引的注意事项 1. 根页面位置万年不动 2. 内节点中目录项记录的唯一性 3. 一个页面最少存储2条记录 MyISAM中的索引方案 B树索引适用存储引擎如表所示: 即使多个存储引擎支持同一种类型的索引,但是他们的实现原理也是不同的。
那么具体是如何实现的呢? ,发现与二叉树前序遍历规则非常像,我们可以参照二叉树前序遍历(如有疑问请参考:【数据结构和算法】— 二叉树(3)–二叉树链式结构的实现(1))规则即可快速写出来,后序只需分析如何按照基准值来对区间中数据进行划分的方式即可 1.4 快排非递归版 根据递归版快排的特性,相当于二叉树的前序遍历,那么我们便可利用栈后进先出的特性,来模拟递归并实现排序,栈的实现还请参考:【数据结构和算法】— 栈。 *= 2; } free(tmp); } 在非递归的归并排序中,有这么两个问题值得思考: 对比栈实现快排的非递归,归并排序为什么不可以使用栈? 两种排序的非递归写法,本质上与二叉树遍历极其相似。区别在于快速排序的非递归相当于二叉树的前序遍历,可以利用栈后进先出的特性来实现;而归并排序相当于二叉树的后序遍历,只能用循环来模拟实现。
前言 本文将介绍链表常见的功能的实现 头文件 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #include ,直接将头结点指向空即可; 2.当头结点不为空时,意味着这是一个好的链表,需要遍历到该链表的末结点,将末结点的指针指向新结点完成尾插。 2.当链表为空或是只有一个结点时,直接释放即可,当然,为什么头结点为空时也能进行释放?不会构成对空指针的引用吗? 2.当有多个结点时,我们需要保留头结点的下一个指针,在删除头结点后,将头结点指向刚刚保留的指针即可。 1.如果pos在头结点上的话,直接头插 2.否则,用一个前驱指针保留pos位置的前驱指针,再做插入操作。
队列 队列的特性是先进先出。每次数据出去只能的队列的头部,每次数据进来只能加在队列的尾部。 队列实现一般有两种方式,线性队列,链表队列。 链表队列 链表队列的实现可以参考单向链表。 先建立一个普通的单向链表,然后设置三个属性。队列头,用来标识当前队列头的地址;队列尾,用来标识队列尾的地址;队列长,记录当前的队列长,理论上不给队列设置长度可以无限扩展。 每次删除数据,就把队列头的标识移到下一个node的地址。每次增加数据则就把队列尾的指针指向的node加上下一个node的地址,同时把队列尾的标识移过去即可。 线性队列 超简单的,基于数组实现,每次删除数据则把数组第一个删除,把后续的往前面移动,最后一个直接置空;添加数据只需要在最后继续添加即可;数组会有定长,删除和添加数据一定要检验。
1.栈的概念及结构 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。 2.栈的实现 栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。 ; } 2.4数据进栈 数据进栈的话首先要考虑一下是否需要扩容,所以先判断一下top是否等于capacity,如果满了的话再判断一下capacity是否是第一次扩容,如果是的话则扩容至4,不是的话则扩2倍 4:ps->capacity * 2; STDataType* tmp = (STDataType*) realloc(ps->a, sizeof(STDataType) * newCapacity 4:ps->capacity * 2; STDataType* tmp = (STDataType*) realloc(ps->a, sizeof(STDataType) * newCapacity
前言 在上一篇关于树和二叉树的博客中,最后提到了堆。有小根堆和大根堆。 左边的结构是我们想象出来的,右边才是实际存储的结构。 这次来实现堆。 2. 堆的实现 用数组来实现,这里以实现小堆为例子,它的特点是父节点小于子节点。 先定义一个堆的结构体:为了方便扩容,加了size。 2.2.2 插入代码实现 先判断空间是否足够,不够就扩容,够就直接插入x,再将php->size++。 这里直接写成while循环,交换之后向上走,将child 的位置给parent,然后parent = (child - 1) / 2,一直向上走,当child=0时结束或者parent >= 0时结束。 2.3.2 删除代码实现 首尾交换删除,然后将php->size--,最后向下调整。
访问数据集中的字典可以获取任意类别的变量。然而,xarray正是利用了索引和计算之间的差异。坐标中表示的是常数/固定/独立的量,而数据中表示的是变化/测量/依赖的量。 注: 因为数据集使用的是投影坐标,因此 latitude 和 longitude 表示2D数组,而 reference_time 表示做出预测时的参考时间,不是应用预测的有效时间 time。 * np.random.randn(2, 2, 3) >> precip = 10 * np.random.rand(2, 2, 3) >> lon = [[-99.83, -99.32], [-99.79 >> ds2 <xarray.Dataset> Dimensions: (time: 3, x: 2, y: 2) Coordinates: lat ( 使用 assign 和 assign_coords 可以改变类字典,而且会返回具有额外变量的新数据集: >> ds.assign(temperature2 = 2 * ds.temperature) <
接上一篇内容,我们继续完善堆的相关操作实现。 ('c', 5), Element('h', 1)]` 从结果上看,update的实现逻辑基本上是正确的。 从parent函数的实现可以看到,当一个元素下标为i时,其父节点对应下标为 i/2,由此可以判断,高度每增加1,元素的个数相对于原来高度元素的个数就得少一半。 于是高度为h的节点,对应个数最多为 n/(2^h)个,由于每个这样的元素在执行push_down时对应时间为o(h),因此高度为h的元素全都执行完push_down,对应时间就是 o( n/(2^h)* ,相对于前面做法的两千万次运算,其在效率上的改进是相当明显,特别是随着n值得增大,同时k值远远小于n值时,效率的提升就是本质性的,感兴趣的读者可以自己尝试实现一下。
链表的概念 概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。 这里我来解释一下什么叫做逻辑上连续而物理上补连续。 在进行函数声明之前我还要在详细的解释一下这个结构体和链表的关系,我们来画个图来看看。 我先假设1,2,3的地址就是这些,这么看来他们在内存中肯定不是连续的,要这么把他们关联起来呢? next就起到了重要的作用,我们把2的地址存在1的next中,把3的地址存在2的next中,最后把3的next指向NULL就可以了,再画一个图就是这样的。 我还用了一个头节点来指向第一个节点,从图上来看是不是每个节点都联系了起来,这就是有了逻辑上的连续。 下面我来讲解实现链表都要哪些函数。 头插函数 想要实现头部插入,我们先画图。 要想吧新的节点插入头部,我们就要把,新节点的nest指向第一个节点,然后把pphead指向新的节点。
之前我们学习过数据结构中的栈和队列,详情可点击这里数据结构——lesson5栈和队列详解进行查看,队列是一种先进先出的结构,但是我们之前讲的队列都是类似于线性的物理结构,这次我们所介绍的队列则是一直类似于环状的循环结构 1.循环队列的介绍 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。 2.循环队列实现思路分析 首先根据题目要求,队列长度为k,所以一开始我们要使用malloc开辟k个节点的空间,而不是和之前的队列一样在增加数据时再开辟节点,循环队列的长度是固定的,最开始就已经开辟好了 ;当然这里土土会将两种方法都写下来,并和大家一起分析两种方法的优劣之处,以便大家选择合适和喜欢的形式(对于顺序表链表有疑问的可以在土土的数据结构专栏里——数据结构学习笔记 进行查看复习哦~) 3.用单链表实现循环队列 ,对应数组实现循环队列则需要front,rear不断进行取模运算以防越界; 但是链表实现需要手动将开辟的节点链接在一起,数组则不一样它一开辟就是地址连续的一段空间; 其他的实现链表和数组都差不多;
《Redis内部数据结构详解》是我准备写的一个系列,也是我个人对于之前研究Redis的一个阶段性总结,着重讲解Redis在内存中的数据结构实现(暂不涉及持久化的话题)。 Redis本质上是一个数据结构服务器(data structures server),以高效的方式实现了多种现成的数据结构,研究它的数据结构和基于其上的算法,对于我们自己提升局部算法的编程水平有很重要的参考意义 本文的重点在于讨论第二个层面,Redis数据结构的内部实现,以及这两个层面的数据结构之间的关系:Redis如何通过组合第二个层面的各种基础数据结构来实现第一个层面的更高层的数据结构。 有时候,这两个目标是矛盾的。 单线程(single-threaded)。Redis的性能瓶颈不在于CPU资源,而在于内存访问和网络IO。而采用单线程的设计带来的好处是,极大简化了数据结构和算法的实现。 dict的数据结构定义 为了实现增量式重哈希(incremental rehashing),dict的数据结构里包含两个哈希表。在重哈希期间,数据从第一个哈希表向第二个哈希表迁移。
之前编写了自己的数组,下来基于之前的基础之上实现了栈的基本内容
namespace DataStructure
{
class Program
{
///
这时我们就可以使用另一种数据结构--链表。 和顺序表一样,链表也是一种线性表。和顺序表不同的是,虽然链表在逻辑上是连续的,链表在物理上并不是连续的。 链表中的节点通过指针域相互连接,形成一个动态的数据结构,可以方便地进行插入、删除等操作,而不需要像数组一样需要连续的内存空间。 单链表的实现 模块划分 和实现顺序表一样,分为三个文件来实现,SList..c用来实现顺序表的各种方法,SList.h用来包含实现方法所需的头文件和所需方法的初始化。 test.c用来测试写的方法是否有问题。 节点 实现链表的节点需要创建两个变量,数据域用来储存数据,指针域用来存放下一个节点的地址。我们用结构体来实现。 SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode)); node2->data = 2; SLTNode* node3 = (SLTNode*)
线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就是说是连续的一条线。但是在物理结构上并不一定是连续的,比如链表。 线性表在物理上存储时,通常以数组和链式结构的形式存储。 2.顺序表 2.1 概念及结构 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。 * a; int sz;//有效数据个数 int capacity;//存储空间大小 }SL; 3.模拟实现 静态顺序表只适合于确定知道要存储多少数据的场景下。 下面是模拟实现: 3.1 准备工作 定义一个结构体 typedef int Datatype;//为了适用不同类型的顺序表 typedef struct SeqList { Datatype* a; 同时还要删除该顺序表中的数据也又两种情况: 1.顺序表中的数据已经删完了,无法再删。 2.顺序表中的数据足够删除。
上一次我们实现了顺序表的应用,但是对于顺序表还是会有以下几个问题: 1. 中间/头部的插入删除,时间复杂度为O(N)。 2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 3. 单链表的概念 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。单链表中的数据元素由一个数据域和一个指针域组成,其中指针域指向下一个数据元素。 同顺序表一样我们先创建一个SList.h的头文件和一个SList.c的源文件,.h文件实现函数的声明,.c文件实现函数的定义。 现在我们来实现尾插,如下图: 尾插很简单,我们只需要注意,循环遍历时结束条件是尾节点的next指针为空,而不是尾节点为空。 测试运行一下: 2)指定位置之后插入数据 指定位置之后插入非常简单,代码同样满足尾插的情况,所以不需要再额外讨论尾插的情况。
上一次我们实现了单链表,也就是不带头单向不循环链表,这次我们首先双链表(带头双向循环链表)。 对于单链表来说,还是存在一些缺点的,例如:查找效率低(需要从头开始遍历链表),不能随机访问。 在List.h文件中,首先创建一个结构体用来存放节点的信息,这里和单链表的区别是我们额外增加了一个结构体指针成员prev用来指向上一个节点的指针。 接下来我们来实现双链表的增删查改操作。 所以我们要养成良好的编程习惯。 我们创建一个test.c文件调试一下: 2.链表的尾插 双链表尾插实现就很简单了,不同于单链表还要先找到尾节点,这里phead->prev指向的就是尾节点。 为了方便测试我们先将打印方法实现。 3.链表的打印 打印就需要循环遍历一遍,注意我们的条件现在不再是pcur->next != NULL,而是!= phead。 next; free(pcur); pcur = next; } //此时就剩下了哨兵位 free(phead); phead = NULL; pcur = NULL; } 到这里我们的双链表实现增删查改操作就完成了
堆的介绍 如果有一个关键码的集合K= {k0,k1,k2,…,kn-1},把它的所有元素按照完全二叉树的顺序储存方式储存在一个一维数组中,并满足:Ki<=K2i+1且ki<=K2i+2(Ki>=K2i+ 堆的实现 介绍的话就到此为止,下面我们来进行堆的实现。无非就是那几样。 函数的声明 我们先把初始化,销毁,插入,删除等等要实现的函数声明一下: void HpInit(HP* php); void Hppush(HP* php, HpDataType x); void Hpdestroy 我们发现下面我有写了有个函数,向上调整的函数adjustup,没错上面我们讲的谁小于谁交换就要通过这个函数来实现。 else { break; } } } 里面还有一个swap函数,就是交换函数,它的实现在下面。
栈满足的特性是先进后出,就像货车装货物,把货物一次放进去,但是卸货的时候,你得先把最外面的卸载了,才能继续卸载里层的货物。 栈的实现有两种形式,一种是数组,一种是链表。 ? 对于一个栈,需要至少三个属性 top:记录当前栈的顶部,超过栈的长度和长度小于0都应报错。 push:往栈里面存东西,当超过栈的长度需要警报,当然,链表栈理论上是可以无限存东西的。 pop:把栈顶部的数据移除。 链表栈 链表栈实际上可以看成链表的一种约束,约束了链表的长度,链表的插入和删除位置。 对于链表栈,需要两个变量 top:记录当前栈顶的地址和位置。 链表:记录当前的数据和下一个,上一个的链表块的地址。top永远指向了链表栈的最后一个元素,记录其位置。链表栈和链表结构本质相同。 数组栈(顺序栈) 对数组进行约束,成为栈。 关于实现一个顺序栈 #include <stdio.h> #include <iostream> #include <stdlib.h> using namespace std; class Stack
目录 前言 堆的概念和结构 堆的实现 接口展示 堆结构创建 堆的初始化 堆的销毁 入堆 数据向上调整 入堆测试 出堆 向下调整数据 出堆测试 堆顶数据获取 堆数据个数 判断空堆 堆数据打印 堆源码 ---- 前言 ---- 本章主要讲解: 数据结构中的堆的知识以及实现 堆的概念和结构 ---- 概念: 将所有元素按完全二叉树的顺序存储方式存储在一个一维数组中并以一定的数据要求存储 堆总是一棵完全二叉树 注:在上节基础知识讲解中我们知道父节点和左右子节点的编码关系,以此可以对应到数组中的下标关系,这里我们主要用数组来表示和实现堆 图示:数组堆 堆的实现 ---- 注:这里我们主要实现大堆,对于小堆的实现与大堆只有数据调整上的出入 接口展示 //堆初始化 void HeapInit(HP* hp); //堆销毁 void HeapDestroy(HP* hp) 删除后数据依旧要保持大堆的特性 对于空堆无法出堆 出堆方式: 首先我们将堆顶数据也就是下标为0的数据与堆尾数据交换 交换后让记录存入数据的变量减减,实现删除堆顶数据 再让现在堆顶的数据向下调整 参考代码
上一节我们看到treap结构能对两组数据进行索引,其中一组数据能实现完全排序,另一组数据能实现部分排序,对后者而言就是,我们能快速获取其最大值或最小值。 : 如上图所示,执行到这一步之后所有节点都满足两个条件,他们根据字符串进行了二叉树排序,然后对应的数值都能满足小堆排序,由此插入操作对应代码实现如下: def insert(self, key 以上实现的treap结构和操作有一个问题,那就是容易产生左右子树不平衡,后面我们再看如何处理这个问题。 return None key = self.root.key self.remove(key) return key 其他接口的实现相对简单,就是update share=2&shareId=7600199)