关于数据结构和算法:Programming-Abstractions-in-C阅读笔记p312p326

《Programming Abstractions in C》学习第77天,p312-p326,总计15页,第7章完结。 一、技术总结第7章次要讲算法剖析——引入工夫复杂度这一概念来评估算法的快慢。工夫复杂度应用大O符号来示意。 第7章以排序算法为示例,蕴含:抉择排序,归并排序以及疾速排序,这些根本的排序算法都是咱们要把握的,尽管工作中较少用到,但面试时遇到的概率还是很大的,最好能手写进去。书中解说这些算法时,很具体,值得一看。 二、英语总结1.go about sth/ go about doing sth是什么意思?答:to begin to do sth。p312, If you were skeptical about this simplication, how would go about proving that the simplication formula is indeed correct? 2.geometric是什么意思?答: (1)geometry: gē (earth, land) + -metry(a measuring of),geometry最早的意思是“measuring of earth or land(土地的测量)”,随着一直的倒退,前面示意“the area(畛域) of mathematics relating to the study of space and the relationship between points, lines, curves, and suffaces”,即数学畛域的“几何”。 (2)geometric: adj. a geometric pattern is made of shapes。p312, One possibility is to represent the original extended sum in a geometric form。 ...

March 3, 2024 · 1 min · jiezi

关于数据结构和算法:如何在单台8G内存机器上实现1TB日志排序

当初有一批日志数据,大小为1TB,数据是某一天的生产的日志数据,当初只有一台单CPU及8G内存的机器,请应用算法对此日志数据按工夫的先后顺序进行排序。 解决方案1:桶排序它的原理是利用分区字段的特点,将数据别离写入到多个桶文件中,再针对每个桶文件应用内存排序算法(如疾速排序),将文件内容进行排序输入到有序桶文件中。最初将这些文件按分桶的程序组合起来,造成最终有序的文件。 划分桶,在问题中,咱们须要排序的是日志中的工夫,而工夫是很容易划分出桶的,而工夫应用哪个维度来划分桶是咱们所面临的问题?如果抉择分钟可失去1440个桶,每个桶均匀就是0.7G左右,但像日志中数据可能有数据集中的问题,尤其是像1TB这么大的日志,顶峰时间段可能是均匀的10倍,分桶可能十分的不均匀;这样倡议再升高维度应用秒,一天可最多失去86400个分桶。均匀每个文件只有12M左右,顶峰也不过才120M,所以抉择秒是比拟适合的分桶。再将数据按秒划分到各个桶即可。再将各个桶内的数据进行排序。最初按桶的先后顺序,合并到一个文件中,就失去了1TB日志的排序后果。桶排序的特点: 桶排序的工夫复杂度为O(n),如果有N个数据,划分出M个桶。每个桶内元素(T=N/M),如果桶内排序算法采纳疾速排序,工夫复杂度O(TlogT),M个桶的工夫复杂度就是O(MTlogT),再加上T=N/M,即可失去Nlog(N/M),当M与N靠近时,工夫复杂度为常量级,这时候桶排序工夫复杂度靠近O(N)对排序的数据要求很刻薄,数据必须很容易的划分出M个桶,桶与桶之前有着人造的大小程序。对每个桶内的数据排序实现后,桶与桶之前无需再进行排序。桶内的数据在各个桶内须必较均匀。如果通过桶的划分。桶内的数据十分不均匀,有的十分多,有的非常少,工夫复杂度将不再是O(N),可能进化为(N*logN)。桶排序比拟适宜内部排序。即为对磁盘文件进行排序,因为个别磁盘文件数据量较大,无奈一次全副加载到内存中解决方案2:归并排序它的核心思想:分而治之,具原本说就是将大数据分解成一个一个小数据,将小数据进行排序,而后再将这些小数据合并排序成终最终有序的数据。 将1TB的文件按固定大小进行切分,比方大小256M,读取时可按行读取,限度大小在256M以内,需保障残缺的行,这样能够切出大概4096个文件。将这4096个文件,应用疾速排序算法对这每个文件进行排序操作,可失去4096个排序的文件将这4096个文件进行合并输入,合并的逻辑就是从每个文件中顺次获取数据,排序,输入,就像示例中的那样。就能够失去一个最终有序的文件。

September 21, 2023 · 1 min · jiezi

关于数据结构和算法:二叉搜索树

原理二叉查找树(Binary Search Tree),(又:二叉搜寻树,二叉排序树)它或者是一棵空树,或者是具备下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;它的左、右子树也别离为二叉排序树。二叉搜寻树作为一种经典的数据结构,它既有链表的疾速插入与删除操作的特点,又有数组疾速查找的劣势;所以利用非常宽泛,例如在文件系统和数据库系统个别会采纳这种数据结构进行高效率的排序与检索操作。 二叉搜寻树操作不管哪一种操作,所花的工夫都和树的高度成正比。因而,如果共有n个元素,那么均匀每次操作须要O(logn)的工夫。 查找 查找 val 是否存在,从根递归的开始查找 若以后节点的 value 等于要查找的 val ,则已找到若 val < 小于节点的 value ,则递归的查找左子树,找不到则不存在若 val > 小于节点的 value ,则递归的查找右子树,找不到则不存在插入,和查找的过程相似 先检索是否存在 存在: 放弃插入将雷同元素放入右子树(右边的节点值必须小于父节点值)应用计数器计数,存在时计数器+1不存在: 间接在对应的地位新建节点即可删除 叶子节点:间接删除,不影响原树仅仅有左或右子树的节点:节点删除后,将它的左子树或右子树整个挪动到删除节点的地位就能够,子承父业。既有左又有右子树的节点:找到须要删除的节点p的间接前驱或者间接后继s,用s来替换节点p,而后再删除节点s。上述操作理论演示能够在以下网站操作演示: https://www.cs.usfca.edu/~galles/visualization/BST.htmlhttps://www.comp.nus.edu.sg/~stevenha/ 前驱:BST中小于 val 的最大节点后继:BST中大于 val 的最小节点其中 val 为要找节点的值如上图所示:9的前驱就是5,后继为1010的前驱为9,后继为12

August 19, 2023 · 1 min · jiezi

关于数据结构和算法:数据结构与算法

一、算法1.1、算法根底概念:算法是独⽴存在的⼀种解决问题的⽅法和思维算法的个性: 输出:算法具备0个或多个输⼊输入: 算法⾄少有1个或多个输入有穷性: 算法在无限的步骤之后会⾃动完结⽽不会⽆限循环,并且每⼀个步骤能够在可承受的工夫内实现确定性:算法中的每⼀步都有确定的含意,不会呈现⼆义性可⾏性:算法的每⼀步都是可⾏的,也就是说每⼀步都可能执⾏无限的次数实现1.2、算法效率掂量⼤O记法对于枯燥的整数函数f,如果存在⼀个整数函数g和实常数c>0,使得对于充沛⼤的n总有f(n)<=c*g(n),就说函数g是f的⼀个渐近函数(疏忽常数),记为f(n)=O(g(n))。也就是说,在趋势⽆穷的极限意义下,函数f的增⻓速度受到函数g的束缚,亦即函数f与函数g的特色类似工夫复杂度假如存在函数g,使得算法A解决规模为n的问题示例所⽤工夫为T(n)=O(g(n)),则称O(g(n))为算法A的渐近工夫复杂度,简称工夫复杂度,记为T(n)了解“⼤O记法” 对于算法的工夫性质和空间性质,最重要的是其数量级和趋势,这些是剖析算法效率的次要局部。⽽计量算法基本操作数量的规模函数中那些常量因⼦能够忽略不计。例如,能够认为3n^2 和100n^2 属于同⼀个量级,如果两个算法解决同样规模实例的代价别离为这两个函数,就认为它们的效率“差不多”,都为n^2 级复杂度分类解释阐明剖析哪个复杂度为最优最优工夫复杂度算法实现⼯作起码须要多少基本操作价值不⼤,因为它没有提供什么有⽤信息,其反映的只是最乐观最现实的状况,没有参考价值最坏工夫复杂度算法实现⼯作最多须要多少基本操作提供了⼀种保障,表明算法在此种水平的基本操作中⼀定能实现⼯作均匀工夫复杂度算法实现⼯作均匀须要多少基本操作对算法的⼀个全⾯评估,因而它残缺全⾯的反映了这个算法的性质。但另⼀⽅⾯,这种掂量并没有保障,不是每个计算都能在这个基本操作内实现。⽽且,对于均匀状况的计算,也会因为应⽤算法的实例散布可能并不平均⽽难以计算因而,只需关注算法的最坏状况,亦即最坏工夫复杂度 工夫复杂度的计算规定:1.基本操作,即只有常数项,认为其工夫复杂度为O(1)2.程序构造,工夫复杂度按加法进⾏计算3.循环构造,工夫复杂度按乘法进⾏计算4.分⽀构造,工夫复杂度取最⼤值5.判断⼀个算法的效率时,往往只须要关注操作数量的最⾼次项,其它主要项和常数项能够疏忽6.在没有非凡阐明时,咱们所剖析的算法的工夫复杂度都是指最坏工夫复杂度 空间复杂度S(n) 空间复杂度(SpaceComplexity)是对⼀个算法在运⾏过程中长期占⽤存储空间⼤⼩的量度算法的工夫复杂度和空间复杂度合称为算法的复杂度1.3、常见的工夫复杂度执行次数函数阶非正式术语100O(1)常数阶2n+1O(n)线性阶3n²+2n+1O(n²)平方阶4n³+3n²+2n+1O(n³)立方阶2^nO(2^n)指数阶5log2n + 1O(logn)对数阶3nlog2n + 2n + 1O(nlogn)nlogn阶留神,将log2n(以2为底的对数)简写成logn 常⻅工夫复杂度之间的关系 所耗费的工夫从⼩到⼤O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2^n) < O(n!) <O(n^n) 二、数据结构2.1、数据结构根底数据是⼀个形象的概念,将其进⾏分类后失去程序设计语⾔中的根本类型。如:int,float,char,bool等。数据元素之间不是独⽴的,存在特定的关系,这些关系便是构造(eg:一个人有名字和年龄,名字和年龄都属于这个人,因而数据元素不是独立的)数据结构指数据对象中数据元素之间的关系2.2、算法和数据结构的区别算法数据结构⾼效的程序须要在数据结构的根底上设计和抉择算法数据结构只是动态的形容了数据元素之间的关系算法是为了解决理论问题⽽设计的数据结构是算法须要解决的问题载体综上:程序 = 数据结构 + 算法 2.3、抽象数据类型(ADT)抽象数据类型面向对象中的封装指⼀个数学模型以及定义在此数学模型上的⼀组操作class蕴含属性和办法,属性就是存储的数据,办法就是对数据的操作抽象数据类型就相当于面向对象中的封装,因而相当于class的概念罕用的数据运算:插入、删除、批改、查找、排序 2.4、根本的数据结构2.4.1、程序表2.4.1.1、程序表概念相干将一组元素看成一个序列,用元素在序列里的地位和程序,示意理论利用中的某种有意义的信息,或者示意数据之间的某种关系,这样的一组序列元素的组织模式,形象的称为线性表,一个线性表是某类元素的一个汇合,还记录着元素之间的一种程序关系; 线性表两种存储模式: 程序表,将元素程序地寄存在⼀块间断的存储区⾥,元素间的程序关系由它们的存储程序⾃然示意链表,将元素寄存在通过链接结构起来的⼀系列存储块中2.4.1.2、程序表的模式 图a图b程序表的根本模式,数据元素自身间断存储,每个元素所占的存储单元⼤⼩固定雷同如果元素的⼤⼩不统⼀,则须采⽤图b的元素外置的模式,将理论数据元素另⾏存储,⽽程序表中各单元地位保留对应元素的地址信息(即链接)元素的下标是其逻辑地址,⽽元素存储的物理地址(理论内存地址)能够通过存储区的起始地址Loc (e )加上逻辑地址(第i个元素)与存储单元⼤⼩(c)的乘积计算⽽得,即:Loc(e ) = Loc(e ) + c*图b中的c不再是数据元素的⼤⼩,⽽是存储⼀个链接地址所需的存储量,这个量通常很⼩**拜访指定元素时⽆需从头遍历,通过计算便可取得对应地址,其工夫复杂度为O(1)** 2.4.1.3、程序表的构造与实现构造:程序表的残缺信息蕴含两个局部: 1、表中的元素汇合2、实现正确操作而需记录的信息(无关表的整体状况的信息,这部分信息次要包含存储区的**容量**和以后表中已有的**元素个数**)两种实现形式: 图a图b⼀体式构造,存储表信息的单元与元素存储区以间断的⽅式安顿在⼀块存储区⾥,两局部数据的整体造成⼀个残缺的程序表对象分离式构造,表对象⾥只保留与整个表无关的信息(即容量和元素个数),理论数据元素寄存在另⼀个独⽴的元素存储区⾥,通过链接与根本表对象关联。⼀体式构造整体性强,易于治理。然而因为数据元素存储区域是表对象的⼀局部,程序表创立后,元素存储区就固定了元素存储区替换 ⼀体式构造分离式构造因为程序表信息区与数据区间断存储在⼀起,所以若想更换数据区,则只能整体搬迁,即整个程序表对象(指存储程序表的构造信息的区域)扭转了若想更换数据区,只需将表信息区中的数据区链接地址更新即可,⽽该程序表对象不变元素存储区裁减 采⽤分离式构造的程序表,若将数据区更换为存储空间更⼤的区域,则能够在不扭转表对象的前提下对其数据存储区进⾏了裁减,所有使⽤这个表的地⽅都不用批改,这种技术实现的程序表称为动静程序表,因为其容量能够在使⽤中动态变化裁减的两种策略: 每次裁减减少固定数⽬每次裁减容量加倍每次裁减减少固定数⽬的存储地位,如每次裁减减少10个元素地位,这种策略可称为线性增⻓每次裁减容量加倍,如每次裁减减少⼀倍存储空间特点:节俭空间,然而裁减操作频繁,操作次数多特点:缩小了裁减操作的执⾏次数,但可能会节约空间资源。以空间换工夫,举荐的⽅式2.4.1.4、程序表的操作减少元素:a、尾端加⼊元素,工夫复杂度为O(1)b、保序的元素加⼊,工夫复杂度为O(n) 删除元素:a、删除表尾元素,工夫复杂度为O(1)b、保序的元素删除,工夫复杂度为O(n) 2.4.1.5、Python中的程序表list和tuple两种类型采⽤了程序表的实现技术,tuple是不可变类型,即不变的程序表,因而不⽀持扭转其外部状态的任何操作,⽽其余⽅⾯,则与list的性质相似2.4.2、链表1.定义链表(Linked list)是⼀种常⻅的根底数据结构,是⼀种线性表,然而不像顺序表⼀样间断存储数据,⽽是在每⼀个节点(数据存储单元)⾥寄存下⼀个节点的地位信息(即地址) 2.单向链表单向链表也叫单链表,是链表中最简略的⼀种模式,它的每个节点蕴含两个域,⼀个信息域(元素域)和⼀个链接域。这个链接指向链表中的下⼀个节点,⽽最初⼀个节点的链接域则指向⼀个空值 元素域elem⽤来寄存具体的数据链接域next⽤来寄存下⼀个节点的地位(python中的标识)变量p指向链表的头节点(⾸节点)的地位,从p登程能找到表中的任意节点3.节点的实现与节点的操作实现 class SingleNode(object): """ 单链表的节点实现 """ def __int__(self, item): self.item = item self.next = Noneclass SingleLinkList(object): """ 单链表的操作实现 """ def __init__(self): self.__head = None # 判断链表为空 def is_empty(self): return self.__head is None # 链表长度 def length(self): cur = self.__head count = 0 while cur is not None: count += 1 cur = cur.next return count # 遍历链表 def travel(self): cur = self.__head while cur is not None: print(cur.item) cur = cur.next print("") # 头部增加元素 def add(self, item): node = SingleNode() node.item = item node.next = self.__head self.__head = node # 尾部增加元素 def append(self, item): node = SingleNode() node.item = item if self.is_empty(): self.__head = node else: cur = self.__head while cur.next is not None: cur = cur.next cur.next = node # 指定地位增加元素 def insert(self, pos, item): if pos <= 0: self.add(item) elif pos >= self.length(): self.append(item) else: cur = self.__head count = 0 node = SingleNode() node.item = item while count < (pos - 1): count += 1 cur = cur.next node.next = cur.next cur.next = node # 删除元素 def remove(self, item): cur = self.__head pre = None while cur is not None: if cur.item == item: if cur == self.__head: self.__head = cur.next else: pre.next = cur.next return pre = cur cur = cur.next # 查找元素 def search(self, item): cur = self.__head while cur is not None: if cur.item == item: return True cur = cur.next return False2.4.3、双向链表每个节点有两个链接:⼀个指向前⼀个节点,当此节点为第⼀个节点时,指向空值;⽽另⼀个指向下⼀个节点,当此节点为最初⼀个节点时,指向空值 ...

May 28, 2023 · 4 min · jiezi

关于数据结构和算法:灵活快捷低运维成本的数据集成方法数据联邦架构

在传统的企业数据使用中,企业应用多种零碎,数据散落在各个存储设备中,数据分析需要往往是跨库的,数据入湖入仓在做剖析会有平安问题,或影响业务零碎性能。企业须要一种灵便、快捷、低运维老本的数据集成办法,就有了数据联邦架构。本文介绍数据联邦架构。 — 数据联邦概述—在传统的企业数据使用中,经常会呈现这样的窘境:企业应用多种零碎,数据散落在各个数据存储,数据分析需要往往是跨库的,有两种形式能够解决这类需要。一是先做数据集成(ETL)将数据整合到数据湖或数据仓库后再做剖析,这种个别适宜需要绝对确定、同时存量零碎能够迁徙到新数据平台的业务场景下。而对另外一些企业,因为其平台建设的阶段性特点,可能存在需要在疾速变动、或者存量零碎因为各种起因不能间接迁徙的状况,基于ETL入湖再剖析的计划可能会存在业务响应速度慢、灵便度低和过程简单的弊病,企业须要一种能更灵便、快捷地进行数据集成的办法,这就是数据联邦架构。 数据联邦解决了“数据孤岛”问题,并且防止了传统ETL流程长,开发和运维老本较高的问题,能够利用在对数据采集有灵活性、实时性要求,或者存在异构数据源解决的场景。 虚构的操作型数据库(ODS)通过虚构操作型数据存储(ODS),构建可操作的数据集成视图,数据变动会很快反映到ODS,且联邦的数据源数据源可随具体的剖析需要灵便增减变动,因而能满足一些轻量、短期的数据分析,或者实时灵便的仪表盘利用。建造数据直达区利用数据联邦构建数据直达区,能够对大量从生产零碎进入数仓的数据进行快照合并,极大缩小数据复制对生产零碎的烦扰。数据直达区对数据变动的实时存储,能记录残缺的数据变更信息。数据仓库的扩大企业部署数据仓库后存在问题:一方面,整个企业不太可能只应用繁多数仓;另一方面,企业依然有大量的数据未存入任何数仓,须要构建对立视角。而数据联邦和联邦计算能在无需转换格局和挪动数据的状况下,提供所有企业数仓和零散数据的对立视角,升高了数据挪动转换的老本。异构平台迁徙在异构平台迁徙过程中应用联邦计算,能使迁徙过程更平滑,无需思考数据的迁徙和异构平台语法不兼容等问题,保障利用对数据的应用不受影响,且能在迁徙实现后在不影响新利用的前提下更改数据源配置。异构数据分析企业能够利用数据联邦的能力,实现跨结构化数据、非结构化或者半结构化数据的剖析。只管数据联邦和联邦计算在优化数据采集、平台迁徙等方面有很大的价值,然而并不是万能的:一方面,因为数据的集成视图能够被疾速施行,许多企业疏忽了数据治理,导致联邦过程中产生了不必要的冗余;另一方面,因为数据联邦没有事后针对业务做数据架构设计,因而对于频繁应用的固定状态的数据需要,还是以数据湖、数据仓库等形式提供才有最佳的性能体现。因而,仍然不能齐全摒弃数据治理、ETL等办法,须要将它们独特联合利用,能力解决企业内更宽泛的数据管理问题。数据联邦更多解决的是仍在频繁演进的数据需要或者Adhoc类开发场景等演进中的企业数据开发需要。 — 数据联邦架构简析—从技术架构的视角来看,数据联邦技术通过在现有的各种数据源上减少一个联邦计算引擎的形式,提供对立的数据视图,并且反对开发者通过联邦计算引擎来对立查问和剖析异构数据源里的数据,开发者无需思考数据物理地位、数据结构、操作接口和贮存能力等问题,即可在一个零碎上对同构或者异构数据进行拜访和剖析。数据联邦架构可能为企业的数据管理带来几个要害的架构劣势,次要包含: 虚拟化的数据集成:与传统ETL相比,数据联邦仅进行了虚构的集成,能更快、更低成本地集成大量数据,晋升数据集成速度,能够疾速地摸索一些翻新的数据业务;对一些简单的存量零碎能够在尽量保证系统不动的状况下,提供跨库的数据分析能力,从而爱护企业的现有投资;不便开发者灵便的发现和应用数据:用户不需感知数据源的地位和构造,数据源零碎不须要做改变,数据处理灵便度失去晋升;能够实现对立的数据安全管控:因为通过虚构视图而不是复制的形式集成,配套在数据联邦平台上做对立的平安管控,可能做到数据对立进口的平安管控,缩小因为复制数据引入的数据泄露危险,尤其适宜用于剖析一些不适宜将数据导入数据湖的业务零碎,从而保障静态数据和动态数据的平安;打消不必要的数据挪动:对于一些不太罕用的数据,如果应用数据联邦架构后这部分数据就不须要每天整合进入数据湖,而是在有理论剖析需要的时候才去做数据分析,这样缩小了不必要的数据挪动;麻利的数据服务开明和门户性能:有了数据联邦的性能后,企业能够进一步基于其开发一个数据服务门户,提供开发工具和服务工具,让用户更灵便的应用数据。 数据联邦技术架构图如上所示,其关键点即实现基于对立的SQL查问引擎,能够联邦多个同构或异构的自治数据源,用户能够随便查问在联邦零碎中任意地位的数据,而不用关怀数据的寄存地位、理论数据源零碎的SQL语言品种或存储能力。其上图所示,该架构须要实现了四个方面对立,能力实现以上的外围能力,包含对立的元数据管理、对立的查问加工接口、对立的开发运维工具和对立的平安治理。 * 对立的元数据管理元数据管理模块须要构建各个同构、异构数据源的形象整体视图,提供对立数据源连贯治理和元信息管理,后续业务开发层只须要通过这个集中的元数据管理模块去获取不同数据库下的元数据信息,而无需去治理各个不同数据库的不同连贯细节。 数据源连贯模块通过数据联邦平台,开发者能够构建跨数据库实例的虚构连贯,从而在以后数据库中实现跨库拜访,个别采纳相似DBLink的SQL拓展。该层负责管理接入数据源,既反对传统数据源的连贯,也反对大数据平台的连贯,设计上最好既反对结构性数据,也反对非构造数据接入。DBLink的语法实例如下,后续开发者就能够间接应用dblink上的table名来拜访不同数据源上的数据表,甚至能够在一个SQL中拜访多个表。CREATE DATABASE LINK <link_name> CONNECT TO <jdbc_username> IDENTIFIED BY '<jdbc_password>' USING '<jdbc_URL>' with '<jdbc_driver>';CREATE EXTERNAL TABLE <table_name> (col_dummy string) STORED AS DBLINK WITH DBLINK <link_name> TBLPROPERTIES('dblink.table.name'=<ref_table_name>); 元信息管理模块因为各个数据库的元数据随着业务的运行而继续变动,数据联邦模块也须要及时的获取数据变动,如表的字段增删或类型变动等,因而元信息管理模块就负责提供这个性能,它须要从各数据源获取元信息并集中管理,通过对数据源的查问来获取和保护最新的元信息,从而保障元数据在各个平台之间的一致性,在构建、运行、保护的整个数据联邦计算的生命周期中起到要害撑持作用。 对立的查问加工接口这个模块为数据分析人员提供服务入口,次要包含用对立的规范SQL语句实现跨平台的数据加工,以及企业能够基于其再提供的一些数据门户治理性能,如SQL审查、数据申请等。因为波及到多种数据库的对立解决,这个模块技术要求比拟高,除了要兼容多个数据库的数据类型和SQL方言的差别以外,SQL性能是另外一个十分要害的技术因素。 联邦查问SQL引擎层作为对立的语法解析层,解析SQL指令。其外围是SQL编译器、优化器和事务管理单元,它是保障能够给开发者提供比拟好的数据库体验,无需基于底层不同平台且有差异化API来做业务开发,同时会通过优化器来生成最佳的执行打算,最终将执行打算推送给计算引擎层。对开发者来说,SQL引擎层须要反对规范的JDBC/ODBC/REST接口。 联邦查问计算引擎层一些SQL操作会须要操作多个数据库中的数据并做关联剖析,因而计算引擎层须要具备能力加载不同数据库中的数据进入引擎并做各种数据运算,这就要求计算引擎须要基于分布式计算的架构,同时须要无效的解决各个数据库的数据类型的差异性。譬如varchar等类型在不同的数据库中,如果字符串在左右侧呈现空格时解决会不同,trim函数的行为也略有差别,另外局部数据库Decimal/Numeric的类型定义也不同,这些类型零碎的差别会带来十分大的解决复杂度,也是计算引擎的一个外围能力。另外一个重要的个性是性能优化相干,因为波及多个远端数据库,因而与传统数据库不同,联邦计算引擎须要反对两个典型的SQL优化:数据后果缓存和计算下推到远端数据库。 数据后果Cache层联邦计算引擎主动统计后果集查问频率,针对热点后果集以及两头缓存数据集,抉择缓存或同步底层数据库后果集至联邦计算平台中,并在查问过程中抉择最佳查问门路,优化查问性能。 咱们以下面一个SQL查问来举例。咱们都晓得MySQL数据库并不善于做数据统计分析,针对1000万行记录的lineitem表的聚合运算大概须要200秒。而如果采纳联邦计算引擎,计算引擎依据屡次查问历史记录发现这个表被屡次统计分析,它将数据从MySQL中长久化到st_lineitem中,后续查问到MySQL的tpch1.lineitem的时候,间接用st_lineitem。因为分布式计算引擎比拟善于做统计分析,后续的查问都通过联邦计算引擎,性能也失去数量级上的晋升,并且原有的MySQL数据库的资源耗费也大幅缩小。 数据下推到远端数据库在另外一些Ad-hoc的统计分析场景下,如果将源端数据库内所有的数据都加载到联邦计算引擎,可能会有大量的数据IO和网络IO,从而导致性能迟缓。如果这类SQL查问的局部执行打算可能在远端数据库中间接执行,计算引擎能够在制订执行打算时抉择在底层数据库中下推算子,由各个远端数据库来实现根底计算,而最初将执行后果汇总至联邦计算层进行对立计算,从而优化查问性能。 咱们举一个简略的例子,如果须要从MySQL和Oracle数据库中找到员工号小于一个给定值的员工数量,如果没有聚合下推的优化,那么开发者下发统计SQL后,联邦计算引擎须要从MySQL和Oracle中都将全量的千万级数据加载到计算引擎中再计算,这个加载过程可能须要几百秒(次要受限于数据库对联邦计算引擎凋谢的并发线程数量)。而如果计算引擎反对计算下推到远端数据库,那么波及到数据搬迁就非常少,性能也从几百秒晋升到数十秒。 对立的平安治理因为数据联邦会将各个数据库凋谢给业务用户,数据凋谢的同时就裸露了更多的数据危险,因而咱们就须要更加细粒度的数据安全防护性能,爱护数据认证、审计、受权,以及提供数据加密、脱敏,以及密级分类等性能,保证数据在存储、传输、加工过程的平安。 对立权限治理数据联邦平安层须要提供对立用户模块,用于多个数据库的相干权限治理和受权应用,咱们通过元信息映射(表面创立)提供与数据库表级权限管控雷同体验的联邦数据源权限管控,以及提供细粒度的用户级别行列级权限管控性能。这种形式,可近似认为在编译期间提供对于原表->视图的查问限度,保障在计算过程中敏感数据不泄露。 SQL动静审核SQL审核指的是在业务SQL提交平台时,平台能够依据提交业务的用户或行为等维度,依据内置或自定义的审核规定,对于敏感查问(如敏感字段等值查问)进行阻断解决或者优化解决倡议。为了不便了解,咱们列举一些比拟典型的DML审核规定以及对应的规定形容,如下表: 大型企业随着业务的凋谢,其对SQL审核的需要比拟大,很多企业都有本人的SQL凋谢和审核平台,开源社区也有一些针对性的解决方案。总体架构能够参考下图的架构,以DSL语言来最规定的定义,由对应的解析器来实现相干的SQL审核的操作(如阻断或优化倡议等)。 敏感数据动静脱敏与数据库表的行列级权限不同,敏感数据动静脱敏须要保障的是数据的内容平安,是配合权限管控的更加偏差业务级的数据安全爱护。须要留神的一点,为了保障后果的正确性,在查问计算阶段应用实在数据计算,仅在输入时做动静脱敏从而保障敏感数据不泄露。为了保障数据脱敏的有效性,数据联邦引擎须要保护全局的表、字段血统图,这样能够基于数据血统来欠缺全局的敏感规定传递性能,这样就能辨认更多的敏感数据表,以及针对这些表提供可配置的主动敏感数据辨认并脱敏。 对立的运维治理除了有基础架构作为撑持,联邦计算的落地还须要有下层的数据开发工具的反对,与数据联邦配合实现从数据获取、加工、到价值变现的残缺过程,同时跨数据源的数据安全也应该失去保障。开发治理运维工具个别包含数据开发、治理、运维工具平台,使企业能够更有效率的利用联邦计算构建企业外部的数据服务层,以及数据业务价值层。 — Presto与Trino —Presto(或 PrestoDB)是一种开源的分布式 SQL 查问引擎,从头开始设计用于针对任何规模的数据进行疾速剖析查问。它既可反对非关系数据源,例如 Hadoop 分布式文件系统 (HDFS)、Amazon S3、Cassandra、MongoDB 和 HBase,又可反对关系数据源,例如 MySQL、PostgreSQL、Amazon Redshift、Microsoft SQL Server 和 Teradata。Presto可在数据的存储地位查问数据,无需将数据挪动到独立的剖析零碎。查问执行可在纯正基于内存的架构上平行运行,大多数后果在秒级返回。 ...

April 17, 2023 · 1 min · jiezi

关于数据结构和算法:这几个算法可视化网站太牛了

本文曾经收录到Github仓库,该仓库蕴含计算机根底、Java根底、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构等外围知识点,欢送star~ Github地址:https://github.com/Tyson0314/... 大家好,我是大彬~ 很多初学者在学习数据结构与算法的时候,都会感觉很难,很大一部分是因为数据结构与算法自身比拟形象,不好了解。对于这一点,能够通过一些可视化动画来帮忙了解。 上面大彬举荐几个学习数据结构和算法的可视化工具。 Data Structure Visualizations这是一个在线数据可视化工具,能够手动创立各种数据结构,包含队列、栈、堆、树等等,并且反对递归、排序、搜寻等算法的动静演示。该工具由旧金山大学开发,地址:https://www.cs.usfca.edu/~gal... 这个工具通过可视化的形式展示了数据结构和算法,不便咱们了解其中的原理。网站容易操作、内容丰盛且容易了解,十分nice~尽管网站是英文的,不过都是些容易了解的术语,英文不好的小伙伴也不会有很大的阅读障碍。 下图演示红黑树插入节点的操作,十分直观! visualgo该网站由 Steven Halim 博士开发,对于了解数据结构与算法十分有帮忙。网站外面蕴含了排序、链表、哈希表、二叉搜寻树、递归树、循环查找等常见算法动画。 地址:https://visualgo.net/zh 在动画执行的过程中,还会在网站右下角高亮展现动画的代码逻辑。非常适合初学者学习坚固本人的算法常识。 BinaryTreeVisualiser一款二叉树可视化的工具,能够用来学习二叉树,超级好用。地址:http://btv.melezinek.cz/home.... btree-js这是一个专门演示B树的工具,能够在下面插入节点模仿B树的构建过程,对于了解B树这种数据结构十分有帮忙。 地址:https://yangez.github.io/btre... Algorithm VisualizerAlgorithm Visualizer 是一个可视化代码算法的交互式平台,内含多种算法(回溯、动静布局、贪婪等)并进行了可视化动画出现,让学习算法和数据结构更加直观。 地址:https://algorithm-visualizer.... 目前反对的算法包含回溯法、动静布局、贪心算法、排序算法、搜索算法等。 Algorithm Visualizer反对js/C++/Java语言,运行会有动态图演示代码运行过程,日志输出区记录每次搜寻的过程。 bigocheatsheet这个网站总结了罕用算法的时空Big-O复杂性,常见数据结构操作的工夫复杂度。 链接中转:https://www.bigocheatsheet.com/ Algorithms-DataStructures-BigONotation这也是一个能够查看算法剖析的网站工具,性能相比bigocheatsheet,更丰盛一些。 地址:http://cooervo.github.io/Algo... 以上就是明天要举荐的几个学习数据结构和算法的可视化网站,心愿对大家有帮忙~ 最初给大家分享一个Github仓库,下面有大彬整顿的300多本经典的计算机书籍PDF,包含C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,能够star一下,下次找书间接在下面搜寻,仓库继续更新中~ Github地址:https://github.com/Tyson0314/...

January 5, 2023 · 1 min · jiezi

关于数据结构和算法:KMP的代码解读

如何更好地了解和把握 KMP 算法? - 海纳的答复 - 知乎解说了KMP算法 ,清晰明了,令人醍醐灌顶。但我对其代码中不少合并与简化的局部仍须要多思考一番,这里记录下其代码思路。倡议读完下面文章后食用。 局部匹配表(Partial Match Table) PMTPMT 中的值是字符串前缀汇合和后缀汇合的交集中最长元素的长度 匹配过程假如曾经求出了PMT。 设指针 i 指向主字符串 t,指针 j 指向模式字符串 p。也就是说,若模式字符串 p 的 PMT[j] = k,那么模式字符串 p 的substring(0, k) 与 substring( j - k + 1, j + 1) 是相等的。 也就是说,如果主字符串 t 与模式字符串 p 在某处不匹配了,PMT[j - 1] = k 的值示意模式字符串 p 的substring(0, k) 曾经与主字符串 t 的 i 地位之前的 k 个地位 substring( i - k , i ) 匹配过了。因而只须要 i 不变( substring( i - k , i ) 后一个),j = PMT[j - 1] (substring(0, k) 后一个)即可。 ...

November 29, 2022 · 2 min · jiezi

关于数据结构和算法:搜索中常见数据结构与算法探究一

1 前言ES当初曾经被宽泛的应用在日常的搜寻中,Lucene作为它的内核值得咱们深入研究,比方FST,上面就用两篇分享来介绍一些本文的主题: 第一篇次要介绍数据结构和算法根底和分析方法,以及一些罕用的典型的数据结构;第二篇次要介绍图论,以及自动机,KMP,FST等算法; 上面开始第一篇2 引言“算法是计算机科学畛域最重要的基石之一“ “编程语言尽管该学,然而学习计算机算法和实践更重要,因为计算机算法和实践更重要,因为计算机语言和开发平台突飞猛进,但万变不离其宗的是那些算法和实践,例如数据结构、算法、编译原理、计算机体系结构、关系型数据库原理等等。“——《算法的力量》 2.1 提出问题2.1.1 案例一 设有一组N个数而要确定其中第k个最大者,咱们称之为抉择问题。惯例的解法如下: 该问题的一种解法就是将这N个数读进一个数组中,在通过某种简略的算法,比方冒泡排序法,以递加程序将数组排序,而后返回地位k上的元素。略微好一点的算法能够先把前k个元素读入数组并对其排序。接着,将剩下的元素再一一读入。当新元素被读到时,如果它小于数组中的第k个元素则疏忽之,否则就将其放到数组中正确的地位上,同时将数组中的一个元素挤出数组。当算法终止时,位于第k个地位上的元素作为答案返回。这两种算法编码都很简略,然而咱们天然要问:哪个算法更好?哪个算法更重要?还是两个算法都足够好?应用N=30000000和k=15000000进行模仿将发现,两个算法在正当的工夫量内均不能完结;每一种算法都须要计算机解决若干工夫能力实现。 其实还有很多能够解决这个问题,比方二叉堆,归并算法等等。 2.2.2 案例二 输出是由一些字母形成的一个二维数组以及一组单词组成。指标是要找出字谜中的单词,这些单词可能是程度、垂直、或沿对角线上任何方向搁置。下图所示的字谜由单词this、two、fat和that组成。 当初至多也有两种直观的算法来求解这个问题: 对单词表中的每个单词,咱们查看每一个有序三元组(行,列,方向)验证是否有单词存在。这须要大量嵌套的for循环,但它基本上是直观的算法。对于每一个尚未越出迷板边缘的有序四元组(行,列,方向,字符数)咱们能够测试是否所指的单词在单词表中。这也导致应用大量嵌套的for循环。上述两种办法相对来说都不难编码,但如果减少行和列的数量,则下面提出的两种解法均须要相当长的工夫。 以上两个案例中,咱们能够看到要写一个工作程序并不够。如果这个程序在微小的数据集上运行,那么运行工夫就变成了重要问题。 那么,应用自动机实践能够疾速的解决这个问题,下一篇中给大家具体的剖析。 3 数据结构与算法根底3.1 数据结构根底3.1.1 什么是数据结构 在计算机领域中,数据是信息的载体,是可能输出到计算机中并且能被计算机辨认、存储和解决的符号的总称。数据结构是指数据元素和数据元素之间的互相关系或数据的组织模式。数据元素是数据的的根本单位,数据元素有若干根本项组成。 3.1.2 数据之间的关系 数据之前的关系分为两类: 逻辑关系 示意数据之间的形象关系,按每个元素可能具备的前趋数和间接后继数将逻辑构造分为线性构造和非线性构造。逻辑关系或逻辑构造有如下特点:只是形容数据结构中数据元素之间的分割法则;是从具体问题中形象进去的数学模型,是独立于计算机存储器的(与硬件无关)逻辑构造的分类如下: 线性构造树形构造图状构造其余构造2)物理关系 逻辑关系在计算中的具体实现办法,分为顺序存储办法、链式存储办法、索引存储办法、散列存储办法。物理关系或物理构造有如下特点: 是数据的逻辑构造在计算机存储其中的映像;存储构造是通过计算机程序来实现,因而是依赖于具体的计算机语言的;物理构造分类如下: 程序构造链式构造索引构造3.2 算法根底3.2.1 根底概念算法是为求解一个问题须要遵循的、被分明指定的简略指令的汇合。对于一个问题,一旦某种算法给定并且被确定是正确的,那么重要的一步就是确定该算法将须要多少诸如工夫或空间等资源量的问题。如果一个问题的求解算法居然须要长达一年工夫,那么这种算法就很难能有什么用途。同样,一个须要若干个GB的内存的算法在以后的大多数机器上也是无奈应用的。 3.2.2 数学根底一般来说,估算算法资源耗费所需的剖析是一个实践问题,因而须要一套数学分析法,咱们先从数学定义开始。 定理1:如果存在失常数c和n0,使得当N>= n0时,T(N) <= cf(N),则记为T(N) = O(f(N))。定理2:如果存在失常数c和n0,使得当N>=n0时,T(N) <= cg(N),则记为T(N) = (g(N))。定理3:T(N) = (h(N))当且仅当T(N) = O(h(N)) 和 T(N) = (h(N))。定理4:如果对每一个失常数c都存在常数n0使得当N>n0时,T(N) < cp(N),则T(N) = o(p(N))。这些定义的目标是要在函数间建设一种绝对的级别。给定两个函数,通常存在一些点,在这些点上一个函数的值小于另一个函数的值,因而,个别声称f(N)<g(N),是没有什么意义的。于是,咱们比拟他们的绝对增长率。当将绝对增长率利用到算法剖析时,会明确它是重要的度量。 如果用传统的不等式来计算增长率,那么第一个定义T(N) = O(f(N))是说T(N)的增长率小于或者等于f(N)的增长率。第二个定义T(N) = (g(N))是说T(N)增长率大于或者等于g(N)的增长率。第三个定义T(N) = (h(N))是说T(N)的增长率等于h(N)的增长率。最初一个定义T(N) = o(p(N))说的则是T(N)的增长率小于p(N)的增长率。他不同于大O,因为大O蕴含增长率雷同的可能性。 ...

October 11, 2022 · 2 min · jiezi

关于数据结构和算法:图论-拓扑排序

介绍咱们把做从筹备食材,做菜到摆盘当作一个我的项目工程 当咱们每次只能做一个流动,并且咱们想要得出实现整个我的项目工程的程序 并且这个程序要确保咱们能实现整个工程 难道还有一个程序是咱们没法实现这个工程的?不是从左到右顺次做就行了吗?还真有,例如 筹备食材,切番茄,打鸡蛋,炒鸡蛋,下番茄,混合翻炒,放调料,紫菜蛋花汤,摆盘也能够依照这样的形式进行 筹备食材,紫菜蛋花汤,切番茄,下番茄,打鸡蛋,炒鸡蛋,混合翻炒,放调料,摆盘又或者这样的形式进行 筹备食材,切番茄,下番茄,打鸡蛋,紫菜蛋花汤,炒鸡蛋,混合翻炒,放调料,摆盘然而不能以这样的形式去进行 筹备食材,切番茄,下番茄,混合翻炒,打鸡蛋,下鸡蛋,放调料,紫菜蛋花汤,摆盘因为都还没有鸡蛋,哪来的混合翻炒 到这里,咱们就能看出拓扑排序的作用了 就是保障咱们的我的项目工程可能实现,具备实际意义 外围就一点:在每个流动开始时,保障它的所有前驱流动都已实现 例如,混合翻炒这个流动的所有前驱流动就是 筹备食材,切番茄,下番茄,打鸡蛋,炒鸡蛋但紫菜蛋花汤并不属于混合翻炒的前驱流动,而是属于摆盘的前驱流动 实现定义

September 28, 2022 · 1 min · jiezi

关于数据结构和算法:自己动手实现-HashMapPython字典彻底系统的学习哈希表上篇不看血亏

HashMap(Python字典)设计原理与实现(上篇)——哈希表的原理在此前的四篇长文当中咱们曾经实现了咱们本人的ArrayList和LinkedList,并且剖析了ArrayList和LinkedList的JDK源代码。 本篇文章次要跟大家介绍咱们十分罕用的一种数据结构HashMap,在本篇文章当中次要介绍他的实现原理,下篇咱们本人入手实现咱们本人的HashMap,让他能够像JDK的HashMap一样工作。 如果有公式渲染不了,可查看这篇内容雷同且可渲染公式的文章 HashMap初识如果你应用过HashMap的话,那你必定很相熟HashMap给咱们提供了一个十分不便的性能就是键值(key, value)查找。比方咱们通过学生的姓名查找分数。 public static void main(String[] args) { HashMap<String, Integer> map = new HashMap<>(); map.put("学生A", 60); map.put("学生B", 70); map.put("学生C", 20); map.put("学生D", 85); map.put("学生E", 99); System.out.println("学生B的分数是:" + map.get("学生B")); }咱们晓得HashMap给咱们提供查问get函数性能的工夫复杂度为O(1),他在常数级别的工夫复杂度就能够查问到后果。那它是如何做到的呢? 咱们晓得在计算机当中一个最根本也是惟一的,可能实现常数级别的查问的类型就是数组,数组的查问工夫复杂度为O(1),咱们只须要通过下标就能拜访对应的数据。比方咱们想拜访下标为6的数据,就能够这样: String[] strs = new String[10];strs[6] = "一无是处的钻研僧";System.out.println(strs[6]);因而咱们要想实现HashMap给咱们提供的O(1)级别查问的工夫复杂度的话,就必须应用到数组,而在具体的HashMap实现当中,比如说JDK底层也是采纳数组实现的。 HashMap整体设计咱们实现的HashMap须要满足的最重要的性能是依据键(key)查问到对应的值(value),比方下面提到的依据学生姓名查问问题。 因而咱们能够有一个这样的设计,咱们能够依据数据的键值计算出一个数字(像这种能够将一个数据转化成一个数字的叫做哈希函数,计算出来的值叫做哈希值咱们后续将会认真阐明),将这个哈希值作为数组的下标,这样的话键值和下标就有了对应关系了,咱们能够在数组对应的哈希值为下标的地位存储具体的数据,比方下面谈到的问题,整个流程如下图所示: 然而像这种哈希函数计算出来的数值个别是没有范畴的,因而咱们通常通过哈希函数计算出来的数值通常会通过一个求余数操作(%),对数组的长度进行求余数,否则求进去的数值将超过数组的长度。比方数组的长度是16,计算出来的哈希值为186,那么求余数之后的后果为186%16=10,那么咱们能够将数据存储在数组当中下标为10的地位,下次咱们来取的时候就取出下标为10地位的数据即可。 如何设计一个哈希函数?首先咱们须要理解一个常识,那就是在计算机世界当中咱们所含有的两种最根本的数据类型就是,整型(short, int, long)和字符串(String),其余的数据类型能够由这些数据类型组合起来,上面咱们来剖析一下常见的数据类型的哈希函数设计。 整型的哈希函数对于整型数据,他原本就是一个数值,因而咱们能够间接将这个值返回作为他的哈希值,而JDK中也是这么实现的!JDK中实现整型的哈希函数的办法: /** * Returns a hash code for a {@code int} value; compatible with * {@code Integer.hashCode()}. * * @param value the value to hash * @since 1.8 * * @return a hash code value for a {@code int} value. */ public static int hashCode(int value) { return value; }字符串的哈希函数咱们晓得字符串底层存储的还是用整型数据存储的,比说说字符串hello world,就能够应用字符数组['h', 'e', 'l', 'l', 'o' , 'w', 'o', 'r', 'l', 'd']进行存储,因为咱们计算出来的这个哈希值须要尽量不和别的数据计算出来的哈希值抵触(这种景象叫做哈希抵触,咱们前面会认真探讨这个问题),因而咱们要尽可能的充分利用字符串外面的每个字符信息。咱们来看一下JDK当中是怎么实现字符串的哈希函数的 ...

July 10, 2022 · 2 min · jiezi

关于数据结构和算法:数组容器ArrayList设计与Java实现看完这个你不懂ArrayList你找我

数组容器(ArrayList)设计与Java实现本篇文章次要跟大家介绍咱们最常应用的一种容器ArrayList、Vector的原理,并且本人应用Java实现本人的数组容器MyArrayList,让本人写的容器能像ArrayList那样工作。在本篇文章当中首先介绍ArrayList的一些基本功能,而后去剖析咱们本人的容器MyArrayList应该如何进行设计,同时剖析咱们本人的具体实现办法,最初进行代码介绍!!! ArrayList为咱们提供了哪些性能?咱们来看一个简略的代码,随机生成100个随机数,查看生成随机数当中是否存在50这个数。 public class MyArrayList { public static void main(String[] args) { Random random = new Random(); ArrayList<Integer> list = new ArrayList<>(); for (int i = 0; i < 100; i++) { list.add(random.nextInt(5000)); } for (int i = 0; i < 100; i++) { if (list.get(i) == 50) { System.out.println("蕴含数据 50"); } } list.set(5, 1000);// 设置下标为5的数据为100 list.remove(5);// 删除下标为5的数据 list.remove(new Integer(888));// 删除容器当中的第一个值为5的数据 }}上述代码蕴含了ArrayList最根本的一个性能,一个是add办法,向数组容器当中退出数据,另外一个办法是get从容器当中拿出数据,set办法扭转容器里的数据,remove办法删除容器当中的数据。ArrayList的很多其余的办法都是围绕这四个最根本的办法开展的,因而咱们在这里不认真介绍其余的办法了,前面咱们本人实现的时候遇到问题的时候天然会须要设计相应的办法,而后咱们进行解决即可。 当初咱们就须要去设计一个数组容器实现“增删改查”这四个基本功能。 设计原理剖析首先明确一点咱们须要应用什么工具去实现这样一个容器。咱们手里有的工具就是Java提供给咱们的最根本的性能——数组(这个如同是废话,咱们的题目就是数组容器)。 当咱们在Java当中应用数组去存储数据时,数据在Java当中的内存布局大抵如下图所示。 咱们在设计数组容器这样一个数据结构的时候次要会遇到两个问题: 咱们申请数组的长度是多少。当数组满了之后怎么办,也就是咱们的扩容机制。对于这两个问题,首先咱们数组的初始大小能够有默认值,在咱们本人实现的MyArrayList当中设置为10,咱们在应用类时也能够传递一个参数指定初始大小。第二个问题当咱们的数组满的时候咱们须要对数组进行扩容,在咱们实现的MyArrayList当中咱们采取的形式是,新数组的长度是原数组的两倍(这个跟JDK的ArrayList形式不一样,ArrayList扩容为原来的1.5倍)。 ...

July 6, 2022 · 7 min · jiezi

关于数据结构和算法:BitMap原理以及Go语言实现

1. BitMap介绍BitMap能够了解为通过一个bit数组来存储特定数据的一种数据结构。BitMap罕用于对大量整形数据做去重和查问。在这类查找中,咱们能够通过map数据结构进行查找。但如果数据量比拟大map数据结构将会大量占用内存。 BitMap用一个比特位来映射某个元素的状态,所以这种数据结构是十分节俭存储空间的。 BitMap用处 BitMap用于数据去重BitMap可用于数据的疾速查找,判重。BitMap用于疾速排序BitMap因为其自身的有序性和唯一性,能够实现疾速排序:将其退出bitmap中,而后再遍历获取进去,从而失去排序的后果。如何判断数字在bit数组的地位在前面的代码中,咱们应用[]byte来存储bit数据,因为一个byte有8个二进制位。因而: 数字/8=数字在字节数组中的地位。数字%8=数字在以后字节中的地位。例如:数字10,10/8=1,即数字10对应的字节数组的地位为:110%8=2,即数字10对应的以后字节的地位为:2设置数据到bit数组num/8失去数字在字节数组中的地位 => rownum%8失去数字在以后字节中的地位 => col将1左移col位,而后和以前的数据做|运算,这样就能够将col地位的bit替换成1了。从bit数组中革除数据num/8失去数字在字节数组中的地位 => rownum%8失去数字在以后字节中的地位 => col将1左移col位,而后对取反,再与以后值做&,这样就能够将col地位的bit替换成0了。数字是否在bit数组中num/8失去数字在字节数组中的地位 => rownum%8失去数字在以后字节中的地位 => col将1左移col位,而后和以前的数据做&运算,若该字节的值!=0,则阐明该地位是1,则数据在bit数组中,否则数据不在bit数组中。2. Go语言位运算在Go语言中反对以下几种操作位的形式: & 按位与:两者全为1后果为1,否则后果为0| 按位或:两者有一个为1后果为1,否则后果为0^ 按位异或:两者不同后果为1,否则后果为0&^ 按位与非:是"与"和"非"操作符的简写模式<< 按位左移:>> 按位右移:左移将二进制向左挪动,左边空出的位用0填补,高位左移溢出则舍弃该高位。因为每次移位数值会翻倍,所以通常用代替乘2操作。当然这是建设在移位没有溢出的状况。例如:1<<3 相当于1×8=8,3<<4 相当于3×16=48 右移将整数二进制向右挪动,右边空出的位用0或者1填补。负数用0填补,正数用1填补。正数在内存中的二进制最高位为符号位——应用1示意,所以为了保障移位之后符号位的正确性,所以须要在高位补1。绝对于左移来说,右移通常用来代替除2操作。例如:24>>3 相当于24÷8=3 应用&^和位移运算来给某一地位0这个操作符通常用于清空对应的标记位,例如 a = 0011 1010,如果想清空第二位,则能够这样操作:a &^ 0000 0010 = 0011 1000 3. BitMap的Go语言实现接下来咱们给出BitMap的Go语言实现,目前代码曾经上传到github中,下载地址 定义首先给出BitMap构造的定义: type BitMap struct { bits []byte vmax uint}创立BitMap构造func NewBitMap(max_val ...uint) *BitMap { var max uint = 8192 if len(max_val) > 0 && max_val[0] > 0 { max = max_val[0] } bm := &BitMap{} bm.vmax = max sz := (max + 7) / 8 bm.bits = make([]byte, sz, sz) return bm}将数据增加到BitMapfunc (bm *BitMap)Set(num uint) { if num > bm.vmax { bm.vmax += 1024 if bm.vmax < num { bm.vmax = num } dd := int(num+7)/8 - len(bm.bits) if dd > 0 { tmp_arr := make([]byte, dd, dd) bm.bits = append(bm.bits, tmp_arr...) } } //将1左移num%8后,而后和以前的数据做|,这样就替换成1了 bm.bits[num/8] |= 1 << (num%8)}从BitMap中删除数据func (bm *BitMap)UnSet(num uint) { if num > bm.vmax { return } //&^:将1左移num%8后,而后进行与非运算,将运算符右边数据相异的位保留,雷同位清零 bm.bits[num/8] &^= 1 << (num%8)}判断BitMap中是否存在指定的数据func (bm *BitMap)Check(num uint) bool { if num > bm.vmax { return false } //&:与运算符,两个都是1,后果为1 return bm.bits[num/8] & (1 << (num%8)) != 0}

May 8, 2022 · 1 min · jiezi

关于数据结构和算法:PIPIOJ1271-反转链表

题目形容反转长度为N的单链表从地位 L 到 R 的子段。请在常数空间复杂度下应用一趟扫描实现反转。 输出第一行三个整数N,L,R,1<=L<=R<=N接下来N个数示意N个节点的值 输入输入反转后的单链表节点值 样例输出5 2 41 2 3 4 5 样例输入1 4 3 2 5 局部反转链表的状态图演示状态1 状态2 状态3 状态4 状态5 C++参考解答#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>typedef int ElemType;typedef struct LNode{ ElemType data; struct LNode* next;}LNode,*LinkList;LinkList CreateList(LinkList& L,int n) { L = (LinkList)malloc(sizeof(LNode)); L->next = NULL; LNode* s, * r = L; int i = 0, x; while (i < n) { scanf("%d", &x); s = (LNode*)malloc(sizeof(LNode)); s->data = x; s->next = r->next; r->next = s; r = s; i++; } r->next = NULL; return L;}void Reverse(LinkList& L,int left,int right) { LNode* p=NULL, * q=NULL, * r=NULL,*t=NULL; p = L; int i = 0; while (i < left - 1) { p = p->next; i++; } q = p; p = p->next; q->next = NULL; t = p; while (i < right) { r = p->next; p->next = q->next; q->next = p; p = r; i++; } t->next = r;}void PrintList(LinkList L) { LNode* p = L->next; while (p) { printf("%d ", p->data); p = p->next; } printf("\n");}int main() { LinkList L; int N, left, right; scanf("%d%d%d", &N, &left, &right); CreateList(L,N); Reverse(L,left,right); PrintList(L);}

April 19, 2022 · 1 min · jiezi

关于数据结构和算法:PIPIOJ1447-PIPI的线性表问题Ⅰ

题目形容已知线性表中的元素以递增序列排列,并以单链表作存储构造。设计算法删除表中所有值雷同的多余元素(使得操作后的线性表中所有的值均不雷同),同时开释被删结点空间,并剖析算法的工夫复杂度。 输出第一行输出一个正整数n,示意元素个数,n<=100。第二行输出n个正整数元素,保障元素以递增排列,元素的值<=100。 输入输入删除雷同元素后的链表。 样例输出51 1 3 4 4 样例输入1 3 4 C++代码实现#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>typedef int ElemType;typedef struct LNode { ElemType data; struct LNode* next;}LNode,*LinkList;LinkList CreateList(LinkList& L, int n) { L = (LNode*)malloc(sizeof(LNode)); L->next = NULL; LNode* s, * r = L; int i = 0; int x; while (i < n) { scanf("%d", &x); s = (LNode*)malloc(sizeof(LNode)); s->data = x; s->next = r->next; r->next = s; r = s; i++; } r->next = NULL; return L;}void PrintList(LinkList L) { LNode* p = L->next; while (p) { printf("%d ", p->data); p = p->next; } printf("\n");}void DeleteRepeated(LinkList& L) { LNode* p = L->next; LNode* q, * r; while (p->next != NULL) { q = p->next; if (p->data == q->data) { r = q->next; p->next = r; free(q); } else { p = p->next; } }}int main() { LinkList L; int n; scanf("%d", &n); CreateList(L, n); DeleteRepeated(L); PrintList(L); return 0;}

April 19, 2022 · 1 min · jiezi

关于数据结构和算法:PIPIOJ1267-删除单链表的倒数第K个节点

题目形容给定一个长度为n的单链表,删除倒数第K的节点,而后从头到尾输入每个节点的值。 输出第一行蕴含两个整数N,k,k<=N.第二行蕴含N个整数,代表从表头到表尾每个节点的值。你须要建设单链表把这N个数串起来~ 输入按程序输入删除了倒数第K个节点后每个节点的值。 样例输出5 21 2 3 4 5 样例输入1 2 3 5 C++参考解答#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>typedef int ElemType;typedef struct LNode { ElemType data; struct LNode* next;}LNode,*LinkList;//尾插法创立单链表LinkList CreateList(LinkList& L,int N) { L = (LinkList)malloc(sizeof(LNode)); L->next = NULL; LNode* s, *r = L; int i = 0,x; while (i < N) { scanf("%d", &x); s = (LinkList)malloc(sizeof(LNode)); s->data = x; s->next = r->next; r->next = s; r = s; i++; } r->next = NULL; return L;}//输入单链表void PrintList(LinkList L) { LNode* s = L->next; while (s) { printf("%d ", s->data); s = s->next; } printf("\n");}//按值查找LinkList GetElem(LinkList L,int i) { int j = 1; LNode* p = L->next; if (i == 0) { return L; } if (i < 1) { return NULL; } while (p && j < i) { p = p->next; j++; } return p;}//删除单链表中第i个结点bool ListDelete(LinkList& L, int i) { LNode* p = GetElem(L, i - 1); if (p == NULL) { return false; } LNode* q = p->next; if (q == NULL) { return false; } p->next = q->next; free(q); q = NULL; return true;}int main() { int N, k; scanf("%d%d", &N, &k); int seqloc = N - k+1; LinkList L; CreateList(L,N); ListDelete(L, seqloc); PrintList(L); return 0;}

April 17, 2022 · 1 min · jiezi

关于数据结构和算法:PIPIOJ1496查找链表的中位数

题目形容给定带头结点的单链表L,假如L存储了n个元素(n为奇数,是未知的)。设计算法返回该链表两头的那个元素。要求仅对链表进行一次遍历。 输出输出N个数字,不必关怀N是多少,应用while循环读入链表中元素,直至EOF。(0<N≤1e5) 输入输入链表最两头的元素。 样例输出1 2 3 4 5 样例输入3 解题思路用一个全局变量count统计链表元素的个数,中位数的位序=(count+1)/2。 C++参考解答#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>typedef int ElemType;typedef struct LNode { ElemType data; struct LNode* next;}LNode,*LinkList;int count = 0;//尾插法LinkList CreateList(LinkList& L) { L = (LinkList)malloc(sizeof(LNode)); L->next = NULL; LinkList s, r = L; int x; while (scanf("%d",&x)!=EOF){ s = (LinkList)malloc(sizeof(LNode)); s->data = x; s->next = r->next; r->next = s; r = s; count++; } r->next = NULL; return L;}//输入链表中位数void ListMedian(LinkList L,int n) { LinkList p = L->next; int pos = (n + 1) / 2; int i = 1; while (i < pos) { p = p->next; i++; } printf("%d", p->data);}int main() { LinkList L; CreateList(L); //PrintList(L); ListMedian(L, count);}

April 17, 2022 · 1 min · jiezi

关于数据结构和算法:南京晓庄学院数据结构与算法习题册2线性表

一、填空题1.在程序表中,等概率状况下,插入和删除一个元素均匀需挪动___个元素,具体挪动元素的个数与___和___无关。参考解答:表长的一半表长和插入地位解析:在程序表中插入1个元素,则能插入的地位为第1个地位、第2个地位、第3个地位、...、第n+1个地位;故在第i个地位上插入一个结点的概率为1/(n+1)。如果在第1个地位插入元素,则本来的n个元素,从最初1个元素开始需顺次挪动1位,总共需挪动n个元素;如果在第2个地位插入元素,则本来的第2个地位的元素往后的每1个元素,从最初1个元素开始需顺次挪动1位,总共需挪动n-1个元素;如果在第n+1个地位插入元素,则无需挪动元素,总共需挪动0个元素。故插入一个元素均匀需挪动的元素个数=$$\begin{aligned}&\frac{1}{n+1}*n+\frac{1}{n+1}*n-1+...\frac{1}{n+1}*0\\&=\frac{1}{n+1}*[n+(n-1)+...+1]\\&=\frac{1}{n+1}*\frac{n*(n+1)}{2}\\&=\frac{n}{2}&\end{aligned}$$ 即插入一个元素均匀需挪动一半的元素。2.在一个长度为n的程序表的第i(1≤i≤n+1)个元素之前插入一个元素,需向后挪动___个元素,删除第 i(1≤i≤n)个元素时,需向前挪动___个元素。参考解答:n-i+1n-i解析:在第i个元素之前插入1个元素,即表明需将第i个元素,始终到第n个元素进行后移操作,第i个元素到第n个元素,共有(n-i+1)个元素,故需向后挪动(n-i+1)个元素。删除第i个元素,则意味着需将第i+1个元素,到第n个元素进行前移操作,第i+1个元素到第n个元素,共有(n-i)【n-(i+1)+1】个元素,故需向前挪动(n-i)个元素。3.在单循环链表中,由rear指向表尾,在表尾插入一个结点s的操作程序是___;删除开始结点的操作程序为___参考解答:表尾插入1个结点s的操作程序:s->next=rear->next;rear->next=s;rear=s;解析: 二、选择题1.数据在计算机存储器内示意时物理地址与逻辑地址雷同并且是间断的,称之为:A.存储构造B.逻辑构造C.顺序存储构造D.链式存储构造参考解答:C解析:存储构造:数据结构在计算机中的示意(又称为映像),也称为物理构造。逻辑构造:数据元素之间的逻辑关系,即从逻辑关系上形容数据。顺序存储:把逻辑上相邻的元素存储在物理地位上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来间接体现。链式存储:不要求逻辑上相邻的元素在物理地位上也相邻,借助批示元素存储地址的指针来示意元素之间的逻辑关系。故C选项合乎题意。附思维导图: 2.在n个结点的程序表中,算法的工夫复杂度是 O(1)的操作是:A.拜访第i个结点(1≤i≤n)和求第i个结点的间接前驱(2≤i≤n)B.在第i个结点后插入一个新结点(1≤i≤n)C.删除第i个结点(1≤i≤n) D.将n个结点从小到大排序参考解答:A解析:准备常识:线性表的顺序存储称为程序表。它是用一组地址间断的存储单元顺次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理地位上也相邻。A.程序表最次要的特点是随机拜访,即通过首地址和元素序号可在O(1)内找到指定的元素。故A选项正确。B.程序表的插入均匀工夫复杂度为O(n),故排除B选项。C.程序表的删除均匀工夫复杂度为O(n),故排除C选项。D.程序表排序的工夫复杂度不可能是O(1)。3.线性表L在状况下实用于应用链式构造实现。A.需常常批改L中的结点值 B.需一直对L进行删除插入C.L中含有大量的结点 D.L中结点结构复杂参考解答:B解析:链式构造的线性表,插入和删除操作不须要挪动元素,只需批改头指针。故B选项合乎题意。4.单链表的存储密度A.大于1 B.等于 1 C.小于 1 D.不能确定参考解答:C解析:(见下图) 三、判断题1.线性表的逻辑程序和存储程序总是统一的。参考解答:谬误解析:顺序存储的线性表,即程序表,逻辑程序和存储程序总是统一的;链式存储的线性表,即链表,只具备逻辑上的程序性,链表中的元素离散地散布在存储空间中。故应改为线性表的逻辑程序总是统一的。存储构造并不总是统一的。2.线性表的顺序存储构造优于链接存储构造。参考解答:谬误解析:存储构造并没有相对的优劣之分;线性表的顺序存储的益处是能实现元素的随机拜访,以及存储密度高;线性表的链式存储的益处是插入、删除元素时,无需挪动元素。3.设 p、q 是指针,若p=q,则*p=*q。参考解答:谬误解析:(见下图) 4.线性构造的基本特征是:每个元素有且仅有一个间接前驱和一个间接后继。参考解答:谬误解析:第1个元素没有间接前驱;最初1个元素没有间接后继。四、简答题1.剖析下列状况下,采纳何种存储构造更好些。(1)若线性表的总长度根本稳固,且很少进行插入和删除操作,但要求以最快的速度存取线性表中的元素。参考解答:程序构造(2)如果n个线性表同时并存,并且在处理过程中各表的长度会动静发生变化。参考解答:链式存储(3)形容一个城市的设计和布局。参考解答:链式存储解析: 五、算法设计题参考解答:贴一下参考答案 这边,我还特意去写了一下c++代码。 #include <stdio.h>void swap(int &a, int &b) { int temp = a; a = b; b = temp;}void Adjust(int A[], int n) { int i = 0, j = n - 1; while (i < j) { while (A[i] % 2 != 0) { i++; } while (A[j] % 2 == 0) { j--; } if (i < j) { swap(A[i], A[j]); } }}void Print(int A[],int n) { int i; for (i = 0; i < n; i++) { printf("%2d", A[i]); }}int main() { int A[10] = { 1,6,5,7,6,9,3,2,7,4}; Adjust(A,10); Print(A, 10); return 0;}后果如下: ...

April 8, 2022 · 1 min · jiezi

关于数据结构和算法:B树B树速记

B树每个节点最多有m-1个关键字(能够存有的键值对)。根节点起码能够只有1个关键字。非根节点至多有m/2个关键字。每个节点中的关键字都依照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都雷同。每个节点都存有索引和数据,也就是对应的key和value。所以,根节点的关键字数量范畴:1 <= k <= m-1,非根节点的关键字数量范畴:m/2 <= k <= m-1 插入直接插入到叶节点的对应地位,如果数量超过m则决裂,将两头k回升到父结点中 删除间接删除,如果删除后数量少于m/2(m/2-1),则从兄弟节点中借k(兄弟节点k回升到父节点,父节点中一个k降落到以后节点中),如果兄弟节点中没有能借的k(数量不多于m/2),则与兄弟节点合并(此时会从父节点中借一个k)。 B+树B+树其实和B树是十分类似的,咱们首先看看相同点。相同点: 根节点至多一个元素非根节点元素范畴:m/2 <= k <= m-1不同点: B+树有两种类型的节点:外部结点(也称索引结点)和叶子结点。外部节点就是非叶子节点,外部节点不存储数据,只存储索引,数据都存储在叶子节点。外部结点中的key都依照从小到大的顺序排列,对于外部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也依照key的大小排列。每个叶子结点都存有相邻叶子结点的指针,叶子结点自身依关键字的大小自小而大程序链接。父节点存有右孩子的第一个元素的索引。B树和B+树繁多节点存储的元素更多,使得查问的IO次数更少,所以也就使得它更适宜做为数据库MySQL的底层数据结构了。所有的查问都要查找到叶子节点,查问性能是稳固的,而B树,每个节点都能够查找到数据,所以不稳固。所有的叶子节点造成了一个有序链表,更加便于查找。

March 28, 2022 · 1 min · jiezi

关于数据结构和算法:PIPIOJ1214-逆置顺序表

题目链接戳我 题目形容PIPI当初由一个程序表L,他想把程序表所有元素逆置,要求除了存储L的空间外,辅助的空间复杂度为O(1). 输出输出蕴含一个整数n代表程序表L长度。接下来蕴含n个整数,代表程序表L中的元素。 输入输入逆置后的程序表 样例输出31 2 3 样例输入3 2 1 解题思路传送门 C++参考解答#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <string.h>#define MAXSIZE 50typedef int ElemType;typedef struct { ElemType data[MAXSIZE]; int length;}Sqlist;//初始化程序表void InitList(Sqlist& L) { memset(L.data, 0, sizeof(ElemType)); L.length = 0;}//逆置程序表void ListReverse(Sqlist& L) { int i, j,temp; for (i = 0, j = L.length - 1; i < j; i++, j--) { temp = L.data[i]; L.data[i] = L.data[j]; L.data[j] = temp; }}//打印程序表void PrintList(Sqlist L) { int i; for (i = 0; i < L.length; i++) { printf("%d ", L.data[i]); }}int main() { Sqlist L; int i,n; scanf("%d", &n); L.length = n; for (i = 0; i < L.length; i++) { scanf("%d", &L.data[i]); } ListReverse(L); PrintList(L); return 0;}

March 19, 2022 · 1 min · jiezi

关于数据结构和算法:南京晓庄学院数据结构与算法习题册1绪论

一、填空题1.从逻辑关系上讲,数据结构的类型次要分为_____、_____、_____和_____。参考解答:1)汇合构造2)线性构造3)树形构造4)图状构造解析:(见下图)留神题干中的逻辑关系。 2.数据的存储构造次要有_____和_____两种根本办法,不管哪种存储构造,都要存储两方面的内容:_____和_____。参考解答:1)顺序存储2)链式存储3)数据元素的值4)数据元素之间的关系解析:(见下图)数据的存储构造有4种办法,别离为顺序存储、链式存储、索引存储以及散列存储。次要有顺序存储和链式存储两种根本办法。 3.算法具备五个个性,别离是_____、_____、_____、_____、_____。参考解答:1)有穷性2)确定性3)可行性4)输出5)输入解析:(见下图) 4.算法设计要求中的健壮性指的是_____。参考解答:算法能解决一些异常情况。解析:(见下图)王道书上对于健壮性的残缺表述为输出非法数据时,算法的适当地作出反应或进行解决,而不会产生莫名其妙的输入后果。 二、选择题1.顺序存储构造中数据元素之间的逻辑关系是由_____示意的,链接存储构造中的数据元素之间的逻辑关系是由_____示意的。A.线性构造 B.非线性构造 C.存储地位 D.指针参考解答:C、D解析:(见下图)数据的逻辑构造分为线性构造和非线性构造。线性表、栈、队列属于线性构造;树、图、汇合属于非线性构造。A、B选项不合乎题干要求。 2.假如有如下遗产继承规定:丈夫和妻子能够互相继承遗产;子女能够继承父亲或母亲的遗产;子女间不能互相继承。则示意该遗产继承关系的最合适的数据结构应该是。A.树 B.图 C.线性表 D.汇合参考解答:A解析:(见下图)从画出的逻辑结构图中,可知数据元素之间存在一对多的关系,合乎树形构造的定义,故抉择A选项。 3.算法指的是_____。A.对特定问题求解步骤的一种形容,是指令的无限序列。B.计算机程序 C.解决问题的计算方法D.数据处理参考解答:A解析:(见下图) 三、简答题 参考解答:(1)根本运算(最深层循环内的语句,k=k+10*i; i++;)执行了n-2次,因而T(n)=O(n)。问:n-2是怎么失去的?答:一开始i=1,执行一次while循环,i的值自增1,当i的值减少到等于n-1的时候,则while循环完结。故从1减少到n-1,一共执行了n-2次自增操作。(2)根本运算(最深层循环内的语句,,k=k+10*i; i++;)执行了n次,因而Tn=O(n)。问:n是怎么失去的?答:do-while循环,一开始必定会执行1次,即第1次while循环判断的条件是2<=n。当i的值自增至n+1的时候,不满足while循环条件,才完结。故从2减少到n+1,一共执行了n-1次自增操作,加上第1次的自增操作,共执行了n次。 参考解答:数据结构(D、R),其中D(Data)示意数据,R(Relation)示意分割。用空心圆点示意各个数据,用实线示意各个数据之间的分割。绘制的逻辑结构图如下图所示:属于图状构造。 参考答案:

March 18, 2022 · 1 min · jiezi

关于数据结构和算法:PIPIOJ1213-顺序表的删除

题目链接戳我 题目形容从程序表L中删除具备最小值的元素(假如惟一)并友函数返回被删元素的值。空出的元素由最初一个元素填补。 输出输出蕴含一个整数n代表程序表L长度。接下来蕴含n个整数,代表程序表L中的元素。 输入若程序表为空,输入 "error".若不为空,输入最小元素的值并输入删除最小值之后的程序表。 样例输出31 2 3 样例输入13 2 留神点这里要留神审题,删除最小元素,并把最初一个元素放到删除元素的地位上,并不需要删除元素后,挪动后续元素。 C++参考解答#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>#define MAXSIZE 50typedef int ElemType;typedef struct { ElemType data[MAXSIZE]; int length;}SqList;//初始化程序表void InitList(SqList& L) { L.length =0; memset(L.data, 0, sizeof(ElemType));}//打印程序表void PrintList(SqList L) { int i; for (i = 0; i < L.length; i++) { printf("%d ", L.data[i]); } printf("\n");}//删除程序表元素bool ListDelete(SqList& L, int i) { if (i<0 || i>L.length) { return false; } if (L.length == 0) { return false; } //不须要挪动元素 //int j; /*for(j=i;j<L.length-2;j++){ L.data[j] = L.data[j+1]; }*/ L.data[i - 1] = L.data[L.length - 1]; L.length--; return true;}//求程序表中最小的元素ElemType ListMin(SqList L) { int x = L.data[0]; int i; for (i = 1; i < L.length; i++) { if (L.data[i] < x) { x = L.data[i]; } } return x;}//求出程序表中最小元素的地位int MinPos(SqList L,int x) { int i; for (i = 0; i < L.length; i++) { if (L.data[i] == x) { return i + 1; } }}int main() { SqList L;int i,n; ElemType x,min; InitList(L); scanf("%d", &n); L.length = n; for (i = 0; i < L.length; i++) { scanf("%d", &L.data[i]); } min = ListMin(L); ListDelete(L, MinPos(L,min)); if (L.length == 0) { printf("error\n"); }else { printf("%d\n", min); PrintList(L); } return 0;}

March 17, 2022 · 1 min · jiezi

关于数据结构和算法:cs61b-week8-Binary-Search-Tree

1.ADT 抽象数据类型抽象数据类型就是只定义一些操作,而不去具体实现这些操作,例如双端队列(Deque): Deque ADT:addFirst(Item x);addLast(Item x);boolean isEmpty();int size();printDeque();Item removeFirst();Item removeLast();Item get(int index);在Project 1中,Deque只给了一些API,而具体的实现代码交由咱们解决,由此产生了ArrayDeque和LinkedListDeque。还有就是之前课上讲的List61B接口,只申明一些办法,具体实现为AList和SLList精确来说,Java的interface并不是ADT,因为interface容许存在一些default的办法。 一个乏味的问题现有一种抽象数据类型名为GrabBag,反对以下操作: insert(int x)向GrabBag中插入xint remove():随机地从GrabBag中移除int sample():随机地从GrabBag中返回一个样本值in size():返回GrabBag的元素个数 那么选取何种底层数据结构来实现GrabBag性能更佳?(数组 or 单链表) 答案是:数组GrabBag中最须要思考的操作就是随机地从Grab中删除一个元素,在数组中的实现为: \(把数组最初一个元素B与待删除的元素A进行替换,而后指向数组开端的指针减一\) Map Example简直java.util library中最重要的interface是Collections,List,Set,Map均继承了该接口: Map即<Key,Value>的映射,内置的Map有HashMap和TreeMap,上面是一个HashMap的应用例子,为列表中的单词与其呈现次数之间建设映射关系: Map<String, Integer> m = new TreeMap<>();String[] text = {"sumomo", "mo", "momo", "mo", "momo", "no", "uchi"};for (String s : text) { int currentCount = m.getOrDefault(s, 0); m.put(s, currentCount + 1);}其中,getOrDefault(Object key, V defaultValue)办法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。 2.BST的定义BST(Binary Search Tree)二叉搜寻树的定义:二叉搜寻树是一棵有根树,且满足BST性质:树的所有左子树的结点的值(key)小于根结点,且所有右子树的结点的值(key)大于根节点BST的有序性必须满足:完整性,传递性和反对称性给定两个key p和q: ...

January 18, 2022 · 1 min · jiezi

关于数据结构和算法:cs61b-week8-Disjoint-Sets

1.Introduce并查集是一种数据结构,能够很不便地判断两个元素是否属于同一汇合,对于并查集的演示demo,能够参考slides或其余路径,本次课Josh老师循序渐进地从并查集的数据结构抉择开始一步步优化,最终使并查集失去绝对较好的性能体现。简略来讲,并查集须要领有两个性能: isConnected():判断两个元素之间是否相连接(属于同一汇合)Connect():连贯两个元素,在连贯之前应该判断元素是否已相连因而咱们创立一个DisjointSets接口,并在继承它的子类里实现上述两种办法: public interface DisjointSets { /** Connects two items P and Q. */ void connect(int p, int q); /** Checks to see if two items are connected. */ boolean isConnected(int p, int q);}2.ListOfSetsDS首先的问题是:如何去示意各个不相交汇合,一说到汇合,很容易想到应用Java内置的Set,那么就会有\(Set_{0},Set_{1},Set_{2}......\)因而能够应用List<Set<Integer>>的模式示意,这里假如元素类型是Integer,其实视频里说,这是Berkeley学生的想法,我也没想到,(= =)这种数据结构看起来比较复杂,Josh说实现起来也很简单,然而我感觉不算简单,说说我的想法。比方想要把\(Set_{0}中的元素1和Set_{1}中的元素2\)相连,思考:IsConnected():\(调用Set_{0}.contains()即可\),应用HastSet的话,工夫复杂度应该低于\(\Theta(N)\)Connect():\(调用Set_{0}.add()即可,假如Set_{1}的大小为M,则工夫复杂度为\Theta(M)\)然而只是最开始还算比拟快,如果把一个List中的Set全副两两合并,越到前面执行单次合并的工夫复杂度就越高。 Performance Summary 3.QuickFindDS(疾速查找)经验了下面的想法之后,咱们的想法是:并查集的底层数据结构应该是数组。且为了实现疾速查找的性能,须要一些改变具体实现为:首先对于各个不相交的汇合,给每一个汇合一个id,数组下标代表单个元素的值,数组外面存值为id,对于属于同一个汇合内的元素,则存储雷同的id值,图例:如图,假如{0,1,4,2}属于汇合4,则在数组下标为0 , 1 , 2 , 4处的值都贮存为4,那么当初考虑一下并查集的两个操作:IsConnected():i和j是否属于同一汇合,只需比拟a[i]与a[j]的值,工夫复杂度为O(1)Connect():遍历数组,如果连贯(2,3)以上图为例,只需将a[0],a[1],a[2],a[4]改为5,也就是将汇合1中全副的元素的汇合id都改为汇合2的汇合id,工夫复杂度为O(N) 代码实现public class QuickFindDS implements DisjointSets { private int[] id; public QuickFindDS(int N) { id = new int[N]; for (int i = 0; i < N; i++) id[i] = i; } } public boolean isConnected(int p, int q) { return id[p] == id[q]; } public void connect(int p, int q) { int pid = id[p]; int qid = id[q]; for (int i = 0; i < id.length; i++) { if (id[i] == pid) { id[i] = qid; } }...Performance Summary ...

January 16, 2022 · 2 min · jiezi

关于数据结构和算法:数据结构和算法二复杂度分析

执行效率:运行的更快,更省空间工夫复杂度假如:每行代码执行的工夫都一样,为unit_time剖析起因:测试后果依赖测试环境和数据规模,所以不能预先剖析,须要事先粗略预计。大O复杂度表示法:不具体示意代码真正的执行工夫,只代表代码执行工夫随数据规模增长的变化趋势,也叫渐进工夫复杂度,简称工夫复杂度。复杂度排序:O(1) < O(logn) < O(n) < O(nlogn) < O(n的平方)分析方法只关注循环执行次数最多的一段代码加法法令:总复杂度等于量级最大的那段代码的复杂度。乘法法令:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。非多项式量级指数和阶乘多项式工夫复杂度1、 O(1)示意常量级的工夫复杂度,不是说只执行了一行代码。 个别状况下,只有不存在递归,循环,或者成千上万行代码,工夫复杂度都是O(1) 2、 O(logn)、O(nlogn)对数级工夫复杂度,最难剖析。归并排序、疾速排序的工夫复杂度都是 O(nlogn)。如下: i=1; while (i <= n) { i = i * 2; }3、 O(m+n)、O(m*n)复杂度有两个数据的规模决定,然而无奈评估m和n谁的量级大,故不能省略任何一个。如下: int cal(int m, int n) { int sum_1 = 0; int i = 1; for (; i < m; ++i) { sum_1 = sum_1 + i; } int sum_2 = 0; int j = 1; for (; j < n; ++j) { sum_2 = sum_2 + j; } return sum_1 + sum_2;}空间复杂度算法的存储空间和数据规模之间的增长关系。常见的空间复杂度:O(1),O(n),O(n的平方),对数阶的用得少。思考: ...

January 4, 2022 · 1 min · jiezi

关于数据结构和算法:数据结构和算法一概览

数据结构和算法关系:数据结构是动态的,为算法服务;算法要作用在特定的数据结构上。重点复杂度剖析10个数据结构:1、数组2、链表3、栈4、队列5、散列表6、二叉树7、堆8、跳表9、图10、trie树(字典树)10个算法:1、递归2、排序3、二分查找4、搜寻5、哈希算法6、贪婪算法7、分治算法8、回溯算法9、动静布局10、字符串匹配算法

January 4, 2022 · 1 min · jiezi

关于数据结构和算法:数据结构之队列知识详解

目录:什么是队列?队列有什么个性?队列名词阐明队列类型详解队列实操注释:什么是队列?队列是一种非凡的线性表,非凡之处在于它只容许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列有什么个性?1. 只容许在一端插入和另一端删除2. 遵循先进先出(FIFO)的准则队列名词阐明1. 队列元素队列的数据元素又称为队列元素2. 入队队列的数据元素又称为队列元素3. 出队从队列中删除一个队列元素称为出队队列类型程序队列定义: 程序队列构造必须为其动态调配或动静申请一片间断的存储空间,并设置两个指针进行治理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储地位。操作逻辑: 每次在队尾插入一个元素是,rear增1;每次在队头删除一个元素时,front增1。随着插入和删除操作的进行,队列元素的个数一直变动,队列所占的存储空间也在为队列构造所调配的间断空间中挪动。当front=rear时,队列中没有任何元素,称为空队列。当rear减少到指向调配的间断空间之外时,队列无奈再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是曾经出队的队列元素已经占用过得存储单元。程序队列中的溢出景象: "下溢"景象:当队列为空时,做出队运算产生的溢出景象。“下溢”是失常景象,罕用作程序控制转移的条件。"真上溢"景象:当队列满时,做进栈运算产生空间溢出的景象。“真上溢”是一种出错状态,应设法防止。"假上溢"景象:因为入队和出队操作中,头尾指针只减少不减小,以致被删元素的空间永远无奈从新利用。当队列中理论的元素个数远远小于向量空间的规模时,也可能因为尾指针已超过向量空间的上界而不能做入队操作。该景象称为"假上溢"景象。循环队列定义为了解决程序队列的空间节约状况,在此循环队列入世,能够使队列空间失去重复使用。无论插入或删除,一旦rear指针增1或front指针增1 时超出了所调配的队列空间,就让它指向这片间断空间的起始地位。本人真从MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。这实际上是把队列空间设想成一个环形空间,环形空间中的存储单元循环应用,用这种办法治理的队列也就称为循环队列。操作逻辑在循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种状况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就曾经满了。因而,队列判空的条件时front=rear,而队列判满的条件时front=(rear+1)%MaxSize。 队列实操程序队列实操代码: /** * 程序队列 */System.out.println("**********程序队列**************");SequentialQueue<String> sequentialQueue = new SequentialQueue<>(5, new String[5]);//判断是否为空队列System.out.println("判断是否为空队列:");System.out.println(sequentialQueue.isEmpty());//入队System.out.println("程序队列,入队 sequential queue hello world!:");sequentialQueue.inQueue("sequential ");sequentialQueue.inQueue("queue ");sequentialQueue.inQueue("hello ");sequentialQueue.inQueue("world!");sequentialQueue.inQueue("1!");sequentialQueue.display();//获取队列大小System.out.println("获取队列大小:");System.out.println(sequentialQueue.getSize());//获取队头System.out.println("获取队头:");System.out.println(sequentialQueue.getFront());//出队System.out.println("程序队列,出队一次!");sequentialQueue.outQueue();//获取队列大小System.out.println("获取队列大小:");System.out.println(sequentialQueue.getSize());//获取队头System.out.println("获取队头:");System.out.println(sequentialQueue.getFront());//判断是否为空队列System.out.println("判断是否为空队列:");System.out.println(sequentialQueue.isEmpty());//展现队列sequentialQueue.display();sequentialQueue.inQueue("11");sequentialQueue.inQueue("12");sequentialQueue.display();后果: **********程序队列**************判断是否为空队列:true程序队列,入队 sequential queue hello world!:展现队列:sequential queue hello world! 1! 获取队列大小:5获取队头:sequential 程序队列,出队一次!获取队列大小:4获取队头:queue 判断是否为空队列:false展现队列:queue hello world! 1! 展现队列:queue hello world! 1! 循环队列实操代码: /** * 循环队列 */System.out.println("**********循环队列**************");CircularQueue<String> circularQueue = new CircularQueue<>(10, new String[10]);//判断是否为空队列System.out.println("判断是否为空队列:");System.out.println(circularQueue.isEmpty());//入队System.out.println("循环队列,入队:");circularQueue.inQueue("circular ");circularQueue.inQueue("queue ");circularQueue.inQueue("hello ");circularQueue.inQueue("world!");circularQueue.inQueue("5!");circularQueue.inQueue("6!");circularQueue.inQueue("7!");circularQueue.inQueue("8!");circularQueue.inQueue("9!");circularQueue.inQueue("10!");circularQueue.display();//获取队列大小System.out.println("获取队列大小:");System.out.println(circularQueue.getSize());//获取队头System.out.println("获取队头:");System.out.println(circularQueue.getFront());//出队System.out.println("循环队列,出队一次!");circularQueue.outQueue();//获取队列大小System.out.println("获取队列大小:");System.out.println(circularQueue.getSize());//获取队头System.out.println("获取队头:");System.out.println(circularQueue.getFront());//判断是否为空队列System.out.println("判断是否为空队列:");System.out.println(circularQueue.isEmpty());//展现队列circularQueue.display();circularQueue.inQueue("10!");circularQueue.inQueue("11!");circularQueue.inQueue("12!");circularQueue.display();后果: **********循环队列**************判断是否为空队列:true循环队列,入队:展现队列:circular queue hello world! 5! 6! 7! 8! 9! 获取队列大小:9获取队头:circular 循环队列,出队一次!获取队列大小:8获取队头:queue 判断是否为空队列:false展现队列:queue hello world! 5! 6! 7! 8! 9! null 展现队列:queue hello world! 5! 6! 7! 8! 9! 10! —————————————END———————————— ...

November 30, 2021 · 1 min · jiezi

关于数据结构和算法:1-分钟学会-30-种编程语言

原文链接: 1 分钟学会 30 种编程语言 我始终很艳羡能开发出独立网站的人,这个网站呢,不肯定须要如许浅近的技术,但足够有想法,有意思,好玩。 明天给大家举荐三个网站,我集体很喜爱,好玩又十分实用。 Programming-Idioms第一个举荐的是 Programming-Idioms,idioms 意思是惯用语,习语。 在这里能够了解为罕用的代码片段,或者也能够有其余了解,总之,意思你懂的。 首页是这样的,怎么样,看到这句话: Show me an idioms有没有感觉很亲切? 毕竟咱们程序员约架都是从来不入手的,间接就是徒手撸一个红黑树。 show me the code网站提供了 250+ idioms,怎么玩呢?咱们用 Hello World 举个例子: Hello World 好忙啊,程序员学习编程的第一课,根本相当于英语词典的 abandon。 这个页面列举了泛滥编程语言,点击对应语言就能看到该语言实现输入 Hello World 的形式。 当然了,还能够采纳更直观的形式。这样来看,每种语言的格调高深莫测。 除了 Hello World,还有其余 idioms,比方迭代 map,类型转换等等。 在这里看到了很多之前都没听过的语言,有些语言给人感觉很难受。而有些就一言难尽了,光看个申明变量的形式,就让人喜爱不起来。 CodingFont有了这么多代码片段,是时候抉择一个称手的 IDE 了,而 IDE 什么最重要呢,当然是难看最重要了。 这个网站展现了几十种字体,能够间接在页面上抉择对应字体,实时查看成果,比照两种不同字体的格调。 还能够调整字体大小,直到本人感觉难受为止。 重点来了,网站还提供了字体下载性能,能够说是相当贴心了。 我的最爱当然是 Fira Code,有雷同爱好的小伙伴吗?欢送留言。 VISUALGO最初一个网站也十分棒,学习数据结构和算法者的福音。当然,也给写文章的人提供了很好的可视化素材。 罕用的数据结构和算法,比方排序,链表,树,图等都提供了可视化展现,以动图的形式。 有的时候学习数据结构,只能在脑海里一直想和推演,很累。在这里就能看到一个更直观的展现模式。 比方冒泡排序,把元素之间的比拟和替换过程体现的十分清晰。除此之外,右下角还有文字说明。 图遍历的过程。 还有更多好玩的,就留给读者敌人们缓缓摸索吧。 以上就是本文的全部内容,如果你也珍藏了一些好玩的网站,欢送留言交换。如果你感觉这篇文章还不错的话,欢送点赞和转发。 网站地址: Programming-IdiomsCodingFontVISUALGO举荐浏览: ...

November 15, 2021 · 1 min · jiezi

关于数据结构和算法:数据结构和算法-哈希表

1.什么是哈希表哈希表是联合哈希算法联合其余数据结构形成的一种数据结构。 为什么会产生这样的数据结构呢? 次要起因是在现实状况下,取出和放入元素所耗费的工夫复杂度为 \( O(1) \) 比方咱们看一种常见的实现形式,哈希算法+数组+链表形成的哈希表: 假如数组index从1开始,那么依据哈希算法,元素1就放在数组下标为1的中央,元素3就放在数组下标为3的中央,元素5放在数组下标为1的中央,然而因为元素1曾经在数组中了,对于这种状况,咱们的一种解决形式就是元素1和元素5通过链表连贯,元素1依然在数组中,新退出的元素5放在元素1前面,元素1的next节点指向元素5. 2. java实现哈希表的用处很广,比方java的HashMap、HashSet。 对于哈希表,咱们的操作次要是PUT和GET操作。 上面就来看看怎么实现繁难版本的HashMap。 import java.util.Arrays;import java.util.Objects;/** * key and value are not permitted null * @author bennetty74 * @since 2021.10.24 */public class HashMap <K,V> implements Map<K,V>{ // 申明一个ENTRY数组用于寄存元素 private Entry<K,V>[] entries; private final double loadFactor = 0.75d; private int capacity = 8; private int size = 0; @SuppressWarnings("unchecked") public HashMap() { entries = new Entry[this.capacity]; } @SuppressWarnings("unchecked") public HashMap(int capacity) { this.capacity = capacity; entries = new Entry[capacity]; } @Override public boolean contains(K key) { int idx = getIndex(key); if (entries[idx] == null) { return false; } Entry<K, V> entry = entries[idx]; while (entry != null) { if (entry.key.equals(key)) { return true; } entry = entry.next; } return false; } // put操作,不容许key和value为null public boolean put(K key, V value) { if (Objects.isNull(key) || Objects.isNull(value)) { throw new IllegalArgumentException("Key or value is null"); } int idx = getIndex(key); // if null, put in entries array if (Objects.isNull(entries[idx])) { entries[idx] = new Entry<>(key, value); } else { // else put at the head of Entry list Entry<K, V> newEntry = new Entry<>(key, value); newEntry.next = entries[idx]; entries[idx] = newEntry; } // if load factor greater than 0.75, then rehash if (size++ / (double) capacity > 0.75f) { rehash(); } return true; } private void rehash() { capacity = 2 * capacity; Entry<K, V>[] oldEntries = Arrays.copyOf(this.entries, capacity); entries = new Entry[capacity]; for (Entry<K, V> oldEntry : oldEntries) { if (oldEntry != null) { put(oldEntry.key, oldEntry.value); } } } public V get(K key) { if (Objects.isNull(key)) { throw new IllegalArgumentException("Key is null"); } // get idx by key's hashCode int idx = getIndex(key); Entry<K, V> entry = entries[idx]; if (!Objects.isNull(entry)) { while (!Objects.isNull(entry)) { if (key.equals(entry.key)) { return entry.value; } entry = entry.next; } } // not found the specific key, return null return null; } // 此处的hash算法很简略,就是依据key的hashCode除以entry数组的容量的余数作为数组下标寄存元素 private int getIndex(K key) { return key.hashCode() % capacity; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("HashMap { "); for (int i = 0; i < capacity; i++) { if (entries[i] != null) { Entry<K, V> entry = entries[i]; while (entry != null) { sb.append(entry.key).append("=").append(entry.value).append(","); entry = entry.next; } } } sb.replace(sb.length() - 1, sb.length(), "").append(" }"); return sb.toString(); } // Entry类,蕴含HashMap的key和value private static class Entry<K,V> { K key; V value; public Entry(K key, V value) { this.key = key; this.value = value; } Entry<K,V> next; }}

November 4, 2021 · 2 min · jiezi

关于数据结构和算法:数据结构与算法分析学习笔记四-栈

软件世界是事实世界的投影。引言软件世界的一些概念大多都不是凭空发明,这些概念大多都是从事实世界形象而来。就像咱们本文所探讨的"栈"一样,日常生活中也有这样的模型,比方叠成一摞的碗,最先搁置的反而放在最上面,像上面这样: 这与排队相同。排队对应的一种数据结构,咱们称之为队列。事实上栈这种数据结构在日常应用中还是十分罕用的,就比如说撤销操作,这是软件内广泛内置的操作,就像咱们在日常编码中做了一些批改,想回到上一步,咱们个别就是应用ctrl + z。软件按工夫记录下了你的操作或者是输出,像上面这样: 工夫最靠前的反而在最上面,这样咱们就能吃后悔药了。在比方咱们写代码中的括号匹配,如果少写了一个,编译器是如何晓得的呢? 咱们来剖析一下这个问题: 编号为括号的进入程序, 每个右括号将与最近遇到的那个未匹配的左括号相匹配,即"后进的先出, 或者先进的后出"。领会一下下面的话,每个右括号将与最近遇到的那个未匹配的左括号相匹配。 咱们用p记录左括号的最新地位,用q指向以后新进入的括号,将p与q相比对,如果相匹配,则删除左括号,p后退一位。如果匹配到最初还是有残余的左括号那么这个括号就是不匹配的。 再比方咱们罕用的函数相互调用,Java总是从一个main函数开始,如果咱们的代码是main办法调用a办法,a办法外面又调用c办法,c办法外面又调用d办法,那么不言而喻的是d办法执行结束之后再执行c办法,c办法执行完才执行a办法。这种执行模型也很像栈: 认真领会下面的用例,这些问题的独特的特点就是处理过程中都具备"后进先出"的个性,这是一类典型的问题,咱们将其形象进去也就是数据结构中的"栈"模型。上面开始解说一些栈的概念和一些根本利用。 栈概述上面是栈的一种示意图: 你能够将示意图中的长方体了解为一个竹筒,最下面闭口,最上面关闭,外面放的是写有编号的小球,放入程序根据小球程序,从小到大。如果咱们想从这个竹筒中取球,能够发现一种法则: 先放进去的只能后拿进去; 反之,后放进去的小球可能先拿进去,也就是先进后出,这是栈的典型特点。 当咱们把下面构造中的小球形象为数据结构中的结点时,因为结点间的关系是线性的,因而它也属于线性表,又因为它的操作只能在同一端进行,因而它是一个运算受限的线性表,咱们将这种类型线性表称之为栈。 当初咱们给出栈的定义: 栈是一种非凡的线性表,它所有的插入和删除都限度在表的同一端进行。栈中容许容许进行插入、删除操作的一段叫栈顶(top),另一端则叫栈底(bottom)。当栈中没有元素时,称之为空栈 栈中的插入运算咱们通常称之为压栈、进栈或入栈(PUSH),栈的删除运算通常被称为弹栈(push)出栈(pop).第一个进栈的元素在栈底,最初一个进栈的元素在栈顶,第一个出栈的元素为栈顶元素,最初一个出栈的元素为栈底元素。 可能会有人感觉为什么要引入栈,或者间接用数组和链表实现栈不就行了吗? 为什么要专门划出一种数据结构来? 原则上咱们前面讲的队列、树、图也都是用数组和链表来实现,每一种数据结构都对应了一类专门的数据处理问题,像栈专门解决"先进后出",同时也简化了程序设计思路,放大了咱们的思考范畴。就算是用数组来实现栈,咱们也得让内部调用者无需放心下标越界以及增减的问题,那么这就是一种新的数组,只不过咱们习惯上用栈来称说而已。 同线性表根本相似,栈的根本运算如下: 栈初始化操作: 建设一个空栈表栈判空: 判断栈是否为空取栈顶元素: 取栈顶元素的值压栈: 在栈S中插入元素e, 使其成为新的栈顶元素弹栈: 删除栈S的元素。当初的许多高级语言,比如说Java、C#都内置了对栈数据结构的封装,咱们能够不必关注栈的实现细节,而间接应用对栈的push和Pop的操作。同时也能够借鉴设计思路。 用数组实现的栈咱们称之为程序栈,用链表实现的栈咱们个别就称之为链栈。对于程序栈其实也能够再进行细分,一种是一开始就给定栈的大小,入栈超出大小的时候咱们能够抉择将栈底的上一个元素称为栈底元素。第二种咱们称之为动静栈,栈的大小是动静的,也就是动静扩容,超出栈的下限的时候,主动扩容,栈的大小根本取决于内存的大小。 程序栈咱们尝试用数组来实现以下程序栈,其实程序栈相干的设计也能够参考Java中的相干的设计思路,咱们来看下Java中的Stack: 操作也与咱们下面探讨的统一。那peek和pop有什么区别: peek 取栈顶的元素不删除pop 取栈顶的元素删除并返回栈顶元素Java中Stack的设计思路:栈判空 看数组的理论容量push操作 将元素增加到数组的开端peek 操作 返回数组最初一个元素pop 操作 返回数组的最初一个元素, 同时栈顶元素向后挪动一个地位咱们也能够让咱们建的程序栈继承咱们在数据结构与算法剖析(三) 线性表SequenceList,这样扩容操作咱们就不必在重写一遍,只用实现栈的办法就能够了。 对于办法的设计咱们能够齐全借鉴Java中的Stack: public class SequenceStack extends SequenceList { public SequenceStack() { } public int push(int data){ add(data); return data; } /** * 返回数组的最初一个元素, 同时栈顶元素向后挪动一个地位 * @return */ public int pop(){ if (size() == 0){ // 抛出异样 } int data = peek(); remove(size() - 1); return data; } public int peek(){ if (size() == 0){ // 抛出异样 } return get(size() - 1); } public boolean empty(){ return size() == 0; }}链式栈采纳链式存储形式的栈咱们称之为链栈或链式栈。链栈有一个个结点形成,每个结点蕴含数据数据域和指针域两局部。在链式栈中,利用每一个结点的存储域存储栈中的每一个元素,利用指针域来示意元素之间的关系。像上面这样: ...

September 20, 2021 · 2 min · jiezi

关于数据结构和算法:记一次看到的小心得数据结构和算法

学习数据结构和算法,肯定会在你的工作中派上用场常常会感觉学习数据结构和算法没有什么大的用途,因为工作根本用不上。写完代码就丢了不去优化所以感觉算法没意义,又难又容易遗记。 例如下边代码: data.filter(({id}) => { return selectedIds.includes(id);})逻辑就是筛选出 data 外面曾经被勾选的数据。当200条数据,齐全没压力,性能没影响。但当数据调到了2万条再去测试,代码弊病就裸露进去了,界面进入卡顿,从新抉择的时候也会卡顿。而后就开始了优化,思路如下: 依照代码来看,这是一个两层循环的暴力搜寻工夫复杂度为 O(n^2) 。从 selectedIds.includes(id) 这句动手优化 扭转暴力搜寻的办法根本都是: 上指针数组升维利用 hash 表利用 hash 表思维解决这个问题,因为 js 外面有一个人造的 hash 表构造就是对象。咱们晓得 hash 表的查问是 O(1) 的,所以将代码改写如下: const ids = {};selectedIds.forEach(id => ids[id] = 1);data.filter(({id}) => { return !!ids[id];})将从 selectedIds 查问变成从 ids 查问,这样工夫复杂度就从 O(n^2) 变成了 O(n) 了,这段代码减少了 const ids = {};selectedIds.forEach(id => ids[id] = 1);其实减少了一个 selectedIds 遍历也是一个 O(n) 的复杂度,总来说复杂度是 O(2n),然而从工夫复杂度长期冀望来看还是一个 O(n) 的工夫复杂度,只不过额定减少了一个对象,所以这也是一个典型的空间换工夫的例子,然而也不要放心,ids 用完之后垃圾回收机制会把它回收的。

August 17, 2021 · 1 min · jiezi

关于数据结构和算法:linear-list-链式存储方式经典问题-判定链表中有无cycle-以及-cycle-entrance-附数学推导

仅探讨单向链表中单个cycle的断定和cycle entrance的返回本文内容 快慢指针解决环形链表断定和入口寻找快慢指针成立原理办法推导问题形容给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。阐明:不容许批改给定的链表。应用 O(1) 空间解决此题(即常数内存空间) 作者:力扣 (LeetCode)题源链接:https://leetcode-cn.com/leetb...起源:力扣(LeetCode)题解作者:WildDuck /** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */struct ListNode *detectCycle(struct ListNode *head) { struct ListNode* fast = head; struct ListNode* slow = head; while(fast != NULL && slow != NULL && fast->next != NULL) { slow = slow -> next; fast = fast -> next -> next; if(slow == fast) { struct ListNode* entrance = head; while(entrance != slow) { entrance = entrance -> next; slow = slow -> next; } return entrance; } } return NULL;}------------------------------------------------------------------------------------------------------------原理推导 ...

August 5, 2021 · 1 min · jiezi

关于数据结构和算法:码不停题算法篇二叉树的层序遍历

题目给你一个二叉树,请你返回其按 层序遍历 失去的节点值。 (即逐层地,从左到右拜访所有节点)。 示例: 二叉树:{val: 3, left: {val: 9}, right: {val: 20, left: {val: 15}, right: {val: 7}}} 返回其层序遍历后果: [[3],[9,20],[15,7]] 广度优先遍历和深度优先遍历深度优先遍历其实相似于树的先序遍历,而广度优先遍历相似树的档次遍历。 上图树的深度优先遍历就是程序 ABDEGCF 办法:从树的根节点开始,沿着树边缘划线,第一次遇到的节点便拜访它。 上图树的广度优先遍历就是程序 ABCDEFG 办法:从树的根节点开始,从上到下,从左到右顺次拜访节点。 题解思路二叉树的层序遍历其实就是二叉树的广度优先遍历。广度优先遍历即一层一层往下遍历查找,能够设想一下,一个二叉树型数据结构,从上到下遍历,找到以后层级的数据后把以后层级的数据归类到一个数组外面,而后持续往下走,持续分类,这里的要害就是你要始终判断你以后正在查找的树所在的节点和下一次要去的节点地位。每个节点都有一个“指针指向下一个节点”,并且你下一次遍历的层级所领有的节点个数正是你以后遍历的节点领有的指针的个数(left和right),那么,咱们只有遍历到以后节点,并且判断他身上的“指针”,有的话就push到下一次要遍历的数组中,以后层遍历完后要把以后层扔掉,因为你下次遍历树的时候就不能反复去遍历了 。咦,先遍历到的要先扔掉,这不就是先进先出,队列刚好满足先进先出的条件。 代码实现/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } *//** * @param {TreeNode} root * @return {number[][]} */var levelOrder = function(root) { const res = []; // 定义一个用来装后果的数组 if (!root) return res; // 树为空的时候返回为空数组 const queue = []; // 定义一个队列 queue.push(root); // 把树结构放进队列,先遍历跟节点(最上层) while (queue.length) { // 只有队列中还有下次要遍历的树层构造,就要持续遍历 const subRes = []; // 寄存每一层级分类好的节点数据 const len = queue.length; // 树的每一层须要遍历的节点数量 for(let i = 0; i < len; i++) { // 遍历每一个节点,拿到数据(val)后推动以后层数组(subRes) const node = queue.shift(); // 拿到以后遍历到的节点,并扔掉(解决完就出队列) subRes.push(node.val); // 把以后节点数据拿到推动以后层数组(subRes) if (node.left) queue.push(node.left); // 判断以后节点左指针有没有数据,有的话,放进队列 if (node.right) queue.push(node.right); // 同理 } res.push(subRes); // 解决分类后的以后层数据放入后果数组中,持续下一次循环 } return res;};

July 21, 2021 · 1 min · jiezi

关于数据结构和算法:码不停题算法篇变位词组

题目编写一种办法,对字符串数组进行排序,将所有变位词组合在一起。变位词是指字母雷同,但排列不同的字符串。 示例: 输出: ["eat", "tea", "tan", "ate", "nat", "bat"],输入:[ ["ate","eat","tea"], ["nat","tan"], ["bat"]]阐明: 所有输出均为小写字母。不思考答案输入的程序。 题解思路办法一:遍历数组,将以后遍历到的元素跟剩下的其余元素进行比照,符合要求(字母雷同,但排列不同)的归类到同一组,不符合要求的独自归类一组。重点就是如何判断以后元素是否符合要求,是否有对应能够归类 的数组。字母雷同,但排列不同,可知,两个元素length雷同,并且A字符串外面的所有字符都应该在B字符串里能找到雷同的元素,同时B字符串外面的所有字符也都应该在A字符串里能找到雷同的元素。并且以后元素没有被归类过。这样的话,如果,已归类的数组里没有找到与该元素字母雷同,但排列不同的元素,那就独自归类到一组,期待其余元素退出,否则,就把该元素退出到符合要求的那一组中去。 弊病:屡次循环遍历数组实现,效率较低,执行工夫慢。 办法二:看到字母雷同,但排列不同这几个字眼,应该能立马想到既然是字母都雷同,那我让所有元素都按字母排序好再进行比拟,不就能直接判断两个元素是否相等了吗,相等的话push到同一个归类数组里,否则独自给他push到一个归类数组,期待下一个元素退出。然而你排序后,要返回的还是排序前的字母。所以,有没有一种数据结构既能保留两种状态?相比拟于Object,Map更能轻松的获取键值对和判断是否存在某个值。这道题用Map和排序的思路能够很轻松的实现。并且代码量较少。代码实现办法一: /** * @param {string[]} strs * @return {string[][]} */const groupAnagrams = function(strs) { let results = []; // 后果数组 const includesEl = []; // 曾经归组的元素 for(let i = 0; i < strs.length; i++) { const tempArr = []; // 长期的分组数组 if (includesEl.findIndex(item => item.value === strs[i]) === -1 || ((includesEl.findIndex(item => item.value === strs[i]) !== -1) && includesEl.findIndex(item => item.index === i) === -1)) { // 不在曾经归组的元素外面或者在曾经归组的元素外面然而只是值一样,不是同一个元素 tempArr.push(strs[i]); includesEl.push({value: strs[i], index: i}); } for(let j = i + 1; j < strs.length; j++) { if (strs[i].length === strs[j].length) { const iArr = strs[i].split(''); const jArr = strs[j].split(''); const flag = iArr.every(char => { return jArr.includes(char) && jArr.filter(item => item === char).length === iArr.filter(item => item === char).length; }) if (flag && (includesEl.findIndex(item => item.value === strs[j]) === -1 || ((includesEl.findIndex(item => item.value === strs[j]) !== -1) && includesEl.findIndex(item => item.index === j) === -1))) { tempArr.push(strs[j]); includesEl.push({value: strs[j], index: j}); } } } results.push(tempArr); } results = results.filter(i => i.length); return results;};办法二: ...

July 21, 2021 · 2 min · jiezi

关于数据结构和算法:数据结构与算法分析学习笔记一-数据结构简论

仁厚光明的地母呵,愿在你怀里永安她的魂灵!— 《阿长与山海经》 鲁迅从新视角来对待旧问题,则须要创造性思维。— 爱因斯坦前言咱们先不看数据结构,权且能够将数据当做一个限定词,单纯来看构造,仅以构造来看,构造是什么? 构造一次呈现在各个行业,比方修建构造,人体构造,物质构造。那构造是什么: 构造是指在一个零碎或者资料之中,相互关联的元素的排列、组织 --《维基百科》直观上能被人所感触到的是就是屋宇的构造,在走入一间房子,各个房间之间的排列。再进入到卧室,家具之间的排列。如果设计的好,那么这个房间通常状况下会让刚进来的人充斥好感。那么如果排列摆放的不好,那么就可能让人对这个房间产生不好的感觉。比方将卧室灯的开关不放在床边,对立放在门口,这样在睡觉的时候,你就不得再下床,将灯关掉。个别的设计是门口放一一个开关,这样设计的目标是为了在进门的时候就间接能将灯关上。因为修建面向的始终是人,所以咱们总会讲宜居的修建。 那么咱们尝试在构造下面加上数据,那么就能够得出类比数据结构的定义,在一些数据之中,数据之间的排列、组织。在计算机科学中,这个数据是涵盖比拟广的名词,咱们以日常十分罕用的ctrl+z(撤销操作)来阐明,咱们的一个一个操作被操作系统记录成了上面这种模式: 以工夫为轴线,最先的操作在最上面,这样咱们ctrl+z的时候就能复原到离以后工夫最近的操作。通常这种组织数据结构的模式,咱们个别称之为栈(也有称之为堆栈),先进后出, 在这个场景下,操作也是数据,数据在计算机科学的含意是多种多样的,可能在其余畛域数据更偏差于数字多一点。这种组织数据的模式也是合乎直觉的。 咱们在来看一下数据结构在维基百科中的定义: 在计算机科学中,数据结构(英语:data structure)是计算机中存储、组织数据的形式。这次的类比剖析,看来咱们类比的方向是正确的,这个存储咱们能够了解为容器,我想起之前和我共事之前的一次下厨,他从家带来了一桶泡菜,这个桶我过后记得是那种油桶,就是口比拟细,他过后让我取点酸豆角,这一度让我很苦楚,因为用筷子夹进去真的很不容易,咱们能够将泡菜里的菜了解为数据结构中的数据,存储就由那个油桶来实现,组织数据的形式没有什么法则。悄悄的吐槽一下,那这个数据结构设计的就蛮不合理的,除了在运输的时候,显的略微不便一点,然而要吃的时候是真的不不便。数据结构简论程序所要解决的数据学过程序设计的都晓得,用人脑驱动计算机的形式是编程。瑞士驰名的计算机科学家,赫赫有名的Pascal之父尼古拉斯·沃斯(Niklaus Wirth)就变成的概念,提出了一个驰名的公式,并由此取得了计算机科学界的最高荣誉"图灵奖",公式仅仅有一行文字: Algorithm(算法) + Data Structures(数据结构) = Program(程序)软件程序倒退到明天,咱们能够说这个公式并不是那么大的恰如其当,因为软件前面开始跟上工程了这个名词,思考的因素更多了。编程首先要解决两个问题: 算法设计和数据结构设计(许多高级语言都内置了相当成熟的数据结构,但这并不代表学习数据结构曾经失去了意义,一方面这是考点,另一方面它也的确可能帮忙咱们解决不少问题)。算法是解决问题的策略,而数据结构是形容问题信息的数据模型,程序则是计算机依照解决问题的策略解决问题信息的一组指令集。 咱们程序设计的目标就是让计算机帮忙人们主动地实现所要解决的简单工作,计算机科学与技术的基本问题就是--什么可能被自动化以及无效的自动化? 具体的由理论的问题到最终的计算机求解,要通过怎么的过程? 咱们经常要剖析的就是,问题模型中信息蕴含的数据是什么、数据间的分割是什么、以什么样的模式存储在计算机中,剖析后造成数据结构。在上世纪三四十年代,最后创造电子计算机的目标是进行迷信和工程计算,其解决的对象是纯数值性的信息,通常人们把这类问题称之为数值计算(具体的说就是无效利用计算机求解数学问题近似解的办法与过程)。 近十几年来,随着计算机的疾速倒退,这不仅体现在计算机本身性能一直进步,更重要的是可输出到计算机中的数据,其领域被极大扩充,比方,符号、声音、图像等多种信息都能够通过编码存储到计算机中,与此对应,计算机的解决对象也从简略的纯数值型信息倒退到具备肯定构造的信息,计算机的应用领域也在不断扩大。 所谓的"非数值计算"问题,是为了辨别后面提到的"数值问题"而言的,非数值问题波及的数据及数据间的互相关系(留神这里的互相关系,粗略的说咱们能够认为,数据和数据之间的关系形成了数据结构),个别无奈用数学公式、方程等形容。如排序问题,检索问题等。须要另外设计数据的形容办法和相应的算法来解决。 举个例子,求的值咱们就能够认为是数值运算的典型问题。对于非数值运算,让咱们来看一下现实生活的一个理论场景,比如说打电话,通常当初的智能手机的通讯录都是按姓氏首字母的拼音来进行排序的,这样也能疾速查找。这是事实上就是一个非数值问题,咱们在查找数据,每个数据由姓名+电话形成,咱们在查找的时候,查找策略就有如下几种: 程序查找(一个一个查找,如果这个人的姓名的首字母排名靠前还算比拟侥幸,然而如果可怜排在最初,比如说姓张,如果就要查找到最初能力找到你要分割的人)依据拼音首字母查找,而后在程序查找。然而这样其实这会有些问题,如果你姓张的敌人比拟多,那可能你还是要查找一段时间(个别的智能手机都反对按名字来搜寻,事实上这是另一种搜寻策略)再比方咱们日常司空见惯的红绿灯,咱们天然会提出这么一个问题,在十字路口,要设置几种色彩的交通灯能力放弃失常的程序,这个问题咱们很快就能得出答案,两种。然而如果输出到计算机中呢? 让计算机来求解,就不是一个容易的问题了。首先要解决的问题是如何把题目中的信息存储到计算机中,而后在此基础上能力设计算法求解。 咱们来尝试来解决一下这个问题,或者说将问题所波及的信息建模而后输出到计算机,路口如下图: 问题波及的对象,四个路口ABCD,及相应的通路。用AC示意A到C有一条路线。某方向通行时,另外某些方向不能通行。AC-BD示意AC、BD不能同时通行。假如左拐通行规定和直行统一、右拐随时都容许。依据以上剖析咱们能够画出上面的路线图: 这种构造咱们在数据结构中个别称之为图数据结构,每个路线咱们称之为一个结点。 咱们再来看下计算机的文件目录,咱们晓得一个文件夹能够蕴含一个文件夹,能够这样层层包下去,在操作系统中咱们也是将每个文件目录形象成一个结点,则这个文件目录的结构图就如下所示: 这在数据结构中通常被称作树,最下面的文件咱们称之为根结点。 数据结构的引入从后面的例子咱们能够看出,非数值问题中的树、图等构造模型,无奈用方程式等加以形容,因而解决此类问题的要害不再是剖析数学和计算方法,而是先找出问题中要解决的数据及数据之间的分割、组织模式、存储形式、示意办法等,再设计出适宜计算机解题的模型。 个别认为,数据结构是由数据元素根据其自身外在的逻辑组织分割组织起来的。对数据元素间的逻辑关系的形容称为数据的逻辑构造(下面咱们提到的树与图就能够认为是逻辑构造);数据必须存储在计算机内,数据的存储构造是数据结构的实现模式。是计算机内的示意;除此之外,探讨一个数据结构必须同时探讨在该类型上执行的运算才有意义。一个逻辑存储构造(有的时候咱们也称之为abstract data type))能够有多种存储构造,在不同的存储构造之上,数据处理的效率是不同的。 数据结构的基本概念通过下面的探讨置信各位同学对数据结构曾经有了一些根本的意识,上面咱们来介绍一些数据结构的罕用术语: 数据(Data) 在计算机科学中,数据是指所有能输出到计算机中并被计算机程序解决的符号的总称。数据元素(Data Element) 数据的根本单位,也称"元素"和"结点",在计算机程序中通常作为一个整体进行思考和解决。一个元素能够蕴含多个数据项。数据项(Data Item) 数据项是具备独立含意的最小标识单位,是数据最根本的、不可再分的数据单位数据结构 由某一数据对象及该对象中所有数据成员之间关系的组成。数据机构蕴含三个因素: 数据的逻辑构造、数据的存储构造、数据的运算。数据的逻辑构造数据的逻辑构造,反映了咱们对数据含意的解释,它能够用一组数据以及这些数据之间的关系示意。数据的逻辑关系又分为以下几种类型: 汇合 只是被放在一个容器中,像我下面提到的泡酸菜一样,菜与菜之间没有什么分割。线性构造 结点间的关系是一对一的,像是排队一样,也像是的撤销操作。树形构造。 结点间是一对多的关系,像下面的文件目录一样。图形构造 结点间是多对多的关系,像下面的图构造一样。数据的存储构造数据的存储构造又称为物理构造,是数据及其逻辑构造在计算机中示意办法,指数据如何在计算机中寄存,本质上是内存单元调配,在具体实现时用计算机语言中的数据类型(Data Type)。 这里的数据类型咱们也能够了解为容器,对于整数咱们采取一个容器,对于小数咱们也独自采纳一个容器。常见的数据存储形式有四类: 顺序存储构造 用一组间断的存储单元来顺次存储数据元素,数据之间的逻辑关系由元素的存储相邻地位来体现。数组是一种常见的数据存储构造链式存储构造 在每一个数据元素中减少指针项(没指针的通过援用),以记录数据元素间的逻辑关系。索引存储构造 在存储结点信息的同时,建设一个附加的索引表,相似于字典中的索引项。散列存储构造 散列存储形式,以结点的关键字为自变量,通过函数关系,间接计算出该结点的存储地址。数据的运算数据的运算有两个方面的定义,运算的定义与运算的实现,运算的定义,取决于数据的逻辑构造,晓得了问题中的数据及数据间的分割,咱们就能够设计相应的数据处理办法,个别常见的运算操作有如下这些: 初始化: 对存储构造设置初始值,或者申请存储空间判空: 判断存储空间是否未寄存有效值的状态求长度: 统计元素个数查找: 判断是否蕴含指定元素遍历: 按某种秩序拜访所有的元素,每个元素只拜访一次取值: 获取指定元素值插入: 减少指定元素删除: 删除指定元素写在最初本篇就拉开了数据结构与算法学习的尾声,我的想法是在解说算法题目之前,先将根底打牢。有些文字兴许只有有一些经历能力懂得一样,就像初中学习的鲁迅学生的文章《阿长与山海经》一样。有的时候,总须要经验,能力有所领会。 参考资料《数据结构与算法剖析新视角》 周幸妮 任智源 马彦卓 樊凯 编著《 低等代数扼要教程》 蓝以中

July 4, 2021 · 1 min · jiezi

关于数据结构和算法:数据结构快速排序

疾速排序是效率比拟高的一种排序算法,其思维次要是递归 package mainimport "fmt"func QuickSort(left int, right int, arr *[6]int) { l := left r := right pivot := arr[(left + right) / 2] for ; l < r; { for ; arr[l] < pivot; { l++ } for ; arr[r] > pivot; { r-- } if l >= r { break } arr[r], arr[l] = arr[l], arr[r] if arr[l] == pivot { r-- } if arr[r] == pivot { l++ } } if l==r { l++ r-- } if left < r { QuickSort(left, r, arr) } if right > l { QuickSort(l, right, arr) }}func main(){ arr := [6]int{-9, 78, 0, 23, -57, 70} fmt.Println(arr) QuickSort(0, len(arr) - 1, &arr) fmt.Println(arr)}输入: ...

July 2, 2021 · 1 min · jiezi

关于数据结构和算法:数据结构插入排序

插入排序的一种Go实现 package mainimport "fmt"//插入排序是仅次于疾速排序的高效排序算法,插入排序和冒泡排序、抉择排序一样都是外部排序(内存)//与其余两个不同的是插入排序是将无序的向有序的数组外面插入,因而区别于其余两个都是循环无序的排序算法func InsertSort (arr *[5]int) { //插入排序的核心思想是假如第一个元素为有序的,前面的都是无序的,挨个将前面无序的插入到后面有序的过程 for i := 1; i < len(arr); i++ { insertVal := arr[i] insertIndex := i - 1 //降序 for insertIndex >= 0 && arr[insertIndex] < insertVal { arr[insertIndex + 1] = arr[insertIndex] //插入到以后地位的下一个地位 insertIndex-- //如果没有适合的地位向前持续找插入地位 } //插入 if insertIndex + 1 != i { arr[insertIndex + 1] = insertVal } fmt.Printf("第%d次插入后%v\n", i, *arr) }}func main(){ arr := [5]int{23, 0, 12, 56, 34} InsertSort(&arr)}输入: ...

July 2, 2021 · 1 min · jiezi

关于数据结构和算法:数据结构选择排序

抉择排序的一种Go实现 package mainimport "fmt"//抉择排序的外围是第n次将数组中前面len(arr) - n个元素的最大或者最小值与第n个元素进行替换func selectSort(arr *[5]int){ for j := 0; j < len(arr) - 1; j++ { //循环次数为len(arr) - 1 //假如第一个元素的值最大(降序排序) max := arr[j] maxIndex := j //遍历前面的元素进行比拟 for i := j + 1; i < len(arr); i++ { if max < arr[i] { max = arr[i] //这里不能焦急替换值,只须要把最大的给max maxIndex = i } } //替换值 if maxIndex != j { arr[j], arr[maxIndex] = arr[maxIndex], arr[j] } fmt.Printf("第%d次 %v\n", j+1, *arr) }}func main(){ arr := [5]int{1,3,6,9,8} selectSort(&arr)}输入: ...

July 2, 2021 · 1 min · jiezi

关于数据结构和算法:栈这种数据结构不就后进先出

什么是栈栈在咱们日常编码中遇到的十分多,很多人对栈的接触可能仅仅局限在 递归应用的是栈 和 StackOverflowException,栈是一种后进先出的数据结构(能够设想生化金字塔的牢房和生化角斗场的狗洞)。 栈是这么定义的: 栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,绝对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的下面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。 略微介绍一下要害名词: 运算受限:也就是这个表你不能轻易的删除插入。只能依照它的规定进行插入删除。比方栈就只能在一端进行插入和删除。同样,队列也是运算受限,只能在中间操作。 线性表:栈也是一种线性表,后面具体介绍过线性表,它表白的是一种数据的逻辑关系。也就是在栈内各个元素是相邻的。当然在具体实现上也分数组和链表实现,他们的物理存储构造不同。然而逻辑构造(实现的目标)雷同。 栈顶栈底: 这个形容是偏差于逻辑上的内容,因为大家晓得数组在开端插入删除更容易,而单链表通常在头插入删除更容易。所以数组能够用开端做栈顶,而链表能够头做栈顶。 栈的利用: 栈的利用宽泛,比方你的程序执行查看调用堆栈、计算机四则加减运算、算法的非递归模式、括号匹配问题等等。所以栈也是必须把握的一门数据结构。最简略大家都经验过,你拿一本书高低叠在一起,就是一个后进先出的过程,你能够把它看成一个栈。上面咱们介绍数组实现的栈和链表实现的栈。 数组实现数组实现的栈用的比拟多,咱们常常刷题也会用数组去实现一个简略的栈去解决简略的问题。 结构设计 对于数组来说,咱们模仿栈的过程很简略,因为栈是后进先出,咱们很容易在数组的开端进行插入和删除。所以咱们选定开端为栈顶。所以对于一个栈所须要的根底元素是 一个data[]数组和一个top(int)示意栈顶地位。 那么初始化函数代码为: private T data[];private int top;public seqStack() { data=(T[]) new Object[10]; top=-1;}public seqStack(int maxsize){ data=(T[]) new Object[maxsize]; top=-1;}push插入 栈的外围操作之一push():入栈操作。 如果top<数组长度-1。入栈,top++;a[top]=value;如果top==数组长度-1;栈满。 pop弹出并返回首位 如果top>=0,栈不为空,能够弹出。return data[top--];如下图,原本栈为1,2,3,4,5,6(栈顶),执行pop操作,top变为3的地位并且返回4; 其余操作 例如peek操作时返回栈顶不弹出.所以只需满足要求时候return data[top]即可。 数组实现: package 队栈;public class seqStack<T> { private T data[]; private int top; public seqStack() { data=(T[]) new Object[10]; top=-1; } public seqStack(int maxsize) { data=(T[]) new Object[maxsize]; top=-1; } boolean isEmpty() { return top==-1; } int length() { return top+1; } boolean push(T value) throws Exception//压入栈 { if(top+1>data.length-1) { throw new Exception("栈已满"); } else { data[++top]=value; return true; } } T peek() throws Exception//返回栈顶元素不移除 { if(!isEmpty()) { return data[top]; } else { throw new Exception("栈为空"); } } T pop() throws Exception { if(isEmpty()) { throw new Exception("栈为空"); } else { return data[top--]; } } public String toString() { if(top==-1) { return ""; } else { String va=""; for(int i=top;i>=0;i--) { va+=data[i]+" "; } return va; } }}链表实现有数组实现,链表当然也能实现。对于栈的设计,大抵能够分为两种思路: ...

June 22, 2021 · 4 min · jiezi

关于数据结构和算法:数据结构和算法汇总后续不断更新

体系结构: 一、工夫复杂度和空间复杂度1、什么是工夫复杂度和空间复杂度如何辨别一个算法的好坏,如果在程序上执行,会被各种因素所烦扰,所以引出了工夫复杂度和空间复杂度的概念。 工夫复杂度就是这个算法执行的是不是很快,空间复杂度就是这个算法是不是很消耗程序的空间。 算法的渐进工夫复杂度:T(n) = O(F(n))------>大O表示法。 2、工夫复杂度比方咱们这个算法要执行很屡次,那么它的表达式是怎么样的?取最高次项即可。比方一行最根本的代码,它的就是O(1);如果某个算法中计算的工夫雷同,它必定是次数越多工夫越长,而且是线性增长,比方每次吃鸡腿都是1分钟,那么它执行n次,工夫就是n分钟,所以它的工夫复杂度就是O(n);如果计算1到100的和,计算的表达式就是(1 + n)* n/2-->也就是0.5n² + 0.5n,疏忽掉n的一次方,那么它的工夫复杂度就是O(n²)再比方一根绳子16厘米,每次剪掉残余的一半,那么多久会剩下1厘米,那么就须要用到对数了,这个时候工夫复杂度是O(log16)3、空间复杂度空间复杂度不须要过于深刻理解,然而要晓得它不是形容占用多少内存,而是比拟的内存变动。 比方一个变量=1,而后每次都对它进行++赋值运算,变量还是一个,只不过一直的赋值,内存占用不会变动,所以还是1再比方一个for循环,每次都创立一个新的变量,必定它的空间复杂度是n如果是一个二维数组,再用双层for循环赋值,它的空间复杂度就是n²二、数组1、介绍数组是最根本的数据结构之一,能够存储无限个元素(固定长度),能够增删改查2、代码实现public class MyArray { //定义一个数组 int[] elements; //初始化数组 public MyArray(){ elements = new int[0]; } //获取数组的长度 public int size(){ return elements.length; } //往数组的开端增加一个元素 public void add(int ele){ int[] newArr = new int[elements.length + 1]; for (int i = 0;i < elements.length;i++){ newArr[i] = elements[i]; } newArr[elements.length] = ele; elements = newArr; } //遍历数组的办法 public void arrayShow(){ System.out.println(Arrays.toString(elements)); } //删除一个元素 public void delete(int index){ if (index < 0 || index > elements.length - 1){ throw new RuntimeException("传入下标不正确"); } int[] newArr = new int[elements.length - 1]; for (int i = 0;i < newArr.length;i++){ if (i < index){ newArr[i] = elements[i]; }else{ newArr[i] = elements[i + 1]; } } elements = newArr; } //取出指定地位的元素 public int get(int index){ if (index < 0 || index > elements.length -1){ throw new RuntimeException("传入下标不正确,不能读取元素"); } return elements[index]; } //插入一个元素到指定地位 public void insert(int index,int ele){ int[] newArr = new int[elements.length + 1]; for (int i = 0;i < newArr.length;i++){ if (i < index){ newArr[i] = elements[i]; }else{ newArr[i + 1] = elements[i]; } } //插入新的元素 newArr[index] = ele; //替换数组 elements = newArr; } //替换其中的一个元素 public void update(int index,int ele){ if (index < 0 || index > elements.length - 1){ throw new RuntimeException("传入下标不正确,不能批改数组"); } elements[index] = ele; } //线性查找 public int search(int target){ for (int i = 0;i < elements.length;i++){ if (elements[i] == target){ return i; } } //未找到相应元素 return -1; }}测试数组: ...

June 9, 2021 · 29 min · jiezi

关于数据结构和算法:学生信息管理系统c语言简版章节实验作业

学生信息管理系统(c语言简版)——章节试验作业#include<stdio.h>#include<stdlib.h>#include<string.h>#include<windows.h>#define MaxSize 50typedef struct { char number[20]; char name[20]; int math; int computer; int ad_math; int average;}Student;typedef struct{ Student student[MaxSize]; int length;}SqList;void Show() //菜单{ printf("**********************************\n"); printf(" WELCOME!\n"); printf("\n"); printf(" 新科院学生信息管理系统\n\n"); printf(" \t 1.查问\t 2.插入\n\n"); printf(" \t 3.删除\t 4.打印\n\n"); printf(" \t 5.排序\t 6.退出\n\n"); printf(".............7.录入.............\n"); printf("\n"); printf(" 输出相干序号实现相干性能!\n"); printf("**********************************\n");}void InitList(SqList*& L) //初始化{ L = (SqList*)malloc(sizeof(SqList)); L->length = 0;}void CreateList(SqList*&L) //录入信息{ int i = 0, n; printf("请输出录入学生人数:"); scanf("%d", &n); for (i = 0; i < n; i++) { printf("姓名:"); scanf("%s", &L->student[i].name); getchar(); //排汇换行符 printf("学号:"); scanf("%s", &L->student[i].number); getchar(); printf("数学问题,计算机问题,高数问题:\n"); scanf("%d%d%d", &L->student[i].math, &L->student[i].computer, &L->student[i].ad_math); L->student[i].average = (L->student[i].ad_math + L->student[i].computer + L->student[i].math) / 3; //均匀问题 getchar(); //排汇换行符 if (i == n - 1) //强制跳出循环 break; } L->length = n; printf("录入胜利!"); Sleep(3000);}void DispList(SqList*&L) //打印{ int i, flag; if (L->length==0) //判空 { printf("道歉!无数据,请录入!"); Sleep(3000); return; } while (1) { for (i = 0; i < L->length; i++) { printf("姓名:%s\t", L->student[i].name); printf("学号:%s\n", L->student[i].number); printf("数学问题:%d\t", L->student[i].math); printf("计算机问题:%d\t", L->student[i].computer); printf("高数问题:%d\t", L->student[i].ad_math); printf("均匀问题:%d\n", L->student[i].average); printf("............................\n"); } printf("输出0退出打印!\n"); scanf("%d", &flag); if (flag == 0) //跳出打印结果显示 break; }}void SeekElem(SqList* L) //查问{ int i = 0, flag; char num[MaxSize]; printf("请输出要查找学生信息的学号:\n"); scanf("%s", num); while (1) { while (i < L->length && strcmp(num, L->student[i].number) != 0) { i++; } if (strcmp(num, L->student[i].number) == 0) { printf("姓名:%s\t", L->student[i].name); printf("学号:%s\n", L->student[i].number); printf("数学问题:%d\t", L->student[i].math); printf("计算机问题:%d\t", L->student[i].computer); printf("高数问题:%d\t", L->student[i].ad_math); printf("均匀问题:%d\n", L->student[i].average); } else printf("查无此人!\n"); printf("输出0退出打印!\n"); scanf("%d", &flag); if (flag == 0) //跳出打印结果显示 break; }}void ListInsert(SqList*& L) //插入{ int n, i, flag; printf("请输出插入学生信息个数:"); scanf("%d", &n); printf("请输出插入学生信息:\n"); for (i = 0; i < n; i++) { printf("姓名:"); scanf("%s", &L->student[L->length + i].name); getchar(); //排汇换行符 printf("学号:"); scanf("%s", &L->student[L->length + i].number); getchar(); printf("数学问题,计算机问题,高数问题:\n"); scanf("%d%d%d", &L->student[L->length + i].math, &L->student[L->length + i].computer, &L->student[L->length + i].ad_math); getchar(); //排汇换行符 L->student[i].average = (L->student[L->length + i].ad_math + L->student[L->length + i].computer + L->student[L->length + i].math) / 3; //均匀问题 } L->length = L->length + n; printf("\a"); printf("插入胜利!"); Sleep(2000); //显示插入胜利提醒}void ListDelete(SqList*& L) //删除{ int i, j; char num[MaxSize]; printf("请输出要删除学生信息的学号:\n"); scanf("%s", num); for (i = 0; i < L->length; i++) { if (strcmp(num, L->student[i].number) == 0) { for (j = i; j < L->length - 1; j++) L->student[j] = L->student[j + 1]; L->length--; printf("删除胜利!"); Sleep(3000); return; //删除胜利,强制退出函数 } } printf("输出谬误!\a"); Sleep(3000);}void Sort(SqList*&L) //排序{ int i, j; Student temp; printf("依照均匀问题降序输入学生的信息如下:\n"); for (i=0; i<L->length-1; i++) for (j = 0; j < L->length - i - 1; j++) //冒泡排序(降序排列) { if (L->student[j].average < L->student[j + 1].average) { temp = L->student[j]; L->student[j] = L->student[j+1]; L->student[j+1] = temp; } } DispList(L);}int main(){ int n; SqList* L; InitList(L); while (1) { system("cls"); //清屏函数 Show(); printf("请输出1-7的数:"); scanf("%d", &n); switch (n) { case 1: SeekElem(L); break; case 2: ListInsert(L); break; case 3: ListDelete(L); break; case 4: DispList(L); break; case 5: Sort(L); break; case 6: printf("........正在退出中........"); Sleep(3000); system("cls"); printf("\a退出胜利!"), exit(0); break; case 7: CreateList(L); break; default: printf("\a输出谬误!请从新输出(1—7)!"), Sleep(2000); break; } }}

May 23, 2021 · 3 min · jiezi

关于数据结构和算法:LeetCode刷题日记之盛最多水的容器

节后第一天,鉴于五一五天都没做过题,有点忘记了,明天来看一道简略点的题,练下手。 先看下题: 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点别离为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴独特形成的容器能够包容最多的水。 输出:[1,8,6,2,5,4,8,3,7]输入:49 解释:图中垂直线代表输出数组 [1,8,6,2,5,4,8,3,7]。在此状况下,容器可能包容水(示意为蓝色局部)的最大值为 49。 暴力法个别看完这个题优先的思路是暴力法,两层循环嵌套,而后失去最大值,工夫复杂度为O(n^2),因为官网曾经加大了测试用例的规模,暴力法会超时,曾经过不了了。 那咱们想想其余的办法吧,在数组和链表两个比较简单的线性表求面积的时候,常常会用到一种思维,双指针,这个前面遇到这类题的时候,没思路能够往这方面想。 双指针什么叫双指针呢,顾名思义,就是用两个指针(内存里的指针是记录援用地址的内存空间,这个地址会指向援用的理论存储地位),记录左右边界地位,而后依照肯定条件挪动双指针,达到求最大面积的目标。 以这个题为例,咱们要求面积最大值,就须要宽和高足够大就能够,然而咱们须要晓得柱子的面积最大值,就须要挪动左右指针,该怎么定义挪动规定呢? 其实很简略,咱们假如左指针left=0,右指针right=a.length-1;,这时候有V=(right-left)*Math.min(a[left],a[right]),而后咱们这时候想要失去比这个面积大的区域,首先left和right肯定会往两头走,宽肯定是会变小的,要使面积变大,就肯定要高竟可能大,因而咱们舍弃高较小的那根柱子,而后一直反复上述过程,直到left==right。 咱们不难发现,利用双指针法,胜利将工夫复杂度由O(n^2)降为了O(n).所以能够晓得双指针法能缩小一层遍历。 思路想明确之后代码实现就很简略了,上面是其中一种实现: int max = 0;for (int left = 0, right = a.length - 1; left < right; ) { int v = (right - left) * (a[left] < a[right] ? a[left++] : a[right--]); max = max < v ? v : max;}return max;写在最初数组相干的题大多比较简单,很多都是一些固定的模式,一开始可能感觉很神奇,这货色还能这么玩?然而摸清楚套路之后感觉也就那样,所以数组相干的题没啥留神的,多练就行。 ...

May 19, 2021 · 1 min · jiezi

关于数据结构和算法:数据结构与算法数组的增删改查

前言作为重要的线性数据结构, 咱们常常会跟数组打交道。所谓数组,就是一系列雷同数据类型元素的汇合,数据类型能够是 int、float、String、类……。而对数组的增删改查则是日常用到的操作。为了弄清楚这些罕用操作,此博客则对这些操作进行一一梳理。 数组简介如何创立数组咱们以 Java 中创立数组为例,创立语法如下: dataType[] arrName = new dataType[size];其中各个字段的含意如下: dataType:也就是咱们数组中元素的数据类型;arrName:即数组名;size:即数组所能包容的元素数量;new:Java 语言中的关键词;假如咱们要创立一个由 10 个元素的数组,其中元素的数据类型为 int,则创立的办法如下: int[] arr = new int[10];创立数组时,咱们肯定要留神,必须明确指定数组的元素个数,也就是上边说的 size。 数组长度与容量在咱们日常应用中,大家都容易把这两个概念一概而论,然而实际上,两者是不一样的,两者的定义如下: 容量:指以后数组最多能包容的元素个数,也就是咱们创立数组时所指定的元素个数;长度:指以后数组中的元素个数,它不肯定等于容量,小于容量时示意数组还能够增加元素;两者关系:长度 <= 容量;int[] arr = new int[10];length = 0;for(int i = 0; i < 5; i++){ arr[length++] = i + 1;}System.out.println(“数组容量: ” + arr.length);System.out.println(“数组长度: ” + length;插入元素到数组要插入元素到数组中,能够分为如下 3 中状况: 插入数组结尾插入数组结尾插入数组两头插入元素到数组结尾要将元素插入数组结尾地位,咱们只须要先把原来数组的元素整体都向后挪动一个地位,而后将插入元素赋值给数组第一个元素即可; /*** 插入元素到数组结尾* @param arr 待插入元素的数组* @param val 待插入的元素* @return 插入元素后的数组*/public int[] insertStart(int[] arr, int val){ // 用于寄存插入元素后的数据 int[] destArr = new int[arr.length + 1]; // 将元素插入新数组结尾,同时将原数组整体赋值给新数组 destArr[0] = val; for(int i = 0; i < arr.length; i++){ destArr[i + 1] = arr[i]; } return destArr;}插入元素到数组结尾这是最简略的一种状况,要将元素插入到数组结尾,间接将插入的元素赋值给数组尾部即可; ...

May 8, 2021 · 3 min · jiezi

关于数据结构和算法:常用排序算法之归并排序

归并排序(Merge Sort)归并排序是创立在归并操作上的一种无效的排序算法,1945年由约翰·冯·诺伊曼首次提出.该算法是采纳分治法(Divide and Conquer)的一个十分典型的利用,且各层分治递归能够同时进行。 -- 维基百科 归并排序算法采纳分治思维+递归实现: 归并排序是一种非常高效的稳固排序,每次合并操作的均匀工夫复杂度为O(n),总的均匀工夫复杂度为O(nlogn)。goloang code: package mainimport ( "fmt" _ "os")func main() { var arr = [9] int {9, 8, 7, 6, 5, 4, 3, 2, 1} mergeSort(&arr) fmt.Println(arr)}func mergeSort(arr *[9]int) { var temp = [len(arr)] int {} sort(arr, 0, len(arr) - 1, &temp)}func sort(arr *[9]int, left int, right int, temp *[9] int) { if left < right { mid := (left + right) / 2 sort(arr, left, mid, temp) sort(arr, mid + 1, right, temp) merge(arr, left, mid, right, temp) }}func merge(arr *[9]int, left int, mid int, right int, temp *[9] int) { fmt.Println(left, mid, right) //os.Exit(1) i := left j := mid + 1 t := 0 for i <= mid && j <= right { if arr[i] <= arr[j] { temp[t] = arr[i] t++ i++ } else { temp[t] = arr[j] t++ j++ } } for i <= mid { temp[t] = arr[i] t++ i++ } for j <= right { temp[t] = arr[j] t++ j++ } t = 0 for left <= right { arr[left] = temp[t] left++ t++ }}

April 19, 2021 · 1 min · jiezi

关于数据结构和算法:常用排序算法之选择排序

抉择排序(Selection Sort)抉择排序是一种简略直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,寄存到排序序列的起始地位,而后,再从残余未排序元素中持续寻找最小(大)元素,而后放到已排序序列的开端。以此类推,直到所有元素均排序结束。--维基百科 排序流程示意图: 1.初始化乱序序列后,序列区域均为未排序序列。2.从未排序序列中查找最小(大)的元素,与序列第一个元素进行替换,此时替换后的区域为已排序序列。3.反复以上操作,直至遍历残缺个未排序序列。 抉择排序算法的空间复杂度为O(1),工夫复杂度为O(n2) 代码演示:func selectionSort(array [6]int) [6]int { if len(array) == 1 { return array } for i := 0; i < len(array) -1; i++ { key := i for j := i; j < len(array); j ++ { if array[j] < array[key] { key = j } } if i != key { temp := array[i] array[i] = array[key] array[key] = temp } } return array}

April 14, 2021 · 1 min · jiezi

关于数据结构和算法:常用排序算法之插入排序

插入排序(Insertion Sort)插入排序是一种简略直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应地位并插入。插入排序在实现上,通常采纳in-place排序(即只需用到O(1)的额定空间的排序),因此在从后:向前扫描过程中,须要重复把已排序元素逐渐向后挪位,为最新元素提供插入空间。--维基百科 剖析插入排序的过程:假设数组arr = [23, 69, 51, 12, 76, 16],从第二个元素开始向前比拟。 插入排序过程中,只须要用到常量级的存储空间,因而空间复杂度为O(1)。元素每次都须要从后向前一一扫描比照,均匀工夫复杂度为O(n2). 附上实现代码: func insertionSort(array [6]int) [6]int { if len(array) == 1 { return array } for i := 1; i < len(array); i++ { value := array[i] j := i - 1 for ; j >= 0; j-- { if array[j] > value { array[j+1] = array[j] } else { break } } array[j + 1] = value } return array}

April 13, 2021 · 1 min · jiezi

关于数据结构和算法:消失的数字

题目数组nums蕴含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有方法在O(n)工夫内实现吗? 留神:本题绝对书上原题稍作改变 示例 1: 输出:[3,0,1]输入:2  示例 2: 输出:[9,6,4,2,3,5,7,0,1]输入:8 起源:力扣(LeetCode)链接:https://leetcode-cn.com/probl...著作权归领扣网络所有。商业转载请分割官网受权,非商业转载请注明出处。 解释数组中蕴含0到n所有整数,但就短少了一个,所以,针对这个问题有下列办法给出: 办法1:求和求差法先计算0-n所有数之和。再计算数组中所有元素之和。做差代码如下:` 求和法 int n = numsSize+1;int ret1= 0;for(int i = 0; i<n; ++i){ ret1 += i;//sum}int ret2 = 0;for(int j = 0; j<numsSize; ++j){ ret2 +=nums[j];//sum of array}return ret1-ret2;` 办法2:位运算法思路:应用位运算的异或运算来排除。 数组中元素0-n短少一个。在造一组数0-n。后面两组数全副异或,后果就是所求的短少的数。代码如下:` //位运算 int x = 0;for(int i = 0;i<numsSize+1; ++i){ x^=i;//x先和所有数异或一次}for(int j = 0;j<numsSize; ++j){ x ^= nums[j];//持续异或数组中所有元素}return x;` 总结针对此类问题,求和求差法体现了数学的思维,位运算的奇妙在于能够应用异或去除反复两次呈现的数,最初剩下的数天然就是呈现一次的数。如若各路大神还有更好的思路,请分享。

April 9, 2021 · 1 min · jiezi

关于数据结构和算法:数据结构与算法-各类二叉树的概述以及二叉树遍历的三种方式

原文链接: https://gobea.cn/blog/detail/p6aeO26N.html, 转载请注明出处! 二叉树的品种满二叉树如上图所示,满二叉树的性质如下: 除最初一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。第k层上的节点数为: 2^(k-1)一个层数为k的满二叉树的总结点数为: (2^k) - 1齐全二叉树如上图所示,满二叉树的性质如下: 在满二叉树的根底上,最底层从右往左删去若干节点,失去的都是齐全二叉树最低层能够不满,但最底层的节点都必须集中在最右边,也就是说次底层的节点肯定会有左子节点,但可能没有右子节点如果对一棵有n个结点的齐全二叉树的结点按层序编号, 则对任一结点i (1≤i≤n): 如果i=1, 则结点i是二叉树的根, 无双亲;如果i>1, 则i节点的父节点是i/2(取整,不四舍五入)如果2i>n, 则结点i无左孩子, 否则其左孩子是结点2i;如果2i+1>n, 则结点i无右孩子, 否则其右孩子是结点2i+1均衡二叉树 树的左右子树的高度差不超过1的数空树也是均衡二叉树的一种二叉搜寻树 二叉搜寻树的特点:对于树中的每个节点,它的左子树中所有关键字值小于父节点关键字值,而它的右子树中所有关键字值大于父节点的关键字值。依据这个性质,对一个二叉树进行中序遍历,如果是枯燥递增的,则能够阐明这个树是二叉搜寻树。二叉搜寻树能够用栈构造来实现,并不一定要用递归b树https://www.cnblogs.com/mayjors/p/11144874.htmlb树是一种多路均衡查找树,每一个节点最多蕴含m个子节点,m被成为b树的阶,m的大小取决于磁盘页的大小,b树次要用于文件系统以及局部数据库索引,例如mongodb 最多有m个子节点,起码有m/2个子节点,m是b树的阶每个节点有多个key,key数量比子节点少1(除叶子节点)所有叶子节点都在同一层,并且是有序的b树和二叉树的区别b树一个节点能够有多个子节点,而二叉树只能有两个mysql应用b+树是因为能够缩小io次数,二叉树最坏状况下io次数等于树的高度b树是矮胖,二叉树是高瘦如果一个页蕴含更多key,查问效率可能更快b+树b+树是b树的变种,具体如下: 每个节点的key数量等于子节点数量,每个key不保留数据,只保留索引,所有数据存储在子节点上所有叶子节点蕴含了全副的key在最底层,每一个叶子节点指向下一个叶子节点的指针,造成了一个有序链表b树和b+树的区别b+树节点不蕴含数据,所有能够领有更多的key,所以更加矮胖,io次数更少b+树肯定会查找到叶子节点,查问性能稳固反对范畴查问二叉树的遍历前序遍历先序遍历就是先拜访根节点,在拜访左节点,最初拜访右节点 中序遍历中序遍历就是先拜访左节点,再拜访根节点,最初拜访右节点 后序遍历后序遍历就是先拜访左节点,再拜访右节点,最初拜访根节点 二叉树遍历代码package mainimport "fmt"// 二叉树的数据结构type TreeNode struct { Data int Left *TreeNode Right *TreeNode}// 二叉树的实现type Tree struct { root *TreeNode}// 增加数据func (self *Tree) Add(data int) { var queue []*TreeNode newNode := &TreeNode{Data:data} if self.root == nil { self.root = newNode }else { queue = append(queue, self.root) for len(queue) != 0 { cur := queue[0] queue = append(queue[:0], queue[0+1:]...) // 往右树增加 if data > cur.Data { if cur.Right == nil { cur.Right = newNode } else { queue = append(queue, cur.Right) } // 往左数增加 } else { if cur.Left == nil { cur.Left = newNode } else { queue = append(queue, cur.Left) } } } }}// 前序遍历 根 ---> 左 --->右func (self *Tree )preorderTraverse(node *TreeNode) { if node == nil { return } else { fmt.Print(node.Data, " ") self.preorderTraverse(node.Left) self.preorderTraverse(node.Right) }}// 中序遍历 左 ---> 根 --->右func (self *Tree) inorderTraverse(node *TreeNode) { if node == nil { return } else { self.inorderTraverse(node.Left) fmt.Print(node.Data, " ") self.inorderTraverse(node.Right) }}// 后序遍历 左 ----> 右 ---> 根func (self *Tree) postTraverse(node *TreeNode) { if node == nil { return } else { self.postTraverse(node.Left) self.postTraverse(node.Right) fmt.Print(node.Data, " ") }}func main() { tree := &Tree{} tree.Add(50) tree.Add(45) tree.Add(40) tree.Add(48) tree.Add(51) tree.Add(61) tree.Add(71) fmt.Println("前序遍历") tree.preorderTraverse(tree.root) fmt.Println("") fmt.Println("中序遍历") tree.inorderTraverse(tree.root) fmt.Println("") fmt.Println("后续遍历") tree.postTraverse(tree.root)}以上代码来源于: https://blog.csdn.net/lucky404/article/details/92440857 ...

January 6, 2021 · 2 min · jiezi

关于数据结构和算法:我是如何把简单题目做成困难的

作者:小漾起源:https://github.com/suukii/91-...大家好,我是 lucifer,家喻户晓,我是一个小前端 (不是) 。其实,我是 lucifer 的 1379 号迷妹观察员,我是一粒纳米前端。(不要答复,不要答复,不要答复!!!) 这是第一次投稿,所以能够废话几句,说一下我为什么做题和写题解。刚开始做算法题的时候,只是纯正感觉好玩,所以不仅没有刷题打算,写题解也只是轻易记下几笔,几个月后本人也看不懂的那种。一次偶尔机会发现了 lucifer 的明星题解仓库,是找到了 onepiece 的感觉。受他的启发,我也开始写些尽量能让人看懂的题解,尽管还赶不上 lucifer,但跟本人比总算是有了些提高。 身为迷妹观察员,lucifer 的 91 天学算法当然是不能错过的流动,当初流动的第二期正在 ???? 热进行中,有趣味的同学理解一下呀。言归正传,跟着 91 课程我不再是漫无目的,而是打算清晰,依照课程安顿的专题来做题,这样不仅更有利于理解某一类题波及的相干常识,还能相熟这类题的套路,再次遇见类似题型也能更快有思路。 废话就这么多,以下是注释局部。等等,还有最初一句,下面的"不要答复"是个三体梗,不晓得有没有人 GET 到我。 明天给大家带来一道力扣简略题,官网题解只给出了一种最优解。本文比拟贪婪,打算带大家用四种姿态来解决这道题。 <!-- more --> 题目形容题目地址:https://leetcode-cn.com/probl... 给定一个字符串 S 和一个字符 C。返回一个代表字符串 S 中每个字符到字符串 S 中的字符 C 的最短距离的数组。示例 1:输出: S = "loveleetcode", C = 'e'输入: [3, 2, 1, 0, 1, 0, 0, 1, 2, 2, 1, 0]阐明:字符串 S 的长度范畴为 [1, 10000]。C 是一个单字符,且保障是字符串 S 里的字符。S 和 C 中的所有字母均为小写字母。解法 1:核心扩大法思路这是最合乎直觉的思路,对每个字符别离进行如下解决: 从以后下标登程,别离向左、右两个方向去寻找指标字符 C。只在一个方向找到的话,间接计算字符间隔。两个方向都找到的话,取两个间隔的最小值。 复杂度剖析咱们须要对每一个元素都进行一次扩大操作,因而工夫复杂度就是 $N$ * 向两边扩大的总工夫复杂度。 而最坏的状况是指标字符 C 在字符串 S 的左右两个端点地位,这个时候工夫复杂度是 $O(N)$,因而总的工夫复杂度就是 $O(N^2)$ 工夫复杂度:$O(N^2)$,N 为 S 的长度。空间复杂度:$O(1)$。代码JavaScript Code ...

December 8, 2020 · 4 min · jiezi

关于数据结构和算法:学习数据结构和算法心得

通过一段时间的数据结构与算法的学习,和学习了前人的教训,为了更好的领导本人(心愿也能帮忙到他人)之后数据结构与算法的学习,总结一下数据结构与算法学习的办法。以及举荐大家看看一套学习教程,有助于疾速入门:https://4m.cn/7MHVd一、记住数据结构,记住算法思维(是什么) 我感觉这个是数据结构与算法学习最根底的局部。学完之后,你至多得能给人说明确,什么是”堆栈“,什么是”均衡二叉树“等等等吧。我之所以说”记住“,是心愿这些可能造成短暂记忆,存储到你的”硬盘“里,而不仅仅在学习的时候过了一遍你的”内存“。还有一个问题,什么才叫”记住数据结构“。我感觉,第一步,数据结构最直观的货色你得记住吧(如题目背景图,来自数据结构和算法动静可视化 (Chinese))。这种直观的记忆可能在人不知;鬼不觉中就实现了,但为了更好的记住,还需去刻意记忆和偶然的温习。第二步,你得记忆该数据结构的定义与性质与特点等等等吧。例如,学习哈夫曼树的时候。哈夫曼树的定义:WPL(带权门路长度)最小的二叉树;哈夫曼树的特点:(1)没有度为1的结点(2)n个叶子结点的哈夫曼树共有2n-1个结点(3)哈夫曼树的任意非叶节点的左右子树替换后仍是哈夫曼树。对于”数据结构“,须要记忆的内容也须要本人在其中缓缓领悟。至于”记住算法思维“,举个例子,思考咱们如何结构一个”哈夫曼树“。当然,在晓得它的定义后,咱们能够本人去设计一个算法。如果,本人能够想进去,祝贺你。如果本人没想到,再看到后人的解决办法后,不是仅仅“惊叹”一声,更要去记住它。我强调“记住”,并没有死记硬背的意思,而是,很多货色的了解和翻新都是以记忆为前提的。 二、进行大量相干编程练习,用编程语言去实现某一数据结构上的算法(怎么办)就我而言,这个过程是最难的。很多时候,了解一个算法很容易,很容易在纸下来模仿一个算法的实现过程。但,具体实现,则是另一回事。肯定得先本人思考,而后再去看书中给的编程语言实现。在我看来,这一过程曾经不属于“数据结构与算法”的内容了。而是你综合素质的体现,如何真正了解问题和用编程技巧实现,很考验本人。这一过程,很难靠记忆。而在一直敲代码的过程中去领会一些直觉上的货色。如何用递归解决问题,如何应用循环,如何应用"哨兵”等等等等。当然,敲完后须要去思考总结,看看能不能总结出一些”小套路“并记住。 三、”记住“特定情景下,利用某一特定的数据结构,去解决问题 (为什么+怎么办)每介绍一种数据结构,浙大数据结构与算法的MOOC课程都会有一个理论问题来作为“引子”,答复了“这种数据结构为什么会呈现”。有的是为了实现特定的操作,有的是为了工夫和空间上(大部分思考的是工夫复杂性)效率的更高(所以,没事的时候,剖析一下算法的工夫复杂性)。这些货色,咱们也须了解记忆。每一数据结构都有其个性,去解决某一类问题,咱们须要去记忆,去感悟。最初,在学习过程中,如何造成一个属于本人的常识体系(筹备在“印象笔记”中单开一个“数据结构与算法”的笔记本);如何去“记住”(记好笔记,多多温习);在学习过程中,遇到挫折,产生挫败感该如何解决(这个是必然会产生的,总有难以了解不会的中央);如何进行心态方面的调整(欲速则不达,不过也有”麻利学习“的概念)。当然这边能够举荐大家看看这套教程, 让你少走弯路,少花工夫 :4m.cn/7MHVd

November 30, 2020 · 1 min · jiezi

关于数据结构和算法:术篇数据结构与算法-数据结构与算法概述

一、引言来大学学习的第二个重要的课程就是《数据结构与算法》,过后学下来云里雾里饶,也不晓得有什么作用,只是把重要的概念记下来应酬考试。工作当前发现除了面试考官常常问起来,更重要的时候工作上很多时候都能用上,用上适合的数据存储构造、作用于特定数据结构上算法后,在空间复杂度和工夫复杂度上都有显著的效率晋升。为了回顾数据结构与算法并加上印象,特发展专题记录。 二、介绍来1.何为数据结构与算法狭义上,也就是从课本上说,数据结构就是一组存储数据的构造,而算法能就是操作数据的一组办法。广义上,其实是针对驰名的数据结构与算法。驰名的意思即前人的智慧结晶,咱们时常听到的堆,栈,二分查找,疾速排序等都是驰名的数据结构与算法。站在伟人的肩膀上,咱们能有更卓越的成就。2.数据结构与算法的关系总的来说两者是相辅相成的,缺一不可。数据结构是为算法服务的,算法要作用在特定的数据结构之上。 因而,咱们无奈孤立数据结构来讲算法,也无奈孤立算法来讲数据结构。 数据结构是动态的,它只是组织数据的一种形式。如果不在它的根底上操作、构建算法,孤立存在的数据结构就是没用的。 3.学习两者的方向3.1. 把握罕用的数据结构和算法的特点(口诀)重点是学习他们的: “ 来历 ” 、 “ 特点 ” 、 “ 适宜解决什么问题 ” 和 “理论的利用场景 ” 。数据结构:数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、 Trie 树 算法:递归、排序、二分查找、搜寻、哈希算法、贪婪算法、分治算法、回溯算法、动静布局、字符串匹配算法 3.2. 能进行复杂度剖析(心法)效率和资源耗费的度量衡即复杂度剖析是数据结构和算法学习的精华数据结构和算法解决的是如何更省、更快地存储和解决数据的问题,因而,咱们就须要一个考量效率和资源耗费的办法,这就是复杂度分析方法。

November 19, 2020 · 1 min · jiezi

关于数据结构和算法:你以为只是简单的排序一

始终在犹豫要不要写排序的文章,因为真的烂大巷了。可是一旦细看,还真是很多值的思考的中央,所以还是抉择记录一下 以下残缺代码,均可从这里获取 https://github.com/Rain-Life/data-struct-by-go/tree/master/sort排序算法效率剖析理解如何剖析一个排序算法,能够帮忙咱们在理论工作场景中抉择适合的排序算法,比方,如果排序的数据比拟少,能够抉择冒泡或插入排序,如果排序的数据量较大,抉择归并或疾速排序,尽管它们两两的工夫复杂度是雷同的,然而还是有很大的区别的,下边会对它们做比照 排序算法执行效率个别剖析一个排序算法的复杂度,咱们都是去剖析它的工夫复杂度,工夫复杂度反馈的是数据规模n很大的时候的一个增长趋势。所以,通常在剖析工夫复杂度的时候会疏忽到系数、常数、低阶。然而,在理论开发场景中,可能咱们排序的数据并不多,因而,在对排序算法进行剖析的时候,还是须要将系数、常数、低阶也思考进来 剖析一个排序算法的工夫复杂度的时候,通常会剖析它的最好状况、最坏状况以及均匀状况下的工夫复杂度。因为对于要排序的数据,它的有序度,对排序算法的执行工夫是有影响的,所以,要想抉择最合适的排序算法,这些状况的工夫复杂度都应该思考到(其实不光是排序,在实现任何一个算法的时候,当有多种形式可供选择的时候,都应该剖析多重状况下的工夫复杂度) 下边要分享的三个排序算法都是基于比拟的排序算法,基于比拟的排序算法的在执行过程中,个别波及两种操作,一个是比拟大小,一个是数据交换。因而,在比照这几种排序算法的时候,比拟次数和挪动次数也应该思考进去。这也是为什么基于比拟排序的算法,咱们通常不会抉择冒泡排序,而抉择插入排序 排序算法内存耗费算法的内存耗费能够通过空间复杂度来掂量。针对排序算法的空间复杂度,有一个新的概念是原地排序。原地排序算法,就是特指空间复杂度是O(1)的排序算法。下边要分享的三种排序算法,都是原地排序算法 排序算法稳定性只靠执行效率和内存耗费来掂量排序算法的好坏是不够的。针对排序算法,还有一个重要的度量指标,稳定性。意思是,如果待排序的序列中存在值相等的元素,通过排序之后,相等元素之间原有的先后顺序不变 如:1、8、6、5、5、7、2、3,依照大小排序之后是:1、2、3、5、5、6、7、8 这组数据里有两个5。通过某种排序算法排序之后,如果两个5的前后程序没有扭转,那咱们就把这种排序算法叫作稳固的排序算法;如果前后程序发生变化,那对应的排序算法就叫作不稳固的排序算法 冒泡排序冒泡排序优化冒泡排序的实现思维,置信大家都十分的相熟了 每次冒泡操作都会对相邻的两个元素进行比拟,看是否满足大小关系要求。如果不满足就让它俩调换。一次冒泡会让至多一个元素挪动到它应该在的地位,反复n次,就实现了n个数据的排序工作 实际上,冒泡过程还能够优化。当某次冒泡操作曾经没有数据交换时,阐明曾经达到齐全有序,不必再继续执行后续的冒泡操作。如图 下边是优化后的代码: func BubbleSort(arr []int) { flag := false n := len(arr) for i := 0; i < n; i++ { flag = false//如果某一次冒泡,没有呈现数据交换,阐明曾经有序,不必再持续冒泡了 for j := 0; j < n-i-1; j++ { if arr[j] > arr[j+1] { tmp := arr[j] arr[j] = arr[j+1] arr[j+1] = tmp flag = true } } if !flag { break } } for _, v := range arr { fmt.Printf("%v\t", v) }}冒泡排序算法剖析首先冒泡排序是一个原地排序算法,因为冒泡排序只波及相邻数据的替换,须要常量级的长期空间,所以空间复杂度是O(1) ...

November 14, 2020 · 2 min · jiezi

关于数据结构和算法:数据结构与算法-C-二叉搜索树的插入查找删除

C语言实现搜寻二叉树 1构造体typedef struct BSTreeNode { int data; //数据域 struct BSTreeNode *left; //左子结点 struct BSTreeNode *right; //右子结点} BSTreeNode;2插入//1.第一种,传入指针,返回根节点BSTreeNode *insert2(BSTreeNode *root, int data){ if (NULL == root) { struct BSTreeNode *node; node = (struct BSTreeNode *)malloc(sizeof(struct BSTreeNode)); if (node == NULL) { printf("malloc error \n"); return NULL; } node->data = data; node->right = NULL; node->left = NULL; printf("null %d \n", data); return node; } printf("%d vs %d \n", data,root->data); if (data >= root->data) { root->right = insert2(root->right, data); }else{ root->left = insert2(root->left, data); } return root;}//第二种 传入二级指针,即指针的指针,无需返回void insert(BSTreeNode **root, int data){ if (NULL == *root) { printf("****ins isnull %d \n", data); *root = (struct BSTreeNode *)malloc(sizeof(struct BSTreeNode)); (*root)->data = data; (*root)->left = NULL; (*root)->right = NULL; }else{ if (data >= (*root)->data) { insert(&(*root)->right, data); }else{ insert(&(*root)->left, data); } }}3查找BSTreeNode *BSearch(BSTreeNode *root, int target){ if (NULL == root) { return NULL; } if (root->data == target) { return root; }else if (target > root->data) { return BSearch(root->right, target); }else{ return BSearch(root->left, target); }}栈和队列的构建 详见其余博文 ...

November 11, 2020 · 2 min · jiezi

关于数据结构和算法:数据结构与算法系列之递归GO

以下残缺代码均可从这里获取 https://github.com/Rain-Life/data-struct-by-go/tree/master/recursion/step了解递归曾经不晓得是第几次被递归阻断我学习数据结构的路线了,每次学到递归,我都自我狐疑,是我脑子有问题吗?我是真的学不明确它! 发现之前了解递归过于刻板和传统,看递归的时候总是依照机器的执行程序始终的往每一层递归里边进,一直的下一层、下一层、下一层,直到本人彻底解体,本人的CPU也没把一个残缺的递归给走完 咱们的脑子总是想着跟着机器的执行,不停的往每一层里边进。其实齐全能够不必关怀每一层,只有假设下一层可能正确的返回,而后该怎么走就怎么走,保障最深的一层递归逻辑正确就行,也就是递归终止的条件 因为不论是递归的哪一层,他们执行的都是一样的代码,惟一不一样的只是数据而已,保障了递归的逻辑是正确的,每一层的的后果就是正确的,直到递归的终止条件被满足,而后每一层都会失去正确的后果 下边进入主题 递归利用非常宽泛,能够说,如果没法齐全的了解递归的思维,后边的一些进阶的数据结构基本没法学,或者十分吃力。比方深度优先搜寻、二叉树的遍历等等 基本上大多数的对于数据结构的书,在介绍递归的时候都是拿斐波那契数列来举例的,其实我集体感觉尽管题目经典,但对我理解递归帮忙不是很大,下边分享一个生存中的示例帮忙了解递归 假如你当初在爬山,曾经爬到了山腰上,假如你当初想晓得本人在第多少级台阶上应该怎么办 此时递归就派上用场了,那如果你晓得你前边一级的台阶是第多少级就行了,晓得了它是第多少级,在它的级数上加一就晓得你所在的地位是第几级台阶了。然而你前边的这一级也不晓得是第几级,毕竟离山底有点远,没法晓得。那就持续的往前推,前一级的前一级台阶是第几级台阶,直到第一级台阶,而后就能够一级一级的把数字传回来,直到你的前一级台阶通知你他在第几级,你就晓得了本人在第几级了 整个过程就是一个递归的过程,往前推的过程是”递“,回传的时候就是”归“。所有的递归问题都是能够用递归来示意的,对于上边的例子,用公式来示意就是 f(n) = f(n-1) + 1,其中f(1)=1f(n)是你想本人本人在哪一级,f(n-1)就是你的前一级台阶是第几级,f(1)示意第一级台阶咱们晓得它是第一级。有了公式写递归代码就很轻松了 func Recursion(int n) int { if n==1 { return 1 } return f(n-1) + 1}什么状况下适宜应用递归只有一个问题能够满足下边三个条件,那就能够思考应用递归 一个问题的解能够分解成几个子问题的解子问题就是规模更小的问题,比方前边的求本人在哪一级台阶的问题,能够分解成”前边一级是哪一级“这样的子问题 子问题除了数据规模不一样,求解思路必须齐全一样还是以上边的例子为例,求本人在哪一级台阶,和求解前一级台阶在哪一级的思路是齐全一样的 存在递归终止条件把问题合成为子问题,把子问题再合成为子子问题,一层一层合成上来,不能存在有限循环,这就须要有终止条件 还是上边的例子,第一级台阶是明确晓得是第一级的也就是 f(1)=1,这就是递归的终止条件 编写递归代码写递归的代码,最要害的就是写出递归公式、找到递归终止条件,剩下的将递归公式转化成代码就简略了 示例以Leetcode上边的一道题为例 如果这里有n个台阶,每次你能够跨1个台阶或者2个台阶,请问走这n个台阶有多少种走法?如果有7个台阶,你能够 2,2,2,1 这样子下来,也能够 1,2,1,1,2 这样子下来,总之走法有很多,下边就是思考如何通过代码来实现 通过思考咱们能够晓得,实际上,能够依据第一步的走法,把所有的走法分为两类 第一类是第一步走了1个台阶第二类是第一步走了2个台阶所以,n个台阶的走法就等于先走1阶后,n-1个台阶的走法加上先走2阶后,n-2个台阶的走法。用公式示意就是: f(n) = f(n-1) + f(n-2)有了递归公式,当初就差终止条件。首先,当只有一个台阶的时候,那必定就只有一种走法,所以f(1) = 1 然而,只有这一个递归终止条件足够吗?必定是不够的,比方当初思考n=2和n=3的状况,靠这一个终止条件是否可能求进去 n=2f(2) = f(1) + f(0)如果递归终止条件只有一个f(1)=1,那 f(2)就无奈求解了。所以除了f(1)=1 这一个递归终止条件外,还要有f(0)=1,示意走0个台阶有一种走法,不过这样子看起来就不合乎失常的逻辑思维了。所以,能够把f(2)=2作为一种终止条件,示意走2个台阶,有两种走法,一步走完或者分两步来走 所以,最终失去的递归终止条件就是(能够找几个数字验证一下) f(1)=1,f(2)=2有了公式和递归终止条件,代码就很容易了 func StepEasy(n int) int { if n==1 { return 1 } if n==2 { return 2 } return StepEasy(n-1) + StepEasy(n-2)}剖析上边的这个例子,人脑简直没方法把整个“递”和“归”的过程一步一步都想分明 ...

November 11, 2020 · 2 min · jiezi

关于数据结构和算法:数据结构与算法系列之栈队列GO

以下残缺代码均可从这里获取 栈栈的基本概念后进先出、先进后出就是典型的栈构造。栈能够了解成一种受了限度的线性表,插入和删除都只能从一端进行 当某个数据汇合只波及在一端插入和删除数据,并且满足后进先出、先进后出的个性,就应该首选“栈”这种数据结构(浏览器的后退、后退性能) 栈的实现栈次要有两种操作,入栈和出栈,这里通过数组(程序栈)和链表(链式栈)两种形式实现栈 程序栈package arrayStackimport "fmt"type Item interface {}type ItemStack struct { Items []Item N int}//init stackfunc (stack *ItemStack) Init() *ItemStack { stack.Items = []Item{} return stack}//push stack Itemfunc (stack *ItemStack) Push(item Item) { if len(stack.Items) > stack.N { fmt.Println("栈已满") return } stack.Items = append(stack.Items, item)}//pop Item from stackfunc (stack *ItemStack) Pop() Item { if len(stack.Items) == 0 { fmt.Println("栈已空") return nil } item := stack.Items[len(stack.Items) - 1] stack.Items = stack.Items[0:len(stack.Items) - 1] return item}链式栈package linkListStackimport "fmt"type Item interface {}type Node struct { Data Item Next *Node}type Stack struct { headNode *Node}//push Stack itemfunc (stack *Stack) Push(item Item) { newNode := &Node{Data: item} newNode.Next = stack.headNode stack.headNode = newNode}//pop Item from stackfunc (stack *Stack) Pop() Item { if stack.headNode == nil { fmt.Println("栈已空") return nil } item := stack.headNode.Data stack.headNode = stack.headNode.Next return item}func (stack *Stack) Traverse() { if stack.headNode == nil { fmt.Println("栈已空") return } currentNode := stack.headNode for currentNode != nil { fmt.Printf("%v\t", currentNode.Data) currentNode = currentNode.Next }}栈的利用场景函数调用栈操作系统给每个线程调配了一块独立的内存空间,这块内存被组织成“栈”这种构造, 用来存储函数调用时的长期变量。每进入一个函数,就会将长期变量作为一个栈帧入栈,当被调用函数执行实现,返回之后,将这个函数对应的栈帧出栈起源:数据结构与算法之美 ...

November 9, 2020 · 3 min · jiezi

关于数据结构和算法:数据结构与算法系列之链表操作全集三GO

以下残缺的代码,及测试代码均可从这里获取github 删除单向链表倒数第n个结点办法一:快慢指针法思路删除倒数第N个结点,假如反过来看,要删除第N个节点。那么,一个指向头结点(头结点中也是一个数据结点)的指针向前挪动N-1个结点后,指向的就是第N个结点 当初再看删除倒数第N个结点,假如此时有两个指针(快指针fastPtr、慢指针lowPtr)均指向头结点,快指针fastPtr向后遍历N-1个结点之后,慢指针和快指针开始一起向后遍历,当快指针达到最初一个结点的时候,慢指针指向的地位,就是倒数第N个结点的地位 代码实现func (list *List) DelLastNNode1(lastN int) { if lastN > list.Length() || lastN <= 0 { fmt.Println("输出的待删结点地位不非法") return } //删除尾结点 if lastN == 1 { list.RemoveLastNode() return } //删除头结点 if lastN == list.Length() { //删除链表头结点 list.headNode = list.headNode.Next return } lowNode := list.headNode fastNode := list.headNode prevNode := list.headNode fastStep := lastN-1 for fastNode.Next != nil { if fastStep > 0 { fastNode = fastNode.Next fastStep-- continue } fastNode = fastNode.Next prevNode = lowNode lowNode = lowNode.Next } prevNode.Next = lowNode.Next}办法二:结点地位和结点数量的关系法思路不晓得怎么删除倒数第N个结点,想方法晓得它是第几个不就行了。所以,要害是通过链表长度以及N来找到倒数第N个结点是负数第几个节点,通过观察能够失去,倒数第N个结点就是负数第length-N+1个结点,length为链表长度 ...

November 9, 2020 · 2 min · jiezi

关于数据结构和算法:数据结构与算法-C语言实现-前中后层序的递归非递归遍历

1.递归遍历递归遍历非常简单,,,,,, 1.1前序遍历void preOrderTraverse(BSTreeNode *root){ if (root != NULL) { printf("%d \n", root->data); preOrderTraverse(root->left); preOrderTraverse(root->right); }}1.2中序遍历void InOrderTraverse(BSTreeNode *root){ if (root != NULL) { InOrderTraverse(root->left); printf("%d \n", root->data); InOrderTraverse(root->right); }}1.3后序遍历void PostOrderTraverse(BSTreeNode *root){ if (root != NULL) { PostOrderTraverse(root->left); PostOrderTraverse(root->right); printf("%d \n", root->data); }}2.非递归遍历栈和队列的构建 详见其余博文 2.1前序遍历void preOrderTraverseNR(BSTreeNode *root){ //建设栈 struct LinkedStack *Stack; Stack = initStack(); while(NULL != root){ printf("%d \n", root->data); if (NULL != root->right) { push(Stack, root->right); } if (NULL != root->left) { push(Stack, root->left); } root = pop(Stack); } free(Stack); Stack = NULL;}下周持续 ...

November 6, 2020 · 1 min · jiezi

关于数据结构和算法:数据结构与算法栈-C语言实现

栈是仅在表尾进行插入、删除操作的线性表。即栈 S= (a1, a2, a3, ………,an-1, an),其中表尾称为栈顶 /top,表头称为栈底/base。 因为只能在表尾进行操作,因而栈的运算规定就是“后进先出”(LIFO) 和线性表相似,栈也有两种存储构造——程序栈与链栈 1.程序栈的C语言实现#include <stdio.h>#include <stdlib.h>typedef struct Stack { int *data;//数据域 int size;//栈长度,也是栈顶数组下标-1 int max;//栈最大容量} Stack;//初始化Stack *initStack(int max){ struct Stack *stack; stack = (struct Stack *)malloc(sizeof(struct Stack)); stack->size = 0; stack->max = max; stack->data = (int*)malloc(sizeof(int)*max); return stack;}//压栈void push(Stack *stack, int item){ if (stack->size >= stack->max) { printf("stack is full! \n"); }else{ stack->data[stack->size++] = item; } }//出栈int pop(Stack *stack){ if (stack->size >= 0) { return stack->data[--stack->size]; }}//testint main(){ struct Stack *stack; stack = initStack(3); push(stack,1); push(stack,2); push(stack,3); push(stack,4); printf("stack out:%d \n", pop(stack)); printf("stack out:%d \n", pop(stack)); push(stack,5); push(stack,6); push(stack,7); printf("stack out:%d \n", pop(stack)); printf("stack out:%d \n", pop(stack)); printf("stack out:%d \n", pop(stack)); return 0;}测试成果:Todo: ...

November 6, 2020 · 1 min · jiezi

关于数据结构和算法:数据结构与算法学习图论

什么是图?在计算机程序设计中,图构造也是一种十分常见的数据结构然而图论其实是一个十分大的话题图构造是一种与树结构有些类似的数据结构图论是数学的一个分支,并且在数学概念上,树是图的一种它以图为钻研对象,钻研顶点和边组成的图形的数学实践和办法次要钻研的目标是事务之间的关系,定点代表事务,边代表两个事物间的关系 图的事实案例人与人之间的关系网甚至科学家们在察看人与人之间的关系网时,还发现了六度空间实践 六度空间实践实践上认为世界任何不意识的两个人只须要很少的中间人就能够建设起分割。并非肯定通过六步,只是须要很少的步骤 图通常有什么特点?一组顶点:通常用V(Vertex)示意顶点的汇合一组边:通常用E(Edge)示意边的汇合边是顶点和顶点之间的连线边能够是有向的,也能够是无向的比方A----B,通常示意无向A---->B,通常示意有向 七桥问题图形一笔画完的条件:有0个奇点或有2个奇点咱们来看一下口这个字有4个偶点0个奇点所以一笔可能画完(由单数的边连贯的都是偶点,由复数的边连贯的都是奇点) 田字不能一笔画成,因为它的奇点有四个(红色)违反了规定。 图的术语顶点顶点方才咱们曾经介绍过了,示意图中的一个节点比方下图中的数字 边边方才咱们也介绍过了,示意顶点和顶点之间的连线比方地铁站中两个站点之间的间接连线,就是一个边留神:这里的边不要叫做门路,门路有其余的概念,待会儿咱们会介绍下图中 0 - 1有一条边,1 - 2有一条边,0 - 2没有边 相邻顶点由一条边连贯在一起的顶点称为相邻顶点 度一个顶点的度是相邻顶点的数量比方0顶点和其余两个顶点相连,0顶点的度是2比方1顶点和其余四个顶点相连,1顶点的度是4 门路门路是顶点v1,v2...,vn的一个间断序列,比方下图中0 1 5 9 就是一条门路简略门路:简略门路要求不蕴含反复的顶点,比方0 1 5 9是一条简略门路回路:第一个顶点和最初一个顶点雷同的门路称为回路 比方0 1 5 6 3 0 无向图:下图图就是一张无向图,因为所有的边都没有方向比方 0 - 1之间有边,那么阐明这条边能够保障0 ->1,也能够保障1 ->0 无权图:下图就是一张无权图(边没有携带权重),图中的边是没有任何意义的。不能说4-9的边比0-1的边更远或者更长。 有向图有向图示意的图中的边是有方向的比方0->1,不能保障肯定能够1->0,要依据方向来定,比方下图就是一张有向图 带权图带权图示意边有肯定的权重这里的权重能够是任意你心愿示意的数据比方间隔或者破费的工夫或者票价 图的示意怎么在程序中示意图呢?咱们晓得一个图蕴含很多顶点,另外蕴含顶点和顶点之间的连线(边)这两个都是十分重要的图信息,因而都须要在程序中提现进去顶点的示意绝对简略,咱们先探讨顶点的示意下面的顶点,咱们形象成1 2 3 4,也能够形象成A B C D在前面的案例中,咱们应用A B C D那么这些A B C D咱们能够应用一个数组来存储起来(存储所有的顶点)当然A B C D有可能还示意其余含意的数据(比方村庄的名字)那么边如何示意呢?因为边是两个顶点之间的关系,所以示意起来会略微麻烦一些 邻接表邻接表由图中每个顶点以及和顶点相邻的顶点列表组成这个列表有很多种形式来存储:数组/链表/哈希表都能够 图片解析比方咱们要示意和A顶点有关联的顶点(边),A和B/C/D有边那么咱们能够通过A找到对应的数组/链表/哈希表,再取出其中的内容即可 邻接表的问题:邻接表计算出度是比较简单的(出度:指向他人的数量,入度:指向本人的数量)邻接表如果须要计算有向图的入度,那么是十分麻烦的事件它必须结构一个逆邻接表,能力无效的计算入度,然而在开发中出度绝对用的比拟少 图的遍历思维图的遍历思维和树的遍历思维是一样的图的遍历意味着须要将图中每个顶点都拜访一遍,并且不能有反复的拜访有两种算法能够对图进行遍历广度优先搜寻(Breadth-first Search,简称BFS)深度优先搜寻(Depth-First Search,简称DFS)两种遍历算法都须要明确指定第一个被拜访的顶点 遍历的思维两种算法的思维:BFS:基于队列,入队列的顶点先被摸索DFS:基于栈或应用递归,通过将顶点存入栈中,顶点是沿着门路被摸索的,存在新的相邻顶点就去拜访 ...

November 6, 2020 · 2 min · jiezi

关于数据结构和算法:西法带你学算法单调栈解题模板秒杀八道题

枯燥栈顾名思义, 枯燥栈是一种栈。因而要学枯燥栈,首先要彻底搞懂栈。 栈是什么? 栈是一种受限的数据结构, 体现在只容许新的内容从一个方向插入或删除,这个方向咱们叫栈顶,而从其余地位获取内容是不被容许的 栈最显著的特色就是 LIFO(Last In, First Out - 后进先出) 举个例子: 栈就像是一个放书本的抽屉,进栈的操作就好比是想抽屉里放一本书,新进去的书永远在最上层,而退栈则相当于从里往外拿书本,永远是从最上层开始拿,所以拿进去的永远是最初进去的哪一个 栈的罕用操作进栈 - push - 将元素搁置到栈顶退栈 - pop - 将栈顶元素弹出栈顶 - top - 失去栈顶元素的值是否空栈 - isEmpty - 判断栈内是否有元素栈的罕用操作工夫复杂度因为栈只在尾部操作就行了,咱们用数组进行模仿的话,能够很容易达到 O(1)的工夫复杂度。当然也能够用链表实现,即链式栈。 进栈 - O(1)出栈 - O(1) 利用函数调用栈浏览器后退后退匹配括号枯燥栈用来寻找下一个更大(更小)元素题目举荐394. 字符串解码946. 验证栈序列1381. 设计一个反对增量操作的栈枯燥栈又是什么?枯燥栈是一种非凡的栈。栈原本就是一种受限的数据结构了,枯燥栈在此基础上又受限了一次(受限++)。 枯燥栈要求栈中的元素是枯燥递增或者枯燥递加的。 是否严格递增或递加能够依据理论状况来。这里我用 [a,b,c] 示意一个栈。 其中 左侧为栈底,右侧为栈顶。 比方: [1,2,3,4] 就是一个枯燥递增栈[3,2,1] 就是一个枯燥递加栈[1,3,2] 就不是一个非法的枯燥栈那这个限度有什么用呢?这个限度(个性)可能解决什么用的问题呢? 实用场景枯燥栈适宜的题目是求解第一个一个大于 xxx或者第一个小于 xxx这种题目。所有当你有这种需要的时候,就应该想到枯燥栈。 那么为什么枯燥栈适宜求解第一个一个大于 xxx或者第一个小于 xxx这种题目?起因很简略,我这里通过一个例子给大家解说一下。 这里举的例子是枯燥递增栈比方咱们须要顺次将数组 [1,3,4,5,2,9,6] 压入枯燥栈。 首先压入 1,此时的栈为:[1]持续压入 3,此时的栈为:[1,3]持续压入 4,此时的栈为:[1,3,4]持续压入 5,此时的栈为:[1,3,4,5]如果持续压入 2,此时的栈为:[1,3,4,5,2] 不满足枯燥递增栈的个性, 因而须要调整。如何调整?因为栈只有 pop 操作,因而咱们只好一直 pop,直到满足枯燥递增为止。下面其实咱们并没有压入 2,而是先 pop,pop 到压入 2 仍然能够放弃枯燥递增再 压入 2,此时的栈为:[1,2]持续压入 9,此时的栈为:[1,2,9]如果持续压入 6,则不满足枯燥递增栈的个性, 咱们故技重施,一直 pop,直到满足枯燥递增为止。此时的栈为:[1,2,6]留神这里的栈依然是非空的,如果有的题目须要用到所有数组的信息,那么很有可能因没有思考边界而不能通过所有的测试用例。 这里介绍一个技巧 - 哨兵法,这个技巧常常用在枯燥栈的算法中。 ...

November 4, 2020 · 2 min · jiezi

关于数据结构和算法:数据结构与算法学习树结构及二叉树的认识

树结构和数组/链表/哈希表的比照有什么长处?数组:长处:数组的次要长处是依据下标值拜访效率会很高然而如果咱们心愿依据元素来查找对应地位呢?比拟好的形式是先对数组排序,再进行二分查找毛病:须要先对数组进行排序,生成有序数组,能力进步查找效率另外数组在插入和删除时,须要大量位移操作,效率很低 链表:长处:链表插入和删除操作效率都很高毛病:查找效率很低,须要从头开始顺次拜访链表中的每个数据项,直到找到,而且即便插入和删除效率很高,然而如果插入和删除两头地位的数据,还是须要重头先找到对应数据再进行操作。 哈希表:长处:咱们学过哈希表后,曾经发现哈希表的插入/查问/删除效率都十分高毛病:空间利用率不高,底层应用的是数组,并且某些单元格没有被利用哈希表中的元素是无序的,不能依照固定程序来遍历哈希表中的元素不能疾速的找出哈希表中的最大值或者最小值这些非凡值。 树结构:咱们不能说树结构比其余构造都要好,因为每种数据结构都有本人特定的利用场景,然而树结构的确也综合了下面的数据结构的长处(当然长处有余于盖过其余数据结构,比方效率个别状况下没有哈希表高),并且也补救了下面数据结构的毛病,而且为了模仿某些场景,咱们应用树结构会更加不便,因为树结构是非线性的,能够示意一对多的关系,比方文件的目录构造。 树树的术语:1.节点的度(Degree):节点的子树个数2.树的度:树的所有节点中最大的度数3.叶节点(leaf):度为0的节点(也称为叶子节点)4.父节点(parent):有子树的节点是其子树的根节点的父节点5.子节点(child):若A节点为B节点的父节点,则成B节点是A节点的子节点;子节点也称孩子节点6.兄弟节点(sibling):具备同一父节点的各节点彼此都是兄弟节点7.节点的档次(level):规定根节点在1层,其余任一节点的层数是其父节点的层数加19.树的深度(Depth):树中所有节点中的最大档次是这棵树的深度 树最一般的示意形式这种示意形式因为不晓得有几个子节点 所以没有方法来定义绝对的连贯 比方下面的A有三个子节点 咱们能够定义left指向左子节点 right指向右子节点 middle指向两头子节点 然而如果它有四个子节点甚至更多那么咱们封装起来并不会这么直白的创立连贯了 儿子兄弟表示法这种办法能够轻松的解决下面的问题 只须要一个left指针和right指针就能够将树结构出现进去 儿子兄弟表示法旋转儿子兄弟表示法旋转将儿子兄弟表示法变成二叉树 树的品种完满二叉树完满二叉树也称为满二叉树在二叉树中,出了最下一层的叶节点外,每层节点都有两个子节点,就形成了满二叉树 齐全二叉树若设二叉树的深度为k,除第 k 层外,其它各层 (1~k-1) 的结点数都达到最大个数,第k 层所有的结点都间断集中在最右边,这就是齐全二叉树。完满二叉树是非凡的齐全二叉树下图并不是齐全二叉树,因为D节点没有右节点,然而E节点就有了左右节点,解决办法:1)减少D的右子节点 2)将9和10删掉 二叉树二叉树概念如果树中每个节点最多只能有两个子节点,这样的树就成为二叉树,下面咱们曾经提到了二叉树的重要性,不仅仅是因为简略,也因为简直所有的树都能够示意成二叉树的模式 二叉树的定义二叉树能够为空,也就是没有节点,若不为空,则它是由根节点和称为其左子树tl和右子树tr的两个不相交的二叉树组成 二叉树有五种状态 (a):空树(b):只有根节点(c):只有左子树(d):只有右子树(e):左右子树都存在 二叉树有几个比拟重要的个性一个二叉树第i层的最大节点数为:2^(i-1),i>=1;如下图比方第三层最大个数 = 2^(3-1) = 2^2 = 4;那么第三层最大的节点数就为4深度为k的二叉树有最大节点的总数为:2^k-1,k>=1;以第四层为例,最大个数 = 2^4-1 = 16 - 1 =15;那么咱们下图中的树所有节点不能超过15 对任何非空二叉树T,若n0示意叶节点的个数,n2是度为2的非叶节点个数,那么两者满足关系n0=n2+1这里的度为2就是左右子树都存在的节点,无论怎么画这颗树叶子节点的个数都等于度为2的非叶子节点+1 二叉树的存储二叉树的存储常见的形式是数组和链表 应用数组齐全二叉树:按从上至下,从左到右顺序存储 非齐全二叉树非齐全二叉树要转成齐全二叉树才能够依照下面的计划存储,然而会造成很大的空间节约 应用链表二叉树最常见的形式还是应用链表存储每个节点封装成一个node,node中蕴含存储的数据,左节点的援用,右节点的援用 二叉搜寻树概念二叉搜寻树(BST,Binary Search Tree),也称为二叉排序树或二叉查找树二叉搜寻树是一颗二叉树,能够为空;如果不为空,满足一下性质:非空左子树的所有键值小于其根节点的键值。非空右子树的所有键值大于其根节点的键值。左,右子树自身也都是二叉搜寻树。 二叉搜寻树的特点二叉搜寻树的特点就是绝对较小的值总是保留在左节点上,绝对较大的值总是保留在右节点上,查找效率十分高,这也是二叉搜寻树中搜寻的起源

October 20, 2020 · 1 min · jiezi

关于数据结构和算法:西法的刷题秘籍电子书开发下载啦

LeetCode 2019-07-10 :留念我的项目 Star 冲破 1W 的一个短文, 记录了我的项目的"衰亡"之路,大家有趣味能够看一下,如果对这个我的项目感兴趣,请点击一下 Star, 我的项目会继续更新,感激大家的反对。2019-10-08: 留念 LeetCode 我的项目 Star 冲破 2W,并且 Github 搜寻“LeetCode”,排名第一。2020-04-12: 我的项目冲破三万 Star。2020-04-14: 官网力扣加加上线啦 ????????????????????,有专题解说,每日一题,下载区和视频题解,后续会减少更多内容,还不连忙珍藏起来?地址:http://leetcode-solution.cn/ 前言这是我将我的所有公开的算法材料整顿的一个电子书,全副题目信息中文化,以前会有一些英文形容,感激 @CYL 的中文整顿。 我写这本电子书破费了大量的工夫和精力,除了内容上的创作,还要做一些电子书的排版,以让大家取得更好的浏览体验。光数学公式的展现,我就钻研了多个插件的要源码,并魔改了一下才使得导出的电子书反对 latex。 不过有些动图,在做成电子书的时候天然就变没了,如果须要看动图的, 能够去我的公众号《力扣加加》或者我的 leetcode 题解仓库看。 因为是电子书,因而浏览体验可能会更好, 然而相应地就不能取得及时的更新,因而你能够珍藏一下我的同步电子书的网站 西法带你学算法 - 在线版。前期可能将每日一题, 91 天学算法其余章节的讲义等也整顿进来。 电子书有更新我也会在公众号《力扣加加》进行告诉, 感兴趣的同学能够关注一下。 目前导出了四种格局,惋惜的是这几种格局都有本人的有余: 在线版。 实时更新,想要及时获取最新信息的能够用在线版。html。 不便大家在线观看,因为是 html ,实际上大家也能够保存起来离线观看。pdf。可应用 pdf 阅读器和浏览器(比方谷歌)间接观看,浏览体验个别,生成的目录不能导航。mobi。 下载一个 Kindle 客户端就可以看,不须要购买 Kindle。epub。 数学公式和主题都比拟不错, 然而代码没有高亮。大家抉择适宜本人的格局下载即可。 在线版html, pdf,mobi 和 epub 格局,关注我的公众号《力扣加加》回复电子书即可。 介绍leetcode 题解,记录本人的 leetcode 解题之路。 本仓库目前分为五个局部: ...

October 18, 2020 · 1 min · jiezi

关于数据结构和算法:数据结构与算法学习哈希表下

引言下面的一篇文章曾经为这篇文章做了很好的铺垫,让咱们意识了哈希表。那么咱们来认识一下什么是扩容。扩容顾名思义就是扩充容量的意思。咱们上面会封装3个办法包含,put(新增/批改数据),get(获取数据),remove(删除数据)。下面咱们曾经提到了先定义一个固定长度的数组。既然是固定长度那么当咱们在不晓得减少多少的状况下这时候就用到了扩容。本篇文章是基于链地址法来实现的哈希表。 什么状况下扩容呢?比方常见的状况是loadFactor(填充因子)>0.75的时候,对哈希表进行扩容 为什么须要扩容?目前,咱们是将所有的数据项放在长度为7的数组中因为咱们应用的是链地址法,loadFactor(填充因子)能够大于1,所以这个哈希表能够无限度的插入新数据然而,随着数据量的增多,每一个index对应的bucket会越来越长,也就造成效率的升高所以在适合的状况对数组进行扩容,比方扩容两倍 如何扩容?扩容能够将简略的将容量增大两倍(不是质数吗?质数问题前面在探讨)然而这种状况下,所有的数据项肯定要同时进行批改(从新调用哈希函数,来获取到不同的地位)比方hashCode=12的数据项,在length=8的时候,index=4 在长度为16的时候呢?index=12这是一个耗时的过程,然而如果数组须要扩容,那么这个过程是必要的 检测质数在写办法之前咱们须要用到检测质数的办法,这样能够用于数组扩容之后的长度为质数从而解决元素的汇集问题;那么咱们先来看一看一般检测 function isPrimeHigh(num) { for (let i = 2; i < num; i++) { if (num % i === 0) { return false } } return true}下面这种办法是能够失常检测的,然而他的效率很慢,如果是一个比拟小的数还是很好的,然而如果传进去的是一个大的数字那么就很慢了。 高效的检测质数办法 //利用开方的办法 例如以下的12// 2 * 6 = 12// 3 * 4 = 12// Math.sqrt(num)*Math.sqrt(num)=12// 4 * 3 = 12// 6 * 2 = 12// 循环时只需循环到Math.sqrt(num)(这里通过parseInt函数获得的是3),因为Math.sqrt(num)前面的乘法就相当于Math.sqrt(num)后面反过来一样,所以如果后面的不能被整除前面的肯定不能被整除/* 拿13举例:parseInt(Math.sqrt(13)) = 3; i=2时 13 % 2 = 1; i=3时 13 % 3 = 1; 下面的流程没有进到办法,所以返回true*/function isPrime(num) { let temp = parseInt(Math.sqrt(num)); for (let i = 2; i <= temp; i++) { // i=2 12 % 2 = 0 那么间接返回false if (num % i == 0) { return false } } return true}哈希表办法删除办法和上图逻辑大体一致 ...

September 30, 2020 · 3 min · jiezi

关于数据结构和算法:阿里饿了么Java4面数据结构框架源码JVM分布式

前言:最近不少人都在找工作,很多人开始埋怨,工作难找,不少人后盾问我怎么办,讲真,我也无能为力,之前我就说过了,往年的工作竞争肯定是比拟强烈的。 面试只是对集体技术及应变能力的一次考验。只有解决了一个问题,你才有机会遇见下一个问题。以下面试题由群友提供或网上收集整理,共勉。 群友牛逼。以下是阿里饿了么Java面试题: Java一面hashmap源码问题HashMap底层构造 put操作讲一下HashMap、HashMap如何保障线程平安、ConcurrentHashMapJVM有哪些回收算法,对应的收集器有哪些?jvm g1的内存模型讲一下,G1和CMS收集器的区别?以及G1收集器对CMS的改良?java线程同步都有哪几种形式,synchonized和reteenlock的区别。cas的原理,变量要用哪个关键字润饰,volatile实现的原理。如果让你实现一个线程平安的队列,你会怎么实现。mysql数据库优化会波及到哪些?手撕代码:按档次遍历二叉树?spring中用到了什么,ioc有什么益处,aop是怎么实现的?Java二面自我介绍&我的项目分布式锁的原理。MySQL的事务隔离级别,别离解决什么问题?常见的分布式事务计划有哪些?如果让你实现一个https,你会怎么实现?dubbo有哪些模块,底层通信的原理?如何从0到1设计一个相似Dubbo的RPC框架? Java三面本人参加的我的项目,技术难度高的有哪些?线上有理论的性能优化教训?从SQL、JVM、架构、数据库四个方面讲讲优化思路,以及如何优先排序?redis的长久化形式,redis3.0原生集群和redis读写拆散+哨兵机制区别如果让你实现一个mq,怎么样保障音讯不失落你相熟哪些中间件,谈谈你对他们的了解,以及对应的应用场景区别?最初,你有什么想问我的?HR 四面你集体的最大的毛病是什么?在工作中和生存中遇见最大的挑战是什么?将来有什么布局?平时有哪些兴趣爱好?面试不免让人焦虑不安。经验过的人都懂的。然而如果你提前预测面试官要问你的问题并想出得体的答复形式,就会容易很多。上述面试题答案都整顿成文档笔记。 也还整顿了一些面试材料&最新2020收集的一些大厂的面试真题(都整顿成文档,小局部截图),有须要的能够在这里获取呦:https://gitee.com/biwangsheng/personal.git

September 11, 2020 · 1 min · jiezi

关于数据结构和算法:面经手册-第10篇扫盲javautilCollections工具包学习排序二分洗牌旋转算法

作者:小傅哥博客:https://bugstack.cn 积淀、分享、成长,让本人和别人都能有所播种!????一、前言算法是数据结构的灵魂! 好的算法搭配上适合的数据结构,能够让代码性能大大的晋升效率。当然,算法学习不只是刷题,还须要落地与利用,否则到了写代码的时候,还是会for循环+ifelse。 当开发一个略微简单点的业务流程时,往往要用到与之符合的数据结构和算法逻辑,在与设计模式联合,这样既能让你的写出具备高性能的代码,也能让这些代码具备良好的扩展性。 在以往的章节中,咱们把Java罕用的数据结构根本介绍完了,都已收录到:跳转 -> 《面经手册》,章节内容下图; 那么,有了这些数据结构的根底,接下来咱们再学习一下Java中提供的算法工具类,Collections。 二、面试题谢飞机,明天怎么垂头丧气的呢,还有黑眼圈? 答:好多常识盲区呀,最近始终在致力补短板,还有面经手册里的数据结构。 问:那数据结构看的差不多了吧,你有思考????过,数据结构里波及的排序、二分查找吗? 答:二分查找会一些,巴拉巴拉。 问:还不错,那你晓得这个办法在Java中有提供对应的工具类吗?是哪个! 答:这!?如同没留神过,没用过! 问:去吧,回家在看看书,这两天也劳动下。 飞机悄悄的出门了,但这次面试题整体答复的还是不错的,面试官决定下次再给他一个机会。 三、Collections 工具类java.util.Collections,是java汇合框架的一个工具类,次要用于Collection提供的通用算法;排序、二分查找、洗牌等算法操作。 从数据结构到具体实现,再到算法,整体的构造如下图; 1. Collections.sort 排序1.1 初始化汇合List<String> list = new ArrayList<String>();list.add("7");list.add("4");list.add("8");list.add("3");list.add("9");1.2 默认排列[正序]Collections.sort(list);// 测试后果:[3, 4, 7, 8, 9]1.3 Comparator排序Collections.sort(list, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.compareTo(o1); }});咱们应用 o2 与 o1 做比照,这样会进去一个顺叙排序。同时,Comparator还能够对对象类依照某个字段进行排序。测试后果如下;[9, 8, 7, 4, 3]1.4 reverseOrder倒排Collections.sort(list, Collections.<String>reverseOrder());// 测试后果:[9, 8, 7, 4, 3]Collections.<String>reverseOrder()的源码局部就和咱们下面把两个比照的类调换过去一样。c2.compareTo(c1);1.5 源码简述对于排序方面的知识点并不少,而且有点简单。本文次要介绍 Collections 汇合工具类,后续再深刻每一个排序算法进行解说。 ...

September 11, 2020 · 4 min · jiezi

关于数据结构和算法:数据结构与算法1排序Sort

1. 总览六种常见排序算法的复杂度和稳定性: 稳定性指的是对于相等的元素,排序前后可能保障这些元素的绝对秩序不变,如[1-A, 2-B, 3-C, 2-D], 字母仅仅示意一个绝对秩序,稳定性的排序后果为[1-A, 2-B, 3-C, 2-D],非稳定性的排序后果为**[1-A, 2-D, 3-C, 2-B]排序算法最坏工夫均匀工夫稳定性冒泡排序 (BubbleSort)O(N2)O(N2)稳固抉择排序 (SelectSort)O(N2)O(N2)不稳固插入排序 (InsertSort)O(N2)O(N2)稳固归并排序 (MergeSort)O(NlogN)O(NlogN稳固疾速排序 (quickSort)O(N2)O(NlogN)不稳固堆排序 (heapSort)O(NlogN)O(NlogN)不稳固2. 冒泡排序 BubbleSort比较简单的一种排序算法,次要思维为:每次都从0地位开始,比拟以后元素和下一个元素的大小,如果逆序则进行替换。每次比对都相当于遍历一次数组的无序局部,并将最大的元素传递到队尾,排序的过程很像冒泡一样。 public class sort_bubbleSort { public static void main(String[] args) { int[] arr = ArrayGenerator.array(20, 20); // 随机数组生成器 System.out.println(Arrays.toString(arr)); bubbleSort(arr); System.out.println(Arrays.toString(arr)); } public static void bubbleSort(int[] arr){ for(int i = 0; i < arr.length; i++){ boolean shutdown = true; // 加状态位提前结束 for(int j = 0; j < arr.length - i - 1; j++){ if(arr[j] > arr[j+1]){ shutdown = false; swap(arr, j, j + 1); } } if(shutdown){ break; } } } public static void swap(int[] arr, int i, int j){ int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; }}3. 抉择排序 SelectSort算法思维:每次从以后无序序列中抉择最大值(最小值)替换到无序序列的队尾(队首)。每一次的抉择最大值(最小值)须要遍历一次无序局部,要做n-1次抉择,因而工夫复杂度相当于1-n之间的间断数相加,天然为O(N2)。但与冒泡排序相比,在最坏状况,每一次的外循环,抉择排序只会做一次替换和n次比拟;而冒泡排序则要做n次比拟和n次替换,因而工夫复杂度尽管一个等级,抉择排序的理论耗时要小于冒泡排序 ...

August 30, 2020 · 4 min · jiezi

关于数据结构和算法:Leetcode分治

50. Pow(x, n)实现 pow(x, n) ,即计算 x 的 n 次幂函数。 示例 1: 输出: 2.00000, 10输入: 1024.00000示例 2: 输出: 2.10000, 3输入: 9.26100示例 3: 输出: 2.00000, -2输入: 0.25000解释: 2-2 = 1/22 = 1/4 = 0.25阐明: -100.0 < x < 100.0n 是 32 位有符号整数,其数值范畴是 [−231, 231 − 1] 。题目难度: Midium 思路1: 二分 + 递归 首先看到题目,最直观的想法就是一次遍历,每次都乘上x。工夫复杂度为$O(n)$, 空间复杂度为$O(1)$.$f(n)=f(n-1)*x$ 通常而言,最暴力的办法不会是效率最高的办法, 这题也不例外。比方 $$\begin{align}2^4=2^2*2^2 \\ 2^5=2^3*2^2\end{align}$$ 咱们其实并不需要计算$2^1$始终到$2^n$. 因而能够失去一个更高效的计算公式$$x^n=\begin{cases} x^{\frac{n+1}{2}}*x^{\frac{n-1}{2}}, if(n\%2!=0)\\ x^{\frac{n}{2}}*x^{\frac{n}{2}}, if(n\%2==0)\end{cases}$$ def myPow(x -> float, n -> int) -> float: if n == 0: return 1 flag = 1 if n > 0 else -1 n = abs(n) def helper(x, n): if n == 0: return 1 # 如果是奇数 if n % 2: res = helper(x, (n-1) // 2) return res * res * x # 如果是偶数 res = helper(x, n // 2) return res * res return helper(x, n) if flag > 0 else 1. / helper(x, n) 工夫复杂度: $O(log(n))$, 空间复杂度:$O(log(n))$. ...

August 19, 2020 · 3 min · jiezi

关于数据结构和算法:数据结构-常用排序算法

排序之间性能的比拟 间接插入排序将一个记录插入到已排序好的有序表中,从而失去一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,而后从第2个记录一一进行插入,直至整个序列有序为止。 插入排序算法的个别步骤: 1.从第一个元素开始,该元素能够认为已被排序; 2.取出下一个元素,在曾经排序的元素序列中从后向前扫描; 3.如果该元素(已排序)大于新元素,将该元素移到下一个地位; 4.反复步骤3,直到找到已排序的元素小于或者等于新元素的地位; 5.将新元素插入到该地位后,反复2~5 void insetSort(int arr[], int n){ for (int i = 1; i < n; i++) { for (int j = i-1; j >= 0; j--) { if (arr[j] > arr[i]) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } }}希尔排序 希尔排序的算法思维:将待排序数组依照步长gap进行分组,而后将每组的元素利用间接插入排序的办法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,实现排序。 void shellSort(int arr[], int len){ int insertNum; int grap = len / 2; while (grap) { for (int i = grap; i < len; i++) { insertNum = arr[i]; int j = i; while (j >= grap && insertNum < arr[j - grap]) { arr[j] = arr[j - grap]; j -= grap; } arr[j] = insertNum; } grap /= 2; }}

August 17, 2020 · 1 min · jiezi

关于数据结构和算法:剑指offer1-二维数组中的查找JavaPython

二维数组中的查找1. 题目形容在一个二维数组中(每个一维数组的长度雷同),每一行都依照从左到右递增的程序排序,每一列都依照从上到下递增的程序排序。请实现一个函数,输出这样的一个二维数组和一个整数,判断数组中是否含有该整数。 2. 示例现有矩阵 matrix 如下: [ [1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30] ] 给定 target = 5,返回 true。 给定 target = 20,返回 false。 3. 解题思路应用以下办法的前提是:该矩阵是有序的。 从矩阵的左下角开始搜寻,即从18 开始查找,如果该数大于指标数,则 行数 减去 1,向上搜寻小的数值; 如果小于指标数,则 列数 + 1 ,向左边搜寻,搜寻更大的数值 4. Java实现// Java语言实现// Java语言实现public class Solution { public boolean Find(int target, int [][] array) { int row = array.length-1; int col = 0; // 从左下角开始搜寻,array.length 示意行的大小,array[0].length示意列的大小 while (row >= 0 && col <= array[0].length-1){ if (array[row][col] == target){ return true; }else if(array[row][col] > target){ row--; }else{ col++; } } return false; }}5. Python实现// Java语言实现public class Solution { public boolean Find(int target, int [][] array) { int row = array.length-1; int col = 0; // 从左下角开始搜寻,array.length 示意行的大小,array[0].length示意列的大小 while (row >= 0 && col <= array[0].length-1){ if (array[row][col] == target){ return true; }else if(array[row][col] > target){ row--; }else{ col++; } } return false; }} ...

August 15, 2020 · 1 min · jiezi

关于数据结构和算法:剑指offer1-二维数组中的查找JavaPython

二维数组中的查找1. 题目形容在一个二维数组中(每个一维数组的长度雷同),每一行都依照从左到右递增的程序排序,每一列都依照从上到下递增的程序排序。请实现一个函数,输出这样的一个二维数组和一个整数,判断数组中是否含有该整数。 2. 示例现有矩阵 matrix 如下: [ [1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30] ] 给定 target = 5,返回 true。 给定 target = 20,返回 false。 3. 解题思路应用以下办法的前提是:该矩阵是有序的。 从矩阵的左下角开始搜寻,即从18 开始查找,如果该数大于指标数,则 行数 减去 1,向上搜寻小的数值; 如果小于指标数,则 列数 + 1 ,向左边搜寻,搜寻更大的数值 4. Java实现// Java语言实现// Java语言实现public class Solution { public boolean Find(int target, int [][] array) { int row = array.length-1; int col = 0; // 从左下角开始搜寻,array.length 示意行的大小,array[0].length示意列的大小 while (row >= 0 && col <= array[0].length-1){ if (array[row][col] == target){ return true; }else if(array[row][col] > target){ row--; }else{ col++; } } return false; }}5. Python实现// Java语言实现public class Solution { public boolean Find(int target, int [][] array) { int row = array.length-1; int col = 0; // 从左下角开始搜寻,array.length 示意行的大小,array[0].length示意列的大小 while (row >= 0 && col <= array[0].length-1){ if (array[row][col] == target){ return true; }else if(array[row][col] > target){ row--; }else{ col++; } } return false; }} ...

August 15, 2020 · 1 min · jiezi

关于数据结构和算法:LeetCode002两数相加medium

题目:给出两个非空的链表用来示意两个非负的整数。其中,它们各自的位数是依照逆序的形式存储的,并且它们的每个节点只能存储一位数字。如果,咱们将这两个数相加起来,则会返回一个新的链表来示意它们的和。您能够假如除了数字0之外,这两个数都不会以0结尾示例: 输出:(2 -> 4 -> 3) + (5 -> 6 -> 4)输入:7 -> 0 -> 8起因:342 + 465 = 807解题思路: 题中说各自的位数是依照逆序的形式存储到链表中,其实这升高了难度,因为刚好进行加法运算时,从个位开始加,而链表中从前到后是从低位开始的,刚好合乎了加法的运算程序(以题中给的数据为例) 既然是求和,那不难晓得,取出两个链表中的每一个节点,而后进行求和(sum),如果大于10则记一个进位1,而后拿sum对10进行求余,得的到后果就是指标链表的第一个无效节点,具体过程如图: 实现过程: 1、外围办法就是对两个链表进行遍历,而后一一节点求和 因为不晓得这两个要求和的链表的节点个数(也就是不晓得循环次数),所以抉择while循环。只有任何一个链表的节点不为null,都须要持续进行循环然而因为有进位的状况,所以,当进位不是0时,也不能进行循环(比方9999+1)2、生成新的链表存储求和的后果 对于链表的问题,通常都会创立一个标记节点,它不存具体的值,它的next负责指向第一个无效节点(始终指向第一个,不挪动),次要是为了不便咱们返回失去的指标链表同时须要创立一个current节点,他负责生成每一个节点,它是挪动的,每生成一个节点,它都须要向后挪动一个节点,从而实现构建新的链表语言表达能力有点差,可能形容的不是很分明,代码中有必要的正文,心愿能够帮忙了解 代码实现 class ListNode{ public $val = 0; public $next = null; public function __construct($val) { $this->val = $val; }}class LeetCode002{ /** * @param ListNode $l1 * @param ListNode $l2 * @return ListNode */ public function addTwoNumbers($l1, $l2) { if ($l1 == null && $l2 == null) { return null; } $tag = new ListNode(0);//创立空节点 $current = $tag; $addOne = 0;//进位 while ($l1 != null || $l2 != null || $addOne != 0) { $val1 = $l1 == null ? 0 : $l1->val; $val2 = $l2 == null ? 0 : $l2->val; $sum = $val1 + $val2 + $addOne; $current->next = new ListNode($sum % 10); $current = $current->next; $addOne = intval($sum / 10);//坑,强类型语言不存在此问题 if ($l1 != null) { $l1 = $l1->next; } if ($l2 != null) { $l2 = $l2->next; } } return $tag->next; }}//Test Case$leetCode = new LeetCode002();$l1 = new ListNode(2);$l1->next = new ListNode(4);$l2 = new ListNode(5);$l2->next = new ListNode(6);$newList = $leetCode->addTwoNumbers($l1, $l2);$str = '';while ($newList != null) { $str .= $newList->val.'->'; $newList = $newList->next;}echo $str;执行后果 ...

July 31, 2020 · 1 min · jiezi

面试操作系统生疏点

这天面试被问到了一些操作系统,和算法题。有些感觉有点陌生了,来回顾一下。 过程之间的通信形式?排序数组二维数组查找?快排工夫复杂度及过程? 过程之间通信形式? 相干好文:[过程间的通信形式](https://www.cnblogs.com/LUO77/p/5816326.html)过程复制:fork()复制一个截然不同的子过程,胜利返回0。通信形式有:管道、有名管道、音讯队列、共享内存、信号量、套接字、信号。信号:半双工,只限于父子过程间应用。在<signal.h>头文件中,软件层面对中断的模仿。管道:半双工,FIFO。匿名管道只能用于父子过程通信。音讯队列:可按音讯类型分类读取的队列。共享内存:间接进行共享内存数据交换。无需用户态和外围态的切换。效率最高。 排序二维数组查找值 剑指offer的题。leetcode地址: 剑指 Offer 04. 二维数组中的查找法一 暴力: 思路: 间接二重循环遍历。工夫复杂度O(n*m)。毛病:没有利用到已排序个性。public boolean findNumberIn2DArray(int[][] matrix, int target) { for(int i=0;i<matrix.length;i++){ for(int j=0;j<matrix[i].length;j++){ if(target==matrix[i][j])return true; } } return false; }法二: 思路:穿插极值点(左下或右上,既是极小值也是极大值)。每次没找到就剔除一行/一列。工夫复杂度O(m+n),最极其状况:在最初一行最右边匹配。剖析:每行的最初边必然大于右边的数。所以先判断每行最大值。代码://每一行的最初边是值最大的 public boolean findNumberIn2DArray(int[][] matrix, int target) { if(matrix.length==0) return false; int col_len = matrix[0].length; for(int i=0;i<matrix.length;i++){ //行下移 for(int j=col_len-1;j>=0;j--){ //列左移 if(matrix[i][j]==target)return true; if(matrix[i][j]<target){ //这行都不够大 j=matrix[0].length-1; //重置列 break; //下移一行 } } } return false; }法三:行列同时二分查找。思路:充分利用二分查找思维。代码:略 快排 工夫复杂度O(nlgn) ...

July 15, 2020 · 1 min · jiezi

Leetcode面试题3面试题数组中重复的数字

题目描述找出数组中重复的数字。 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。示例 1: 输入:[2, 3, 1, 0, 2, 5, 3]输出:2 或 3 该题目的难度级别为Easy。 思路1最直观的方式可能就是排序+遍历的方式, 根据这个思路可以很容易的写出代码 def findRepeatNumber(nums: List[int]) -> int: if nums == None or len(nums) < 2: return nums.sort() for i in range(1, len(nums)): # 只要找到就可以返回 if nums[i] == nums[i-1]: return nums[i]排序的时间复杂度为$O(nlogn)$,遍历的时间复杂度为$O(n)$,即算法的时间复杂度为$O(nlogn)$,空间复杂度为$O(1)$。 思路2使用字典作为计数器,只要某个元素的已经存在于字典中就可以直接返回。按照这个思路可以写出如下代码 def findRepeatNumber(nums: List[int]) -> int: if nums == None or len(nums) < 2: return dicts = {} for i in nums: # 只要找到就可以返回 if i in dicts: return i dicts[i] = 1相比于第一种算法,该算法以空间换时间。因为只有一次循环,时间复杂度为$O(n)$,空间复杂度为$O(n)$。 ...

June 8, 2020 · 1 min · jiezi

重学数据结构之数组篇

数组在我们日常的编程工作中会经常用到,它不仅仅是一种编程语言的数据类型,还是一种最基础的数据结构。今天我们来重新认识一下它。@[toc] 数组是什么?数组是一种线性表数据结构,用一组连续的内存空间,来存储一组具有相同类型的数据。 关键词线性表连续的内存空间和相同类型的数据线性表,表示数据像糖葫芦一样被串起来,每个数据最多只有前后两个方向。除了数组,其他线性表结构的还有, 栈、链表、队列等。 与之对应的就是非线性表,即内部数据元素非简单的前后关系,比如,二叉树,图,堆等结构。连续内存空间和相同类型数据,提供了“随机访问”的特性,但是在插入和删除数据的时候,会导致大量的内存空间需要进行迁移。以保证内存空间的连续性。 数组是如何通过下标随机访问元素的首先我们创建一个数组 int[] a = new int[10]。假如计算机给我们创建的数组内存地址如下,内存地址范围1000~1039, 内存的首地址为k=1000;那么每个元素的内存地址等于首地址加上数据类型占用的字节数,那计算公式就是 a[i]_address = k + i * data_byte_size比如,第三个元素的地址为: 1000 + 2* 4 = 1008 插入和删除操作低效原因假如我们有一个长度为n的数据,现在我们需要将一个元素x,插入到k的位置,那么为了保证数组内存空间的连续性,那么k~n之间的数据都需要顺序的往后挪一位。从时间复杂度上来说看,如果插入的数据正好是数组的末尾,就不需要移动任何数据,那么时间复杂度为O(1);如果恰好插入在数组的头部,那么后面的元素都要顺序后移一位,那么最坏时间复杂度就是O(n);其实在数组每个位置插入元素的概率是一样的,所以平均时间复杂度为O(n)。如果数组中的元素是有序的,那么我们需要遵循上面的方法;如果数组中的元素不需要有序呢,比如我们有个元素需要插入到k位置, 最简单的方式就是将原来k位置的元素放到数组的末尾,将新元素放在k的位置上,这种情景下,插入元素的时间复杂度就是O(1)了。同样的删除操作也是一样,最好的时间复杂度为O(1),最坏的为O(n),平均为O(n)再将元素多次删除时,为了提高效率,可以考虑将删除操作集中在一起。我们在进行删除操作是,并不是真正的删除,只是将需要删除的元素进行标记,当数组没有更大存储空间时,再进行一次真正的删除,删除标记的元素。这就大大减少了每次删除元素进行的搬移操作,这也是JVM 标记清除垃圾回收算法的核心思想。 数组越界问题首先对于Java语言来说,java本身会做数组越界检查,比如下面的代码,就会抛出异常java.lang.ArrayIndexOutOfBoundsException。 int[] a = new int[6];a[6] = 10;而在C语言中,数组越界是一种未决行为,并没有规定数组访问越界时编译器应该如何处理。因为,访问数组的本质就是访问一段连续内存,只要数组通过偏移计算得到的内存地址是可用的,那么程序就可能不会报任何错误。因此需要C语言的开发者,在开发过程中需要处理数组越界问题。 数组和容器的适用场景Java中ArrayList的使用是非常常见的。ArrayList相对于数组来说有两大优点: 分装了数组的需要操作细节,比如插入,删除操作动态扩容。数组是固定的,而ArrayList在存储空间不够的时候,会自动扩容为1.5倍大小。实际业务开发场景中,如果一直数据的数量大小,在使用ArrayList时建议指定大小,减少内存扩容申请和数据搬移的耗时,比如我们已知数据大小为10000 ArrayList<Books> books = new ArrayList(10000);for (int i = 0; i < 10000; ++i) { books.add(xxx);}那么什么时候使用数组更合适呢? 数据大小已确定,数据操作比较简单,多维数组,使用数组比较直观Java ArrayList无法存储基本类型数据,比如int,long 等,需要进行装箱拆箱,有一定的性能损耗,使用数组会比较合适为什么数据的下标索引是从零开始首先我们回到上面计算元素内存地址的公式如果用 a 来表示数组的首地址,a[0]就是偏移为 0 的位置,也就是首地址,a[i]就表示偏移 i 个 type_size 的位置 a[i]_address = base_address + i * data_byte_size如果下标索引从1开始,那么公式就变成了 ...

June 3, 2020 · 1 min · jiezi

go语言数据结构和算法库GoSTL

GoSTLGoSTL是一个go语言数据结构和算法库,类似C++的STL,但功能更强大。结合go语言的特点,大部分数据结构都实现了线程安全,可以在创建对象的时候通过配置参数指定是否开启。 功能列表数据结构 切片(slice)数组(array)向量(vector)列表(list)双端队列(deque)队列(queue)优先队列(priority_queue)栈(stack)红黑树(rbtree)映射(map/multimap)集合(set/multiset)位映射(bitmap)布隆过滤器(bloom_filter)哈希映射树(hamt)一致性哈希(ketama)跳表(skiplist)算法 快排(sort)稳定排序(stable_sort)二分查找(binary_search)二分查找第一个元素的位置(lower_bound)二分查找第一个大于该元素的位置(upper_bound)下一个排列组合(next_permutation)例子切片(slice)这个库中的切片是对go原生切片的重定义。 下面是一个如何将go原生的切片转成IntSlice,然后对其排序的例子。 package mainimport ( "fmt" "github.com/liyue201/gostl/algorithm/sort" "github.com/liyue201/gostl/ds/slice" "github.com/liyue201/gostl/utils/comparator")func main() { a := slice.IntSlice(make([]int, 0)) a = append(a, 2) a = append(a, 1) a = append(a, 3) fmt.Printf("%v\n", a) // sort in ascending sort.Sort(a.Begin(), a.End()) fmt.Printf("%v\n", a) // sort in descending sort.Sort(a.Begin(), a.End(), comparator.Reverse(comparator.BuiltinTypeComparator)) fmt.Printf("%v\n", a)}数组(array)数组是一种一旦创建长度就固定的数据结构,支持随机访问和迭代器访问。 package mainimport ( "fmt" "github.com/liyue201/gostl/ds/array")func main() { a := array.New(5) for i := 0; i < a.Size(); i++ { a.Set(i, i + 1) } for i := 0; i < a.Size(); i++ { fmt.Printf("%v ", a.At(i)) } fmt.Printf("\n") for iter := a.Begin(); iter.IsValid(); iter.Next() { fmt.Printf("%v ", iter.Value()) }}vector向量是一种大小可以自动伸缩的数据结构,内部使用切片实现。支持随机访问和迭代器访问。 ...

November 5, 2019 · 5 min · jiezi

字典序的一个生成算法

字典序的一个生成算法。最近在LeetCode刷题,刷到一个题,链接:https://leetcode-cn.com/problems/permutation-sequence/这个题要求得长度为n的字典序列的第k个排列。我们知道,字典序列是一个长度为n(n>=1),元素为1~n的无重复整数序列。之前还真没仔细了解过如何按照顺序,从小到大生成这个序列。这次就探究一下。我先在纸上枚举了n=3、4、5这几种简单的序列的生成,从中找到规律,然后推理出一般方法。以n=4为例,字典序从小到大生成如下: 1234 → 1243 → 1324 → 1342 → 1423 → 1432 → 2134 → 2143 → 2314 → 2341 → 2413 → 2431 → 3124 → 3142 → 3214 → 3241 → 3412 → 3421 → 4123 → 4132 → 4213 → 4231 → 4312 → 4321当我们拥有了从第m个排列到m+1个排列的生成方法时,就可以写一个算法findNext(),通过k-1次生成排列,就可以求出第k次的排列。 那么接下来就是寻找字典序的规律:我们能够知道 如果当前字典序排列为M,假设M的下一个字典序为N,N也有下一个字典序O,那么有以下推论: 1. N = findNext(M)2. O = findNext(N)3. M < N < O所以可得:N是大于M的最小的排列既然我们要生成这样的一个排列,那么就要尽可能变动位数更低的数去增大序列:以 findNext(1243)为例,为了尽可能变动位数更低的数去增大序列,由于“43”已经是降序排列的子序列,无法通过变动“4”这个位及更低的位去增大序列,那么只能从上一位“2”去增大序列,所以我们要从“43”这个降序序列中找到一个最的数“3”,换到“2”的位置,把“2”放入降序序列中,然后重新按照升序排序,这样就生成了“1324”,即1324 = findNext(1243) 所以我们有以下思路: ...

October 16, 2019 · 2 min · jiezi

23-Merge-k-Sorted-Lists

合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。 输入:[  1->4->5,  1->3->4,  2->6]输出: 1->1->2->3->4->4->5->6class Solution(object): def mergeKLists(self, lists): """ :type lists: List[ListNode] :rtype: ListNode """ amount = len(lists) interval = 1 while interval < amount: for i in range(0, amount - interval, interval * 2): lists[i] = self.merge2Lists(lists[i], lists[i + interval]) interval *= 2 return lists[0] if amount > 0 else lists def merge2Lists(self, l1, l2): head = point = ListNode(0) while l1 and l2: if l1.val <= l2.val: point.next = l1 l1 = l1.next else: point.next = l2 l2 = l1 l1 = point.next.next point = point.next if not l1: point.next=l2 else: point.next=l1 return head.next复杂度分析 ...

August 8, 2019 · 1 min · jiezi

JavaScript-数据结构与算法之美-非线性表中的树堆是干嘛用的-其数据结构是怎样的

1. 前言想学好前端,先练好内功,内功不行,就算招式练的再花哨,终究成不了高手。非线性表(树、堆),可以说是前端程序员的内功,要知其然,知其所以然。 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算法和方便以后复习。 非线性表中的树、堆是干嘛用的 ?其数据结构是怎样的 ? 希望大家带着这两个问题阅读下文。 2. 树 树的数据结构就像我们生活中的真实的树,只不过是倒过来的形状。 术语定义 节点:树中的每个元素称为节点,如 A、B、C、D、E、F、G、H、I、J。父节点:指向子节点的节点,如 A。子节点:被父节点指向的节点,如 A 的孩子 B、C、D。父子关系:相邻两节点的连线,称为父子关系,如 A 与 B,C 与 H,D 与 J。根节点:没有父节点的节点,如 A。叶子节点:没有子节点的节点,如 E、F、G、H、I、J。兄弟节点:具有相同父节点的多个节点称为兄弟节点,如 B、C、D。节点的高度:节点到叶子节点的最长路径所包含的边数。节点的深度:根节点到节点的路径所包含的边数。节点层数:节点的深度 +1(根节点的层数是 1 )。树的高度:等于根节点的高度。森林: n 棵互不相交的树的集合。 高度是从下往上度量,比如一个人的身高 180cm ,起点就是从 0 开始的。深度是从上往下度量,比如泳池的深度 180cm ,起点也是从 0 开始的。高度和深度是带有度字的,都是从 0 开始计数的。而层数的计算,是和我们平时的楼层的计算是一样的,最底下那层是第 1 层,是从 1 开始计数的,所以根节点位于第 1 层,其他子节点依次加 1。 二叉树分类 二叉树每个节点最多只有 2 个子节点的树,这两个节点分别是左子节点和右子节点。如上图中的 1、 2、3。不过,二叉树并不要求每个节点都有两个子节点,有的节点只有左子节点,有的节点只有右子节点。以此类推,自己想四叉树、八叉树的结构图。 满二叉树一种特殊的二叉树,除了叶子节点外,每个节点都有左右两个子节点,这种二叉树叫做满二叉树。如上图中的 2。完全二叉树一种特殊的二叉树,叶子节点都在最底下两层,最后一层叶子节都靠左排列,并且除了最后一层,其他层的节点个数都要达到最大,这种二叉树叫做完全二叉树。如上图的 3。完全二叉树与不是完全二叉树的区分比较难,所以对比下图看看。 堆之前的文章 栈内存与堆内存 、浅拷贝与深拷贝 中有说到:JavaScript 中的引用类型(如对象、数组、函数等)是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript 不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用。 ...

July 16, 2019 · 5 min · jiezi

算法第一课学习笔记

一、哈佛大学智商测试(离散数学)1. 题目皇帝不是穷人,在守财奴之中也有穷人,所以,有一些( )并不是( )。 A. 皇帝,皇帝B. 守财奴,守财奴C. 守财奴,皇帝D. 皇帝,守财奴2. 解答这题可以采用离散数学中的逻辑推理来做,首先,假设下列命题为真: p: 这个人是皇帝q: 这个人是穷人r: 这个人是守财奴皇帝不是穷人: p → ¬q在守财奴之中也有穷人: ∃x(x∈r ⋀ x∈q)然后,根据p → ¬q得到其逆否命题q → ¬p也为真, 此时,由∃x(x∈r ⋀ x∈q)和q → ¬p,根据假言三段论可推知∃x(x∈r ⋀ x∈¬p), 翻译之后就是:存在一些人是守财奴且不是皇帝。 故而此题选C,有一些守财奴并不是皇帝。 3. 相关知识:假言三段论假言三段论又称假言推理。假言推理总是以假言判断为前提来进行推理的。在逻辑运算符记号中表示为: p → qq → r所以 p → r经典例子: 人都是要死的,苏格拉底是人,所以苏格拉底是要死的。参见: Hypothetical syllogism - Wikipedia 二、天平和硬币1. 题目现在有16枚外形相同的硬币,其中一枚是假币,且己知假币比真币重量轻。先给定一架没有砝码的天平,问至少需要多少次称量才能找到这枚假币? 思考:给出一种称量方案容易,但如何证明这种方法用到的称量次最少呢? 2. 解答可以采取三分法,将硬币分为三堆: A:5枚B:5枚C:6枚先称量A和B,若A(或B)较轻,则可以通过将5枚硬币再分为2枚、2枚、1枚三堆来称量,最多需要两次称量找出假币。 若A和B平衡,则假币在剩下的六枚硬币中,将其分为2枚、2枚、2枚三堆,天平两端各放两枚,无论平或不平,最多只需要两次称量就能找出假币。 综上,至少需要称量3次才能找到假币。 3. 理论下界既然一次天平称量能得到左倾、右倾、平衡三种情况,若把一次称量当成一位编码,该编码是3进制的,则该问题转换为:用多少位编码能够表示16呢? 解答:假设需要n位,则3^n >= 16,两边取对数得到n >= 2.52,这表示至少3次才能找到该轻质的假币。 三、链表相加1. 题目给定两个链表,分别表示两个非负整数,它们的数字按逆序存储在链表中,且每个结点只存储一个数字,计算两个数的和,并且返回和的链表头指针。 ...

July 10, 2019 · 3 min · jiezi

JavaScript-数据结构与算法之美-递归

1. 前言算法为王。排序算法博大精深,前辈们用了数年甚至一辈子的心血研究出来的算法,更值得我们学习与推敲。因为之后要讲有内容和算法,其代码的实现都要用到递归,所以,搞懂递归非常重要。 1. 定义方法或函数调用自身的方式称为递归调用,调用称为递,返回称为归。简单来说就是:自己调用自己。 现实例子:周末你带着女朋友去电影院看电影,女朋友问你,咱们现在坐在第几排啊 ?电影院里面太黑了,看不清,没法数,现在你怎么办 ? 于是你就问前面一排的人他是第几排,你想只要在他的数字上加一,就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也问他前面的人。就这样一排一排往前问,直到问到第一排的人,说我在第一排,然后再这样一排一排再把数字传回来。直到你前面的人告诉你他在哪一排,于是你就知道答案了。 基本上,所有的递归问题都可以用递推公式来表示,比如: f(n) = f(n-1) + 1; // 其中,f(1) = 1 f(n) 表示你想知道自己在哪一排,f(n-1) 表示前面一排所在的排数,f(1) = 1 表示第一排的人知道自己在第一排。 有了这个递推公式,我们就可以很轻松地将它改为递归代码,如下: function f(n) { if (n == 1) return 1; return f(n-1) + 1;}2. 为什么使用递归 ?递归的优缺点 ?优点:代码的表达力很强,写起来简洁。缺点:空间复杂度高、有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题。3. 什么样的问题可以用递归解决呢 ?一个问题只要同时满足以下 3 个条件,就可以用递归来解决。 问题的解可以分解为几个子问题的解。何为子问题 ?就是数据规模更小的问题。比如,前面讲的电影院的例子,你要知道,自己在哪一排的问题,可以分解为前一排的人在哪一排这样一个子问题。 问题与子问题,除了数据规模不同,求解思路完全一样比如电影院那个例子,你求解自己在哪一排的思路,和前面一排人求解自己在哪一排的思路,是一模一样的。 存在递归终止条件比如电影院的例子,第一排的人不需要再继续询问任何人,就知道自己在哪一排,也就是 f(1) = 1,这就是递归的终止条件。 4. 递归常见问题及解决方案警惕堆栈溢出:可以声明一个全局变量来控制递归的深度,从而避免堆栈溢出。警惕重复计算:通过某种数据结构来保存已经求解过的值,从而避免重复计算。5. 如何实现递归 ?1. 递归代码编写 写递归代码的关键就是找到如何将大问题分解为小问题的规律,并且基于此写出递推公式,然后再推敲终止条件,最后将递推公式和终止条件翻译成代码。 2. 递归代码理解 对于递归代码,若试图想清楚整个递和归的过程,实际上是进入了一个思维误区。 那该如何理解递归代码呢 ? 如果一个问题 A 可以分解为若干个子问题 B、C、D,你可以假设子问题 B、C、D 已经解决。而且,你只需要思考问题 A 与子问题 B、C、D 两层之间的关系即可,不需要一层层往下思考子问题与子子问题,子子问题与子子子问题之间的关系。屏蔽掉递归细节,这样子理解起来就简单多了。因此,理解递归代码,就把它抽象成一个递推公式,不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。 ...

July 4, 2019 · 2 min · jiezi

JavaScript-数据结构与算法之美-栈内存与堆内存-浅拷贝与深拷贝

前言想写好前端,先练好内功。栈内存与堆内存 、浅拷贝与深拷贝,可以说是前端程序员的内功,要知其然,知其所以然。 笔者写的 JavaScript 数据结构与算法之美 系列用的语言是 JavaScript ,旨在入门数据结构与算法和方便以后复习。 栈 定义 后进者先出,先进者后出,简称 后进先出(LIFO),这就是典型的栈结构。新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端就叫栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。从栈的操作特性来看,是一种 操作受限的线性表,只允许在一端插入和删除数据。不包含任何元素的栈称为空栈。栈也被用在编程语言的编译器和内存中保存变量、方法调用等,比如函数的调用栈。 堆定义 堆数据结构是一种树状结构。它的存取数据的方式,与书架与书非常相似。我们不关心书的放置顺序是怎样的,只需知道书的名字就可以取出我们想要的书了。好比在 JSON 格式的数据中,我们存储的 key-value 是可以无序的,只要知道 key,就能取出这个 key 对应的 value。 堆与栈比较 堆是动态分配内存,内存大小不一,也不会自动释放。栈是自动分配相对固定大小的内存空间,并由系统自动释放。栈,线性结构,后进先出,便于管理。堆,一个混沌,杂乱无章,方便存储和开辟内存空间。栈内存与堆内存JavaScript 中的变量分为基本类型和引用类型。 基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问,并由系统自动分配和自动释放。这样带来的好处就是,内存可以及时得到回收,相对于堆来说,更加容易管理内存空间。JavaScript 中的 Boolean、Null、Undefined、Number、String、Symbol 都是基本类型。 引用类型(如对象、数组、函数等)是保存在堆内存中的对象,值大小不固定,栈内存中存放的该对象的访问地址指向堆内存中的对象,JavaScript 不允许直接访问堆内存中的位置,因此操作对象时,实际操作对象的引用。JavaScript 中的 Object、Array、Function、RegExp、Date 是引用类型。 结合实例说明 let a1 = 0; // 栈内存let a2 = "this is string" // 栈内存let a3 = null; // 栈内存let b = { x: 10 }; // 变量 b 存在于栈中,{ x: 10 } 作为对象存在于堆中let c = [1, 2, 3]; // 变量 c 存在于栈中,[1, 2, 3] 作为对象存在于堆中 ...

July 2, 2019 · 4 min · jiezi

你知道和你不知道的选择排序

1. 什么是选择排序?首先贴上从wiki上弄下来的关于选择排序的定义。 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。更加直白的解释是,每次都从数组中选出最大或者最小的元素,然后放到数组的左边。 2. 选择排序的过程展示老规矩,我们还是通过动图来看一下选择排序的过程。以下的gif来自于wiki。 然后我们再通过我制作的gif,配上数据再了解一下过程。假设我们的待排序数组还是[5, 1, 3, 7, 6, 2, 4]。 3. 选择最小值的算法我们使用Java来实现最常见的,选择最小值的选择排序,其代码如下。 private void![clipboard.png](/img/bVpLs2)![clipboard.png](/img/bVbuzbu)t(int[] arr) { int min; int minIndex; for (int i = 0; i < arr.length - 1; i++) { min = arr[i]; minIndex = -1; for (int j = i; j < arr.length; j++) { if (arr[j] < min) { min = arr[j]; minIndex = j; } } // 排序结束 交换位置 if (minIndex != -1) { exchange(arr, i, minIndex); } }}private void exchange(int arr[], int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}int[] arr = new int[]{5, 1, 3, 7, 6, 2, 4};selectionSort(arr);System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5, 6, 7]假设数组的长度为7,那么算法就需要进行6轮。如果数组的长度为n,则算法需要进行n - 1轮。 ...

July 2, 2019 · 2 min · jiezi

数据结构与算法三带你读懂选择排序Selection-sort

1. 基本介绍选择式排序(select sorting)也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。 2. 选择排序思想基本思想是:第一次从 arr[0]~arr[n-1]中选取最小值,与 arr[0]交换,第二次从 arr[1]~arr[n-1]中选取最小值,与 arr[1]交换,第三次从 arr[2]~arr[n-1]中选取最小值,与 arr[2]交换,…,第 i 次从 arr[i-1]~arr[n-1]中选取最小值,与 arr[i-1]交换,…, 第 n-1 次从 arr[n-2]~arr[n-1]中选取最小值,与 arr[n-2]交换,总共通过 n-1 次,得到一个按排序码从小到大排列的有序序列。 3. 选择排序理解![选择排序动图]() 3.1 选择排序图解 1.选择排序一共有数组大小-1 次排序2.每一次排序,又是一个循环,循环规则如下 2.1 先假定当前这个数是最小数 2.2 然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标 2.3 当遍历到数组的最后时,就得到本轮最小数和小标 2.4 交换代码,再继续 4. 选择排序代码/** * @author: Coder编程 * @create: 2019-6-20 22:06 * @description: 选择排序 **/public class SelectionSort { /** * 选择排序 * @param arr 待排序数组 */ public void selectionSort(Integer[] arr) { // 需要遍历获得最小值的次数 // 要注意一点,当要排序 N 个数,已经经过 N-1 次遍历后,已经是有序数列 for (int i = 0; i < arr.length - 1; i++) { int minindex = i; // 用来保存最小值得索引 int min = arr[i]; // 用来保存最小值 for (int j = i + 1; j < arr.length; j++) { if (min > arr[j]) {// 说明假定的最小值,并不是最小 min = arr[j]; // 重置 min minindex = j; // 重置 minIndex } } // 如果假定最小值的索引发生了改变,则进行交换 if(minindex != i){ arr[minindex] = arr[i]; //此时minindex为j,因此i与j交换 arr[i] = min; //最小值给下标i } System.out.format("\n第 %d 趟:\t", i + 1); Arrays.asList(arr).stream().forEach(x -> System.out.print(x + " ")); } } public static void main(String[] args) { //初始数组 Integer arrays[] = {2,9,7,5,3}; // 调用选择排序方法 SelectionSort selection = new SelectionSort(); System.out.print("欢迎个人公众号Coder编程:选择排序前:\t"); Arrays.asList(arrays).stream().forEach(x -> System.out.print(x + " ")); selection.selectionSort(arrays); System.out.print("\n欢迎个人公众号Coder编程:选择排序后:\t"); Arrays.asList(arrays).stream().forEach(x -> System.out.print(x + " ")); } }打印结果 ...

June 26, 2019 · 2 min · jiezi

你知道和你不知道的冒泡排序

这篇文章包含了你一定知道的,和你不一定知道的冒泡排序。 gif看不了可以点击【原文】查看gif。 1. 什么是冒泡排序可能对于大多数的人来说比如我,接触的第一个算法就是冒泡排序。 我看过的很多的文章都把冒泡排序描述成我们喝的汽水,底部不停的有二氧化碳的气泡往上冒,还有描述成鱼吐泡泡,都特别的形象。 其实结合一杯水来对比很好理解,将我们的数组竖着放进杯子,数组中值小的元素密度相对较小,值大的元素密度相对较大。这样一来,密度大的元素就会沉入杯底,而密度小的元素会慢慢的浮到杯子的最顶部,稍微专业一点描述如下。 冒泡算法会运行多轮,每一轮会依次比较数组中相邻的两个元素的大小,如果左边的元素大于右边的元素,则交换两个元素的位置。最终经过多轮的排序,数组最终成为有序数组。2. 排序过程展示我们先不聊空间复杂度和时间复杂度的概念,我们先通过一张动图来了解一下冒泡排序的过程。 这个图形象的还原了密度不同的元素上浮和下沉的过程。 3. 算法V13.1 代码实现private void bubbleSort(int[] arr) { for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr.length - 1; j++) { if (arr[j] > arr[j + 1]) { exchange(arr, j, j + 1); } } }}private void exchange(int arr[], int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}int[] arr = new int[]{5, 1, 3, 7, 6, 2, 4};bubbleSort(arr);System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5, 6, 7]3.2 实现分析各位大佬看了上面的代码之后先别激动,坐下坐下,日常操作。可能很多的第一个冒泡排序算法就是这么写的,比如我,同时还自我感觉良好,觉得算法也不过如此。 ...

June 25, 2019 · 4 min · jiezi

数据结构与算法一带你了解时间复杂度和空间复杂度到底是什么

1. 前言算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。那么我们应该如何去衡量不同算法之间的优劣呢? 主要还是从算法所占用的「时间」和「空间」两个维度去考量。 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是「鱼和熊掌」,不可兼得的,那么我们就需要从中去取一个平衡点。 2. 算法的介绍排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。 3. 排序的分类3.1 内部排序指将需要处理的所有数据都加载到内部存储器(内存)中进行排序。 3.2 外部排序法数据量过大,无法全部加载到内存中,需要借助外部存储(文件等)进行排序。 3.3 常见的排序算法分类(见下图) 4. 算法的时间复杂度4.1 度量程序(算法)执行时间方法4.1.1 事后统计的方法这种方法可行, 但是有两个问题:一是要想对设计的算法的运行性能进行评测,需要实际运行该程序;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素, 这种方式,要在同一台计算机的相同状态下运行,才能比较哪个算法速度更快。 4.1.2 事前估算的方法因事后统计方法更多的依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优劣。因此人们常常采用事前分析估算的方法。在编写程序前,依据统计方法对算法进行估算。一个用高级语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:(1) 算法采用的策略、方法(2) 编译产生的代码质量(3) 问题的输入规模(4) 机器执行指令的速度。 通过分析某个算法的时间复杂度来判断哪个算法更优。 4.2 时间频度4.2.1 基本介绍时间频度:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。 一个算法中的语句执行次数称为语句频度或时间频度。记为 T(n)。 举例说明-基本案例 比如计算 1-1000 所有数字之和, 我们设计两种算法: 举例说明-忽略常数项结论:1) 2n+20 和 2n 随着 n 变大,执行曲线无限接近, 20 可以忽略2) 3n+10 和 3n 随着 n 变大,执行曲线无限接近, 10 可以忽略 举例说明-忽略低次项 结论:1) 2n^2+3n+10 和 2n^2 随着 n 变大, 执行曲线无限接近, 可以忽略 3n+102) n^2+5n+20 和 n^2 随着 n 变大,执行曲线无限接近, 可以忽略 5n+20 ...

June 20, 2019 · 2 min · jiezi

数据结构与算法LeetCode-格雷编码No89

LeetCode 89. 格雷编码格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。格雷编码序列必须以 0 开头。第一个数与最后一位数 也只差以为一位数 ‘首尾相连’ 所以又称为循环码或反射码 示例 1: 输入: 2输出: [0,1,3,2]解释:00 - 001 - 111 - 310 - 2对于给定的 n,其格雷编码序列并不唯一。例如,[0,2,3,1] 也是一个有效的格雷编码序列。00 - 010 - 211 - 301 - 1示例 2: 输入: 0输出: [0]解释: 我们定义格雷编码序列必须以 0 开头。 给定编码总位数为 n 的格雷编码序列,其长度为 2n。当 n = 0 时,长度为 20 = 1。 因此,当 n = 0 时,其格雷编码序列为 [0]。这题的难度主要是将给定的n转化为格雷编码 第一步 将n转变为格雷编码1=>['0','1']n = 101n = 20001--1110n = 3000001011010---110111101100分析上面的数字排列 我们可以注意到3点 以--为间隔上面的编码与下面的编码是轴对称的(除了第一位以外)后一个格雷编码 是以上一个为基础 做轴对称生成,并且前一半编码每项'0'+'xxx',后一半编码每项'1'+'xxx',每组的编码的长度为2^n先实现这部分逻辑 ...

May 14, 2019 · 2 min · jiezi

算法之字符串匹配算法

前言一说到两个字符串匹配,我们很自然就会想到用两层循环来匹配,用这种方式就可以实现一个字符串是否包含另一个字符串了,这种算法我们称为 BF算法。 BF算法BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串 S 的第一个字符与模式串 T 的第一个字符进行匹配,若相等,则继续比较 S 的第二个字符和 T的第二个字符;若不相等,则比较 S 的第二个字符和 T 的第一个字符,依次比较下去,直到得出最后的匹配结果。 BF算法实现 public static int bruteforce(String s,String t){ int length = s.length();//目标字符串的长度 int plength = t.length();//模式串的长度 //循环目标字符串 for(int i=0;i<length-plength;i++){ //循环模式串 int j=0; while((j < plength) && (s.charAt(i+j) == t.charAt(j))){ j++; } if(j == plength){ return i; } } return -1;}BF算法是一种蛮力算法,没有任何优化,就是用两层循环的比较,当字符串比较大的时候,执行效率就非常低下,不适合比较非常大的字符串。 该算法最坏情况下要进行 $M*(N-M)$ 次比较,时间复杂度为 $O(M*N)$。因此,如果我们使用暴力搜索一串'm'字符中的'n'个字符串,那么我们需要尝试 n*m 次。 虽然暴力搜索很容易实现,并且如果解决方案存在它就一定能够找到,但是它的代价是和候选方案的数量成比例的,由于这一点,在很多实际问题中,消耗的代价会随着问题规模的增加而快速地增长。因此,当问题规模有限,或当存在可用于将候选解决方案的集合减少到可管理大小的针对特定问题的启发式算法时,通常使用暴力搜索。另外,当实现方法的简单度比运算效率更重要的时候,也会用到这种方法。 我们看到暴力搜索算法虽然不需要预处理字符串,但效率比较低下,因为它需要做很多不必要的匹配,因此我们需要更高效的算法。 AC自动机算法Aho–Corasick算法是由 Alfred V. Aho 和 Margaret J.Corasick 发明的字符串搜索算法,用于在输入的一串字符串中匹配预先构建好的 Trie 树中的子串。它与普通字符串匹配的不同点在于同时与所有字典串进行匹配。算法均摊情况下具有近似于线性的时间复杂度,约为字符串的长度加所有匹配的数量。 ...

May 14, 2019 · 2 min · jiezi

数据结构与算法LeetCode-种花问题No605

LeetCode 605. 种花问题假设你有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花卉不能种植在相邻的地块上,它们会争夺水源,两者都会死去。 给定一个花坛(表示为一个数组包含0和1,其中0表示没种植花,1表示种植了花),和一个数 n 。能否在不打破种植规则的情况下种入 n 朵花?能则返回True,不能则返回False。 示例 1: 输入: flowerbed = [1,0,0,0,1], n = 1输出: True示例 2: 输入: flowerbed = [1,0,0,0,1], n = 2输出: False注意:数组内已种好的花不会违反种植规则。输入的数组长度范围为 [1, 20000]。n 是非负整数,且不会超过输入数组的大小。我的思路总的来讲,这道题在leetCode 中不算难的,关键就是要有思路。下面是我自己做题时的分析。1. 在两边都是花 中间都是空地的情况下(关键前提) ,算出可以种花的最值。如[1,0,0,0,0,1]=>1 连续空地数 可种花的最值 0 => 0 1 => 0 2 => 0 3 => 1 4 => 1 5 => 2 6 => 2 7 => 3有感觉的老哥 ,估计已经有了想法,没错就是 parseInt((n - 1) / 2 ) = 可以种几颗 // (n为最近两个花 之间的空地数量)得出了这个结论 就基本完成了 但是还有2种特殊情况,以下是完整代码(战胜84%的js提交) ...

May 11, 2019 · 2 min · jiezi

LeetCode电话号码的字母组合No17-递归hash

LeetCode 17. 电话号码的字母组合给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。示例: 输入:"23"输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. 这道题的难点主要就是首先你能将输入的号码对应的结果映射出来,最后通过递归的形式两两组合得出结果let letterCombinations = (digits) => { if (digits.length == 0) return [] //为空 情况 let map = ['', '', 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz'] let arr = digits.split('') let resarr = arr.map(item => map[item]) if (resarr.length == 1) return resarr[0].split('')//仅输入一个 情况 let compute = (arr) => {//组合传入数组的前两项 ['ab','cd','ewe'] let temp = [] //['ac','ad','bc','bd'] // 将前两项组合结果放入临时数组中 for (let i = 0; i < arr[0].length; i++) { for (let j = 0; j < arr[1].length; j++) { temp.push(`${arr[0][i]}${arr[1][j]}`) } } // [['ac','ad','bc','bd'],'ewe'] arr.splice(0, 2, temp)//将原来的数组前两项结果用临时数组替换 if (arr.length > 1) { compute(arr) } return arr[0] } return compute(resarr)};你也可以用这种哈希表的形式 ...

May 7, 2019 · 1 min · jiezi

数据结构队列

数据结构-队列定义队列(queue)在计算机科学中,是一种先进先出的线性表。它只允许在表的前端进行删除操作,而在表的后端进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。 基于自定义数组实现的队列新建queue接口,用来规范所有queue子类package com.datastructure.queue;import java.awt.*;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-03 16:44 **/public class LoopQueue<E> implements Queue<E> { private E[] data; //指向队列的第一个元素,初始指向0 private int front; //指向队列的最后一个元素的后一个位置,初始指向0 private int tail; private int size; public LoopQueue(int capacity){ data = (E[]) new Object[capacity+1]; front=0; tail=0; size=0; } public LoopQueue(){ this(10); } /** * 因为容量放的时候多了个1,所以get容量的时候,需要减1 * @return */ public int getCapacity(){ return data.length-1; } /** * 1.if((tail + 1) % data.length == front) 如果tail + 1 超过了data.length的大小, * 代表当前tail指向已经超出了容量的大小,因为是循环式,所以需要tail去循环头元素中查看值是否有被占用, * 如果 == front 代表循环头没有,就需要扩容了。 * 2.举例: 元素容量为8,tail目前指向7 front 指向2 * if((7 + 1) % 8 == 2 ) if(0 == 2) 这里是false,因为front指向了2,所以代表 第0,1位是没有值的 * 所以这个值需要在在第0位放(空间利用) * 3.data[tail] = param tail当前指向的地方需要赋值,然后tail自增 循环体 的1,size+1 * @param param */ @Override public void enqueue(E param) { if((tail + 1) % data.length == front){ resize(getCapacity() * 2); } data[tail] = param ; tail = (tail + 1) % data.length; size ++ ; } /** * 扩充队列的容量 * 1.front代表了当前元素初始位置的指向 * 2.newData的第i位元素,应该等于 i + front % data.length 的值 * 3.举例:元素容量20,i 等于 0 ,front 等于 2,结果: newData[0] = data[(0 + 2) % 20] * = data[2] 意思就是,newData的第一位元素,应该等于data有值的第一位元素 * % data.length 的原因主要是为了防止数组越界错误 * 4.新数组赋值完成需要将 front 重新指向0,因为新数组的front指针是从0开始的。 * tail最后要指向等于size大小的值, * @param newCapacity */ private void resize(int newCapacity) { E[] newData = (E[]) new Object[newCapacity + 1]; for(int i = 0 ; i < size ; i++){ newData[i] = data[(i + front ) % data.length]; } data=newData; front = 0 ; tail = size; } /** * 1.如果队列为空抛出异常 * 2.用ret变量来接受当前队列头的值 * 3.接收成功之后将,队列头元素置空 * 4.front指针指向下一个元素 * 5.size大小-1 * 6.如果size大小占据了容量的1/4和size为容量的1/2且不等于0的时候,对容量进行缩减,缩减为原来容量的1/2 * 7.返回ret变量 * @return */ @Override public E dequeue() { if(isEmpty()){ throw new IllegalArgumentException("dequeue is fail ,because queue is empty"); } E ret = data[front]; data[front] = null; front = (front + 1) % data.length; size -- ; if(size == getCapacity() / 4 && getCapacity() / 2 != 0 ){ resize(getCapacity() / 2 ); } return ret; } @Override public E getFront() { if(isEmpty()){ throw new IllegalArgumentException("queue is empty"); } return data[front]; } @Override public int getSize() { return size; } /** * 当front和tail的值相等时,队列为空,初始两个指向的是同一个值(只有初始的时候,指向的是同一个地方) * @return */ @Override public boolean isEmpty() { return front == tail; } /** * 1.元素从 front位置开始循环遍历,i的值不能等于tail, * 也就是到tail的前一位,i = i + 1 且%data.length, * 因为i有可能从循环头重新开始 * 2.( i + 1 ) % data.length != tail 如果当前i + 1 % data.length * 不等于tail表示不到最后一个元素,就拼接, * @return */ @Override public String toString(){ StringBuffer sb = new StringBuffer(); sb.append(String.format("Array: size = %d , capacity = %d\n", size, getCapacity())); sb.append("front ["); for (int i = front; i != tail ; i = (i + 1) % data.length) { sb.append(data[i]); if (( i + 1 ) % data.length != tail) { sb.append(", "); } } sb.append("] tail"); return sb.toString(); }}新建ArrayQueue实现类package com.datastructure.queue;import com.datastructure.array.Array;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-03 18:19 **/public class ArrayQueue<E> implements Queue<E>{ Array<E> array; public ArrayQueue(int capacity){ array=new Array<E>(capacity); } public ArrayQueue(){ array=new Array<E>(); } @Override public void enqueue(E param) { array.addLast(param); } @Override public E dequeue() { return array.removeFirst(); } @Override public E getFront() { return array.getFirst(); } @Override public int getSize() { return array.getSize(); } @Override public boolean isEmpty() { return array.isEmpty(); } @Override public String toString(){ StringBuffer sb = new StringBuffer(); sb.append("front: "); sb.append("["); for(int i=0;i<array.getSize();i++){ sb.append(array.get(i)); if(i!=array.getSize()-1){ sb.append(", "); } } sb.append("] tail"); return sb.toString(); }}测试类package com.datastructure.queue;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-03 18:26 **/public class QueueTest { public static void main(String[] args) { ArrayQueue<Integer> integerArrayQueue = new ArrayQueue<>(); for (int i = 0; i < 10; i++) { integerArrayQueue.enqueue(i); System.out.println(integerArrayQueue); if(i % 3 == 2){ integerArrayQueue.dequeue(); System.out.println(integerArrayQueue); } } }}测试结果front: [0] tailfront: [0, 1] tailfront: [0, 1, 2] tailfront: [1, 2] tailfront: [1, 2, 3] tailfront: [1, 2, 3, 4] tailfront: [1, 2, 3, 4, 5] tailfront: [2, 3, 4, 5] tailfront: [2, 3, 4, 5, 6] tailfront: [2, 3, 4, 5, 6, 7] tailfront: [2, 3, 4, 5, 6, 7, 8] tailfront: [3, 4, 5, 6, 7, 8] tailfront: [3, 4, 5, 6, 7, 8, 9] tail可以看到测试结果是正确的,也符合队列结构的数据存取,但是因为是基于自定义数组来实现的,所以会调用数组方法的removeFirst方法,删除第一个元素的同时,会重新将后面所有元素前移,索引前移,均摊时间复杂度为O(n)。 ...

May 3, 2019 · 7 min · jiezi

数据结构栈

数据结构-栈定义栈(英语:stack)又称为堆栈或堆叠,栈作为一种数据结构,它按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。 由于堆叠数据结构只允许在一端进行操作,因而按照后进先出(LIFO, Last In First Out)的原理运作。栈也称为后进先出表 栈的应用场景undo操作(撤销)例如:将操作的每组数据存入栈中,如果想要撤销,只需要弹出栈顶元素,就可以恢复上一步操作了。程序调用的系统栈例如:A方法调用B方法得到返回值,B调用C得到返回值,A操作走到了B方法,这个时候可以将A的代码位置存储到栈中,然后走到B方法,B操作走到了C方法,这个时候可以将B的代码位置存储到栈中。最后C执行完成,根据栈的结构开始弹出数据,一步一步再走回A方法。判断括号是否有效。下文会有代码实现(详细规则描述可以参考leetcode第20题)开括号必须用同一类型的括号闭合。开方括号必须按正确顺序闭合。例如:正确的:{[()]} [{()}] ({[]}) 等 。错误的:[{(})] [}{()] 等。自定义栈基类的代码实现栈在java.util有一个工具类,先不用,自定义实现一个创建一个接口,用来统一规范所有栈实现package com.datastructure.stack;public interface Stack<E> { /** * 向栈插入元素 * @param e */ public void push(E e); /** * 取出最上面的元素,并且返回 * @return */ public E pop(); /** * 获取栈的大小 * @return */ public int getSize(); /** * 判断栈是否为空 * @return */ public boolean isEmpty(); /** * 获取栈最上面的元素 * @return */ public E peek();}用基于数组的方式来实现一个栈(上文所写的自定义数组)package com.datastructure.stack;import com.datastructure.array.Array;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-02 15:27 **/public class ArrayStack<E> implements Stack<E>{ Array<E> array; public ArrayStack(int capacity){ array=new Array<E>(capacity); } public ArrayStack(){ array=new Array<E>(); } @Override public void push(E e) { array.addLast(e); } @Override public E pop() { return array.removeLast(); } @Override public int getSize() { return array.getSize(); } @Override public boolean isEmpty() { return array.isEmpty(); } @Override public E peek() { return array.getLast(); } /** * 获取容量值 * @return */ public int getCapacity(){ return array.getCapacity(); } @Override public String toString(){ StringBuffer sb = new StringBuffer(); sb.append("stack: "); sb.append("["); for(int i=0;i<array.getSize();i++){ sb.append(array.get(i)); if(i!=array.getSize()-1){ sb.append(", "); } } sb.append("] right value is stack top"); return sb.toString(); }}测试代码package com.datastructure.stack;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-02 16:11 **/public class StackTest { public static void main(String[] args) { ArrayStack<Integer> integerArrayStack = new ArrayStack<>(); for(int i=0;i<5;i++){ integerArrayStack.push(i); System.out.println(integerArrayStack); } Integer pop = integerArrayStack.pop(); System.out.println("----移除上级元素----value is "+pop); System.out.println("-------------移除之后的栈打印------------------"); System.out.println(integerArrayStack); }}测试结果stack: [0] right value is stack topstack: [0, 1] right value is stack topstack: [0, 1, 2] right value is stack topstack: [0, 1, 2, 3] right value is stack topstack: [0, 1, 2, 3, 4] right value is stack top----移除上级元素----value is 4-------------移除之后的栈打印------------------stack: [0, 1, 2, 3] right value is stack topleetCode第20题,花括号正确闭合思路根据栈的数据结构特点,我们可以先将所有左括号‘[{(’放进栈中,然后判断当前字符如果是‘)]}’这种的右括号,但是栈顶的括号却不匹配,返回false注意控制判断这里使用java自带的栈工具类来实现leetcode给的测试例子: 12345输入例子()()[]{}(]([)]{[]}代码实现package com.datastructure.stack;import java.util.Stack;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-02 16:59 **/public class Solution { public static void main(String[] args) { Solution solution = new Solution(); System.out.println(solution.isValid("{\"name\": \"网站\",\"num\": 3,\"sites\": [ \"Google.com\", \"Taobao.com\", \"Waibo.wang\" ]}")); } public boolean isValid(String s) { Stack<Character> characters = new Stack<>(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '{' || c == '[' || c == '(') { characters.push(c); } else { if(characters.isEmpty()){ return false; } Character peek = characters.pop(); switch (c) { case '}': if (!peek.equals('{')) { return false; } continue; case ']': if (!peek.equals('[')) { return false; } continue; case ')': if (!peek.equals('(')) { return false; } continue; } } } return characters.isEmpty(); } /*public boolean isValid(String s) { Stack<Character> characters = new Stack<>(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == '{' || c == '[' || c == '(') { characters.push(c); } else { if(characters.isEmpty()){ return false; } Character toChar = characters.pop(); if(c == ')' && toChar != '('){ return false; } if(c == '}' && toChar != '{'){ return false; } if(c == ']' && toChar != '['){ return false; } } } return characters.isEmpty(); }*/}如果想实现更多字符串关于括号的匹配,如JSON等等,可以根据栈的特点来实现代码例子GIT地址:https://git.dev.tencent.com/y...项目简介:这个项目是我做测试,学习的主要项目,目前里面包含了: ...

May 2, 2019 · 3 min · jiezi

数据结构数组

数据结构-数组数组数据结构中最基本的一个结构就是线性结构,而线性结构又分为连续存储结构和离散存储结构。所谓的连续存储结构其实就是数组。优点:插入块如果知道坐标可以快速去地存取缺点:查找慢,删除慢,大小固定二次封装数组的增删改查基类的定义定义一个工具类名称-Array接受的参数包括基本类型和自定义类型(用泛型来接受,基本类型用包装类)自定义属性两个:size用来表示数组的大小,data用来表示一个准确的集合概念区分:size表示数组的大小,capacity表示数组容量的大小构造函数:有参构造,接受一个int值,用来初始化数组容量;无参构造:给容量一个默认值toString()方法,输出数组的大小和数组容量的大小,以及数组中的值getSize()方法,调用方通过方法来获取数组的大小getCapacity()方法,调用方通过方法来获取数组容量的大小isEmpty()方法,调用方通过方法来判断数组是否为空(指的是数组中是否有值,没值就为空)基类的代码package com.datastructure.array;/** * @program: test * @description: 自定义数组封装工具类 * @author: Mr.Yang * @create: 2019-05-01 15:36 **/public class Array<E> { private Integer size; private E[] data; /** * 有参构造函数 * @param capacity 封装data的容量值 */ public Array(Integer capacity){ data= (E[]) new Object[capacity]; size=0; } /** * 无参构造函数,设置默认容量为10 */ public Array(){ this(10); } /** * 获取数组中的元素个数 * @return */ public Integer getSize(){ return size; } /** * 获取数组的容量 * @return */ public Integer getCapacity(){ return data.length; } /** * 判断数组是否为空 * @return */ public boolean isEmpty(){ return size==0; } @Override public String toString(){ StringBuffer sb = new StringBuffer(); sb.append(String.format("Array: size = %d , capacity = %d\n",size,data.length)); sb.append("["); for(int i =0;i<size;i++){ sb.append(data[i]); if(i !=size-1){ sb.append(", "); } } sb.append("]"); return sb.toString(); }}增添加的方法add()方法,两个参数,添加元素的索引位置,和元素的值addFirst()方法,在所有元素的最前面添加addLast()方法,在所有元素的最后面添加添加的代码(增)/** * 在索引为index的位置,插入param * 1.假如在索引为2的位置插入元素 * 2.需要将原来索引为2及其以后的位置向后移动一整位 * 3.移动完成之后,在索引为2的位置插入指定的值 * 4.因为数组中多了一个值,所以size需要+1 * * @param index 元素的索引 * @param param 值 */ public void add(int index, E param) { if (index < 0 || index > size) { throw new IllegalArgumentException("添加失败,索引的值不能小于0,并且索引的值不能大于数组的大小"); } if (size == data.length) { throw new IllegalArgumentException("添加失败,数组的大小已经等于了数组容量的大小"); } for (int i = size - 1; i >= index; i--) { data[i + 1] = data[i]; } data[index] = param; size++; } /** * 在所有元素之前添加值 * * @param param */ public void addFirst(E param) { add(0, param); } /** * 在所有元素之后添加值 * * @param param */ public void addLast(E param) { add(size, param); }测试代码package com.datastructure.array;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-01 16:46 **/public class ArrayTest { public static void main(String[] args) { Array<Integer> integerArray = new Array<Integer>(20); for(int i = 0; i < 10 ; i ++){ integerArray.addLast(i); } System.out.println(integerArray); System.out.println("--------------------索引为3的地方添加元素-----------------------"); integerArray.add(3,666); System.out.println(integerArray); System.out.println("--------------------所有元素的最后面添加值-----------------------"); integerArray.addLast(10000); System.out.println(integerArray); System.out.println("--------------------所有元素的最前面添加值-----------------------"); integerArray.addFirst(-1); System.out.println(integerArray); }}测试结果Array: size = 10 , capacity = 20[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]--------------------索引为3的地方添加元素-----------------------Array: size = 11 , capacity = 20[0, 1, 2, 666, 3, 4, 5, 6, 7, 8, 9]--------------------所有元素的最后面添加值-----------------------Array: size = 12 , capacity = 20[0, 1, 2, 666, 3, 4, 5, 6, 7, 8, 9, 10000]--------------------所有元素的最前面添加值-----------------------Array: size = 13 , capacity = 20[-1, 0, 1, 2, 666, 3, 4, 5, 6, 7, 8, 9, 10000]改添加的方法set,两个参数,修改的元素的索引位置,和将要修改的值添加的代码(改)/** * 修改数组中元素的值 * @param index * @param param */ public void set(int index,E param){ if(index<0 || index>= size){ throw new IllegalArgumentException("获取失败,索引值无效"); } data[index]=param; }测试代码package com.datastructure.array;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-01 16:46 **/public class ArrayTest { public static void main(String[] args) { Array<Integer> integerArray = new Array<Integer>(20); for (int i = 0; i < 10; i++) { integerArray.addLast(i); } System.out.println(integerArray); System.out.println("--------------------索引为3的地方修改值为1010-----------------------"); integerArray.set(3, 1010); System.out.println(integerArray); }}测试结果Array: size = 10 , capacity = 20[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]--------------------索引为3的地方修改值为1010-----------------------Array: size = 10 , capacity = 20[0, 1, 2, 1010, 4, 5, 6, 7, 8, 9]查添加的方法get()方法,一个参数,索引值,根据索引返回对应的值contains()方法,一个参数,判断数组中是否包含某个元素find()方法,一个参数,查找数组中是否包含param,如果包含返回索引值,不包含返回-1findAll()方法,一个参数,查找数组中是否包含param,返回包含的索引数组添加的代码(查) /** * 获取索引位置的元素 * @param index * @return */ public E get(Integer index){ if(index<0 || index>=size){ throw new IllegalArgumentException("获取失败,索引的值不能小于0,并且索引的值不能大于等于数组的大小"); } return data[index]; } /** * 查看数组中是否包含某个元素 * @param param * @return */ public boolean contains(E param){ for(int i = 0 ; i < size ; i++){ if(data[i] == param){ return true; } } return false; } /** * 查找数组中是否包含param,如果包含返回索引值,不包含返回-1 * @param param * @return */ public Integer find(E param){ for(int i = 0 ; i < size ; i++){ if(data[i] == param){ return i; } } return -1; } /** * 查找数组中是否包含param * 1.创建一个int数组用来接收返回的索引值 * 2.索引容量最大为数组的大小 * 3.用临时变量来存储int数组的大小 * 4.如果相等,给 int数组 为临时变量的元素赋值(相等的索引),临时变量自增 * @param param * @return */ public Integer[] findAll(E param){ int intTemp=0; Integer[] integers = new Integer[size]; for(int i = 0 ; i < size ; i++){ if(data[i] == param){ integers[intTemp]=i; intTemp++; } } return integers; }测试代码package com.datastructure.array;import java.util.Arrays;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-01 16:46 **/public class ArrayTest { public static void main(String[] args) { Array<Integer> integerArray = new Array<Integer>(20); for (int i = 0; i < 10; i++) { integerArray.addLast(i); } integerArray.addLast(3); System.out.println(integerArray); System.out.println("--------------------得到索引为3的值-----------------------"); System.out.println(integerArray.get(3)); System.out.println("--------------------判断是否包含有包含3的值-----------------------"); System.out.println(integerArray.contains(3)); System.out.println("--------------------查找是否包含参数,并返回索引-----------------------"); System.out.println(integerArray.find(3)); System.out.println("--------------------查找是否包含参数,并返回索引数组-----------------------"); System.out.println(Arrays.asList(integerArray.findAll(3))); }}测试结果Array: size = 11 , capacity = 20[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 3]--------------------得到索引为3的值-----------------------3--------------------判断是否包含有包含3的值-----------------------true--------------------查找是否包含参数,并返回索引-----------------------3--------------------查找是否包含参数,并返回索引数组-----------------------[3, 10, null, null, null, null, null, null, null, null, null]删添加的方法remove()方法,数组中删除index位置的元素,并且返回对应的值removeFirst()方法,删除第一位元素removeLast()方法,删除最后一位元素removeElement()方法,查看某个值是否在数组中存在,如果存在,删除并返回true,如果不存在 返回false、removeLast()方法, 查找所有元素,获取所有相等的索引,遍历移除添加的代码(删)/** * 从数组中删除index位置的元素,并且返回对应的值 * 1.假如删除索引为3的元素 * 2.需要将索引大于3的元素向前移动一位 * 3.因为数组中少了一个值,所以元素-1 * 4.用临时变量来存储删除的值,用来返回 * @param index * @return */ public E remove(int index){ if(index<0 || index>=size){ throw new IllegalArgumentException("删除失败,索引的值不能小于0,并且索引的值不能大于等于数组的大小"); } E temp=data[index]; for(int i = index+1 ; i < size ; i++){ data[i-1]=data[i]; } size--; return temp; } /** * 删除第一位元素 * @return */ public E removeFirst(){ return remove(0); } /** * 删除最后一位元素 */ public E removeLast(){ return remove(size-1); } /** * 查看某个值是否在数组中存在,如果存在,删除并返回true,如果不存在 返回false * @param param */ public boolean removeElement(E param){ Integer index = find(param); if(index != -1){ remove(index); return true; } return false; } /** * 遍历数组,移除元素 * @param param */ public void removeAllElement(E param){ for(E d:data){ removeElement(param); } }测试代码package com.datastructure.array;import java.util.Arrays;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-01 16:46 **/public class ArrayTest { public static void main(String[] args) { Array<Integer> integerArray = new Array<Integer>(20); for (int i = 0; i < 10; i++) { integerArray.addLast(i); } integerArray.addLast(3); integerArray.addLast(4); System.out.println(integerArray); System.out.println("--------------------数组中删除6位置的元素,并且返回对应的值-----------------------"); System.out.println(integerArray.remove(6)); System.out.println(integerArray); System.out.println("--------------------删除第一位元素-----------------------"); System.out.println(integerArray.removeFirst()); System.out.println(integerArray); System.out.println("--------------------删除最后一位元素-----------------------"); System.out.println(integerArray.removeLast()); System.out.println(integerArray); System.out.println("--------------------查看8是否在数组中存在,返回true和fals-----------------------"); System.out.println(integerArray.removeElement(8)); System.out.println(integerArray); System.out.println("--------------------查找所有元素,获取所有相等的索引,遍历移除-----------------------"); integerArray.removeAllElement(3); System.out.println(integerArray); }}测试结果Array: size = 12 , capacity = 20[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 3, 4]--------------------数组中删除6位置的元素,并且返回对应的值-----------------------6Array: size = 11 , capacity = 20[0, 1, 2, 3, 4, 5, 7, 8, 9, 3, 4]--------------------删除第一位元素-----------------------0Array: size = 10 , capacity = 20[1, 2, 3, 4, 5, 7, 8, 9, 3, 4]--------------------删除最后一位元素-----------------------4Array: size = 9 , capacity = 20[1, 2, 3, 4, 5, 7, 8, 9, 3]--------------------查看8是否在数组中存在,如果存在,删除并返回true,如果不存在 返回false、-----------------------trueArray: size = 8 , capacity = 20[1, 2, 3, 4, 5, 7, 9, 3]--------------------查找所有元素,获取所有相等的索引,遍历移除-----------------------Array: size = 6 , capacity = 20[1, 2, 4, 5, 7, 9]数组动态扩容添加的方法resize()方法,定义为私有,调用方不能通过方法来调用,去修改,否则容易造成BUG扩容倍数,如果用固定值,不好抉择。比如:100容量,扩容10个,没有意义,很快就满了;100容量,扩充1000个,太多了,容易早证浪费。最好选择倍数,都在同一个单位级别,这里代码选择的是2倍添加的时候需要判断扩容,删除的时候需要删除容量,减少资源浪费删除的时候,当元素减少到容量的1/4的时候开始缩,缩减到容量的1/2如果选择1/2的时候开始缩减,然后缩减到1/2会有一定的问题,例如:容量10,现在容量满了,来了一个元素,需要扩容,按照设置的扩容机制,会扩容2倍,也就是20容量,如果删除了一个元素,元素达到了容量的1/2,我们开始进行缩减,缩减1/2,容量又变为10。如果一直在这个临界值,那么元素会一直进行扩容缩减扩容缩减,性能影响太大。如果选择1/4的时候开始缩减,然后缩减到1/2,这样能够较好的避开以上问题,例如:容量10,容量满了,来了一个元素,扩容容量到了20,如果删除一个元素,还不到容量的1/4,所以还不会进行缩减,如果真的到了容量的1/4的时候,我们选择缩减1/2,容量也需要一定的元素,才会进行扩容,防止了容量一直扩容或者缩减添加的代码 /** * 扩容方法 * 1.需要new一个object,new E[i] java不支持这样写 * 2.object是所有类的基类,用object然后强转一下就可以 * 3.扩展之后,需要将原数组中的值,放入扩展之后的数组中 * @param newCapacity */ private void resize(int newCapacity) { E[] newData = (E[]) new Object[newCapacity]; for(int i=0;i<size;i++){ newData[i]=data[i]; } data=newData; }修改的代码add() 和 remove()/** * 在索引为index的位置,插入param * 1.假如在索引为2的位置插入元素 * 2.需要将原来索引为2及其以后的位置向后移动一整位 * 3.移动完成之后,在索引为2的位置插入指定的值 * 4.因为数组中多了一个值,所以size需要+1 * * @param index 元素的索引 * @param param 值 */ public void add(int index, E param) { if (index < 0 || index > size) { throw new IllegalArgumentException("添加失败,索引的值不能小于0,并且索引的值不能大于数组的大小"); } if (size == data.length) { resize(2 * data.length); } for (int i = size - 1; i >= index; i--) { data[i + 1] = data[i]; } data[index] = param; size++; }/** * 从数组中删除index位置的元素,并且返回对应的值 * 1.假如删除索引为3的元素 * 2.需要将索引大于3的元素向前移动一位 * 3.因为数组中少了一个值,所以元素-1 * 4.用临时变量来存储删除的值,用来返回 * @param index * @return */ public E remove(int index){ if(index<0 || index>=size){ throw new IllegalArgumentException("删除失败,索引的值不能小于0,并且索引的值不能大于等于数组的大小"); } E temp=data[index]; for(int i = index+1 ; i < size ; i++){ data[i-1]=data[i]; } size--; if(size == data.length / 4 ){ resize(data.length / 2 ); } return temp; }测试代码package com.datastructure.array;import java.util.Arrays;/** * @program: test * @description: * @author: Mr.Yang * @create: 2019-05-01 16:46 **/public class ArrayTest { public static void main(String[] args) { Array<Integer> integerArray = new Array<Integer>(10); for (int i = 0; i < 10; i++) { integerArray.addLast(i); } System.out.println(integerArray); System.out.printf("--------------------将容量设置为10,添加10个元素,元素的容量 : %d -------------------\r\n",integerArray.getCapacity()); integerArray.addLast(21); System.out.printf("--------------------元素+1,元素的容量 : %d --------------------\r\n",integerArray.getCapacity()); integerArray.removeLast(); System.out.printf("--------------------元素-1,元素的容量 : %d --------------------\r\n",integerArray.getCapacity()); integerArray.removeLast(); System.out.printf("--------------------元素-1,元素的容量 : %d --------------------\r\n",integerArray.getCapacity()); for(int x=0;x<=5;x++){ integerArray.removeLast(); } System.out.printf("--------------------元素-1/4,元素的容量 : %d --------------------\r\n",integerArray.getCapacity()); }}测试结果Array: size = 10 , capacity = 10[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]--------------------将容量设置为10,添加10个元素,元素的容量 : 10 ---------------------------------------元素+1,元素的容量 : 20 ----------------------------------------元素-1,元素的容量 : 20 ----------------------------------------元素-1,元素的容量 : 20 ----------------------------------------元素-1/4,元素的容量 : 10 --------------------

May 1, 2019 · 6 min · jiezi

数据结构之二叉搜索树

二叉搜索树二叉搜索树也叫二叉查找树或者二叉排序树,它要么是一颗空树,要么满足以下几点: 1.若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值。 2.若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值。 3.任意节点的左、右子树也分别为二叉搜索树。 4.没有键值相等的节点。 二叉搜索树的实现1.二叉搜索树的存储结构 public class BinarySearchTree { public static Node root; public BinarySearchTree(){ this.root = null; }}class Node{ int data; Node left; Node right; public Node(int data){ this.data = data; left = null; right = null; }}2.二叉搜索树的插入 a.循环二分查找到需要插入的地方。 b.假如插入的值小于当前的值,并且当前左节点为空,那么左节点就指向新节点。 c.假如插入的值大于当前的值,并且当前右节点为空,那么右节点就指向新节点。 public void insert(int id){ Node newNode = new Node(id); if(root == null){ root = newNode; return; } Node current = root; Node parent = null; while(true){ parent = current; if(id < current.data){ current = current.left; if(current == null){ parent.left = newNode; return; } } else { current = current.right; if(current == null){ parent.right = newNode; return; } } }}3.二叉搜索树的删除 a.当删除节点为叶子节点时,直接删除节点。 b.当删除节点只有左子树时,重接左子树。 c.当删除节点只有右子树时,重接右子树。 d.当删除节点既有左子树,又有右子树时,先找一个可以替换删除节点的节点。由于二叉树的性质,左子树的值小于根节点的值,右子树的值大于根节点的值。所以右子树的最左的节点就是替换删除的节点,然后在重接右子树。 第 d 点的图例: ...

April 29, 2019 · 2 min · jiezi

排序算法分析总结附js实现

本文对一些排序算法进行了简单分析,并给出了 javascript 的代码实现。因为本文包含了大量的排序算法,所以分析不会非常详细,适合有对排序算法有一定了解的同学。本文内容其实不是很多,就是代码占了很多行。 总览默认需要排序的数据结构为数组,时间复杂度为平均时间复杂度。 排序算法时间复杂度空间复杂度是否稳定冒泡排序O(n^2)O(1)稳定插入排序O(n^2)O(1)稳定选择排序O(n^2)O(1)不稳定归并排序O(nlogn)O(n)稳定快速排序O(nlogn)O(1)不稳定下面代码实现,排序默认都是 从小到大 排序。 所有代码我的 js 代码实现都放在 github:https://github.com/F-star/js-... 代码仅供参考。 冒泡排序(Bubble Sort)假设要进行冒泡排序的数据长度为 n。 冒泡排序会进行多次的冒泡操作,每次都会相邻数据比较,如果前一个数据比后一个数据大,就交换它们的位置(即让大的数据放在后面)。这样每次交换,至少有一个元素会移动到排序后应该在的位置。重复冒泡 n(或者说 n-1) 次,就完成了排序。 详细来说,第 i(i 从 0 开始) 趟冒泡会对数组的前 n - i 个元素进行比较和交换操作,要对比的次数是 size - i - 1。 冒泡排序总共要进行 n-1 次冒泡(当然你可以说是 n 次冒泡,不过最后一次冒泡只有一个元素,不用进行比较)。 优化有时候,可能只进行了 n 次冒泡,数组就已经是有序的了,甚至数组本来就是有序的。这时候我们希望:当发现一次冒泡后,数组有序,就停止下一次的冒泡,返回当前的数组。 这时候我们可以在每一趟的冒泡前,声明一个变量 exchangeFlag,将其设置为 true。冒泡过程中,如果发生了数据交换,就将 exchangeFlag 设置为 false。结束一趟冒泡后,我们就可以通过 exchangeFlag 知道 数据是否发生过交换。如果没有发生交换,就说明数组有序,直接返回该数组即可;否则说明还没有排好序,继续下一趟冒泡。 代码实现const bubbleSort = (a) => { // 每次遍历找到最大(小)的数放到最后面的位置。 // 优化:如果某次冒泡操作没有数据交换,说明已经有序了。 // 双重循环。 if (a.length <= 1) return a; // 这里的 i < len 改成 i < len - 1 也是正确的,因为最后第 len - 1次并不会执行。 for (let i = 0, len = a.length; i < len; i++) { let exchangeFlag = false; // 是否发生过换 for (let j = 0; j < len - i - 1; j++) { if (a[j] > a[j + 1]) { [a[j], a[j + 1]] = [a[j + 1], a[j]]; exchangeFlag = true; } } console.log(a) if (exchangeFlag == false) return a; }}// 测试let array = [199, 3, 1, 2, 8, 21,4, 100, 8];console.log (bubbleSort(array));分析1. 冒泡排序的时间复杂度是 O(n^2)。最好时间复杂度是 O(n),即第一趟进行 n-1 次比较后,发现原数组是有序的,结束冒泡。 ...

April 28, 2019 · 4 min · jiezi

数据结构之二叉树

二叉树二叉树(Binary Tree)是每个节点最多只有两个子节点的结构,通常左边的叫左子树,右边的叫右子树,二叉树的节点是具有左右次序的,不能随意颠倒。 二叉树的 4 种形态1.仅仅只有一个根节点,没有子节点。 2.根节点只有左子树。 3.根节点只有右子树。 4.根节点既有左子树,又有右子树。 二叉树的分类1.完全二叉树 假设其深度为 d(d>1)。除了第 d 层外,其它各层的节点数目均已达最大值,且第 d 层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树。 2.满二叉树 所有叶子节点全都出现在最底层的完全二叉树就叫满二叉树。就相当于这棵树的第一层是 1 个节点,第二层是 2 个节点,第三层是 4 个节点,第五层是 8 个节点,以此类推。 3.斜树 所有的节点都只有左子树的二叉树叫左斜树,所有的节点都只有右子树的二叉树叫右子树,它们都叫斜树。实际上这棵二叉树看起来像一撇或者一捺。 4.二叉搜索树(也叫二叉查找树或者二叉排序树) 若它的左子树不为空,则左子树上所有节点的值均小于它的根节点的值;若它的右子树不为空,则右子树上所有节点的值均大于它的根节点的值;它的左、右子树也分别是二叉排序树。说明它是一颗有顺序的树,左子树节点的值小于根节点的值,右子树节点的值大于根节点的值。 5.平衡二叉树 它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。 二叉树的存储以下面的二叉树为例 1.顺序存储 二叉树的顺序存储结构就是用一维数组存储二叉树中的节点,并且节点的存储位置,也就是数组的下标要能体现节点之间的逻辑关系。如果某个节点的索引为 i,(假设根节点的索引为 0)则在它左子节点的索引会是 2i+1,以及右子节点会是 2i+2。 2.链式存储 因为在二叉树中,一个父节点最多只允许 2 个子节点,所以我们只需要一个存储数据和左右子节点的指针,这样的结构就是链式存储,也叫二叉链表。 二叉树的遍历以下面的二叉树为例 1.前序遍历 先访问根节点,然后前序遍历左子树,再前序遍历右子树。根据上面的二叉树前序遍历结果是 ECBADGFH。 2.中序遍历 从根节点开始(注意并不是先访问根节点),中序遍历根节点的左子树,然后是访问根节点,最后中序遍历右子树。根据上面的二叉树中序遍历结果是 ABCDEFGH。 3.后序遍历 从左到右先叶子节点后父节点的方式遍历访问左右子树,最后是访问根节点。根据上面的二叉树后序遍历结果是 ABDCFHGE。 4.层序遍历 从树的第一层,也就是根节点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对节点逐个访问。根据上面的二叉树层序遍历结果是 ECGBDFHA。 总结二叉树有多种形态,多种类型,多种存储方式和多种遍历方法。完全二叉树和满二叉树还是难以理解的,满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满二叉树,主要是「满」和「完全」的区别分清楚。 由于线性查找需要遍历全部数据,在数据量大的时候效率就非常低下,到底有多低,在数据库有几百万几千万以上数据量写过查询语句的就知道有索引和没索引的区别。那为什么有索引的就那么快呢?当然数据库的索引不是用二叉树来实现的,想想如果使用二叉树来实现,那这个索引树得多高啊。而是采用更适合数据库查找的 B+树 来实现的,不过 B+树 也是由二叉树进化而来的。 ...

April 26, 2019 · 1 min · jiezi

数据结构与算法概述

了解和学习一种知识的最好方法是带着相关的问题去探索,当我们把一些常见的问题全部解答了,我们也就能对这种事物有一些初步的了解了。试着回答下面的几个问题,让我们对数据结构和算法有一个基本的认识吧。 什么是数据结构?为什么要了解数据结构?作为一个前端,日常工作当中,我们接触的数据结构有哪几种?数据结构和算法是什么关系?如何判断一个算法是否是最优?什么是数据结构?从字面意思来理解就是一种数据的多种表现方法,什么是结构——由组成整体的各部分的搭配和安排(百度百科)。我的理解是:数据的排列,不同的数据结构就是数据的多种排列形式,然后根据排列的情况我们通常用一个学名来表示它,比如说:数组,集合,树,图等。 为什么要了解数据结构?当我们了解了数据结构之后,在实际的编程过程当中,我们遇到某一类的数据的时候,我们就能够找到一种最合适的数据结构来表示他们了。这样再处理跟数据相关问题的时候就会变得高效,因为确定了数据结构,我们也就确定了针对该结构的数据,使用那些方法来对数据进行相关的操作了。比如说,我们需要一种数据结构来记录每天的天气情况,当我们说到某一天的时候,就能立刻知道当天的天气是怎么样的时候我们可以用一个数组来表现。又如,我们要描述一个家族的族谱的时候,用树型结构比较合适;描述一个人的年龄,身高,体重,民族,学历这种用集合比较合适;描述排队的情况用队列。 作为前端,通常我们接触到的数据结构有哪几种?最常用的应该有两种了:数组,对象。到了ES6又增加了两种新的数据结构Set和Map,其实Set和Map应该算是数组和对象的一种变种,但总的来说它们又是另外两种类型的数据结构; Set类数组结构——成员唯一,没有重复的值Map类对象结构——不同Object,键值通常是字符串,Map的键值可以是任何类型。数据结构和算法的关系数据结构只不过是我们描述数据的一种手段,但我们最终的目的通常是对这些数据进行相关的操作,比如:添加,删除,查找,排序等。所谓的算法就是如何去实现这种操作的一种计算方式。但算法往往不止一种,有道是条条道路通罗马,通常要达到某种目的,我们可能会有很多种的算法。比如一个数组我们要给他进行去重,就有很多种方法。你可以循环遍历,把每个值跟其他的值比较一遍,也可以把数组转成Set结构的数据。 如何判断一个算法是否最优?上面说到实现某种操作的方法有很多种,但是哪一种是最好的,我们要如何判断呢。我们可以通过算法的执行时间对吧,那种算法执行的速度越快,当然那种算法就最好。但计算机当中,还要考虑到算法的空间复杂度,也就是算法执行过程当中,可能占用的内存空间,一个算法执行的速度非常块,但执行的时候,需要1T的内存空间,这就不行。所以好的算法往往有两个条件: 运算的速度快占用的内存(存储空间) 小我还有个第三点,那就是代码便于理解,但这部分优秀的算法,往往涉及到很多数学相关的问题,如果没有这部分相关的概念,理解起来是非常不容易的。 其它涉及到前端数据结构和算法的一些学习笔记,我放到了github上面,内容还在更新,尽量一周分享一个,欢迎大家一起来讨论并参与分享,github地址如下: https://github.com/mmcai/Stru...

April 26, 2019 · 1 min · jiezi

数据结构之树

什么是树?树是由n(n>0)个有限节点组成一个具有层次关系的集合,一个父节点有0个或多个子节点。用树结构来表示一对多的关系。树的特点:1.没有父节点的节点称为根节点。2.每一个非根节点有且只有一个父节点。3.除了根节点外,每个子节点可以分为多个不相交的子树。4.每个节点都 0 个或多个子节点。5.树里没有环路,就是节点只能向下衍生,跟树一样,不能相交于其它子树。 树的术语根节点:没有父几点,并且每棵树只有一个根节点。父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点。子节点:一个节点含有的子树的根节点称为该节点的子节点。叶子节点:最底层的,度为零的节点。兄弟节点:具有相同父节点的节点互称为兄弟节点。堂兄弟节点:父节点在同一层的节点互为堂兄弟。分支节点:中间的,度不为零的节点。节点的度:一个节点含有的子树的个数称为该节点的度。树的度:一棵树中,最大的节点的度称为树的度。深度:对于任意节点 n, n 的深度为从根到 n 的唯一路径长,根的深度为 0。高度:对于任意节点 n, n 的高度为从 n 到一片树叶的最长路径,所有叶子节点的高度为 0。节点的祖先:从根到该节点所经分支上的所有节点。节点的子孙:以某节点为根的子树中任一节点都称为该节点的子孙。节点的层次:从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推。森林:由m(m >= 0)棵互不相交的树的集合称为森林。 总结树 实际上就是表示一对多的关系,数组和链表遍历查找时间复杂度是 O(n),当 n 很大时,就非常影响查询效率,因此需要其他的数据结构来解决此类问题。就像二叉查找树、平衡二叉树、B树、B+树等,都是用来解决查询效率低的。 PS: 清山绿水始于尘,博学多识贵于勤。 我有酒,你有故事吗? 微信公众号:「清尘闲聊」。 欢迎一起谈天说地,聊代码。

April 25, 2019 · 1 min · jiezi

《数据结构与算法之美》复杂度分析(上):如何分析、统计算法的执行效率和资源消耗 (读后感)

什么是复杂度分析?数据结构和算法解决的是如何让计算机 更快、更省空间的执行。因此需要从两个方面评估数据结构和算法的优越性。分别用时间复杂度和空间复杂度两个概念来描述性能问题,二者统称为复杂度。复杂度描述的是算法的执行时间或者占用空间的大小与数据规模增长关系。为什么需要复杂度分析?和性能测试相比,复杂度分析有不依赖执行环境、成本低、效率高、易操作、指导性强。掌握复杂度分析,将能编写出性能更优的代码,有利于降低系统开发和维护成本。如何进入复杂度分析?大O表示法:就是在不用运行代码的情况下,用“肉眼” 得出一段代码的执行时间。来源:算法的执行时间与每行代码的执行次数成正比,用 T(n) = O(f(n)) 表示,其中 T(n) 表示算法执行总时间,f(n) 表示代码执行总次数,而 n 往往表示数据的规模。特点:以时间复杂度为例,由于时间复杂度描述的是算法执行时间与数据规模的增长变化趋势,所以“常量阶”、低阶、系数实际上对这种趋势不产生决定性影响,所以在做时间复杂度分析时可以忽略这些项;只需要记录一个最大量级就可以了。大O 时间复杂度并不是实际代码真正的运行时间,而是表示代码执行时间岁数据规模增长的变化趋势;所以时间复杂度也叫 进时间复杂度。总结:所有代码的执行时间 T(n) 与每行代码的执行次数n成正比; 总结公式: T(n) = O(f(n))时间复杂度分析只关注循环次数最多的一段代码。加法法则:总复杂度等于量级最大的那段代码的复杂度; 总结公式: T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n)))乘法法则: 嵌套代码的复杂度等于嵌套内外代码复杂度的乘积; 总结公式: T(n)=T1(n)T2(n)=O(f(n))O(g(n))=O(f(n)*g(n))多规模求加法: 比如方法有两个参数控制两个循环的次数,那么这时就取二者复杂度相加。复杂度量级(按数量级递增)多项式阶常量阶 O(1)对数阶 O(logn)线性阶 O(n)线性对数阶 O(nlogn)平方阶 O(n2)、立方阶 O(n3) … k 次阶 O(nk)非多项式指数阶 O(2n)阶乘阶 O(n!)空间复杂度分析空间和复杂度和时间复杂度分析方法基本类似,表示算法的存储空间与数据规模之间的增长关系。原文地址

April 20, 2019 · 1 min · jiezi

《数据结构与算法之美》如何抓住重点,系统高效地学习数据结构与算法 (读后感)

什么是数据结构?储存一组数据的方法,数据结构是为算法服务的,算法要作用在特定的数据结构之上.数据结构和算法相辅相成.什么是算法?广义上讲就是 “操作一组数据的方法”,列如图书馆有很多书,我们怎么才能更快的查询到书籍呢?可以先根据书籍类别的编号,是人文,还是科学、计算机,来定位书架,然后再依次查找。笼统地说,这些查找方法都是算法。初学数据结构和算法知识点思维导图20个常用数据结构和算法10个常用数据结构数组、链表、栈、队列、散列表、二叉树、堆、跳表、图、Trie树10个常用算法递归、排序、二分查找、搜索、哈希算法、贪心算法、分治算法、回溯算法、动态规划、字符串匹配算法学习数据结构和算法最重要的概念 复杂度分析; 数据结构和算法解决的是如何更省、更快的储存和处理数据问题,因此我们需要一个考量效率和消耗资源的方法 复杂度分析法.学习技巧边学边练,适度刷题.多问、多思考、多互动打怪升级学习方法,我们在枯燥的学习过程中是很难持续坚持下去的,我们可以给自己设立一个切实可行的目前.就像玩游戏打怪升级一样,一点点看到自己的成长. 比如: 学习笔记、学习心得.知识点需要沉淀,不要想试图一下子掌握所有; 学习知识的过程是反复迭代、不断沉淀的过程.原文地址

April 20, 2019 · 1 min · jiezi

数据结构之「哈希表」

什么是哈希表?哈希表(Hash table, 也叫散列表),是根据键(Key)来直接访问在内存存储位置的数据结构。它通过一个哈希函数将所需要查询的数据映射到一张哈希表中,来提升查询效率。哈希函数的实现方法:1.除留余数法取关键字被某个不大于哈希表表长的数除后所得的余数为散列地址。2.折叠法将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。3.平方取中法取关键字平方后的中间几位为哈希地址。4.直接定址法取关键字或关键字的某个线性函数值为哈希地址。哈希冲突不管用什么哈希函数去计算哈希地址,都是会产生哈希冲突的,因此我们需要想办法解决哈希冲突,并且在设计哈希函数时,尽可能减少哈希冲突。1.单独的链表法在哈希表的后面单独链上一个单链表来存储冲突的元素,JDK 1.8 里的 HashMap 就是选择的这种方式解决冲突的,不过它对链表做了一层优化。当元素个数大于等于 8 时,会把链表转换成红黑树,提升查询效率。2.线性探测法当发生哈希冲突时,逐个探测存放地址的表,直到查找到一个空单元。这个方式不便于查找,不建议使用。3.建立一个公共溢出区当发生哈希冲突时,就把元素存入到公用的溢出区,查询时遍历溢出区。从上面这几种处理方法来说,还是链表法效率比较高,推荐使用。不过都有现成的工具类使用,因此只需要知道实现原理,最好自己可以去写代码实现它。哈希表有什么用?哈希表在日常开发中还是比较常用的,因为它最优的查询时间复杂度是 O(1),当哈希冲突比较严重的时候,查询效率就相当于线性的,因此哈希算法直接影响到查询的效率。哈希表怎么实现的?哈希表的结构public class HashMap<K,V> { //用节点数组当作哈希表 Node<K,V>[] table; int size; //节点 static class Node<K,V> { //哈希值 final int hash; //键 final K key; //值 V value; //哈希值冲突时存储 Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } }}总结哈希表是一个键值对的存储结构,并且根据键进行哈希算法找到对应的存储位置。哈希算法会直接影响到哈希表的查询效率,一般选择哈希冲突小的实现方式,以便提升查询效率。当哈希冲突时,一般选择链表来存储冲突的元素,当冲突的元素增多时,可以采用红黑树来存储,以提升查询效率。JDK 1.8 版本的 HashMap,当链表个数大于等于 8 时,就是采用红黑树来存储的。在知道元素个数时,初始化哈希表时直接指定哈希表大小,因为当元素达到哈希表大小时,会做 resize 操作。当元素越来越多时,resize 是很耗时的,相当于重建哈希表。因此直接指定哈希表大小,减少 resize 次数以便提升插入性能。PS: 清山绿水始于尘,博学多识贵于勤。 我有酒,你有故事吗? 微信公众号:「清尘闲聊」。 欢迎一起谈天说地,聊代码。 ...

April 17, 2019 · 1 min · jiezi

数据结构之「双端队列」

什么是双端队列?双端队列(deque)是指允许两端都可以进行入队和出队操作的队列,deque 是 “double ended queue” 的简称。那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。双端队列怎么实现?双端队列的存储结构public class LinkedBlockingDeque<E> { //队头 Node<E> first; //队尾 Node<E> last; //元素个数 int count; static final class Node<E> { //存储元素 E item; //上一个元素 Node<E> prev; //下一个元素 Node<E> next; }}从队头入队public boolean offerFirst(Node<E> node) { //头节点临时变量 Node<E> f = first; //把当前的下一个节点指向头节点 node.next = f; //更新当前节点为头节点 first = node; //假如尾节点为空,则把当前节点设置为尾节点 if (last == null) last = node; //就把以前的头节点指向当前节点 else f.prev = node; //总数加一 ++count; return true;}从队头出队public E pollFirst() { Node<E> f = first; //头节点的下一个节点 Node<E> n = f.next; //获取头节点元素 E item = f.item; //置空 f.item = null; //孤立头节点,不指向任何节点 f.next = f; // help GC //重置头节点 first = n; //说明是最后一个节点 if (n == null) last = null; //否则把头节点的上一个节点置空 else n.prev = null; //总数减一 –count; return item;}从队尾入队public boolean offerLast(Node<E> node) { //尾节点临时变量 Node<E> l = last; if (l == null) return null; //把当前的上一个节点指向尾节点 node.prev = l; //更新当前节点为尾节点 last = node; //假如头节点为空,则把头节点置为当前节点 if (first == null) first = node; //否则把临时的尾节点的下一个节点指向当前节点 else l.next = node; //总数加一 ++count; return true;}从队尾出队public E pollLast() { Node<E> l = last; if (l == null) return null; //最后节点的上一个节点 Node<E> p = l.prev; //获取元素 E item = l.item; //置空 l.item = null; //孤立尾节点 l.prev = l; // help GC //更新尾节点 last = p; //假如是最后一个元素,置空头节点 if (p == null) first = null; //否则置空下一个节点指向 else p.next = null; //总数减一 –count; return item;}总结双端队列其实和队列差不多的,只是更加灵活了,队头和队尾均可进行入队和出队操作。这里是基于链表的双端队列实现,具体详情可查看 JDK 的 LinkedBlockingDeque 的实现,它还考虑了线程安全相关的东西,这里只是简单的一个实现,了解双端队列的结构和运作方式。PS: 清山绿水始于尘,博学多识贵于勤。 我有酒,你有故事吗? 微信公众号:「清尘闲聊」。 欢迎一起谈天说地,聊代码。 ...

April 16, 2019 · 2 min · jiezi