关于数据结构:操作系统中进程的就绪和等待两个状态的区别

在操作系统中,过程能够处于不同的状态,其中包含就绪状态和期待状态。就绪状态示意过程曾经筹备好被调度执行,而期待状态示意过程因为某种原因而临时无奈执行,须要期待特定事件的产生或条件的满足。 1. 就绪状态就绪状态是指过程曾经满足了所有的运行条件,并且曾经筹备好被调度执行,只需期待零碎调配处理器资源即可开始运行。过程进入就绪状态时,通常是因为满足了以下条件: 所需的处理器资源可用:操作系统曾经为过程调配了足够的处理器工夫片,过程能够开始执行。所需的内存资源可用:过程须要的内存空间曾经被操作系统调配。所需的输出/输入资源可用:过程所需的输出/输入资源(如文件、网络连接等)曾经就绪。就绪状态的过程通常依照优先级排队期待处理器调配工夫片,一旦取得处理器资源,就会从就绪状态切换到运行状态,开始执行指令。 2. 期待状态期待状态是指过程临时无奈继续执行,因为它须要期待某个事件的产生或某个条件的满足。过程进入期待状态时,通常是因为遇到了以下状况之一: 阻塞式的输出/输入操作:过程须要期待某个输出/输入操作实现,例如读取文件、网络通信等。资源不可用:过程须要拜访的资源以后不可用,例如期待某个锁的开释、期待某个共享资源的可用等。期待其余过程的信号或事件:过程须要期待其余过程发送的信号或事件的产生,例如期待过程间通信的音讯、期待某个过程的终止等。在期待状态下,过程会临时开释占有的处理器资源,期待相应的事件或条件满足后,过程将被操作系统调度回到就绪状态,期待处理器的调配。 区别就绪状态和期待状态之间的次要区别在于过程的执行条件和阻塞起因。 执行条件:就绪状态的过程曾经满足了执行的所有条件,包含处理器资源、内存资源和输出/输入资源等。期待状态的过程临时无奈继续执行,因为它须要期待某个事件的产生或某个条件的满足。阻塞起因:就绪状态的过程没有阻塞起因,能够立刻被调度执行。期待状态的过程因为某种原因无奈继续执行,须要期待特定的事件或条件满足后能力切换回就绪状态。资源占用:就绪状态的过程不会占用特定的资源,能够立刻执行。期待状态的过程可能会占用一些资源,但处于期待状态时会开释处理器资源。调度行为:就绪状态的过程能够随时被调度执行,而期待状态的过程须要期待操作系统或其余过程发送相应的信号或事件,满足条件后能力被唤醒并切换回就绪状态。总的来说,就绪状态示意过程曾经筹备好执行,只需期待处理器资源的调配,而期待状态示意过程临时无奈继续执行,须要期待特定事件或条件的产生。这两种状态在过程调度和资源管理中起着重要的作用,帮忙操作系统正当分配资源,并实现过程的协调运行。

June 19, 2023 · 1 min · jiezi

关于数据结构:哈夫曼树Huffman-Tree的基本概念介绍

哈夫曼树(Huffman Tree)是一种罕用的数据结构,用于实现数据压缩和编码。它是由美国计算机科学家David A. Huffman于1952年提出的,被广泛应用于通信、压缩算法和信息存储等畛域。 哈夫曼树次要用于依据字符呈现的频率构建最优的前缀编码,以便在压缩数据时可能无效地缩小所需的比特数。该树具备如下个性: 最优性:哈夫曼树是一棵最优二叉树,即它的带权门路长度最小。带权门路长度是指树中每个叶子节点的权重(频率)乘以它到根节点的门路长度之和的总和。前缀编码:哈夫曼树的每个字符编码都是惟一的,并且没有编码是其余编码的前缀。这种编码方式被称为前缀编码,它可能确保解码时不会产生二义性。构建哈夫曼树的过程如下: 给定一组字符及其对应的权重(频率),依照权重的大小建设叶子节点。将这些叶子节点组成一个森林(每个节点都是一棵只蕴含本人的树)。从森林中抉择两棵权重最小的树(节点),将它们合并为一棵新的树,新树的根节点的权重是两棵树的权重之和。将新的树放回森林中。反复步骤3和步骤4,直到森林中只剩下一棵树,即哈夫曼树。构建实现后,每个字符都被赋予了一串惟一的二进制编码,其中呈现频率高的字符被赋予较短的编码,呈现频率低的字符被赋予较长的编码,以达到最优压缩成果。编码的生成遵循以下规定: 哈夫曼树的左子树标记为0,右子树标记为1。从根节点到叶子节点的门路示意字符的编码。哈夫曼树的次要利用之一是数据压缩。在压缩数据时,依据字符的频率构建哈夫曼树,并依据生成的编码将字符替换为对应的二进制码。因为高频率的字符具备较短的编码,而低频率的字符具备较长的编码,所以应用哈夫曼编码 能够显著缩小所需的存储空间。 除了数据压缩,哈夫曼树还能够用于其余畛域,如通信中的信道编码、文件压缩、图像压缩、音频编码等。在这些利用中,哈夫曼树的构建和编码方式都施展着重要的作用,使得数据可能以高效、节俭空间的形式进行存储和传输。

June 19, 2023 · 1 min · jiezi

关于数据结构:计算机存储体系中主存地址和-Cache-地址之间的转换工作由谁完成

在存储体系中,位于主存与CPU之间的高速缓存(Cache)是用于寄存主存中局部信息的正本,目标是进步处理器对数据的访问速度。主存地址与Cache地址之间的转换工作由一个硬件模块称为Cache控制器(Cache Controller)实现。 Cache控制器是一个专门设计的硬件模块,通常集成在CPU芯片外部或者作为独立的组件与CPU连贯。它负责管理Cache的读取、写入和替换操作,以及主存地址与Cache地址之间的转换。 主存地址与Cache地址之间的转换是由Cache控制器通过应用一种称为Cache映射(Cache Mapping)的技术来实现的。Cache映射决定了如何将主存中的数据映射到Cache中的地位。 常见的Cache映射形式包含: 间接映射(Direct Mapping):每个主存地址只对应一个固定的Cache地址。这种映射形式简略且成本低,但容易发生冲突,即不同的主存地址可能映射到雷同的Cache地址,导致Cache命中率降落。全关联映射(Fully Associative Mapping):每个主存地址能够映射到任意一个Cache地址,没有固定的映射关系。这种映射形式可能防止抵触,但须要更多的硬件资源和简单的搜寻电路,导致老本较高。组相联映射(Set Associative Mapping):将Cache划分为多个组,每个组蕴含多个Cache行。主存地址与Cache地址之间的映射是在每个组内进行的,一个主存地址能够映射到一个组内的任意一个Cache行。这种映射形式折中了间接映射和全关联映射的特点,能够升高抵触,同时缩小硬件开销。Cache控制器通过在Cache中进行地址映射来确定是否存在所需数据的正本。当CPU须要读取数据时,Cache控制器首先查看Cache中是否存在与申请地址对应的数据。如果存在(即命中),Cache控制器将数据传递给CPU,防止了拜访主存的时间延迟。如果不存在(即未命中),Cache控制器将从主存中获取所需数据,并将其保留到Cache中,以便将来的拜访。 Cache控制器还负责管理缓存的替换策略,当Cache已满时,依据肯定的算法决定哪些数据应该被替换进来以腾出空间来存储新的数据。 因为Cache控制器间接与CPU和Cache之间进行数据交互,并负责地址映射和治理Cache的操作,因而它是实现主存地址与Cache地址之间转换工作的最佳候选者。它可能在硬件级别实现高效的Cache治理和数据拜访,进步了存储系统的整体性能和效率。

June 18, 2023 · 1 min · jiezi

关于数据结构:排序算法

####排序算法 一、插入排序 (1)算法思维(以升序举例): 对于一个曾经有序的序列,又来一个数x,从倒数第一个数进行比拟,如果x比这个数小,这个数就往后挪。 (2)代码实现 //O(N)最好程序-O(N*N)最坏逆序void insert_sort(int*a, int n){ for (int i = 0; i < n-1; ++i) { int end = i; int x = a[i+1]; while (end >= 0) { if (a[end] > x) { a[end+1] = a[end]; --end; } else { break; } } a[end + 1] = x; }}(3)改良:希尔排序 思维:插入排序在整体有序的状况下成果最好,于是希尔排序先预排序让序列先大体有序,最初进行插入排序来获得最好的成果。它的工夫复杂度大略为O(N^1.3)。 步骤:把数字分成gap个一组->对每组数据进行插入排序来达到大体有序的目标->对整体插入排序 //gap越小,越靠近有序。pow(n,1.3)void shell_sort(int* a, int n){ int gap = n; while (gap > 1) { gap = gap/3 + 1;//gap>1都是预排序,gap==1为间接插入排序 for (int i = 0; i < gap; ++i) { for (int j = i; j < n - gap; j += gap) { int end = j; int x = a[end + gap]; while (end >= 0) { if (a[end] > x) { a[end + gap] = a[end]; end -= gap; } else { break; } } a[end + gap] = x; } } } }二、抉择排序 ...

June 3, 2023 · 5 min · jiezi

关于数据结构:算法与数据结构高手养成求职提升特训课江流石不转

download:算法与数据结构高手养成-求职晋升特训课搜寻根底 Basic Searching搜索引擎曾经成为咱们日常生活中必不可少的一部分。不论是寻找某个具体的信息,还是对某个主题进行钻研,人们都会应用搜索引擎。本文将探讨搜寻根底,以帮忙你更好地利用搜索引擎。 搜索引擎如何工作?搜索引擎通过爬虫程序在互联网上扫描网页,并建设一个蕴含网页内容的索引数据库。当用户输出搜索词时,搜索引擎会在这个索引数据库中查找相干的网页,并按相关性排序。 关键词的抉择关键词是搜寻的根底,因而您须要抉择最能代表您想要理解的内容的关键词。通常状况下,关键词应该是与您所搜寻的主题相干的单词或短语。如果您不确定哪些关键词最合适,请尝试不同的组合,直到找到最佳的后果。 搜寻技巧以下是几种能够帮忙您更好地利用搜索引擎的技巧: 应用双引号(" ")示意准确匹配:如果您输出的搜索词是一个短语,则将其放在双引号中能够使搜索引擎只返回蕴含齐全匹配短语的后果。应用减号(-)排除词:如果您晓得某个特定单词与您所搜寻的内容无关,请在该单词前加上减号,以便排除相干的网页。应用整个网站:如果您想对一个特定网站进行搜寻,请应用 "site:" 命令。例如,"site:google.com" 将返回所有蕴含您搜索词的 Google 网站页面。搜寻后果的评估当搜索引擎返回一系列后果时,您须要评估它们是否与您的需要相符,并决定哪些后果最有用。以下是一些能够帮忙您评估搜寻后果的因素: 相关性:后果是否与您输出的搜索词相干?品质:后果是否来自牢靠的起源?其中是否蕴含谬误或不精确的信息?时效性:后果是否最新?是否曾经过期?心愿这篇文章能帮忙您更好地利用搜索引擎,找到您所需的信息。记住,随着您更多地应用搜索引擎,您将变得更加纯熟,并发现更多的技巧和策略,以帮忙您更无效地查找信息。

May 30, 2023 · 1 min · jiezi

关于数据结构:二叉树

堆1.堆的概念 堆中某个结点的值总是不大于或不小于其父结点的值;堆总是一棵齐全二叉树堆能够存储在程序表中,假如父节点下标x,则它的左右孩子下标别离为2x+1、2x+2;若一个节点下标为x,则它的父节点为(x-1)/22.堆的实现 typedef int type;typedef struct Heap{ type* a; int size; int capacity;}Heap;void heap_init(Heap* hp){ assert(hp); hp->a = NULL; hp->size = hp->capacity = 0;}void heap_destroy(Heap* hp){ assert(hp); free(hp->a); hp->a = NULL; hp->size = hp->capacity = 0;}//这是小根堆的up nlognvoid heap_small_up(type* a, int x){ int parent = (x - 1) / 2; while (x > 0) { if (a[x] < a[parent]) { heap_swap(&a[x], &a[parent]); x = parent; parent = (x - 1) / 2; } else { break; } }}//这是大根堆的up nlognvoid heap_big_up(type* a, int x){ int parent = (x - 1) / 2; while (x > 0) { if (a[x] > a[parent]) { heap_swap(&a[x], &a[parent]); x = parent; parent = (x - 1) / 2; } else { break; } }}void heap_push(Heap* hp, type x){ assert(hp); if (hp->size == hp->capacity) { //须要扩容 int newcapa = (hp->capacity == 0) ? 4 : 2 * hp->capacity; type* t = (type*)realloc(hp->a,sizeof(type) * newcapa); if (t == NULL) { printf("realloc fail\n"); exit(-1); } hp->a = t; hp->capacity = newcapa; } hp->a[hp->size] = x; ++hp->size; heap_up(hp->a, hp->size - 1);//nlogn}void heap_print(Heap* hp){ assert(hp); for (int i = 0; i < hp->size; ++i) { printf("%d ", hp->a[i]); } printf("\n");}void heap_swap(type* x, type* y){ type t = *x; *x = *y; *y = t;}type heap_top(Heap* hp){ assert(hp); assert(hp->size > 0); return hp->a[0];}void heap_pop(Heap* hp)//删除top元素{ assert(hp); assert(hp->size > 0); heap_swap(&hp->a[0], &hp->a[hp->size - 1]); hp->size--; heap_down(hp->a,hp->size,0);//O(n)}bool is_empty(Heap* hp){ assert(hp); return hp->size == 0;}int heap_size(Heap* hp){ assert(hp); return hp->size;}//小根堆的下沉 T = nvoid heap_down(type* a,int size, int x){ int child = 2 * x + 1; while (child < size) { if (child+1<size&&a[child + 1] < a[child]) { child++; } if (a[child] < a[x]) { heap_swap(&a[child], &a[x]); x = child; child = 2 * x + 1; } else { break; } }}//大根堆的下沉 T = nvoid heap_down(type* a,int size, int x){ int child = 2 * x + 1; while (child < size) { if (child+1<size&&a[child + 1] > a[child]) { child++; } if (a[child] > a[x]) { heap_swap(&a[child], &a[x]); x = child; child = 2 * x + 1; } else { break; } }}void top_k(int *a, int k,int n)//在N中找最大或者最小的前k个,N>>k{ //N100亿,40G内存能力放下 //eg.找最大的前k个,建设一个k的小堆 //步骤:1.前k个数建设一个小堆;2.剩下的N-k个数字外面,只有数字比堆顶要大,就进入堆; int *kmin = (int*)malloc(sizeof(int)*k); assert(kmin); for(int i=0;i<k;++i) { kmin[i] = a[i]; } for(int i = (k-2)/2;i>=0;--i) { heap_small_down(kmin, k, i); } for(int j = k; k>n; ++k) { if(a[j]>kmin[0]) { kmin[0] = a[j]; heap_small_down(kmin, k, 0); } } }void heap_sort(type* a, int n){ //建堆1.O(nlogn) for (int i = 1; i < n; ++i) { heap_small_up(a, i); } //建堆2.O(n) int i = (n - 2) / 2;//最初一个结点的父亲,就是倒数第一个非叶子结点 for (; i >= 0; --i) { heap_small_down(a, n, i); } //要排升序,如果建设小堆,每次取完头,须要再次建堆。如果每次都是建堆来选数据,那太慢了!(n*n)没有应用到堆的劣势 //所以升序建设大堆,降序建小堆,O(nlogn) int end = n - 1; while (end > 0)//nlogn { heap_swap(&a[0], &a[end]); heap_down(a, end, 0); --end; }}3.链式存储的二叉树实现 ...

May 27, 2023 · 5 min · jiezi

关于数据结构:双向带头循环链表

双向带头循环链表1.程序表和链表 (1)程序表 长处: a、反对随机拜访,很多算法都须要随机拜访(快排、二分) b、cpu高速缓存命中率更高(cpu读数据时,先去找缓存,如果没有就去内存把数据加载到缓存。在加载时它不是只加载一个数据,而是左近一片数据,所以如果是数组,它的数据是间断的,都会被加载到缓存了) 毛病:a、除了最初地位,其余地位插入删除效率低 b、空间增容须要工夫和可能会节约空间 (2)双向带头循环链表 长处: a、任意地位插入删除效率O(1) b、按需申请开释空间,不浪费时间和空间 c、cpu高速缓存命中率更低(还会净化缓存,让缓存有很多无用的数据) 毛病: a、不反对随机拜访,一些算法无奈应用(快排、二分) b、链表存数据还得存储指针 2.模仿实现 #define _CRT_SECURE_NO_WARNINGS 1 #include"list.h"struct node* buy_node(type x){ struct node* newnode = (struct node*)malloc(sizeof(struct node)); newnode->data = x; newnode->next = NULL; newnode->prev = NULL; return newnode;}struct node* list_init(){ struct node* newnode = buy_node(-1); newnode->next = newnode; newnode->prev = newnode; return newnode;}//不须要二级指针,因为有哨兵,只是扭转上一个结点的值void list_push_back(struct node* head, type x){ /*assert(head); struct node* newnode = buy_node(x); struct node* tail = head->prev; tail->next = newnode; newnode->prev = tail; newnode->next = head; head->prev = newnode;*/ //也能够复用 list_insert(head, x);//在Head的后面插入一个节点就是尾插}void list_pop_back(struct node* head){ //assert(head); //assert(head->next != head);//这才是起码有一个节点,而不是assert(head->next) //struct node* tail = head->prev; //struct node* newtail = tail->prev; //free(tail); //newtail->next = head; //head->prev = newtail; //复用 list_erase(head->prev); }void list_push_front(struct node* head, type x){ /*assert(head); struct node* newnode = buy_node(x); struct node* next = head->next; newnode->prev = head; head->next = newnode; newnode->next = next; next->prev = newnode;*/ //复用 list_insert(head->next, x);}void list_pop_front(struct node* head){ /*assert(head); struct node* next = head->next->next; free(head->next); head->next = next; next->prev = head;*/ //复用 list_erase(head->next);}void list_print(struct node* head){ struct node* t = head->next; while (t != head) { printf("%d ", t->data); t = t->next; } printf("\n");}struct node* list_find(struct node* head, type x){ assert(head); struct node* t = head->next; while (t != head) { if (t->data == x) { return t; } t = t->next; } return NULL;}//插入pos之前的那个结点void list_insert(struct node* pos, type x){ assert(pos); struct node* newnode = buy_node(x); struct node* prev = pos->prev; prev->next = newnode; newnode->prev = prev; newnode->next = pos; pos->prev = newnode;}void list_erase(struct node* pos){ assert(pos); struct node* prev = pos->prev; struct node* next = pos->next; free(pos); prev->next = next; next->prev = prev;}void list_destroy(struct node* head){ assert(head); struct node* t = head; while (t != head) { struct node* next = t->next; free(t); t = next; } free(head); head = NULL;//没有用其实 //下面这么写无奈扭转head的值,不能让它变成NULL,用二级指针才能够;然而要放弃接口一致性,用一级指针就须要本人写head = NULL}/*void list_destroy(struct node** phead){ assert(*phead); struct node* t = *phead; while (t != *phead) { struct node* next = t->next; free(t); t = next; } free(*phead); *phead = NULL; }*/ ...

May 16, 2023 · 2 min · jiezi

关于数据结构:解析内存中的高性能图结构

在进行各种图解决、图计算、图查问的时候,内存或是硬盘中如何存储图构造是一个影响性能的关键因素。本文次要剖析了几种常见的内存图构造,及其工夫、空间复杂度,心愿对你有所启发。 通常来说,对于图构造的几种常见的根底操作: 插入一个点插入一个边删除一个边删除一个点的全副邻边找到一个点的全副邻边找到一个点的另一个邻点全图扫描获取一个点的入度或者出度这些图相干的操作,除了要关怀工夫复杂度之外,须要思考空间占用的问题。 对于大多数实时读写型的零碎,增删改查的性能问题会比拟重要,它们比拟关注下面 1-6 的操作;对于局部密集计算的零碎,对批量读取的性能会比拟器重,偏重下面 5-8 的操作。 不过遗憾的是,无论是惯例的图查问,还是进阶的图计算,依据 RUM 猜测[1],读快、写快、又省空间这样”既要又要也要”的坏事是不存在的。 上面,咱们介绍几个数据结构并给出大量的定量分析。 咱们先从三个典型的计划(邻接矩阵、压缩稠密矩阵和邻接表)说起,再介绍几种近几年的钻研的变种构造 PCSR、VCSR、CSR++。 邻接矩阵 Adjacency Matrix用矩阵来示意图构造是大学里数据结构课程中都学过的常识,也是一种十分直观的方法。 点 i 与点 j 之间有一条边,等价对应于矩阵上 $M_{ij}$ 为 1。当然,这里的点 ID 是须要间断排列无空洞。 用矩阵来示意图构造有显著的益处,能够复用大量线性代数的研究成果:比方图上模式匹配类的问题等价于矩阵的相乘。 上面是一个模式匹配问题,它的大体意思是在全图上寻找这样一种图构造: Match (src)-[friend * 2..4]->(fof)WHERE src.age > 30RETURN fof对于这样一个问题能够间接对应左边的矩阵操作: 比方,2 跳遍历等价于矩阵 F 自乘;2 - 4 跳遍历别离等价于 F^2、F^3、F^4;取属性等价于乘以一个过滤矩阵。 更进一步,因为矩阵操作是天生能够分块并行减速,这在性能上有极大的劣势。 上面,咱们对邻接矩阵操作进行一个简略的定量分析 操作工夫复杂度备注插入一个点O(n^2)对于矩阵来说,减少一个点意味着整个矩阵的维度减少,通常须要另外开拓一块空间插入一个边O(1)减少一个边只是将对应的地位置 1删除一个边O(1)置0删除一个点的全副邻边O(n)对于某个点所有出边的删除对应某一行的置0。入边对应某一列,能够批量操作找到一个点的全副邻边O(n) 找到一个点的给定邻点O(1) 全图扫描O(n^2) 其中,n=|V|,m=|E|。 优化上,批量操作(CPU Cache/SSD block)能够线性减少性能,例如 O(n) 能够升高到 O(n/B),但不影响定量分析。 因为绝大多数图构造是极其稠密的,因而简略用邻接矩阵来示意图构造,其内存会有夸大的节约。更为严重的是,当有多种边类型时,每种边类型各须要一个邻接矩阵。这使得裸用矩阵在理论状况中只能解决很小数据量的场景。当然,对于古代服务器动辄几百 G 的内存,如果只有几亿点边的数据量,像是 twitter2010,这并不会是很重大的问题。但大多数状况下,条件容许的话,大家还是心愿找到一些更加经济的构造。 压缩稠密矩阵 CSR/CSC压缩稠密矩阵是一种十分风行和紧凑的图构造示意形式,大多数图计算零碎都采纳 CSR。 ...

May 9, 2023 · 3 min · jiezi

关于数据结构:数据治理实践-网易某业务线的计算资源治理

本文从计算资源治理实际登程,带大家分明意识计算资源治理到底该如何进行,并如何利用到其余我的项目中。 01前言因为数据治理层面能够分多个层面且内容繁多(包含模型合规、数据品质、数据安全、计算/存储资源、数据价值等治理内容),因而须要独自拆分为6个模块独自去论述其中内容。 笔者作为数仓开发常常会收到大量集群资源满载、工作产出延时等音讯/邮件,甚至上游数分及其他同学也会询问工作运行慢的状况,在这里很少数仓同学遇到这类问题第一想到的都是加资源解决,但事实真不肯定是短少资源,而是须要优化以后问题工作。所以本期从团队做计算资源治理视角登程,带大家分明意识计算资源治理到底该如何进行。 02问题呈现在做计算治理之前(2022.12)咱们团队盘点了下以后计算资源存在的几个问题: (1)30+高耗费工作:因为数仓前中期业务扩张,要笼罩大量场景利用,存在大量问题代码运行时数据歪斜,在耗费大量集群计算资源下,产出工夫也久; (2)200w+的小文件:当前任务存在未合并小文件、工作Reduce数量过多、上游数据源接入(尤其是API数据接入)会造成过多小文件呈现,小文件过多会开启更多数据读取,执行会节约大量的资源,重大影响性能; (3)任务调度安顿不合理:少数工作集中在凌晨2-5点执行且该区间CPU满载,导致该时间段资源耗费成了重灾区,所有外围/非核心工作都在争抢资源,局部外围工作不能按时产出始终在期待阶段; (4)线上有效DQC(数据品质监控)&监控配置资源过小:存在局部历史工作没下线表及DQC场景,每日都在空跑无意义DQC浪费资源,同时DQC资源过少导致DQC须要运行过长时间; (5)反复开发工作/无用工作:晚期帮助上游做了较多烟囱数据模型,因为种种原因,局部工作不再被应用,烟囱模型扩散加工导致资源复用率升高; (6)工作短少调优参数&局部工作依然应用MapReduce/Spark2计算引擎:工作短少调优参数导致资源不能适配及动静调整,甚至线上仍有晚期配置MapReduce/Spark2计算引擎导致运行效率较低。 03思考与口头3.1 治理前的思考:在治理之前我想到一个问题,切入点该从哪里开始最合适? 通过与团队屡次脑暴对以后治理优先级/改变老本大小/难度做了一个排序,咱们先抉择从简略的参数调优&工作引擎切换开始->小文件治理->DQC治理->高耗费工作治理->调度安顿->下线无用模型及积淀指标到其余数据资产,同时在初期咱们实现各类元数据接入搭建治理看板以及团队治理产出统计数据模型,并通过网易数帆提供的数据治理平台解决具体细节问题。 数据治理平台截图 3.2 治理口头:(1)大部分工作切换至Spark3计算引擎&补充工作调优参数 补充Spark调优参数(参数内容详见文末),工作对立应用Spark3引擎减速,并充分利用Spark3的AQE个性及Z-Order排序算法个性。 AQE解释:Spark 社区在 DAG Scheduler 中,新增了一个 API 在反对提交单个 Map 阶段,以及在运行时批改 shuffle 分区数等等,而这些就是 AQE,在 Spark 运行时,每当一个 Shuffle、Map 阶段进行结束,AQE 就会统计这个阶段的信息,并且基于规定进行动静调整并修改还未执行的工作逻辑计算与物理打算(在条件运行的状况下),使得 Spark 程序在接下来的运行过程中失去优化。 Z-Order解释:Z-Order 是一种能够将多维数据压缩到一维的技术,在时空索引以及图像方面应用较广,比方咱们罕用order by a,b,c 会面临索引笼罩的问题,Z-Order by a,b,c 成果对每个字段是对等的 (2)小文件治理 在这里咱们应用外部数据治理平台-数据治理360对存在小文件较多表提供内容展现(实质采集HDFS对应门路下文件数的日志去显示) 以后小文件解决: 对于分区较多应用Spark3进行动静分区刷新,(Spark3具备小文件主动合并性能,如未应用Spark3可配置Spark3/Hive小文件合并参数刷新,参数详见文末),代码如下: 1 set hive.exec.dynamic.partition.mode=nonstrict;2 insert overwrite table xxx.xxx partition (ds)3 select column4 ,ds5 from xxx.xxx对于分区较少或未分区的表采纳重建表,补数据办法回刷。 小文件预防: 应用Spark3引擎,主动合并小文件缩小Reduce的数量(能够应用参数进行管制)用Distribute By Rand管制分区中数据量增加合并小文件参数将数据源抽取后的表做一个工作(实质也是回刷分区合并小文件工作)去解决小文件保障从数据源开始小文件不向上游流去(3)DQC治理 有效DQC下线:难点在于须要查找所有DQC对应的线上工作,查看该DQC工作是否与线上工作一一匹配,从而找到有效DQC工作下线,内容繁冗耗时较多。 DQC资源:因为之前DQC配置资源为集群默认参数,效率极低导致所有DQC运行时长均超过10min,从而使得整体工作链路运行时长过久,调整Driver内存为2048M,Executor个数为2,Executor内存为4096M (4)高耗费工作调优 ...

April 19, 2023 · 1 min · jiezi

关于数据结构:时间复杂度的实例

前言此文章是对于工夫复杂度的 提醒:以下是本篇文章正文内容,上面案例可供参考 一、工夫复杂度是什么?工夫复杂度的定义:在计算机科学中,算法的工夫复杂度是一个函数,它定量形容了该算法的运行工夫。一个算法执行所消耗的工夫,从实践上说,是不能算进去的,只有你把你的程序放在机器上跑起来,能力晓得。然而咱们须要每个算法都上机测试吗?是能够都上机测试,然而这很麻烦,所以才有了工夫复杂度这个剖析形式。一个算法所破费的工夫与其中语句的执行次数成正比例,算法中的基本操作的执行次数,为算法的工夫复杂度。大白话来说就是:这个算法最高跑了多少次。不懂?没关系,上面咱们看具体例子,每个例子都有他的特色,可能会打断你上一条的想法,而后你就明确工夫复杂度是个什么货色。 二、实体展现1.工夫复杂度为1void Func4(int N) { int count = 0; for (int k = 0; k < 100; ++ k) { ++count; } printf("%d\n", count);}工夫复杂度为1,是执行常数次的意思,CPU一秒能运行上亿次,这些数字不过尔尔,你的int又能有多大。所以这里尽管运行100次,然而工夫复杂度为1。 2.工夫复杂度为N^2代码如下(示例): // 请计算一下Func1中++count语句总共执行了多少次?void Func1(int N) {int count = 0;for (int i = 0; i < N ; ++ i) { for (int j = 0; j < N ; ++ j) { ++count; }} for (int k = 0; k < 2 * N ; ++ k) { ++count; }int M = 10;while (M--) { ++count; }Func1 执行的基本操作次数 : ...

April 11, 2023 · 2 min · jiezi

关于数据结构:利用Jackson序列化实现数据脱敏

作者:京东物流 张晓旭1.背景在我的项目中有些敏感信息不能间接展现,比方客户手机号、身份证、车牌号等信息,展现时均须要进行数据脱敏,避免泄露客户隐衷。脱敏即是对数据的局部信息用脱敏符号(*)解决。 2.指标在服务端返回数据时,利用Jackson序列化实现数据脱敏,达到对敏感信息脱敏展现。升高反复开发量,晋升开发效率造成对立无效的脱敏规定可基于重写默认脱敏实现的desensitize办法,实现可扩大、可自定义的个性化业务场景的脱敏需要3.次要实现3.1基于Jackson的自定义脱敏序列化实现StdSerializer:所有规范序列化程序所应用的基类,这个是编写自定义序列化程序所举荐应用的基类。 ContextualSerializer:是Jackson 提供的另一个序列化相干的接口,它的作用是通过字段已知的上下文信息定制JsonSerializer。 package com.jd.ccmp.ctm.constraints.serializer;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.databind.BeanProperty;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.SerializerProvider;import com.fasterxml.jackson.databind.ser.ContextualSerializer;import com.fasterxml.jackson.databind.ser.std.StdSerializer;import com.jd.ccmp.ctm.constraints.Symbol;import com.jd.ccmp.ctm.constraints.annotation.Desensitize;import com.jd.ccmp.ctm.constraints.desensitization.Desensitization;import com.jd.ccmp.ctm.constraints.desensitization.DesensitizationFactory;import com.jd.ccmp.ctm.constraints.desensitization.DefaultDesensitization;import java.io.IOException;/** * 脱敏序列化器 * * @author zhangxiaoxu15 * @date 2022/2/8 11:10 */public class ObjectDesensitizeSerializer extends StdSerializer<Object> implements ContextualSerializer { private static final long serialVersionUID = -7868746622368564541L; private transient Desensitization<Object> desensitization; protected ObjectDesensitizeSerializer() { super(Object.class); } public Desensitization<Object> getDesensitization() { return desensitization; } public void setDesensitization(Desensitization<Object> desensitization) { this.desensitization = desensitization; } @Override public JsonSerializer<Object> createContextual(SerializerProvider prov, BeanProperty property) {//获取属性注解 Desensitize annotation = property.getAnnotation(Desensitize.class); return createContextual(annotation.desensitization()); } @SuppressWarnings("unchecked") public JsonSerializer<Object> createContextual(Class<? extends Desensitization<?>> clazz) { ObjectDesensitizeSerializer serializer = new ObjectDesensitizeSerializer(); if (clazz != DefaultDesensitization.class) { serializer.setDesensitization((Desensitization<Object>) DesensitizationFactory.getDesensitization(clazz)); } return serializer; } @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { Desensitization<Object> objectDesensitization = getDesensitization(); if (objectDesensitization != null) { try { gen.writeObject(objectDesensitization.desensitize(value)); } catch (Exception e) { gen.writeObject(value); } } else if (value instanceof String) { gen.writeString(Symbol.getSymbol(((String) value).length(), Symbol.STAR)); } else { gen.writeObject(value); }注:createContextual能够取得字段的类型以及注解。当字段领有自定义注解时,取出注解中的值创立定制的序列化形式,这样在serialize办法中便能够失去这个值了。createContextual办法只会在第一次序列化字段时调用(因为字段的上下文信息在运行期不会扭转),所以无需关怀性能问题。 ...

March 31, 2023 · 3 min · jiezi

关于数据结构:听说火山引擎推出的-DataLeap已经可以支持万级表的数据血缘图谱了

更多技术交换、求职机会,欢送关注字节跳动数据平台微信公众号,回复【1】进入官网交换群数据起源广、量级大、场景多,导致数据之间关系变得异样简单。通过读取、荡涤、存储、计算等一系列流程之后,数据最终汇入指标、报表等服务零碎中。但如何对数据溯源、跟踪变动,成为困扰数据研发工程师的难题之一。 数据血统形容了数据的起源和去向,以及多个处理过程中的转换,是组织内使数据施展价值的重要根底能力。通过构建数据血统图谱,能够间接清晰地察看表之间的上、上游依赖关系,甚至是非凡场景下用户关注的表属性,更清晰查看数据链路和统计信息。 然而,要构建清晰、灵便、便当的数据血统图谱不是易事,特地是在数据量级大的状况下,往往面临层级关系简单、表工作凌乱、分组构造不分明的问题。 在字节跳动外部,有一套反对万级表血统的关系展现图谱每天被近万名员工应用,曾经积淀为火山引擎 DataLeap“数据地图”能力,并对外输入。 通过提供便捷的找数、了解数服务,火山引擎 DataLeap 大大节俭企业外部数据沟通和建设老本。 那么。这套图谱到底是如何设计和实现的? 首先,形象用户应用场景和需要。通过外部场景的深度用户调研,火山引擎 DataLeap 形象出如下需要: 表血缘关系查看:能从图中分明浏览用户关注的表上、上游血缘关系,以及场景的表属性。表血统链路查看:能清晰查看某个上游/上游表到用户关注表的链路状况。按要害指标分组查看:例如当表数据产生变更时,分组查看所有上游表的负责人以便告诉变更。筛选要害信息查看:例如用户找数据指标的时候,仅看相干的报表更高效。其次,在技术选型上,采纳 React + Canvas 的混合模式来实现血统图谱。 因为 Canvas 模仿滚动条研发老本高,与 HTML 相比,实现构造款式简单的节点定制较简单,但联合 React 框架渲染则能够轻松解决以上问题。因而,最终计划为:采纳 Canvas 居于底部,仅负责画连线;React 负责渲染节点、响应 hover 等交互。 最初,在方案设计和实现上,次要从查看关系的效率和属性齐备度两个角度登程,欠缺以下能力: 为了解决数据量大状况下,数据关系不清晰的问题,火山引擎 DataLeap 反对点击任意节点,则高亮该节点到主节点的链路性能,并在列表顶部减少层级信息和节点统计,让用户能同时查看每个节点细节和节点整体散布。当用户找数、了解数或进行归因剖析时,不仅要理解表的上游依赖,更须要了解表的加工逻辑。因而,火山引擎 DataLeap 在节点连线上新增工作信息,当用户 hover 连线即加粗、高亮并弹出工作信息,并匹配大数据开发平台对应的工作链接,点击即可跳转查看。在筛选性能上,火山引擎 DataLeap 采纳服务端筛选,保障符合要求的数据全量展现。不同职能的用户在不同场景下应用血统图谱时,关注的节点属性不雷同。火山引擎 DataLeap 血统图谱上设计了属性展现性能,用户能够勾选本人感兴趣的属性间接显示到图中。 据介绍,火山引擎 DataLeap 能帮忙企业疾速实现数据集成、开发、运维、治理、资产、平安等全套数据中台建设,其中数据地图次要提供数据检索、元数据详情查看、数据了解等性能,解决找数难、了解数据难的痛点,同时反对数据专题、血统图谱、数据发现、库表治理等特色性能。 目前,火山引擎 DataLeap 的数据地图平台已接入全链路外围元数据,包含 LAS、MySQL、ByteHouse CE、ByteHouse CDW、TOS、LasFS、EMR hive 等,提供可视化的血缘关系展现能力,帮忙用户全面的探查理解数据,反对表、字段级别血统可视化查问,以及按层级、范畴筛选展现,可依据用户需要灵便适配。 点击跳转 大数据研发治理套件 DataLeap 理解更多

March 20, 2023 · 1 min · jiezi

关于数据结构:如何实现云数据治理中的数据安全

作者:京东科技 李然辉 摘要 云计算被定义为计算资源的共享池,曾经在不同的应用领域宽泛部署和应用。在云计算中,数据治理在进步整体性能和确保数据安全方面施展着至关重要的作用。本钻研从治理和技术利用两方面探讨如何实现云数据治理中的数据安全。 关键词:大数据,云计算,数据治理,数据安全,管理模式,加密,数据安全利用 1. 介绍 数据治理承当着调配无关决策的权力和任务,以便数据能够作为公司资产进行治理。数据治理体系与整个数据生命周期相干,包含数据的产生、传输、应用、共享、存储、归档、销毁。因而,组织应该有一个整体的企业级数据治理策略,以最大化数据的价值并最小化潜在危险。 面对日益增长的数据量和复杂性,以及对交融、操作、存储和出现信息的需要,组织越来越强调数据的治理。而数据安全是数据治理中的一个重要畛域,安全性被认为是最大的数据治理挑战。 2. 组织治理中的数据安全 2.1 内部组织 思考到云数据治理须要与第三方交互,本文提出了问责机制来增强整体平安程度。它能够被定义为一种模型,能够向管理者和供应商提供交互式共享资源,如网络、存储、利用和服务。问责机制连贯了三方,即组织、云提供商和证书协会(CA)。组织能够受权CA对云供应商采取监管行为 (如图1所示)。 图1 责任治理 在设计阶段,图2中的概念框架展现了对数据治理的设计。 图2 数据治理设计的概念框架 2.2 外部组织 这一部分将钻研治理架构。它次要包含三个相应的组,能够分为三级治理模型:一组高级管理人员,一个中层治理团队和一个数据治理工作组: 图2 数据治理中的三层治理模型 数据始终被认为是组织中的资产,与人力资产、金融资产、物理资产、知识产权资产一样。数据拜访应由首席信息官和数据安全官治理,在数据拜访中管制安全性。为了解决IT和治理方面的组织问题,数据品质治理(DQM)应该建设与公司策略和数据管理法律统一的组织范畴的指导方针和规范。这样,从治理的角度来看,数据安全是绝对具体的,因而,它可能不会达到策略级别。总体数据安全治理模型总结如下: 图4 数据安全治理框架 从平安治理框架中能够看出,组织中的数据安全将基于三层架构来实现。IT治理将与整个过程集成,以实现业务策略的整体应变。 3. 技术层面的云数据安全问题 3.1 云计算平安框架 具体到云计算的技术方面,数据安全问题能够分为几个方面。Garner指出,云用户应该询问七个具体的平安问题,包含特权用户拜访、法规听从性、数据地位、数据隔离、复原、考察反对和长期可行性。云数据安全和云计算基础设施相干的政策、管制和技术不同于传统的IT环境。在这个范畴内,应该在整个数据生命周期中很好地部署数据安全和隐衷。 云环境中的七个重大平安挑战,包含数据定位、考察、数据隔离、长期生存能力、受损服务器、法规合规性和复原。波及未经受权的服务器、暴力攻打、来自云服务提供商的威逼、篡改数据以及失落用户身份或明码。能够在图5中评估整体数据安全威逼。能够看出,平安缓解办法应该集成到数据生命周期的每个过程中,以实现数据的机密性、完整性和可用性。 图5 数据生命周期中的数据安全威逼 依据评估,应重点关注与数据安全危险相干的四种技术办法,以保证数据的机密性、完整性和可用性:用户认证、数据加密、三方协商和数据备份。 3.2 数据危险缓解机制 3.2.1 数据加密 加密被认为是避免入侵的最无效的办法之一,这是一种应用算法辨别信息的办法。这是一种爱护不受信赖的云服务器中的数据的办法。加密是假装信息并将它们转换成密文的过程,而解密是将它们复原成可读的模式。在云计算中,只管有对称加密和非对称加密,但基于属性的加密(ABE)和基于身份的加密(IBE)是具备细粒度访问控制的高效零碎。对于多个组织中生成的数据,拜访策略能够由多个机构执行。 a) 三种加密办法 在对称加密中,用于加密音讯的密钥与用于解密音讯的密钥统一。应用非对称加密时,用于加密音讯的密钥不同于用于解密的密钥它。基于属性的加密(ABE)和基于身份的加密 (IBE)能够通过受权治理来实现,这意味着所有的私钥都应该在受权核心进行治理。为了防止集中攻打,分层IBE和分层ABE在不同的级别进行治理,以便密钥能够在不同的级别进行调配。 图 6:对称加 图 7:不对称加密 图 8:基于属性的加密 b) 三种加密办法的利用 在云计算中,联合应用这三种加密办法来爱护数据安全。为了确保集体或企业应用的平安云存储,能够首先应用惟一密钥应用AES加密数据,而后基于属性的加密能够加密惟一密钥。哈梅内伊和哈纳皮提出了一个应用 RSA和AES加密办法的数据共享办法。在这个框架中,发送者-接收者和云存储系统 (CSS)。在第一个过程中,发送者将他的文档传输到CSS零碎。RSA算法将用于实现加密。之后,文档应该从CSS零碎交付给接收者。零碎收到申请后,接收者的公钥也将应用用户的公钥发送到CSS。最初,AES 加密算法将找到所需的文件,并将其与密钥一起发送给用户。 此外,还引入了许多其余加密应用程序。面向用户的隐衷爱护协定(K2C)容许存储,共享和治理他们的信息,这是不可信的匿名。 3.2.2 用户认证 ...

March 20, 2023 · 1 min · jiezi

关于数据结构:排序

一.间接插入排序 相当于把大的元素挪到前面去 优化:折半插入排序 如果是对链表进行间接插入排序,尽管挪动元素的次数变少了,然而最坏状况的比拟次数仍为n的平方,所以工夫复杂度仍为n的平方 二.希尔排序 工夫复杂度最坏为n的平方,某个状况为n的1.3次方,但优于间接插入排序。不能对链表应用且不稳固 三.替换排序(冒泡和疾速) 冒泡排序: 工夫复杂度最坏为n的平方(逆序),最好状况为n(程序),所以均匀为n的平方 疾速排序: 但数组为程序或者逆序时,因为枢纽每次都会变成最靠边的元素,会把数组划分成很不平均的两份,所以工夫复杂度很高。但当枢纽能够把数组划分成两个比拟平均的局部时工夫复杂度会比拟低。 优化思路:(1)选头中尾三个元素,而后选两头的作为枢纽 (2)随机选一个作为枢纽 均匀工夫复杂度:nlog2n 不稳固 一次划分不等于一次排序,一次排序是对所有未确定地位元素的一次解决,而一次划分只是对一个枢纽元素的解决,所以一次排序可能能够确定多个元素的地位。 四.抉择排序(简略抉择排序和堆排序) 简略抉择排序: 工夫复杂度为n的平方,不稳固,程序表链表皆可 堆排序: 堆的删除与插入 五.归并排序 六.基数排序 稳固排序 七.内部排序 败者树: 置换归并: 归并树的重要性质:

February 13, 2023 · 1 min · jiezi

关于数据结构:查找

一.查找算法的评估规范 ASL:查找关键字的均匀次数 二.程序查找 如名,一个个查找,不限数据排序规定和存储格局 优化: (1)将查找表的数据有序排放(递增或者递加) (2)查找断定树 (3)当关键字的概率不同可按被查概率降序排列,查找胜利时ASL变小 三.折半查找 (1)应用条件:仅实用于有序的程序表(因为程序表有随机拜访特效) (2)代码实现:(/代表取整) (3)查找断定树关键字:左小于中小于右;且失败节点为胜利节点的空域数量,即n+1 如果mid是向上取整的话,则右边比左边多一个 须要留神的是,查找判断树肯定是均衡二叉树,且最上面一层是不满的,所以树高为log2(n+1)向上取整 四.分块查找 (1)用折半查找查索引,如果最终索引表停在low>high,则在low所在分块中查找,如果low超出索引表范畴,则查找失败。索引表的查找可用程序或者折半,但索=索引内进行程序查找 (2)查找效率剖析 五.二叉排序树 (1)结构与查找 代码有两种模式,另一种为递归实现,但递归实现会使空间复杂度变为O(n) (2)二叉排序树的插入 (3)二叉排序树的删除 1.如果z只有左子树或者右子树,则让z的子树成为父节点的子树,代替z的地位 2.若z有左右两颗子树,则让z的间接后继代替z,而后删去这个后继,从而转化为第一种状况 (4)工夫复杂度最好的O(log2n),最坏的O(n) 六.均衡二叉树 (1)插入最重要的为插入后的调整,间接看书 (2)删除能看懂最好,看不懂间接看书或者看视频 七.红黑树 (1)呈现的起因 (2)定义左根右,根叶黑,不红红,黑路同(从一个节点登程,到所有叶子节点通过的黑节点数量雷同) 黑高:从某一节点登程到任一空叶节点的门路上黑节点的总数推论:黑高为n的树,节点树起码为多少? 起码状况:共n层黑节点的满树状况,起码2的n次方减一个(3)性质 1.从根节点到叶子节点的最长门路不大于最短门路的两倍2.有n个节点的高度h不大于2log2(n+1) (4)插入看书或者视频,不过考的概率不大 八.b树,b+树和散列查找看书,内容不多,但比拟琐碎,多练多做

January 17, 2023 · 1 min · jiezi

关于数据结构:二叉树-遍历方式未完

前序遍历中序遍历后序遍历

January 5, 2023 · 1 min · jiezi

关于数据结构:数据结构-03-栈和队列

从数据结构角度看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表操作的子集,它们是操作受限的线性表,因而,可称为限定性的数据结构。但从数据类型角度看,它们是和线性表大不相同的两类重要的抽象数据类型。因为它们广泛应用在各种软件系统中,因而在面向对象的程序设计中,它们是多型数据类型。 1 栈1.1 抽象数据类型栈的定义栈(stack)是限定仅在表尾进行插入或删除操作的线性表。因而,对栈来说,表尾端有其非凡含意,称为栈顶(top),相应地,表头端称为栈底(bottom)。不含元素的空表称为空栈。 假如栈 \(S=(a_1,a_2,\cdots,a_n)\),则称 \(a_1\) 为栈底元素,\(a_n\) 为栈顶元素。栈中元素按 \(a_1,a_2,\cdots,a_n\) 的秩序进栈,退栈的第一个元素应为栈顶元素。即栈的批改是按后进先出的准则进行的,因而,栈又称为后进先出(last in first out)的线性表(简称 LIFO 构造)。 栈的抽象数据类型定义如下: ADT Stack { 数据对象:D = {ai | ai ∈ ElemSet, i = 1, 2, ..., n} 数据对象: R1 = {<a_{i-1}, ai> | a_{i-1}, ai ∈ D, i = 2, 3, ..., n} 约定 an 端为栈顶,a1 端为栈底 基本操作: InitStack(&S) 操作后果: 结构一个空栈 S DestroyStack(&S) 初始条件: 栈 S 存在 操作后果: 销毁栈 S StackEmpty(S) 初始条件: 栈 S 存在 操作后果: 若栈 S 为空栈,则返回 TRUE,否则返回 FALSE StackLenght(S) 初始条件: 栈 S 存在 操作后果: 返回栈 S 的元素个数 GetTop(S, &e) 初始条件: 栈 S 存在且非空 操作后果: 用 e 返回栈 S 的栈顶元素 Push(&S, e) 初始条件: 栈 S 存在 操作后果: 插入新元素 e 为栈 S 的新的栈顶元素 Pop(&S, &e) 初始条件: 栈 S 存在且非空 操作后果: 删除栈 S 的栈顶元素,并用 e 返回其值 StackTraverse(S, visit()) 初始条件: 栈 S 存在 操作后果: 从栈底到栈顶顺次对栈 S 中每个元素调用函数 visit()。一旦 visit() 失败,则操作失败} ADT Stack1.2 栈的示意与实现和线性表相似,栈也有两种存储示意形式。 ...

November 1, 2022 · 4 min · jiezi

关于数据结构:数据结构-02-线性表

线性构造的特点:在数据元素的非空无限汇合中, 存在惟一一个被称作“第一个”的数据元素;存在惟一一个被称作“最初一个”的数据元素;除第一个外,汇合中每一个数据元素都只有一个前驱;除最初一个外,汇合中每一个元素都只有一个后继。1 线性表的类型定义线性表(linear_list)是 $n$ 个数据元素的无限序列,是最常见和最简略的一种数据结构。在稍简单的线性表中,一个数据元素能够由若干个数据项(item)组成。在这种状况下,常把数据元素称为记录(record),含有大量记录的线性表称为文件(file)。 线性表中的数据元素能够是多种多样的,但同一线性表中的元素必然具备雷同的个性,即属于同一数据对象,相邻数据元素之间存在序偶关系。若将线性表记为: $$(a_1,\cdots,a_{i-1},a_i,a_{i+1},\cdots,a_n)$$ 则表中 \(a_{i-1}\) 是 \(a_i\) 的间接前驱元素, \(a_{i+1}\) 是 \(a_i\) 的间接后继元素。 线性表中元素的个数 \(n(n \ge 0)\) 定义为线性表的长度, \(n=0\) 是为空表。非空表中的每个数据元素都有一个确定的地位, \(i\) 为数据元素 \(a_i\) 在线性表中的位序。 线性表很灵便,其长度能够依据须要调整,即对线性表的数据元素能够进行拜访、插入、删除等。 抽象数据类型线性表的定义如下: ADT List { 数据对象: D = {ai | ai ∈ ElemSet, i = 1,2,...,n, n≥0} 数据关系: R1 = {<a{i-1},ai> | a{i-1},ai ∈ D, i=2,...,n} 基本操作: InitList(&L) 操作后果: 结构一个空的线性表 L DestroyList(&L) 初始条件: 线性表 L 已存在 操作后果: 销毁线性表 L ClearList(&L) 初始条件: 线性表L已存在 操作后果: 将L重置为空表 ListEmpty(L) 初始条件: 线性表 L 已存在 操作后果: 若 L 为空表,则返回 TRUE,否则返回 FALSE ListLenght(L) 初始条件: 线性表 L 已存在 操作后果: 返回 L 中数据元素个数 GetElem(L, i, &e) 初始条件: 线性表 L 已存在,1≤i≤ListLength(L) 操作后果: 用 e 返回 L 中第 i 个数据元素的值 LocatElem(L, e, compare()) 初始条件: 线性表 L 已存在,compare() 是数据元素断定函数 操作后果: 返回 L 中第 1 个与 e 满足关系 compare() 的数据元素的位序,若这样的数据元素不存在,则返回值为 0 riorElem(L, cur_e, &pre_e) 初始条件: 线性表 L 已存在 操作后果: 若 cur_e 是 L 的数据元素,且不是第一个,则用 pre_e 返回它的前驱,否则操作失败,pre_e 无定义 NextElelem(L, cur-e, &next-e) 初始条件: 线性表 L 已存在 操作后果: 若 cur_e 是 L 的数据元素,且不是最初一个,则用 next_e 返回它的后继,否则操作失败,next_e 无定义 ListInsert(&L, i, e) 初始条件: 线性表 L 已存在,1≤i≤ListLength(L)+1 操作后果: 在 L 中第 i 个地位之前插入新的数据元素e,L 的长度加 1 ListDelet(&L, i, &e) 初始条件: 线性表 L 已存在且非空,1≤i≤ListLength(L) 操作后果: 删除 L 的第 i 个数据元素,并用 e 返回其值,L 的长度减 1 ListTraverse(L, visit()) 初始条件: 线性表 L 已存在 操作后果: 顺次对 L 的每个数据元素调用函数 visit(),一旦 visit() 失败,则操作失败} ADT List2 线性表的程序示意与实现线性表的程序示意指的是用一组地址间断的存储单元顺次存储线性表的数据元素。 ...

November 1, 2022 · 2 min · jiezi

关于数据结构:数据结构-01-数据结构导论

1 什么是数据结构1.1 数据结构基本概念数据(data) 是对客观事物的符号示意,在计算迷信中是指所有能输出到计算机中并被计算机程序解决的符号的总称问题。图像、声音等都能够通过编码从而纳入到数据的领域。 数据元素(data element) 是数据的根本单位,在计算机中通过作为一个整体进行思考和解决。一个数据元素能够由若干个数据项(data item)组成。 数据对象(data object) 是性质雷同的数据元素的汇合,是数据的一个子集。 数据结构(data structure) 是相互之间存在一种或多种特定关系的数据元素的汇合。从学科角度,数据结构是一门钻研非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等的学科。 在任何问题中,数据元素都不是孤立存在的,而是在他们之间存在某种关系,这种数据元素之间的关系称为构造(structure)。依据数据元素之间关系的不同个性,通常有以下 4 种根本构造: 汇合:构造中的数据元素除了同属一个汇合的关系外,无其余关系。线性构造:构造中的数据元素之间存在一个对一个的关系。树形构造:构造中的数据元素之间存在一个对多个的关系。图状构造或网状结构:构造中的数据元素之间存在多个对多个的关系。 1.2 数据结构的模式定义数据结构的模式定义: 数据结构是一个二元组: $$Data\_ Structure = (D, S)$$ 其中,$D$ 是数据元素的汇合,$S$ 是 $D$ 上关系的无限集。 例如,复数是一种数据结构: $$Complex = (C, R)$$ 其中,C 是两个实数的汇合 {c1, c2},R={P},而 P 是定义在汇合 C 上的一张关系 {<c1, c2>},其中有序偶 <c1, c2> 示意 c1 是复数的实部,c2 是复数的虚部。 构造定义中的“关系”形容的是数据元素之间的逻辑关系,因而又称为数据的逻辑构造。 1.3 数据结构的计算机示意数据结构在计算机中的示意(又称映像)称为数据的物理构造,又称存储构造,它包含数据元素的示意和关系的示意。计算机中示意信息的最小单位是二进制的位(bit),能够用若干个位组合起来造成一个位串示意一个数据元素(如用 8 位二进制示意一个字符),通常称这个位串为元素(element)或结点(node)。当数据元素由若干个数据项组成时,位串中对应于各个数据项的子位串称为数据域(data field)。因而,元素或结点能够看做数据元素在计算机中的映射。 数据元素之间的关系在计算机中有两种不同的示意办法: 程序映像:借助元素在存储器中的绝对地位来示意元素之间的逻辑关系。非程序映像:借助批示元素存储地址的指针示意元素之间的逻辑关系。由此失去两种不同的存储构造:顺序存储构造和链式存储构造。 留神:任何一个算法的设计取决于选定的数据(逻辑)构造,而算法的实现依赖于采纳的存储构造。 存储构造波及数据元素及其关系在存储器中的物理地位,从高级程序语言的角度,能够借用高级程序语言中提供的“数据类型”来形容它,例如,能够应用一维数组来形容顺序存储构造,用 C 语言提供的指针来形容联赛存储构造。 1.4 数据类型、抽象数据类型和多形数据类型数据类型(data type) 是一个值的汇合和定义在这个值集上的一组操作的总称,是一个和数据结构密切相关的概念,最早呈现在高级编程语言中,用来刻画(程序)操作对象的个性。按“值”的不同个性,高级程序语言中的数据类型可分为两类: 原子型:非构造的,不可拆解的,如 C 语言中的根本类型(整型、实型、字符型、枚举类型)、指针类型和空类型。构造类型:由若干个成分按某种构造组成,是可分解的,其成分能够是非构造的,也能够是构造的,如数组。抽象数据类型(abstract data type,简称 ADT) 是指一个数学模型以及定义在该模型上的一组操作抽象数据类型的定义仍取决于它的一组逻辑个性,而与其在计算机外部如何示意和实现无关,无论其内部结构如何变动,只有它的数学个性不变,都不影响其内部应用。 ...

November 1, 2022 · 4 min · jiezi

关于数据结构:35如果面试让我手写红黑树

作者:小傅哥博客:https://bugstack.cn 积淀、分享、成长,让本人和别人都能有所播种!一、前言:挂在树上!不晓得你经验过HashMap的夺命连环问! 为啥,面试官那么喜爱让你聊聊 HashMap?因为 HashMap 波及的货色广,用到的数据结构多,问题延展性好,一个 HashMap 就能聊下来80%的数据结构了。而且面试 HashMap 能通过你对红黑树的理解定位出你哪个级别的研发人员。 而红黑树的知识点能够说是十分宏大,在学习红黑树时也不能一下就能把握,甚至很程序员压根就没搞明确红黑树,只是背下来它的几条限定规定而已。 其实一开始我也是这样! 不过总感觉这块的知识点不搞个明明白白,就闹心。因为不可能一个文科的货色,是须要死记硬背搞下来的。所以在翻阅材料、文档、历史、典籍,找到红黑树的演化过程,它是从2-3树演变而来,而2-3树、AVL树,这类B-树,也就是 BalancedTree 均衡树。它们都是为了解决 BST 二叉搜寻树不自均衡而来的产物。 那么当初分明了,要想搞定红黑树,让懂了就是真的懂,就须要把后面这些常识搞定,并且除了实践还能用落地的案例代码编写进去,才是悟透。好,那么接下来,小傅哥就带着你一起搞定这点事 二、BST 树Binary Search Tree历史 二叉搜寻树算法是由包含 PF Windley、Andrew Donald Booth、Andrew Colin、Thomas N. Hibbard 在内的几位钻研人员独立发现的。该算法归功于 Conway Berners-Lee 和 David Wheeler ,他们在 1960 年应用它在磁带中存储标记数据。 最早和风行的二叉搜寻树算法之一是 Hibbard 算法。 1. 二叉搜寻树数据结构二叉搜寻树(Binary Search Tree),也称二叉查找树。如果你看见有序二叉树(Ordered Binary tree)、排序二叉树(Sorted Binary Tree)那么说的都是一个货色。 <div align="center"> <img src="https://bugstack.cn/images/article/algorithm/tree-bst-01.png?raw=true" width="400px"></div> 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;任意节点的左、右子树也别离为二叉查找树;二叉搜寻树在日常开发中应用的场景还是比拟多的,例如基于组合模式实现的规定引擎,它就是一颗二叉搜寻树。但相似这样的开发中用到的二叉树场景,都是基于配置生成,所以组合进去的节点也更加不便管制树高和平衡性。这与 Java API HashMap 中的红黑树这样为了解决插入节点后仍放弃树的平衡性是有所不同的。 所以二叉搜寻树也是一颗没有通过调衡的基础性数据结构,在肯定概率上它实现有可能进化成链表,也就是从近似O(logn)的工夫复杂度进化到O(n)。对于二叉搜寻树的均衡解决方案,包含;AVL树、2-3树、红黑树等,小傅哥会在后续的章节持续实现。 2. 二叉搜寻树结构实现二叉搜寻树是整个树结构中最根本的树,同时也是树这个体系中实现起来最容易的数据结构。但之所以要应用基于二叉搜寻树之上的其余树结构,次要是因为应用数据结构就是对数据的寄存和读取。那么为了进步吞吐效率,则须要尽可能的均衡元素的排序,体现在树上则须要进行一些列操作,所以会有不同的构造树实现。 而实现二叉搜寻树是最好的根底学习,理解根本的数据结构后才更容易扩大学习其余树结构。 源码地址:https://github.com/fuzhengwei/java-algorithms本章源码:https://github.com/fuzhengwei/java-algorithms/tree/main/data-structures/src/main/java/tree2.1 树枝定义public Integer value;public Node parent;public Node left;public Node right;用于组成一颗树的节点,则须要包含;值和与之关联的三角构造,一个父节点、两个孩子节点。如果是AVL树还须要树高,红黑树还须要染色标记。2.2 插入节点public Node insert(int e) { if (null == root) { root = new Node(e, null, null, null); size++; return root; } // 索引出待插入元素地位,也就是插入到哪个父元素下 Node parent = root; Node search = root; while (search != null && search.value != null) { parent = search; if (e < search.value) { search = search.left; } else { search = search.right; } } // 插入元素 Node newNode = new Node(e, parent, null, null); if (parent.value > newNode.value) { parent.left = newNode; } else { parent.right = newNode; } size++; return newNode;}首先判断插入元素时候是否有树根,没有则会把以后节点创立出一颗树根来。如果以后树是有树根的,则对插入元素与以后树进行一个节点遍历操作,找到元素能够插入的索引地位 parent(挂到这个父节点下)。也就是 search 搜寻过程。最初就是插入元素,通过给插入值创立一个 Node 节点,并绑定它的父元素,以及把新元素挂到索引到的 parent 节点下。2.3 索引节点public Node search(int e) { Node node = root; while (node != null && node.value != null && node.value != e) { if (e < node.value) { node = node.left; } else { node = node.right; } } return node;}值查找的过程,就是对二叉搜寻树的遍历,一直的循环节点,依照节点值的左右匹配,找出最终相当的值节点。2.4 删除节点public Node delete(int e) { Node delNode = search(e); if (null == delNode) return null; return delete(delNode);}private Node delete(Node delNode) { if (delNode == null) return null; Node result = null; if (delNode.left == null) { result = transplant(delNode, delNode.right); } else if (delNode.right == null) { result = transplant(delNode, delNode.left); } else { // 因为删除的节点,有2个孩子节点,这个时候找到这条分支下,最左侧做小的节点。用它来替换删除的节点 Node miniNode = getMiniNode(delNode.right); if (miniNode.parent != delNode) { // 替换地位,用miniNode右节点,替换miniNode transplant(miniNode, miniNode.right); // 把miniNode 晋升父节点,设置右子树并进行挂链。代替待删节点 miniNode.right = delNode.right; miniNode.right.parent = miniNode; } // 替换地位,删除节点和miniNode 可打印测试察看;System.out.println(this); transplant(delNode, miniNode); // 把miniNode 晋升到父节点,设置左子树并挂链 miniNode.left = delNode.left; miniNode.left.parent = miniNode; result = miniNode; } size--; return result;}private Node getMinimum(Node node) { while (node.left != null) { node = node.left; } return node;}private Node transplant(Node delNode, Node addNode) { if (delNode.parent == null) { this.root = addNode; } // 判断删除元素是左/右节点 else if (delNode.parent.left == delNode) { delNode.parent.left = addNode; } else { delNode.parent.right = addNode; } // 设置父节点 if (null != addNode) { addNode.parent = delNode.parent; } return addNode;}2.4.1 删除单节点<div align="center"> ...

October 8, 2022 · 9 min · jiezi

关于数据结构:图及其应用

1.图的概念 (1)简略图:无反复边且没有顶点到本身的边(数据结构都是简略图)(2)度:无向图为附丽该点的边数,有向图为出度和入度的和(3)简略门路:两头顶点不反复的门路(4)对于无向图,为连通和连通图;对于有向图,为强连通和强连通图(5)对于n个顶点的图:为无向图,为连通则起码n-1条边,不为连通最多Cn-1(2)条边 为有向图,为强连通,则起码n条边(6)极大连通子图:(1)为无向图中,子图必须连通且蕴含尽可能多的点和边 (2)为有向图中,子图必须强连通且蕴含尽可能多的点和边(7)连通重量和强连通重量:无向图的极大连通子图和有向图的极大连通子图(8)无向连通图的生成树:为蕴含所有点的极小连通子图(即蕴含所有点且边尽可能少)(n个点则有n-1条边)(9)无向/有向齐全图:任意两个点有边/任意两个点存在方向相同的两条弧 2.图的存储 (1)邻接矩阵法:无向图:第i个节点的度:某行(列)的非零元素的个数有向图:第i节点的出度:第i行的非零元素的个数求度,出度,入度的工夫复杂度:O(v) 工夫复杂度:O(v*2)适宜存储浓密图 性质: (2)邻接表法 性质:街坊表法示意不惟一,但矩阵是惟一的,适宜存储稠密图 (3)十字链表法(只存储有向图) (4)邻接多重表 总结: 3.图的利用 (1)最小生成树:带权连通图的权值之和最小的生成树 Prim算法:不停的加顶点(O(v平方),适宜边稠图)Krusal算法:不停选最小的边,使边两边的点连通(O(elog2e),适宜边疏图) (2)最短门路 (3)有向无环图 (4)拓扑排序 性质:(1)拓扑和逆拓扑可能不惟一 (2)拓扑和逆拓扑内不能有环(5)要害门路(性质,办法可突击)

September 20, 2022 · 1 min · jiezi

关于数据结构:直播预约丨流式湖仓服务大数据的终结这场开源发布会为你揭晓

【点击立即报名】8月11日,网易数帆将举办“企业级流式湖仓服务 Arctic 开源发布会”,邀请网易数帆大数据产品线及合作伙伴相干负责人联袂解读对数据技术演进及 Arctic 开源的思考,介绍 Arctic 我的项目停顿、将来倒退及社区规划,分享企业湖仓一体实际成绩与心得。数据基础设施倒退的脚步从未停歇,以后风头正盛的是湖仓一体(Lakehouse)。湖仓一体,顾名思义是数据湖和数据仓库劣势的联合。随着企业数智化的推动,湖仓一体已不仅仅是开源社区的热点技术,硅谷顶级风头投机构A16Z幅员的视线核心,更是泛滥大数据商业产品家族的重要成员。那么,湖仓一体真的会成为企业大数据基础设施的规范?咱们是否该当关注这一技术?它的将来是什么? 为什么须要湖仓一体借用Databricks的定义,湖仓一体平台能同时提供数据仓库的可靠性、弱小的治理和性能,以及数据湖的开放性、灵活性和机器学习反对。网易数帆湖仓一体我的项目负责人马进认为,湖仓一体是接力Apache Hadoop蓬勃生态的新赛道,它的外围个性就是在数据湖上构建事务层,把数据处理和治理高级性能嫁接到低成本数据存储架构上。这是业务需要驱动的架构演进,毕竟业务数据类型及规模不断扩大,而对计算实时性的要求又更高。以网易为例,从T+1 离线数据生产,到引入实时化并不断完善,如引入Apache Kudu解决Hive离线数仓在实时数据更新上的有余,造成了流批宰割的Lambda架构(这也是业界大数据架构演进的一个缩影),而后数据孤岛、研发体系割裂以及指标和语义的二义性等问题逐步裸露,这就须要一个更加优雅的对立数据基础设施架构,也就是湖仓一体来解决。基于数据湖开源三剑客(Delta Lake、Apache Iceberg、Apache Hudi)的实现计划,则成为了热门的抉择。 网易数帆流式湖仓的翻新只管在造词法上Lakehouse的确是Data Lake和Data Warehouse的缝合怪,然而要成为生产级的新技术,湖仓一体毕竟不是数据湖和数据仓库1+1=2那么简略。在马进看来,目前湖仓一体计划存在两大有余:一是所读即所写,会产生流式摄取导致海量小文件等问题;二是实时能力有余,比方基于湖仓一体的流计算提早在分钟级别。基于此,马进率领团队研发了命名为Arctic的流式湖仓服务,提出了五个设计指标:提供牢靠的湖仓一体服务,解决支流湖仓一体的有余,面向更多流批一体的场景,尽可能不要反复造轮子,和寻求代际型解决方案。技术计划上,Arctic搭建在Iceberg表格局之上,复用Iceberg各种性能,并齐全兼容Hive。Arctic面向流场景提供优化的CDC(变更数据获取)和流式更新能力,也能够开放式地集成 MQ、KV 等中间件,向 Flink、Spark、Trino 等支流计算引擎提供流批对立的表服务,以实现数据湖和数仓的对立,并融入实时的能力,流计算提早可达毫秒级。由此,Arctic 可视为一个独立的实时数仓服务,用户无需关怀数据存储构造、大小和散布,或是否引入其余中间件。 流式湖仓的将来三十年前,东方学者面对社会变迁收回“历史的终结”的感叹,但历史曾经给这一论断打脸。那么,流式湖仓又是否会成为古代大数据基础架构的起点?回顾数据分析畛域,先后呈现的数据仓库、OLAP、BI、大数据、数据中台等各种方法论,都已融入企业数据生命周期,而底层的Hadoop体系仍然在宽泛应用,咱们有理由置信,流式湖仓服务这一源自业务需要的设计,实现形式可能会降级,但这一思维必将长存于数据基础设施。从A16Z的全景图咱们也能够看到,企业级数据基础设施架构的稳固往往随同着长时间的积淀,而Arctic凋谢的架构及对Hadoop生态的兼容,曾经预示着它的生命力。秉承网易数帆“架构凋谢,内核开源”的理念,Arctic行将开源!和咱们一起探讨湖仓一体落地实际的要害因素,独特促成凋谢架构数据基础设施生态欠缺与倒退,欢送点击链接:i.163yun.com/n1pvp4086报名加入。

August 5, 2022 · 1 min · jiezi

关于数据结构:算法与数据结构综合提升-C版某课网盘分享

download:算法与数据结构-综合晋升 C++版某课网盘分享如何实现可插拔配置?我又又又又被吐槽了,随之而来,我的消息推送平台开源我的项目Austin又又又又更新啦,迭代自己的我的项目多是一件美事啊。01、可插拔我的我的项目逐渐成型了之后,有挺多小伙伴吐槽过我的我的项目太重了,依赖的中间件有点多。在最开始的那一版需要强依赖MySQL/Redis/Kafka/Apollo(我的项目启动就需要部署这些中间件),弱依赖prometheus/graylog/flink/xxl-job(想要有完整的我的项目体验,就需要把这些给部署起来)。 MySQL是没有人吐槽的,数据库这种货色,可能说是后端必须的了。Redis临时还没人吐槽,毕竟用的还是太多了,也没有什么弱小的竞品。Apollo常常被吐槽能不能换成Nacos。Kafka时而有人吐槽,想要反对RabbitMQ、RocketMQ。 我以前存在个观点:在公司里中间件是不会轻易替换的,现在我的代码已经实现了一种姿势,感觉没多大必要反对多种中间件实现,你想换就自己动手改改嘛,又不难。「“Apollo太重啦,Apollo不好用!快点反对Nacos!”」「“反对RocketMQ好不好啊”」「“能不能反对RabbitMQ?”」对我来说并不是啥大理由,我还是感觉Apollo挺好用,足够成熟稳固,同理Kafka亦是如此。不过当我被吐槽多了,总会怀疑自己是不是做得不够好,也会跟身边的大佬探讨探讨,有没有必要反对一些功能。思来想去,我变了,我又懂了为了让消息推送平台Austin易上手,我首先把Apollo做成弱依赖,可能通过配置抉择读本地文件还是读配置核心(Apollo)。其实当咱们使用Apollo时,即便Apollo挂了,Apollo本身就有很强的容灾能力(自带本地文件)其次,我把Kafka做成弱依赖,可能通过配置抉择用Guava的eventbus还是走分布式消息队列(Kafka),后续可能还会反对RocketMQ/RabbitMQ,感兴趣的也可能在我的代码基础上实现一把,蹭个pull request也很香的。一方面是升高使用门槛而做的,另一方面是可能对具体实现进行可插拔,这是开源我的项目所需要的。我认为如果是公司级生产环境线上的我的项目,对这块更多考虑的是异构容灾(而非可插拔)。于是乎,现在消息推送平台Austin默认的强依赖只剩下了MySQL和Redis,其余中间件的都是弱依赖,要做到可插拔我是借助配置去实例化不同的中间件。当我的配置austin-mq-pipeline=eventbus时,我就不去实例化Kafka相干的消费者和消费者,转而去初始化eventBus的消费者和消费者,那天然接口下的实现类就是为eventbus的 02、(彩蛋)KAFKA反对TAG过滤我的股东们是能间接用我的近程服务的:Kafka的Topic是共享的,Group消费者也是共享的,在不修改的前提下,间接使用会带来一个问题。当同时有两个或以上的股东在本地启动了Austin,那就会争抢生产这个Topic(相当于一个消费者组里起了多个消费者),导致在测试下发的时候可能收不到自己调试的消息(被别的股东抢去了)。要解决这个问题我第一工夫的想法很简略:不同的股东使用不同的group(相当于每个股东都会有独立的消费者组),那不就完事了嘛?正好我的groupId生成是依赖渠道的code,改掉code就完事咯。 但这还是有问题的:每个股东有独立的消费者组,意味着每个股东能生产整个topic的所有消息,这又意味着股东会接受到其余股东的测试消息(明明只想要自己测试的消息,却来了一条其他人发的)。要解决这个问题,除了给每个股东一个独立的topic,那就是根据tag过滤啦。在Kafka实现这种成果,挺简略的:在发送的时候,把tag写进Kafka的头部,在生产前把非自身tag的消息过滤掉就完事了。 03、总结从开始写这个我的项目到现在还一直在迭代,这个过程受到了不少的吐槽。这种吐槽大多数是正向的,毕竟有人吐槽那才说明我这个我的项目是真的有人在用的,有人在看的。最近有个想法:把这个零碎做成是线上的,可能由各大开发者在推送消息的时候调用我的接口,做成这样肯定会很有意义,面临的挑战和需要也会更多。那我就一直可能迭代,在这过程中肯定我还能学到很多以前所不知道的货色。这次我用@ConditionAlOnProperties这个注解来实现可插拔的配置,但其实如果是提供二方库的形式的话,使用SPI的姿势会更加斯文。

July 25, 2022 · 1 min · jiezi

关于数据结构:小橙子的数据结构课数组链表初介绍

前言作者:嗨皮陈,某公司后端研发。现跟着小橙子一起学习前端常识。程序员中流传着一句话,程序 = 数据结构 + 算法,数据结构是程序员必知必会的知识点。而所有的数据结构实质上都是数组和链表。数组和链表是所有数据结构的基石。在本文中,嗨皮将带着小橙子初步理解一下数组和链表。 内存是什么样的咱们常常说手机内存不够了,这个软件占了我好多内存。那么内存是什么样子的呢?咱们能够把内存设想成好多个格子,照片、软件、小说会依据大小来占据对应数量的格子。【下图中是一个内存的大略样子,其中已应用的内存被标记为蓝色】 尝试把数据放入内存中办法一--并排放当初小橙子的手外面有七个苹果,咱们要把这七个苹果放到内存中。每个苹果要占据一个格子的空间,那么咱们有哪几种放法呢?第一种放法,咱们把七个苹果放在间断的七个格子外面,并给它们每个人一个标号。这种间断放法的数据结构,咱们称之为数组。【数组是有序的元素序列】有序如何了解:这七个苹果依照顺序排列,咱们能够依据标号间接去拿第3个苹果、第5个苹果。 办法二--哪里有空位放哪里在办法一外面 数组和链表的优缺点课后习题

July 24, 2022 · 1 min · jiezi

关于数据结构:数据结构js实现队列

class Queue { constructor() { this.count = 0; this.lowestCount = 0; this.items = {}; } isEmpty() { return this.count === this.lowestCount; } enqueue(element) { this.items[this.count] = element; this.count++; } dequeue() { if (this.isEmpty()) { return void 0; } const result = this.items[this.lowestCount]; delete this.items[this.lowestCount]; this.lowestCount++; return result; } // 查看第一个元素 peek() { if (this.isEmpty()) { return void 0; } return this.items[this.lowestCount]; } size() { return this.count - this.lowestCount; } clear() { this.count = 0; this.items = {}; this.lowestCount = 0; } toString() { if (this.isEmpty()) { return ""; } let result = this.peek(); for (let i = this.lowestCount + 1; i < this.count; i++) { result += `,${this.items[i]}`; } return result; }}

July 20, 2022 · 1 min · jiezi

关于数据结构:数据结构js实现栈

class Stack { constructor() { this.items = []; } push(item) { this.items.push(item); } pop() { return this.items.pop(); } // 返回顶部元素,然而不批改 peek() { return this.items.at(-1); } isEmpty() { return this.items.length === 0; } clear() { this.items = []; } size() { return this.items.length; } toString() { if (this.isEmpty()) { return ""; } let objString = `${this.items[0]}`; for (let i = 1; i < this.count; i++) { objString = `${objString},${this.items[i]}`; } return objString; }}

July 20, 2022 · 1 min · jiezi

关于数据结构:树与二叉树

1.树的一些论断: (1)节点个数=度数+1 (2)一个概念的辨析:树的度(例如为n)和m叉树树的度:示意一个树必有一个节点的度为n,且肯定不为空树,节点数起码为n+1m叉树:能够为空树,能够所有节点的度都小于m 2.二叉树的一些论断 (1)非空二叉树中:n0=n2+1 (2)齐全二叉树的话:n1肯定为0或1,n0与n2的和肯定为奇数 3.二叉树的顺序存储 struct TreeNode{ Elemtype value; bool isEmpty;} 二叉树的顺序存储,是通过数组存储的。但无论是不是齐全二叉树,都必须依照齐全二叉树来进行存储。如果那个地位没有树,就填0,但那个地位必须有。(即最坏状况:哪怕是高度为h的,只有h个节点的树(只有右结点),也须要2的h次方减一个存储单位) 所以,一般来说,顺序存储,只适宜齐全二叉树。(但不是齐全的也能够,但比拟节约) 4.二叉树的链式存储 typedef struct BitNode{ Elemtype data; struct BitNode *lchild,*rchild;}BitNode,*BitTree;n个节点,应该有2n个指向域,但只会有n-1个指针,所以肯定有n+1个空链域。 因为二叉树不便找左右孩子节点,但不不便找父亲节点,所以能够引入一个父亲节点。 typedef struct BitNode{ Elemtype data; struct BitNode *lchild,*rchild; struct BitNode *parent;}BitNode,*BitTree;5.二叉树的遍历先序——前缀表白中序——中断表白后序——后缀表白层序遍历:从上到下,从左至右 6.由遍历推二叉树前/后/档次+中能够推(都是先找根节点在哪,而后在分左右子树,再推) 7.线索二叉树 typedef struct ThreadNode{ Elemtype data; struct ThreadNode *lchild,*rchild; int ltag,rtag;------左右线索标记}ThreadNode,*ThreadTree;中序线索化(只有没有左右孩子,ltag和rtag就为1) 先序线索化(只有先序线索化会有转圈的问题(第一张图),只有ltag=0 能力先序线索化) 后序同中序(根本代码雷同) 线索二叉树找前序和后继(先序是在左孩子存在,后序是在右孩子存在的状况下(即没有对应的线索指针)) 8.树 (1)双亲表示法 增:新增元素,间接在表中增加即可,无需按程序 删:(1)将前面的数字改为-1(会减少空数据,升高效率) (2)删除该元素以及其子元素(如果有) 长处:查找双亲不便毛病:查找孩子节点须要从头遍历 (2)孩子表示法 (3)孩子兄弟表示法(即树与二叉树的转换) (4)森林和树的转换 (5)树的遍历树的先根遍历——对应二叉树的先序遍历(深度优先遍历) 树的后根遍历——对应二叉树的中序遍历(深度优先遍历) 树的层序遍历——从上至下,从左至右(广度优先遍历) (6)森林的遍历森林的先序遍历——所有树的先序遍历 森林的中序遍历——所有树的后序遍历 遍历总结: (7)哈夫曼树的结构 (8)如何对数据编码(无前缀编码:即所有的字母或数据皆为叶子节点) ...

July 19, 2022 · 1 min · jiezi

关于数据结构:LinkedList源码深度剖析

LinkedList源码深度分析LinkedList继承体系首先先直观的看一下LinedList的继承体系和实现的接口 public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable实现了List接口,这个接口定义了一些常见的容器的办法,比方add、addAll、get、set、contains、sort等等。实现了Deque接口,这个接口你能够简略的认为是一个双端队列,中间都能够进能够出,它定义的办法有getFirst、getLast、addFirst、addLast、removeFirst、removeLast等等。实现Cloneable和Serializable接口次要是为了可能进行深拷贝和序列化,这个问题咱们后续再谈。AbstractSequentialList次要是给一些接口办法提供默认实现。依据上篇文章的剖析,咱们很容易晓得链表作为一个容器必定须要将数据退出到容器当中,也须要从容器当中失去某个数据,判断数据是否存在容器当中,因而有add、addAll、get、set、contains、sort这些办法是很天然的。此外LinedList实现的是双向链表,咱们很容易在链表的任意地位进行插入和删除,当咱们在链表的头部和尾部进行插入和删除的时候就能够满足Deque的需要了(双端队列须要可能在队列的头和尾进行出队和入队,就相当于插入和删除),因而LinedList实现getFirst、getLast、addFirst、addLast、removeFirst、removeLast也就很容易了解了。 LinkedList整体构造LinkedList次要几个字段 transient int size = 0; // 用于记录链表当中节点的个数,也就是有几个数据 /** * Pointer to first node. * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */ transient Node<E> first; // 指向双向链表的头结点 /** * Pointer to last node. * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */ transient Node<E> last; // 指向双向链表的尾节点LinkedList当中的外部节点的模式private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; }}依据下面的字段剖析,LinkedList内部结构次要如下: ...

July 4, 2022 · 6 min · jiezi

关于数据结构:链表设计与Java实现手写LinkedList这也太清楚了吧

链表设计与实现在谈链表之前,咱们先谈谈咱们平时编程会遇到的很常见的一个问题。如果在编程的时候,某个变量在后续编程中仍需应用,咱们能够用一个局部变量来保留该值,除此之外一个更加罕用的办法就是应用容器了。 那什么是容器呢?从字面上来说就是用来装某个货色的,比方咱们的杯子,就是容器。在程序设计当中咱们最常见的容器就是数组了,他能够存咱们想保留的货色。在编程当中咱们最常见的容器如下: 在Python当中有列表、字典、元组、汇合等等。在Java当中常见的容器有 ArrayList、LinkedList、HashMap、HashSet等等。在C++当中有vector、list、unordered_map、unordered_set等等。明天要谈到的链表在Java的LinkedList和C++的list当中就有应用到。 那什么是链表呢?链表是由一个一个的节点组成的,每个节点蕴含两个字段,其中一个字段data示意实在须要示意的数据,另外一个字段next示意指向下一个节点的指针(如果不理解指针也没有关系,就将其当做一个一般的变量既可,不影响咱们的了解),data和next两者一起组成链表当中的节点(Node)。 其中data示意链表当中存储的实在的数据,而next示意指向下一个节点的指针(如果不理解指针也没有关系,就将其当做一个一般的变量既可,不影响咱们的了解),data和next两者一起组成链表当中的节点(Node)。 Java代码: class Node<E> { E item; Node<E> next; public Node(E item, Node<E> next) { this.item = item; this.next = next; } }单链表所谓单链表就是只有一个指向其余节点的变量,比方下图当中只有一个next变量指向其余同样的节点。 双向链表双向链表和单链表的区别就是他的指向有两个方向,而单链表只有一个方向,在双向链表的节点当中会有两个指向其余同样节点的变量,一个指向前一个节点,一个指向后一个节点,对应下图prev指向前一个节点,next指向后一个节点。 循环链表这个概念也比较简单,就是链表首尾相连,造成一个环,比方单循环链表: 双向循环链表,第一个节点(头结点)的prev指向最初一个节点(尾节点),尾节点的next指向头结点: 动态链表咱们后面所提到的链表中的节点除了数据域(data)还有一个变量指向其余的节点,节点与节点之间的内存地址是不间断的,而动态链表和后面提到的链表不一样,它是应用数组来实现链表,只是将next变成一个int类型的数据,示意下一跳数据的下标,比方下图当中所示意的那样(其中-1示意链表的结尾,因为next域存储的是下一个节点的下标,下标必定大于等于0,因而能够应用-1示意链表的结尾): 在上图当中对应的链表如下(通过剖析上图当中next域的指向剖析失去下图): 像这种应用数组实现的链表叫做动态链表,下面谈到的就是动态单链表,它对应的数据结构也很分明: private static class StaticNode<E> { // 指向节点的实在存储的数据 E item; // 指向下一个节点的下标 int next; public StaticNode(E item, int next) { this.item = item; this.next = next; } }为什么须要链表?答复这个问题之前,首先须要搞清楚咱们面临什么样的需要: 咱们须要有一个容器能够保留咱们的数据咱们的数据有肯定的程序性,比方咱们当初容器当中的数据个数是10个,咱们想在下标为3的中央插入一个数据 在数组长度够的状况下,咱们须要将下标2之后的数据往后搬一个地位而后将新的数据放到下标为3的地位,这种插入的工夫复杂度为 O(n),至于为什么是O(n)咱们在谈ArrayList时咱们再进行证实。 ...

July 4, 2022 · 6 min · jiezi

关于数据结构:golang泛型实现双向循环链表

一、写在后面规范库的双向循环链表实现是基于interface{}的,性能个别。为了晋升性能,本文基于泛型语法实现一个比规范库更快的链表写法(次要包含双向循环链表的插入和删除的外围操作)。 二、什么是链表链表用于存储逻辑关系为 "一对一" 的数据,与数组不一样,不要求存储地址是间断的。 三、链表的分类链表依据指针的指向分为 单向链表 和 双向链表 。 单向链表:通常应用Next指针type Node[T any] struct { next *Node[T] Element T}双向链表:通常应用Next、Prev两个指针.type Node[T any] struct { next *Node[T] prev *Node[T] Element T}链表依据是否循环, 又分为 单向循环链表双向循环链表单向链表双向链表四、双向循环链表的实现上面次要介绍在工程上用得比拟多的双向循环链表(将双向链表的头结点和尾结点链接起来形成循环的链表)的实现。 4.1 双向循环链表的表头定义和初始化函数循环链表和一般的双向链表最大的区别是初始化的时候root.prev和root.next要指向root节点本人。 阐明: 这是一个递归定义,能够试着打印root.prev,root.prev.prev或者root.next,root.next.next的地址看看。 // 每个Node节点, 蕴含前向和后向两个指针和数据域type Node[T any] struct { next *Node[T] prev *Node[T] Element T}// 返回一个双向循环链表func New[T any]() *LinkedList[T] { return new(LinkedList[T]).Init()}// 指向本人, 组成一个环func (l *LinkedList[T]) Init() *LinkedList[T] { l.root.next = &l.root l.root.prev = &l.root l.length = 0 return l}4.2 插入节点的图示在root节点,如何指向最初一个元素呢? 就是root.prev,下图展现双向循环链表插入节点的过程。 ...

May 28, 2022 · 1 min · jiezi

关于数据结构:数据结构与算法经典题农夫过河问题讲解

题目:一个农夫在河边带了一只狼、一只羊和一颗白菜,他须要把这三样货色用船带到河的对岸。然而,这艘船只能容下农夫自己和另外一样货色。如果农夫不在场的话,狼会吃掉羊,羊也会吃掉白菜。请编程为农夫解决这个过河问题。思路: 看到这道题目,咱们先缕清这几样货色(包含一个人)他们之间的关系。在这道题里,咱们只能两两组合。其中,有几个限度条件1.狼不能和羊在一起2.羊不能和白菜在一起(这两个条件也会成为咱们之后算法里逻辑做限度的一部分) 命名相干变量当初,咱们对它们进行命名,咱们先用数字示意这些货色(和人)0——狼、1——羊、2——白菜、3——农夫(记得肯定是从0开始标记哦) 接下来,咱们就是标记河的两岸,咱们给河的两岸别离示意为东岸和西岸(当然北岸和南岸也行,随便啦)。以此证实他们达到,为了让咱们的计算机可能看懂,咱们示意为0——起始河岸1——达到的河岸 如果狼达到了对岸,咱们用数组就能够示意为aStep 1,其余同理。(这里的a数组示意存储每一步中各个对象所处的地位,除此之外,咱们还须要数组b[N]来存储每一步中农夫是如何过河的) 因而,残缺代码如下: #include <stdio.h>#include <stdlib.h>#include <string.h>#define N 15int a[N][4];int b[N];char* name[] ={ " ", "and wolf", "and goat", "and cabbage"};int search(int step){ int i; if (a[step][0] + a[step][1] + a[step][2] + a[step][3] = 4) { for (i = 0; i <= step; i++) { printf("east;"); if (a[i][0] == 0) printf("wolf "); if (a[i][1] == 0) printf("goat "); if (a[i][2] == 0) printf("cabbage"); if (a[i][3] == 0) printf("farmer"); if (a[i][0] && a[i][1] && a[i][2] && a[i][3]) printf("none"); printf(" "); printf("west;"); if (a[i][0] == 1) printf("wolf "); if (a[i][1] == 1) printf("goat "); if (a[i][2] == 1) printf("cabbage "); if (a[i][3] == 1) printf("farmer "); if (!(a[i][0] || a[i][1] || a[i][2] || a[i][3])) printf("none"); printf("\n\n\n"); if (i < Step) printf(" the %d time\n", i + 1); if (i > 0 && i < Step) { if (a[i][3] == 0) /*农夫在本岸*/ { printf(" -----> farmer "); printf("%s\n", name[b[i] + 1]); } else /*农夫在对岸*/ { printf(" <----- farmer "); printf("%s\n", name[b[i] + 1]); } } } printf("\n\n\n\n"); return 0; } for (i = 0; i < Step; i++) { if (memcmp(a[i], a[Step], 16) == 0) /*若该步与以前步骤雷同,勾销操作*/ { return 0; } } /*若羊和农夫不在一块而狼和羊或者羊和白菜在一块,则勾销操作*/ if (a[Step][1] != a[Step][3] && (a[Step][2] == a[Step][1] || a[Step][0] == a[Step][1])) { return 0; } /*递归,从带第一种动物开始顺次向下循环,同时限定递归的界线*/ for (i = -1; i <= 2; i++) { b[Step] = i; memcpy(a[Step + 1], a[Step], 16); /*复制上一步状态,进行下一步挪动*/ a[Step + 1][3] = 1 - a[Step + 1][3]; /*农夫过来或者回来*/ if (i == -1) { search(Step + 1); /*进行第一步*/ } else if (a[Step][i] == a[Step][3]) /*若该物与农夫同岸,带回*/ { a[Step + 1][i] = a[Step + 1][3]; /*带回该物*/ search(Step + 1); /*进行下一步*/ } } return 0;}int main(){ printf("\n\n 农夫过河问题,解决方案如下:\n\n\n"); search(0); return 0;}

May 17, 2022 · 2 min · jiezi

关于数据结构:线性表

1.定义和定义时留神的细节线性表:(1)个数无限(2)都为数据元素且类型雷同(3)是一种逻辑构造(程序表和链表才是存储构造) 操作定义时,&L和L:带&意味着在操作中会对L进行扭转,即把参数批改带回来,所以须要带&. 2.程序表的定义 #define InitSize 10typedef struct{ Elemtype *data;.......指向程序表元素的指针(Elemtype是示意指针指向的元素的类型,如果是单链表就是struct LNode),通过InitList中的malloc 函数使它指向数组的起始地址 int MaxSize; int length;}Sqlist;=struct{ Elemtype *data; int MaxSize; int length;}typedef struct Sqlist;void InitList(Sqlist &L){ L.data=(Elemtype *)malloc(InitSize*sizeof(int)); L.length=0; L.MaxSize=InitSize;}3.程序表的特点(1)存储密度大,每个元素只存储数据(2)随机拜访,第i个元素:data[i-1] (3)拓展容量不便(4)插入,删除不变 4.程序表的增删查的工夫复杂度(每个元素被的概率乘以均匀次数)插入:O(n)删除:O(n)按值查找:O(n) 5.单链表的定义和判断 typedef struct LNode{ Elemtype data; struct LNode *next;....指向LNode的指针}LNode,*LinkList;=struct LNode{ Elemtype data; struct LNode *next;....指向LNode的指针} typedef struct LNode LNode;typedef struct LNode* LinkList;初始化:bool InitList(LinkList &L){....带头节点L=(LNode*)malloc(sizeof(LNode));L-next=null;}留神:LNode *p示意指向一个节点的指针,用来示意一个节点;LinkList p更多的示意一个单链表的头节点,通常用来定义一个单链表。判断空表:带头节点:L-next==null;不带:L==null; 6.单链表的建设 头插法(单链表逆置)LinkList List_HeadInsert(LinkList &L){LNode *s;int x;InitList(L);scanf("%d",&x);while(x != 999){ s=(LNode *)malloc(sizeof(LNode)); s-data=x; s-next=L-next; L-next=s; scanf("%d",&x);}return L;}尾插法LinkList List_TailInsert(LinkList &L){LNode *s,*r=L;int x;InitList(L);scanf("%d",&x);while(x != 999){ s=(LNode *)malloc(sizeof(LNode)); s-data=x; r-next=s; r=s; scanf("%d",&x);}r-next=null;return L;}7.双链表的建设 ...

May 16, 2022 · 1 min · jiezi

关于数据结构:数据结构-AVL-树

简介基本概念AVL 树是最早被创造的自均衡的二叉查找树,在 AVL 树中,任意结点的两个子树的高度最大差异为 1,所以它也被称为高度均衡树,其本质依然是一颗二叉查找树。 联合二叉查找树,AVL 树具备以下个性: 若任意结点的左子树不为空,则左子树上所有结点的值均小于它的根结点的值若任意结点的右子树不为空,则右子树上所有结点的值均大于或等于它的根结点的值任意结点的左、右子树也别离为二叉查找树任意结点的子树的高度差都小于等于 1上述的前三项都是二叉查找树的个性,第四个是 AVL 树自均衡的个性。 实现原理为了保障二叉树的均衡,AVL 树引入了监督机制,就是在树的某一部分的不均衡度超过一个阈值后触发相应的均衡操作,保障树的均衡度在能够承受的范畴内。 既然引入了监督机制,则必然须要一个监督指标,以此来判断是否须要进行均衡操作。这个监督指标被称为均衡因子(Balance Factor)。定义如下: 某个结点的右子树的高度减去左子树的高度失去的差值。基于均衡因子,就能够这样定义 AVL 树: 所有结点的均衡因子的绝对值都不超过 1 的二叉查找树。为了计算均衡因子,天然须要在结点中引入高度这一属性。结点的高度为以下定义: 左右子树的高度的最大值。class AVLNode { AVLNode left; // 左子树 AVLNode right; // 右子树 int height; // 以后结点的高度 int value; // 以后结点的值}自均衡自均衡是指在对均衡二叉树执行插入或删除结点操作后,可能会导致树中某个结点的均衡因子绝对值超过 1,即均衡二叉树变得“不均衡”,为了复原该结点左右子树的均衡,此时须要对结点执行旋转操作。 二叉树的均衡化有两大根底操作: 左旋和右旋。左旋,即是逆时针旋转;右旋,即是顺时针旋转。 这两种操作都是从失去平衡的最小子树根结点开始的(即离插入结点最近且均衡因子超过 1 的祖结点)。 左旋 所谓左旋操作,就是把上图中的 B 结点和 A 结点进行所谓“父子替换”。在仅有这三个结点时候,是非常简略的。然而当 B 结点处存在左孩子时,事件就变得有点简单了。 通常的操作是:结点 B 摈弃左孩子,将之与旋转后的结点 A 相连,成为结点 A 的右孩子。 右旋 所谓右旋操作,就是把上图中的 B 结点和 C 结点进行所谓“父子替换”。在仅有这三个结点时候,也是是非常简略的。然而当 B 结点处存在右孩子时,事件就变得有点简单了。 ...

May 6, 2022 · 1 min · jiezi

关于数据结构:数据结构-跳表

简介有序的数组能够应用二分查找的办法疾速检索一个数据,然而链表没有方法应用二分查找。 对于一个单向链表来说,即便链表中存储的是有序的数据,但如果想要从中查找某个数据时,也只能从头到尾遍历链表,其工夫复杂度是 $O(n)$。 为了进步链表的查问效率,使其反对相似“二分查找”的办法,对链表进行多层次扩大,这样的数据结构就是跳表。跳表对标的是均衡树,是一种晋升链表插入、删除、搜寻效率的数据结构。 首先,跳表处理的是有序的链表,个别应用双向链表更加不便。 而后,每两个结点提取一个结点到上一级,提取的这一层被称作为索引层。 这时候,当想要查找 19 这个数字,能够先从索引层开始查找;当达到 17 时,发现下一个结点存储 21 这个数字,则能够确定,想要查找的 19 必定是在 17 到 21 之间;这时候能够转到下一层(原始链表)中查找,疾速从 17 开始检索,很快就能够查找出 19 这个数字。 退出一层索引之后,查找一个结点须要遍历的结点个数缩小了,也就是查找效率进步了。实际上,个别会新增多层索引,领有多层索引的跳表,查找一个结点须要遍历的结点个数将再次缩小。 这种链表加多层索引的构造,就是跳表。 效率剖析为了不便对跳表的效率做剖析,在这里设定一个常见的跳表类型。 假如每两个结点会抽出一个结点作为上一级索引的结点,那第一级的索引个数大概就是 $\frac{n}{2}$,第二级的索引个数大概就是 $\frac{n}{4}$,以此类推,第 k 个索引的结点个数是第 k-1 个索引的结点个数的 $\frac{1}{2}$,那么,第 k 个索引的结点个数就是 $\frac{n}{2^k}$。 工夫复杂度假如索引总共有 h 级,最高级的索引有 2 个结点,应用公式 $\frac{n}{2^h} = 2$ 进行反推,能够计算得出 $h = \log_2 n - 1$,如果是蕴含原始链表那一级,跳表的高度就是 $\log_2 n$ 级。 如果想要从跳表中查问某个数据时,每层都会遍历 m 个结点,那么,在跳表中查问一个数据的工夫复杂度就是 $O(m \log n)$。 从下面图中可知,在每一级索引中最多只须要遍历 3 个结点,其实就能够看作是 m = 3。 ...

April 25, 2022 · 2 min · jiezi

关于数据结构:数据结构-散列表

简介散列表也被称为哈希表,其具体实现就是应用到了散列技术。 散列技术是在记录的存储地位和它的关键字之间建设一个确定的对应关系,使得每个关键字对应一个存储地位。 关键字散列表个别都是用在查找的时候,所以,须要存储的原始数据被称作是查找的关键字。 哈希算法散列技术的关键在于将关键字与存储地位建设对应关系,这种建设映射关系的规定被称作哈希算法。 个别的哈希算法都是将任意长度的二进制值串映射为固定长度的二进制值串,而这个固定长度的二进制值串能够匹配上存储地位。 哈希值通过原始数据映射之后失去的二进制值串就是哈希值。 装载因子哈希抵触是十分常见的,因而,在此基础上减少了一个装载因子的概念,用来示意散列表中空位的多少,其计算公式为: 散列表的装载因子 = 填入表中的元素个数 / 散列表的长度装载因子越大,阐明闲暇地位越少,抵触越多,散列表的性能会降落。 构造方法散列表利用了数组反对应用下标疾速随机拜访数据的个性,散列表是由数组演变而来,能够了解为散列表其实就是数组的一种扩大。能够说,如果没有数组,就没有散列表。 散列表通常是基于数组实现的,而相比数组,它还存在创立、删除、查找都十分快的劣势,无论数据量多大,这些操作都靠近于 $O(1)$ 的工夫复杂度;但绝对也存在一些劣势,散列表中的数据没有程序,而且不能在同一存储地址存储反复的哈希值。 不论是什么编程语言,实现一个散列表数据结构的过程都能够分为三个步骤: 实现一个哈希算法;正当解决哈希抵触;实现其余操作方法。哈希算法一个好的哈希算法对于散列表是十分重要的,是散列表具备比拟优的工夫复杂度和空间复杂度的基本。而好的哈希算法须要具备两个准则:疾速计算和均匀分布。 特点具体来说,实现一个好的哈希算法会有以下特点: 哈希算法的方向是单向的,从哈希值不能反向推导出原始数据哈希算法的转换是敏感的,原始数据任何一点变动,失去的哈希值都会大不相同哈希抵触的概率要极小的,不同的原始数据,通过哈希算法失去的哈希值雷同的概率很小哈希算法的执行效率是高效的,即便是很长的文本也能疾速计算出哈希值间接定址法间接定址法就是取关键字的某个线性函数值作为哈希值,或者应用这个线性函数值通过特定的算法计算出哈希值。 例如,存储中国每一年的人数时,能够将年份作为计算哈希值的线性函数值,能够设想失去,年份是间断的,并且没有抵触,非常适合用来计算哈希值。 间接定址法的长处就是简略、散布平均、不会呈现抵触。其毛病也很显著,就是要求选定的线性函数值散布平均、不会呈现抵触。 换一种角度看,间接定址法是有限度的,这个哈希算法并不罕用。 数字分析法数字分析法的外围就是从原始数据中选取一个辨识度较大的数据作为计算哈希算法的关键字,比拟常见的就是用于解决关键字位数比拟大的数字。 数字分析法有个比拟常见的场景,国内的手机号都是 11 位的数字,而且也常见到将两头 4 位数字暗藏的做法,这个其实就是数字分析法的一种用法。因为 11 位手机号的前 3 位是运营商接入号,两头 4 位是归属地辨认号,后 4 位是用户号,当手机号码都在同一个地区时,只须要应用前 3 位和后 4 位就能够作为哈希算法的关键字。 数字分析法也是一种绝对简略的哈希算法,但个别针对较大数字,如果这些较大数字散布平均的话,能够选用这个办法。 平方取中法平方取中法的规定如其名。假如关键字是 1234,计算失去它的平方就是 1522756,再抽取两头三位 227 能够作为哈希值;假如关键字是 4321,计算失去它的平方就是 18671041,抽取两头三位 671 或 710 作为哈希值即可。 平方取中法比拟适宜不晓得关键字的散布、而位数又不是很大的状况。 折叠法折叠法次要是将关键字从左到右宰割成位数相等的几局部,而后将这几局部叠加求和,并依据散列表的长度,取后几位作为哈希值。 比方,对 9876543210 应用折叠法,假如散列表表长为三位,将 9876543210 分成 987|654|321|0 这样四组,而后对这四组应用 987+654+321+0 叠加求和计算失去 1962,再取 1962 的后三位作为哈希值。 折叠法的利用场景能够和平方取中法互补,适宜用在不晓得关键字的散布,而位数较多的状况。 除留取余法除留取余法是最罕用的哈希算法之一,理论就是对关键字求模取余数,这个余数就是哈希值。然而这种办法失去的哈希值非常容易抵触,这个办法的要害就是要抉择适合的除数。 ...

April 22, 2022 · 2 min · jiezi

关于数据结构:数据结构-堆

简介概念堆是一种比拟非凡的数据结构,它用数组实现的二叉树,并且总是满足以下性质: 堆总是一棵齐全二叉树堆中某个结点总是不大于或不小于其父结点的的值属性堆分为两种:根结点最大的堆叫作最大堆或大根堆;根结点最小的堆叫作最小堆或小根堆。 堆属性十分有用,其使得堆经常被当做优先队列应用,因为能够疾速地拜访到“最重要”的元素。 堆和二叉搜寻树的区别堆并不能取代二叉搜寻树,它们之间有相似之处也有一些不同。两者的次要区别如下: 属性堆二叉搜寻树结点的程序在最小堆中父结点必须比子结点小,在最大堆中父结点必须比子结点大,变动是从上到下左子结点比父结点小,父结点比右子结点小,变动是从左到右内存占用应用数组作为底层存储构造,占用内存空间较小应用链表作为底层存储构造,占用内存空间较大均衡不须要整棵树有序必须是均衡的,总体上是有序的搜寻搜寻很慢搜寻很快堆的实现存储实现一个堆,首先是波及到如何存储一个堆。 依据堆总是一棵齐全二叉树的性质,以及齐全二叉树比拟适宜用数组来存储的概念,能够晓得用数组存储堆是比拟好的抉择。 从上图能够看到,数组中下标为 i 的结点的左子结点,就是下标为 2i 的结点,右子结点就是下标为 2i + 1 的结点,父结点就是下标为 i/2 的结点。 堆化往堆中插入或者删除一个元素后,重要的是须要持续满足堆的两个个性,而这个从新满足堆个性的过程称为堆化。 堆化实际上有两种:从下往上、从上往下。 插入元素插入元素时波及的是从下往上的堆化办法。 往堆中插入一个元素其实就是往底层数组的开端增加元素,上面是示例图: 从下往上堆化的过程比较简单,实际上就是将插入的元素与父结点进行比拟,呈现不合乎个性的状况就调换两个结点,始终反复这个过程,直至父子结点之间满足堆的个性。 删除元素从堆的个性能够看出,堆顶元素存储的就是堆中数据的最大值或最小值。 当删除堆顶元素的时候,为放弃堆的个性,则会波及到从上往下的堆化办法。 从上往下堆化不是间接从堆顶元素开始与子结点进行调换,而是先将数组中的最初一个元素移到被删除结点地位(为了满足齐全二叉树的个性),而后利用同样的父子结点比对办法。 通常,对于大根堆会比拟较大的子结点,对于小根堆会比拟较小的子结点,呈现不合乎个性的状况就调换两个结点,始终反复这个过程,直至父子结点之间满足堆的个性。 这种办法堆化之后的后果,必定满足齐全二叉树的个性。 复杂度一个蕴含 n 个节点的齐全二叉树,树的高度不会超过 $\log_2n$。 堆化的过程是顺着节点所在门路比拟替换的,所以堆化的工夫复杂度跟树的高度成正比,也就是 $O(\log n)$。 插入数据和删除堆顶元素的次要逻辑就是堆化,所以往堆中插入一个元素和删除堆顶元素的工夫复杂度都是 $O(\log n)$。

April 20, 2022 · 1 min · jiezi

关于数据结构:数据结构-二叉树

树的定义 树是一种抽象数据类型,用来模仿具备树状构造性质的数据汇合。树的专业术语比拟多,须要理解一下: 树的结点:蕴含一个数据元素及若干指向子树分支的信息结点的度:一个结点含有的子树的数目称为该结点的度树的度:树中最大的结点度称为树的度叶子结点:也称终端结点,结点度为零的结点分支结点:也称非终端结点,结点度不为零的结点子结点:一个结点含有的子树的根结点称为该结点的子结点父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点兄弟结点:具备雷同父结点的结点互称为兄弟结点堂兄弟结点:父结点在同一层的结点互为堂兄弟结点结点的先人:从根到该结点所经分支上的所有结点子孙:以某结点为根的子树中任一结点都称为该结点的子孙结点的档次:从根开始定义起,根为第 1 层,根的子结点为第 2 层,以此类推深度:对于任意结点 n,n 的深度为从根到 n 的惟一门路长,根的深度为 0高度:对于任意结点 n,n 的高度为从 n 到叶子结点的最长门路长,所有叶子结点的高度为 0森林:由 m(m>=0) 棵互不相交的树组成的汇合称为森林二叉树树的构造多种多样,不过最罕用的还是二叉树。 顾名思义,二叉是指每个结点最多只有两个子结点,别离称为左子结点和右子结点。然而,二叉树并不要求所有结点必须领有两个子结点,有的结点只有左子结点,有的结点只有右子结点。 满二叉树 如图 a 所示,除叶子结点以外,其余的结点每个都有 2 个子结点,这种二叉树被称为满二叉树。 齐全二叉树如图 b 所示,除最初一层外,每一层的结点数均达到最大值,而且最初一层的叶子结点都靠左排列,只短少左边的若干结点,这种二叉树被称为齐全二叉树。 能够看得出,满二叉树是一种非凡的齐全二叉树。 二叉查找树 二叉查找树是一种非凡的二叉树,罕用作搜寻应用,也被称为二叉搜寻树、二叉排序树。 它有可能是一棵空树,也可能是具备以下性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值若它的右子树不空,则右子树上所有结点的值均大于等于它的根结点的值它的左、右子树也别离为二叉查找树二叉查找树是一种经典的数据结构,它既具备链表疾速插入、删除的特点,又具备数组疾速查找的劣势。 存储构造链式存储应用链表存储树的构造是一种比较简单、直观的办法。 二叉树中每个结点最多只有两个子结点,因而,能够给结点设计一个数据域和两个指针域,这两个指针域别离指向左子结点和右子结点。 这种状况下,应用链表作为存储形式,只有拎住根结点,就能够通过左右子结点的指针,把整棵树都串起来。 这种形式比拟罕用,大部分二叉树代码都是通过这种形式实现的。 顺序存储二叉树的顺序存储构造是基于数组实现的,用一维数组存储二叉树中的结点,并且数组的下标可能体现出二叉树结点之间的逻辑关系。 在这个存储二叉树结点的数组中,为了使得后续的结点逻辑关系易于了解,下标为 0 的存储地位是不应用的。个别是把根结点存储在 i = 1 的地位上,它的左子结点存储在 2i = 2 的地位上、右子结点存储在 2i + 1 = 3 的地位上。以此类推,左子结点的左子结点存储在 2i = 4 的地位,它的右子结点存储在 2i + 1 = 5 的地位。 总结二叉树结点在数组中的逻辑关系:如果结点 x 存储在数组中下标为 i 的地位,则结点 x 的左子结点存储在数组中下标为 2i 的地位,右子结点存储在数组中下标为 2i+1 的地位。 ...

April 19, 2022 · 1 min · jiezi

关于数据结构:数据结构-队列

简介基本概念队列这个概念很好了解,队列常做的比喻就是排队买票,先到的先买,后到的只能在队尾等着。 队列是一种受限的线性表,非凡之处在于它只容许在一端插入,在另一端删除,因而是先进入队列的元素能最先从队列中删除,故队列又被称为先进先出表。 操作队列与栈十分类似,反对的操作也很无限。以下是队列的一些概念: 进行插入操作的端称为队尾进行删除操作的端称为队头队列中没有元素时,称为空队列向队列中插入一个元素称为入队删除队列中的一个元素称为出队队列的实现队列能够应用数组或链表作为物理存储构造,应用数组实现的队列称为程序队列,应用链表实现的队列称为链式队列。 程序队列 应用数组实现的程序队列比实现残缺性能的数组更加简略,不须要实现往数组中插入元素和删除数组内元素的性能。 首先,须要申请一个大小为 n 的数组;而后,创立一个队头变量和队尾变量;当入队时,须要将队尾变量后移一位;当出队时,也须要将队头变量后移一位。 当数组无限大的时候,这样的实现形式没有任何问题。但数组向来是限度大小的,而且出队之后,数组的前半部分曾经没有数据存储了,十分节约空间。 对于这种状况,通常采纳搬移数据的方法,当队尾变量标识曾经达到 n 的下标时,则做一次数据搬移,并且将队头变量标识和队尾变量标识指向新的下标。 以这种实现思路,入队的均摊工夫复杂度为 $O(1)$,出队的工夫复杂度始终都是 $O(1)$。 链式队列 应用链表实现的链式队列比应用数组实现的程序队列更加不便,缩小了搬移数据的这一步。 链表不须要提前申请内存空间,也不须要放心内存空间不够的问题,只须要创立好队头变量标识和队尾变量标识即可,链式队列是常见的队列实现形式。 如果是采纳尾插法实现的链表,能够将链表的哨兵结点的指向作为队头变量标识,这样只须要新增一个队尾变量标识即可。当新的元素入队时,将这个元素链接到尾结点,而后批改队尾变量标识的指向;须要出队时,则做删除头结点的操作,批改哨兵结点的指向。 以链表实现的队列,无论是入队还是出队,工夫复杂度都能够达到 $O(1)$。 循环队列程序队列在入队时会有搬移数据的状况,存在肯定的性能损耗,循环队列则是在这一方面做了局部优化,缩小这一步操作。 如果把数组看作一条直线,就能够把循环队列看作一个环,通过做成环的形式,胜利防止了数据搬移的操作。 如上图所示,通常会有一个 front 指针指向队头所在地址,有一个 rear 指针指向队尾的下一个地址,其起因次要是:若 front 和 rear 别离指向队头和队尾,无奈判断队空和存在一个元素的状态。 若采纳 rear 指针指向队尾的下一个地址的形式,则能够应用 front 和 rear 指向同一个地址来判断队空;同时,因为循环队列是逻辑上循环,通常应用求余运算判断队满,并且为了防止队空呈现的 rear % n = front 和队满呈现的 (rear + n) % n = front 造成过错,因而总是留出一个空位,应用 (rear + 1) % n = front 判断队满。 双端队列双端队列是一种具备队列和栈的性质的数据结构,即常说的 deque(double-ended queue),是一种限定插入和删除操作在表的两端进行的线性表。 现实生活中双端队列的例子:如果在电影院买票,一个刚刚买完票的人想征询一下简略信息,就能够间接回到队伍的头部,如果在队尾排队的人不想看电影了,就能够从队尾间接来到队伍。 只管双端队列看起来仿佛比栈和队列更灵便,但实际上在应用程序中远不迭栈和队列有用。 ...

April 18, 2022 · 1 min · jiezi

关于数据结构:栈让编译器识别四则运算表达式

简介基本概念从栈的操作个性来看,栈是一种操作受限的线性表,只容许在一端插入和删除数据。 事实上,从性能上来说,数组和链表齐全能够代替栈的应用。然而,从某种角度来说,数组和链表裸露太多的操作接口,实现起来比较复杂,也很容易出错。 因而,当数据汇合只波及在一端插入和删除数据,并且满足先进后出、后进先出的个性,能够首选“栈”这种数据结构。 操作栈是容许在同一端进行插入和删除操作的非凡线性表。以下是栈的一些概念: 容许进行插入和删除操作的一端称为栈顶,栈顶会依据插入、删除操作进行浮动不容许进行插入和删除操作的一端称为栈底,栈底是固定不变的栈中元素个数为零时称为空栈往栈中插入元素称作进栈删除栈顶的元素称作出栈栈的实现实际上,栈既能够用数组实现,也能够用链表实现。应用数组实现的栈被称为程序栈,应用链表实现的栈被称为链式栈。 程序栈 应用数组作为栈存储数据的物理存储构造,须要先申请一个大小为 n 的数组。 将数组尾部作为栈顶,应用一个变量示意数组存储的元素个数,这样入栈、出栈的操作都能达到 $O(1)$ 的工夫复杂度。 基于数组实现的栈存在一个限度,即数组在申明之后是固定大小的,当栈满的时候,则无奈再次往数组中增加数据。这样就波及到对数组进行动静扩容,当数组空间不够的时候,须要从新申请一块更大的内存,将原来数组中数据通通拷贝过来。 事实上,反对动静扩容的程序栈,在理论开发中并不常见。个别应用到程序栈这种数据结构的状况,都是一些规模比拟小的数据,有时候晓得数据的最大规模时也能够防止爆栈。 链式栈 对于数据规模比拟大的状况,应用链表实现栈则会更加不便。这时候,不须要提前申请内存空间,而是创立一个哨兵结点指向头结点。 链式栈个别会应用头插法创立链表。将头结点作为栈顶,每次入栈都当作链表在头结点插入元素,每次出栈都当作链表删除头结点,这些操作都只须要解决好哨兵结点的指向即可。链式栈个别都能达到 $O(1)$ 的工夫复杂度。 基于链表实现的栈不须要像数组一样要动静扩容,链表是天生反对动静扩容的。 利用场景栈作为一个比拟根底的数据结构,利用场景还是蛮多的。 函数调用栈函数调用栈是一个十分经典的利用场景。 操作系统给每个线程调配了一块独立的内存空间,这块内存被组织成“栈”这种构造,用来存储函数调用时的长期变量。每进入一个函数,就会将长期变量作为一个栈帧入栈,当被调用函数执行实现,返回到下层函数之后,再将这个函数对应的栈帧出栈。 直到函数栈为空,则示意最外层的函数曾经执行结束。 逆波兰表达式编译器还会利用栈来实现表达式求值,这部分也有十分成熟的四则运算算法——逆波兰表达式。 通常的四则运算表达式写成 (1 + 2) x (3 + 4),加减乘除等运算符写在两头,因而也被称作“中断表达式”;逆波兰表达式的写法是 1 2 + 3 4 + x,运算符被写在前面,因此也被称作“后缀表达式”;除此之外,还有波兰表达式,其写法是 x + 1 2 + 3 4,也被称作“前缀表达式”。 如果将表达式画出一棵语法树,就能以树的概念来直观了解前缀、中断、后缀的含意,前缀表达式对应树的前序遍历,中断表达式对应树的中序遍历,后缀表达式对应树的后序遍历,如下图所示: 首先,须要先将中断表达式转换成逆波兰表达式,波及到操作数栈和运算符栈: 从左向右遍历中断表达式;若读取的是操作数,将操作数存入到操作数栈中;若读取的是运算符,还须要依据运算符类型进一步判断: 如果是 ( 运算符,则间接存入运算符栈中;如果是 ) 运算符,则输入运算符栈中的运算符到操作数栈,直到遇到 ( 运算符,在这里放弃 ( 和 ) 运算符;如果是非括号运算符,还须要与运算符栈栈顶的运算符比拟优先级: 如果运算符栈栈顶的运算符是括号,则间接存入运算符栈中;如果此运算符比运算符栈栈顶的运算符优先级更高时,则间接存入运算符栈中;如果此运算符比运算符栈栈顶的运算符优先级更低或相等时,则输入栈顶运算符到操作数栈,而后将以后运算符压入操作符栈;当表达式读取完后,将运算符栈顺次取出到操作数栈中,直到运算符栈为空。而后对失去的逆波兰表达式计算,失去表达式的后果: 从左向右遍历逆波兰表达式;若读取的是操作数,将操作数存入栈中;若读取的是运算符,则对栈顶的两个操作数执行该运算,而后将运算后果存入栈中;反复上述的步骤 1~3,最终栈中即为后果值。

April 18, 2022 · 1 min · jiezi

关于数据结构:链表将内存地址链接成一个整体

简介 链表是一种和数组不同的线性表构造,数组的存储应用了一组间断的内存空间,而链表通过链接的形式将零散的内存空间串联起来应用。 链表的定义须要留神两点:链表依然是一个线性表构造;链表不应用间断的内存空间进行存储。 因而,链表克服了数组须要事后晓得数据大小的毛病,并且能充沛利用计算机内存空间,实现灵便的内存动静治理。 但链表也失去了疾速随机存取的长处,同时因为减少了结点的指针域,空间开销较大。 基本概念链表是通过指针将零散的内存块串联在一起的,这里的内存块被称为链表的结点。 结点除了存储数据之外,还会存储下一个结点的地址,这个记录下一个结点地址的指针被称为后继指针。在双向链表中,还会存储上一个结点的地址,这个记录上一个结点地址的指针被称为前驱指针。 链表中存在两个比拟非凡的结点,别离是第一个结点和最初一个结点。因而也给这两个结点取了名称,第一个结点被称为头结点,最初一个结点被称为尾结点。 随机存储把链表和数组作比拟,数组具备疾速随机存取的长处,而链表是随机存取效率非常低的数据结构。 如果通过下标去拜访链表中的结点,是不能应用寻址公式的,只能通过头结点作为入口,依据指针一个结点一个结点地顺次遍历,直到找到对应的结点。 综合计算下来,链表做随机拜访的工夫复杂度为 $O(n)$,效率比数组低得多。 插入、删除尽管链表没有了数组随机存取的长处,但在插入、删除结点的时候,效率比数组高很多。 假如,要在链表中插入一个结点,在晓得插入地位的前后两个相邻结点的前提下,只需将新结点的后继指针指向下一个结点,而后将上一个结点的后继指针指向这个新结点,即可实现插入结点的操作。删除结点也是相似的操作,十分不便。 然而,插入、删除结点时效率高的前提是晓得操作地位的相邻结点,否则仍须要从头结点开始寻找到对应地位,这样的效率会非常低。 形形色色的链表链表有很多不同的类型,在下面说的都是最简略的单向链表,简单一点的还有双向链表、循环链表。 单向链表单向链表是最简略的链表构造,它蕴含两个域,一个信息域和一个指针域。 信息域存储理论的数据,指针域存储此结点的下一个结点地位。 在理论编码中,为了不便,头结点的信息域是空的,其指针域存储理论的第一个结点所在位置,尾结点的指针域个别会是 NULL 地址。 双向链表双向链表是在单向链表的根底上多减少了一个指针域,这个新减少的指针域会存储上一个结点所在位置。 也就是说,在双向链表中,除头结点的任意结点都能够拜访到上一个结点,因而称为双向链表。 双端链表双端链表与双向链表是齐全不同的两个概念。 双端链表是在单向链表的根底上,减少了尾结点的援用。领有这个援用的双端链表在尾部插入结点时特地不便,因而常被用作实现链式队列。 尽管双端链表能够不便地在尾部插入结点,但因为无奈快捷地获取倒数第二个结点,因而依然不能不便地删除尾结点,若须要此性能还是要靠双向链表实现。 循环链表循环链表是一个头结点和尾结点连贯在一起的非凡链表,通过单向链表或双向链表都可能实现。 循环链表的长处就是从链表的尾结点到头结点十分不便,也不便解决具备环形构造特点的数据,如约瑟夫问题。 块状链表快状链表自身是一个链表,然而链式存储的并不是个别的数据,而是由一些数据组成的程序表结点,这些结点也被称作块。 块状链表通过应用可变的程序表的长度和非凡的插入、删除的形式,能够达到 $O(\sqrt{N})$ 的工夫复杂度。 块状链表的另一个特点是绝对一般链表来说更节俭内存,因为不必保留指向每一个数据结点的指针。 应用上的问题数组和链表如何抉择仅个性和效率而言,数组领有疾速随机拜访的个性,链表能够疾速插入、删除。常常利用下标拜访元素能够应用数组,常常插入、删除元素能够应用链表。 然而,不能仅仅只用复杂度剖析决定应用哪种数据结构。数组简略易用,而且可能借助 CPU 缓存机制预读数组中的数据;而链表在内存中不是间断存储的,对 CPU 缓存不敌对,没方法无效预读。 数组的毛病是数据大小固定,而且一经申明要占用整块间断内存空间,如果申明的数组过大,零碎可能没有足够的间断内存空间调配给它。即便是在 Java 中应用能够动静扩容的 ArrayList 类型,也存在扩容耗时的问题。而链表自身没有大小的限度,天生反对动静扩容。 实现链表的技巧编写一个正确的链表是比拟难的,然而其中也有一些技巧: 了解指针或援用的含意。将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,这个内存地址存储这个变量,通过指针能找到这个变量警觉指针失落。插入结点时,先将插入结点的后继指针指向下一个结点,再把前一个结点的指针指向插入结点,这样才不会失落指针防止内存透露。删除结点时,要记得手动开释内存空间利用哨兵简化实现难度。在理论开发当中,如果向空链表中插入第一个结点的时候,还须要判断链表中是否曾经存在头结点,嵌入代码比较严重;然而,如果减少一个哨兵结点,哨兵结点的后继指针指向头结点,则能够省略这一步操作重点注意边界条件解决。当链表为空的时候,代码是否能失常工作?当链表只有一个结点的时候,代码是否能失常工作等等举例画图,辅助思考。链表的指针指向会比较复杂,这种状况能够通过举例画图的方法将各种状况列举进去,这样思路会更加清晰多写多练,游刃有余。写链表代码是十分考验逻辑思维能力的,多多尝试练习能够进步逻辑思维能力

April 18, 2022 · 1 min · jiezi

关于数据结构:数据结构与算法ALL-IN

对数据结构和算法的了解数据结构一维构造数组链表栈队列双端队列哈希表二维构造树二叉搜寻树AVL树红黑树堆B/B+树图其余构造算法leetcode剑指offer其余总结

April 12, 2022 · 1 min · jiezi

关于数据结构:SkipList跳表的原理以及Go语言实现

跳表的原理跳表(Skiplist)是一个非凡的链表,相比个别的链表,有更高的查找效率,可比较二叉查找树。跳表的查找、插入、删除工夫复杂度都是O(logN)。许多出名的开源软件中的数据结构采纳了跳表这种数据结构,例如: Redis中的有序汇合zsetLevelDB、HBase中MemtableApacheLucene中的TermDictionary、Posting List跳表数据结构是由William Pugh创造的,最早呈现于他在1990年发表的论文《Skip Lists: A Probabilistic Alternative to Balanced Trees》。跳表实质上是一个链表, 它其实是由有序链表倒退而来。跳表在链表之上做了一些优化,跳表在有序链表之上退出了若干层用于索引的有序链表。索引链表的结点来源于根底链表,不过只有局部结点会退出索引链表中,并且越高层的链表结点数越少。跳表查问从顶层链表开始查问,而后逐级开展,直到底层链表。这种查问形式与树结构十分相似,使得跳表的查问效率相近树结构。另外跳表应用概率平衡技术而不是应用强制性平衡,因而对于插入和删除结点比传统上的均衡树算法更为简洁高效。因而跳表适宜增删操作比拟频繁,并且对查问性能要求比拟高的场景。为了阐明跳变的原理以及特点,咱们先从有序链表说起。首先看看有序链的特点,并对有序链表的操作性能进行剖析。下图是一个有序链表的例子:有序链表是一个线性构造,不能像有序数组那样应用二分法查找数据(因为二分查找须要用到两头地位的节点,而链表不能随机拜访)。在有序链表中找某个数据,须要从头开始一一进行比拟,直到找到蕴含数据的那个节点,或者找到第一个比给定数据大的节点为止,工夫复杂度为O(n)。当向有序链表中插入数据的时候,也要经验同样的查找过程,从而确定插入地位,因而向链表中插入一个节点的工夫复杂度为也是O(n)。 接下来看看跳表是如何改良有序链表从而达到高性能的查问以及操作。首先为根底链表建设一层索引表,索引表只有根底链表结点的1/2。增加了索引表之后的数据结构如下图所示:索引表相当于根底链表的目录,查问时首先从索引表开始查找,当遇到比待查数据大的结点时,再从根底链表中查找。因为索引表的结点只有根底链表的1/2,因而须要比拟的结点大大减少,从而能够放慢查问速度。 利用同样的形式,咱们还为根底链表增加第二层索引表,第二层索引表结点的数量是第一层索引表的1/2,增加了第二层索引表之后的数据结构如下图所示:第二层索引表的结点的数量只有根底链表的1/4左右,查找时首先从第二层索引表开始查找,当遇到比待查数据大的结点时,再从第一层索引表开始查找,而后再从根底链表中查找数据。这种逐层开展的形式与树结构的查问十分相似,因而跳表的查问性能与树结构靠近,工夫的复杂度为O(logN)。 咱们晓得一个均衡的二叉树,在进行减少或删除结点后可能造成二叉树构造的不均衡,从而会升高二叉树的查问性能。依照下面的形式实现的跳表也会面临同样的问题,新增或删除结点后会毁坏链表之间的比例关系,从而造成跳表查问性能的升高。均衡树通过旋转操作来保持平衡,而旋转一方面减少了代码实现的复杂性,同时也升高了增删操作的性能。真正的跳表不会采纳示例中的形式来建设下层链表,而是采纳了一种概率平衡技术来创立下层链表, 并保障各层链表之间的比例关系。在为跳表减少一个结点时,会调用一个概率函数来计算一个结点的档次(level),例如若level=3,则结点除了呈现在根底链表外,还会呈现在第一层索引以及第二层索引链表中。结点档次的计算逻辑如下: 每个节点必定都在根底链表中。如果一个节点存在于第i层链表,那么它有第(i+1)层链表的概率为p。节点最大的层数不容许超过一个最大值。跳表每一个节点的层数是随机的,而且新插入一个节点不会影响其它节点的层数。因而插入操作只须要批改插入节点前后的指针,而不须要对很多节点都进行调整,这就升高了插入操作的复杂度。实际上这是跳表的一个很重要的个性,这让跳表在增删操作的性能上显著优于均衡树的计划。 用Go语言实现跳表接下来咱们给出跳表的Go语言的实现,目前代码曾经上传到github中,下载地址 定义首先给出链接结点的定义: type SkipNodeInt struct { key int64 value interface{} next []*SkipNodeInt}其中: key用于示意一个结点,查问是应用key来查问一个结点。Val是interface{}类型,能够示意任何类型的数据。next是各层的后向结点指针数组,数组的长度为层高:level接下来是链表的构造定义: type SkipListInt struct { SkipNodeInt mutex sync.RWMutex update []*SkipNodeInt maxl int skip int level int length int32}其中: SkipNodeInt 用于代表列表的header。update:用于查找过程中的长期变量,定义唉这里为了进步拜访性能,缩小频繁创立数组对象。maxl:是最大层数,缺省为:32skip:层之间的比例,例如skip=4,则1/4的结点呈现在下层。level:跳表以后的层数length:跳表的结点数量跳表对象的创立: func NewSkipListInt(skip ...int) *SkipListInt { list := &SkipListInt{} list.maxl = 32 list.skip = 4 list.level = 0 list.length = 0 list.SkipNodeInt.next = make([]*SkipNodeInt, list.maxl) list.update = make([]*SkipNodeInt, list.maxl) if len(skip) == 1 && skip[0] > 1 { list.skip = skip[0] } return list}阐明: ...

April 2, 2022 · 2 min · jiezi

关于数据结构:归并排序

1. 根本思维什么是归并排序?? 归并排序是基于归并的排序。归并,是将两个或两个以上的有序表合成一个有序表。 假如待排序的数组有 n 个元素,将数组看成是 n 个有序的子数组,每个子数组只有一个元素。而后两两合并,失去每个子数组长度为2。而后持续两两合并,直到合并为长度为 n 的数组。 工夫复杂度 均匀复杂度是 O(nlogn),最好复杂度是 O(nlogn),最坏复杂度是 O(nlogn) 。 (图片来源于网络) 将原数组划分子数组的过程看成是一棵二叉树,那么数组划分到每个子数组中只有一个元素时的二叉树高度(递归深度)。最初一层是叶子节点,每个叶子节点是一个元素。那么,最初一层的节点个数是 n (数组长度)。假如二叉树深度是 k ,那么,从 2^(k-1) = n 失去,k 是 logn 。合并的过程是从子数组(其中只有一个元素)开始向上合并 每次排序须要遍历一次数组,工夫是 O(n) 。一共须要 logn 趟排序(二叉树的深度,递归深度)。所以工夫复杂度是 O(nlogn) 。 空间复杂度 空间复杂度是 O(n) 。 每次排序中都须要一个数组临时保留排序前的数组或者是排序后的后果数组,数组占用的空间是 n (数组的长度)。 应用场景 归并排序是稳固的排序。也就是,相等的元素的程序不会扭转。归并排序的速度仅次于疾速排序。个别用于总体是无序的,然而子序列是绝对有序的。 2. 代码实现(java)public void sort(int[] array) { mergeSort(array, 0, array.length);}public void mergeSort(int[] array, int low, int high) { if (low >= high) return; int mid = (low + high) / 2; // 将数组一分为二 mergeSort(array, 0, mid); // 对右边一半进行排序 mergeSort(array, mid, high); // 对左边一半进行排序 merge(array, low, mid, high); // 将两个有序子数组合并为一个有序数组}// 将数组左右两半合并为一个排好序的数组。左右两个子数组曾经是排好序的数组public void merge(int[] array, int low, int mid, int high) { // 将数组array复制到一个新数组copy中,将排序实现后的值写入原数组array int[] copy = Arrays.copyOf(array, array.length); // 复制 int i = low, j = mid, k = low; // 逐个比拟两个子数组,小者排在后面。直至其中一个数组全副写入后果数组中 while (i < mid && j < high) { if (copy[i] <= copy[j]) array[k++] = copy[i++]; // 相等的元素,绝对地位不扭转 else array[k++] = copy[j++]; } // 如果右边的子数组中残余的数据写入数组中 while (i < low) array[k++] = copy[i++]; // 如果左边的子数组中残余的数据写入数组中 while (j < high) array[k++] = copy[j++];}/**********************************************************************/// 将两个有序子数组合并为一个有序数组。第一个有序子数组是[low,mid),第二个有序子数组是[mid,high)public void merge(int[] array, int low, int mid, int high) { // 将数组array复制到一个新数组copy中,将排序实现后的值写入原数组array int[] res = new int[array.length]; // 复制 int i = low, j = mid, k = low; // 逐个比拟两个子数组,小者排在后面。直至其中一个数组全副写入后果数组中 while (i < mid && j < high) { if (array[i] <= array[j]) res[k++] = array[i++]; else res[k++] = array[j++]; } // 如果右边的子数组中残余的数据写入数组中 while (i < low) res[k++] = array[i++]; // 如果左边的子数组中残余的数据写入数组中 while (j < high) res[k++] = array[j++]; // 把后果数组res中[low,high)中的局部复制到原数组中,从而扭转原数组 for (i = low, k = low; i < high; i++, k++) { array[k] = res[i]; }}3. 代码阐明public void mergeSort(int[] array, int low, int high) { if (low >= high) return; int mid = (low + high) / 2; // 将数组一分为二 mergeSort(array, 0, mid); // 对右边一半进行排序 mergeSort(array, mid, high); // 对左边一半进行排序 merge(array, low, mid, high); // 将排好序的两局部合并为一个排好序的数组}mergeSort() 是一个递归函数。递归的过程就像是一个下楼梯的过程。在递归时,调用一次本人,就下一个台阶(调用),直到下到最初一个(终止条件),而后想上返回到第一个台阶(回溯)。 ...

January 28, 2022 · 2 min · jiezi

关于数据结构:快速排序

1. 根本思维疾速排序的根本思维是基于分治法的。 在待排序数组中选取一个元素作为基准,假如以第一个元素为基准。每趟排序都能确定基准的地位。找到基准在数组中最终寄存的地位。右边是所有比该元素小的值,左边是所有比该元素大的值。而后再别离对右边和左边两局部递归排序。直到每局部只有一个元素时进行排序。实用场景 只实用于 程序构造 (也就是数组),不能对链式构造排序。 工夫复杂度 均匀复杂度:O(nlogn),最好复杂度:O(nlogn),最坏复杂度:O(n^2)。 空间复杂度 空间复杂度 :O(logn) 2. 代码实现(java)public void sort(int[] array) { quickSort(array, 0, array.length - 1);}// 对区间是[low, high]的数组进行排序,一轮排完之后,确定了第一个元素的地位public void quickSort(int[] array, int low, int high) { if (low >= high) return; // 终止条件 // 找到第一个元素(阈值)在最终的排序数组中的地位 // 将区间 [0,len-1] 之间按阈值划分为 <阈值, >阈值 两局部 int pos = getPosition(array, low, high); quickSort(array, low, pos - 1); // 对 <阈值 的局部排序 quickSort(array, pos + 1, high); // 对 >阈值 的局部排序}public int getPosition(int[] array, int low, int high) { int tmp = array[low]; // 以第一个元素作为基准 while (low < high) { // 从后向前找到比基准小的数 while (low < high && array[high] >= tmp) high--; array[low] = array[high]; // 把array[high]移到后面 // 从前向后找到比基准大的数 while (low < high && array[low] <= tmp) low++; array[high] = array[low]; // 把array[low]移到前面 } array[low] = tmp; // 把基准放在最终的地位上 return low; // 返回最终的地位}3. 代码阐明1)quickSort() 中会有两个参数 low 和 high ?? ...

January 24, 2022 · 1 min · jiezi

关于数据结构:久远讲算法6队列先进先出的数据结构

你好,我是长远,上次咱们进行了对于栈的解说,咱们先来对常识进行回顾: 什么是栈栈是有序汇合,队列元素的削减和移除总是产生在同一端的,这一端咱们称之为栈顶,另一端称之为栈底,栈中的元素离底端越近,代表其在栈中的工夫越长,最新增加的元素将被最先移除。这种排序准则被称作 LIFO(last-in first-out),即后进先出。它提供了一种基于在汇合中的工夫来排序的形式。最近增加的元素凑近顶端,旧元素则凑近底端。 AI悦创·推出辅导班啦,包含「Python 语言辅导班、C++辅导班、算法/数据结构辅导班、少儿编程、pygame 游戏开发」,全部都是一对一教学:一对一辅导 + 一对一答疑 + 安排作业 + 我的项目实际等。QQ、微信在线,随时响应!V:Jiabcdefh栈的重要操作栈中最重要的两个操作是出栈和入栈,咱们在 python 中个别通过列表来实现栈的出入。 接下来咱们来进行队列的学习,队列和栈一样,是非常简单的数据结构,然而也是十分常见的数据结构。 什么是队列队列和栈一样,也是有序汇合,但它不同于栈的中央在于,队列中的元素是从一端进入,另一端进来。增加操作产生在“尾部”,移除操作则产生在“头部”。新元素从尾部进入队列,而后始终向前挪动到头部,直到成为下一个被移除的元素。最新增加的元素必须在队列的尾部期待,在队列中工夫最长的元素则排在最后面。这种排序准则被称作 FIFO(first-in first-out),即先进先出,也称先到先得。 队列字如其名,它的例子在生活中也是亘古未有的,咱们事实中的排队即为队列的利用。 日常生活中,咱们进电影院要排队,在超市结账要排队,好的队列只容许一头进,另一头出,不可能产生插队或者中途来到的状况。 队列的实现队列的实现分为队列的定义和操作,如前所述,队列是元素的有序汇合,增加操作产生在其尾部,移除操作则产生在头部。队列的操作程序是 先进先出(FIFO),它反对以下操作。 Queue() :创立一个空队列。它不须要参数,且会返回一个空队列。enqueue( item ) :在队列的尾部增加一个元素。它须要一个元素作为参数,不返回任何值。dequeue() :从队列的头部移除一个元素。它不须要参数,且会返回一个元素,并批改队 列的内容。isEmpty() :查看队列是否为空。它不须要参数,且会返回一个布尔值。size() : 返回队列中元素的数目。它不须要参数,且会返回一个整数。创立一个新类来实现队列抽象数据类型是非常正当的。像之前一样,咱们利用简洁弱小的列 表来实现队列。 既然要创立队列,咱们首先要确认队列的头尾,在这里咱们假如队列的尾部在列表的地位 0 处。 首先咱们对队列类进行定义,一个队列中最次要最外围的因素就是队列中的元素,而新生成一个队列时,这个队列中往往没有任何元素,因而咱们对队列的初始化定义为:队列中的元素为空,即援用的列表为空列表。 代码如下: class Queue: def __init__(self): self.items = []当一个队列生成当前,最常见的计算队列长度的操作是必不可少的,因而只须要计算引入列表的长度即可。 代码如下: def size(self): return len(self.items)既然能够计算长度,那么咱们也能够判断队列是否为空,通常咱们只需判断引入的列表是否为空列表即可判断队列是否为空了。 代码如下: def isEmpty(self): return self.items == []接下来就是咱们似曾相识,然而又用处十分宽泛的两种操作了,插入和删除,咱们在后面解说栈的时候进行了出栈和入栈的操作,而在队列中也有相似的操作,即入队和出队,而队列和栈最大的不同便是,入队和出队并不是在同一个中央执行的。增加操作产生在“尾部”,移除操作则产生在“头部”。 咱们在此设引入的列表的 0 号位为队列的尾部,传入要插入的元素 item ,默认将其插入到列表首位,即队列的入队操作,代码如下: def enqueue(self, item): self.items.insert(0,item)咱们在此设引入的列表的表尾为队列的头部,要进行出队操作,只需删除列表的最初一个元素即可。代码如下: def dequeue(self): return self.items.pop()整体的代码如下: ...

January 23, 2022 · 1 min · jiezi

关于数据结构:优先级队列-PriorityQueue

1. 优先级队列是什么??首先,优先级队列是一个队列,队列所有的性质,它也有。 其次,优先级队列每次取出的是优先级最高的元素。 优先级队列的外部是用堆来保护的。将优先级最高的排在后面。 2. 什么时候用这个队列呢??看完优先级队列的定义,如同看懂了,又如同没看懂。这队列,什么用它呢? 1)排序的对象和排序时比拟的对象 常见的排序办法(插入、快排等),排序的对象和比拟的对象是一样的,依据数自身的大小进行排序。 优先级队列能够对排序对象和比拟对象雷同的进行排序,也能够对 排序的对象和排序时比拟的对象不同 的进行排序。 排序的对象和排序时比拟的对象不同的一种状况是对 Map 排序。 在 Map 中,依照值 Value 对 Key 进行排序。这时,排序的对象是 Key ,比拟的对象是 Value 。 2)堆 优先级队列的外部是用堆来保护的。所以,也能够把优先级队列当做堆来用。须要用堆的时候,用优先级队列试试看。 3. 对一数组排序int[] arr = {3, 7, 5, 1, 8};PriorityQueue<Integer> queue = new PriorityQueue<>();for (int t : arr) { queue.offer(t); System.out.println("queue = " + queue);}输入后果: queue = [3]queue = [3, 7]queue = [3, 7, 5]queue = [1, 3, 5, 7]queue = [1, 3, 5, 7, 8]由 queue = [3, 7, 5] 能够看出,在排序时,queue 尽管也是依照整数的天然序来排的,然而不是依照递增的程序(队列中的元素并不是始终是递增排列),是按堆寄存的。 ...

January 22, 2022 · 2 min · jiezi

关于数据结构:堆排序-heapsort

1. 介绍1)什么是堆? 堆是一棵顺序存储的齐全二叉树。 每个结点的关键字都 小于或等于 其所有子结点的关键字,这样的堆称为小根堆。 每个结点的关键字都 大于或等于 其所有子结点的关键字,这样的堆称为大根堆。 2)什么是堆排序? 堆排序(Heapsort)是指利用堆这种数据结构来进行排序的抉择排序算法。沉积是一个近似齐全二叉树的构造,并同时满足子结点的值总是小于(或者大于)它的父节点。 小根堆 在排序算法中用于 升序排列;大根堆 在排序算法中用于 降序排序;3)留神: 堆排序 只实用于 程序构造。堆排序的均匀工夫复杂度是 O(nlongn),最好和最坏的工夫复杂度也是 O(nlongn)。空间复杂度是 O(n)堆排序是先建堆,依此输入堆顶元素(把堆顶元素换到数组前面)后调整堆。2. 算法步骤以 大根堆 为例, 创立一个大根堆替换堆顶和最初一个元素,剩下的元素从新调整为 大根堆3. 残缺代码堆排序的全副代码如下(代码应用 Java 编写): // 堆排序public void heapsort(int[] list) { build(list, list.length - 1); // 每调整一次堆,在数组最初就会多一个曾经排好序的数。堆中个数为1时,不解决 for (int i = list.length - 1; i > 0; i--) { swap(list, 0, i); // 排好序的数组中减少一个数 adjust(list, 0, i - 1); }}// 建堆,自下而上建堆。先保障上面的是堆,再把后面的数退出堆中,调整为堆。public int[] build(int[] list, int end) { // 从最初一个父节点开始 for (int i = (end - 1) / 2; i >= 0; i--) { adjust(list, i, end); } return list;}// 调整堆,从上自下调整。调整堆时,除了堆顶元素,其余的都满足堆的性质,所以从上向下把较大的元素移到下面public int[] adjust(int[] list, int start, int end) { int maxIndex, left, right; for (int i = start; i <= (end - 1) / 2; i++) { maxIndex = i; left = 2 * i + 1; right = 2 * i + 2; if (left <= end && list[left] > list[maxIndex]) maxIndex = left; if (right <= end && list[right] > list[maxIndex]) maxIndex = right; if (maxIndex != i) { int tmp = list[i]; list[i] = list[maxIndex]; list[maxIndex] = tmp; } } return list;}// 替换元素。void swap(int[] list, int a, int b) { int tmp = list[a]; list[a] = list[b]; list[b] = tmp;}4. 代码阐明以 无序序列 a = [1,3,4,5,2] 为例,通过堆排序失去的排好序的数组是 a = [1,2,3,4,5] (也能够降序排列)。从此能够看出,把堆排序看成一个处理过程,那堆排序的输出是一个无序数组(链表不能够),输入是一个有序数组。这里以大根堆为例 ...

January 22, 2022 · 2 min · jiezi

关于数据结构:二叉树和树的小问题

先大略说一下二叉树的根本内容。 1. 二叉树什么是二叉树? 二叉树是一种树形构造,每个结点最多两棵子树,而且子树有左右之分,秩序不能颠倒。左右子树也是一棵二叉树。 1.1 二叉树的存储二叉树能够用数组和链表存储。 1)顺序存储应用一组地址间断的存储单元顺次自上而下,自左而右的顺序存储二叉树上的结点。简略来说,就是用数组存储,存二叉树的程序是从上到下,从左到右。 顺序存储形式适宜齐全二叉树或满二叉树。因为数组的长度是固定的,而这种二叉树的空节点少,不会节约很多数组的地址空间。 // java 定义形式int[] array = new int[3];array[0] = 1;array[2] = 3;// 或者是int[] array = {1, 0, 3}; // 0 示意该处节点为空2)链式存储因为个别二叉树会有很多节点为空,如果应用顺序存储会导致很多空间节约掉。所以个别的二叉树应用链式存储比拟多。大多状况下,用到的也是链式存储,比方常常应用到的遍历。 链式存储是应用链表节点来存储二叉树中的每个节点。所有须要先定义链表的结点。 // java 定义形式class TreeNode { int val; TreeNode left, right; // 构造函数}1.2 二叉树的遍历二叉树的遍历办法:前序遍历、中序遍历、后序遍历,应该没有人不晓得。以下的代码是应用java实现 1)前序遍历void preOrder(TreeNode root) { // 终止条件 if (root == null) return; getValue(root); // 拜访该结点,做一些计算等 preOrder(root.left); // 遍历左结点点 preOrder(root.right); // 遍历右结点}2)中序遍历void preOrder(TreeNode root) { // 终止条件 if (root == null) return; preOrder(root.left); // 遍历左结点点 getValue(root); // 拜访该结点,做一些计算等 preOrder(root.right); // 遍历右结点}3)后序遍历void preOrder(TreeNode root) { // 终止条件 if (root == null) return; preOrder(root.left); // 遍历左结点点 preOrder(root.right); // 遍历右结点 getValue(root); // 拜访该结点,做一些计算等}4)例子这三种遍历办法的代码并不难,平时做题的时候也没有很大的问题。然而有一次在极度缓和下,我发现做题时居然分不清过后用的是哪种遍历,是前序还是中序还是回溯来着??是该用哪种写代码呢?? ...

January 19, 2022 · 2 min · jiezi

关于数据结构:无聊的周末用Java写个扫雷小游戏

周末无聊,用Java写了一个扫雷程序,说起来,这个应该是在学校的时候,写会比拟好玩,毕竟本人实现一个小游戏,还是比拟好玩的。说实话,扫雷程序外面外围的货色,只有点击的时候,去触发更新数据这一步。 Swing 是过期了,然而好玩不会过期,不喜勿喷 源码的地址:https://github.com/Damaer/Gam... 上面讲讲外面的设计: 数据结构设计视图和数据尽可能离开点击时候应用BFS扫描判断成功失败数据结构设计在这个程序外面,为了不便,应用了全局的数据类Data类来保护整个游戏的数据,间接设置为动态变量,也就是一次只能有一个游戏窗口运行,否则会有数据安全问题。(仅仅是为了不便) 有以下的数据(局部代码): public class Data { // 游戏状态 public static Status status = Status.LOADING; // 雷区大小 public static int size = 16; // 雷的数量 public static int numOfMine = 0; // 示意是否有雷,1:有,0没有 public static int[][] maps = null; // 是否被拜访 public static boolean[][] visited = null; // 周边雷的数量 public static int[][] nums = null; // 是否被标记 public static boolean[][] flags = null; // 上次被拜访的块坐标 public static Point lastVisitedPoint = null; // 艰难模式 private static DifficultModeEnum mode; ...}须要保护的数据如下: ...

January 11, 2022 · 2 min · jiezi

关于数据结构:20211220刷题笔记链表系列

链表有很多繁琐的中央。往往再一些细节上的解决,是很重要的,当然,链表我集体认为是最看重细节的中央,然而链表如果说可能一步步理分明细节,实际上是能够写进去的。 从一到最简略的反转链表开始做起剑指 Offer 24. 反转链表 毫无疑问,咱们须要三个指针去反转咱们再脑海中设想一条链表help->1->2->3->4->5->NULL这个时候 脑海中将1指向help,而后呢?咱们是不是须要提前保留2的值,因为咱们将1指向help的时候曾经没有2的援用了,所以再1指向help之前,咱们先保留下2的援用,始终反复这个过程,直到什么时候?是不是直到最初一个节点无奈指向前一个节点的时候,很显然,断定条件就是cur!=null ListNode pre = new ListNode(-1),help = pre,pre.next=head,cur = head,next;while(cur!=null){ next = cur.next; cur.next = pre; pre= cur; cur = next;}return help.next;这是一种办法。还有没有其余的办法?help->1->2->3->4->5->NULL同样是这条链表。如果咱们将节点2摘下来,插入help->1之间 是不是实现了部分的反转,始终反复这个过程,是不是就是实现了链表的反转?当初想,如何实现这个过程? ListNode pre = new ListNode(-1),next;pre.next=head;while(head.next!=null){ next = head.next; head.next = next.next; next.next = pre.next; pre.next = next;}return pre.next;进阶一点呢?25. K 个一组翻转链表区间内反转,这里只将第二种反转,反转完了之后是不是以后区间的head节点成为下一个区间的pre节点?下一个cur节点是不是以后cur节点的next节点? ListNode pre = new ListNode(-1),help = pre,countNode = head,next;pre.next = head ;int count = 0;while(countNode!=null){ count++; countNode = countNode.next;}for(int i =0;i<count/k;i++){ for(int j= o;j<k-1;j++){ next = head.next; head.next = next.next; next.next = pre.next; pre.next = next; } pre= head; head = head.next;}return help.next;

December 20, 2021 · 1 min · jiezi

关于数据结构:20211219刷题笔记滑动窗口系列

窗口的思路是存在一个窗口。满足条件,这时候扭转窗口的大小,到不满足条件时,挪动窗口的右边界线 int[] check = new int[size];//左边界int left =0;//右边界int right = 1;//这个时候就曾经初步创立了窗口freq[in.0] = init;//当初开始滑动窗口ans = init;while(right<in.len){ //当窗口不非法 while(check[in.right] is not valid){ //挪动窗口使窗口非法 check[in.(left++)] = !init; } //这个时候窗口非法 check[in.right] = init; ans = better(ans,right-left+1); right++;}rerurn ans;套用公式3. 无反复字符的最长子串 滑动窗口一道很典型的题。 int[] check = new int[127];//左边界int left =0;//右边界int right = 1;//这个时候就曾经初步创立了窗口freq[s.charAt(0)] = 1;//当初开始滑动窗口ans = 1;while(right<in.length()){ //当窗口不非法 while(check[in.s.charAt(right)] >0){ //挪动窗口使窗口非法 check[s.charAt(left++)]--; } //这个时候窗口非法 check[in.s.charAt(right)]++; ans = Math,max(ans,right-left+1); right++;}rerurn ans;再用公式套用一个比较复杂的题目30. 串联所有单词的子串 //窗口大小HashSet<String> validSet ; HashSet<String> inValidSet ; public List<Integer> findSubstring(String s, String[] words) { int size = words.length * words[0].length(); int left = 0; int right = size - 1; List<Integer> list = new ArrayList<>(); validSet = new HashSet<>(); inValidSet = new HashSet<>(); while (right < s.length()) { while (right<s.length() && !isValid(s, left, right,words)) { left++; right++; } if (right<s.length()){ list.add(left); left++; right++; } } return list; } private boolean isValid(String s,int left,int right,String[] words) { if (validSet.contains(s.substring(left,right+1))) return true; if (inValidSet.contains(s.substring(left,right+1))) return false; HashMap<String, Integer> map = new HashMap<>(); for (String word : words) { map.put(word, map.getOrDefault(word, 0) + 1); } while (left <= right) { String temp = s.substring(left, left + words[0].length()); Integer freq = map.getOrDefault(temp, 0); if (freq > 0) map.put(temp, freq - 1); else { inValidSet.add(s); return false; } left += words[0].length(); } validSet.add(s); return true; }再比方,咱们再套一道题187. 反复的DNA序列这个题目稍为简略 ...

December 19, 2021 · 2 min · jiezi

关于数据结构:ClickHouse-技术系列-ClickHouse-中的嵌套数据结构

简介:本文翻译自 Altinity 针对 ClickHouse 的系列技术文章。面向联机剖析解决(OLAP)的开源剖析引擎 ClickHouse,因其低劣的查问性能,PB级的数据规模,简略的架构,被国内外公司宽泛采纳。本系列技术文章,将具体开展介绍 ClickHouse。 前言本文翻译自 Altinity 针对 ClickHouse 的系列技术文章。面向联机剖析解决(OLAP)的开源剖析引擎 ClickHouse,因其低劣的查问性能,PB 级的数据规模,简略的架构,被国内外公司宽泛采纳。 阿里云 EMR-OLAP 团队,基于开源 ClickHouse 进行了系列优化,提供了开源 OLAP 剖析引擎 ClickHouse 的云上托管服务。EMR ClickHouse 齐全兼容开源版本的产品个性,同时提供集群疾速部署、集群治理、扩容、缩容和监控告警等云上产品性能,并且在开源的根底上优化了 ClickHouse 的读写性能,晋升了 ClickHouse 与 EMR 其余组件疾速集成的能力。拜访 https://help.aliyun.com/docum... 理解详情。 译者:何源(荆杭),阿里云计算平台事业部高级产品专家 ClickHouse 中的嵌套数据结构在这篇博客文章中,咱们将理解 ClickHouse for MySQL 中的嵌套数据结构,以及如何将其与 PMM 联合应用来查看查问。 嵌套构造在关系数据库管理系统中并不常见。通常状况下,它只是立体表。有时,将非结构化信息存储在结构化数据库中会很不便。 咱们正在致力将 ClickHouse 调整为用于 Percona 监控和治理 (PMM) 的长期存储,尤其是存储无关查问的详细信息。咱们试图解决的问题之一是,对导致特定查问失败的不同谬误进行计数。 例如,对于日期为 2017-08-17 的查问: "SELECT foo FROM bar WHERE id=?" 被执行了 1000 次。其中 25 次失败的错误代码为“1212”,8 次失败的错误代码为“1250”。当然,在关系数据中进行存储的传统办法是创立一个表 "Date, QueryID, ErrorCode, ErrorCnt",而后对这个表执行 JOIN。遗憾的是,列式数据库在多个 Join 的状况下体现不佳,通常倡议应用非规范化表。 ...

December 16, 2021 · 2 min · jiezi

关于数据结构:图的存储

邻接矩阵法无向图的邻接矩阵1、肯定是一个对称矩阵2、每一行(列)的非零元素个数正好是顶点的度 有向图的邻接矩阵1、每一行的非零元素个数对应出度2、每一列的非零元素个数对应入度 共性1、邻接矩阵容易确定两个点是否相连然而难以确定边数(须要逐行列遍历)2、邻接矩阵适宜存储浓密图 链接表法存储空间1、无向图的存储空间为顶点数加边数乘2,有向图则为顶点数加边数 个性1、适宜存储稠密图2、很容易找到一个顶点的所有邻边,而邻接矩阵则须要遍历一行3、然而要确定给定的两个顶点之间是否存在边邻接矩阵则能够疾速找到4、链接表示意不惟一 手绘图

October 13, 2021 · 1 min · jiezi

关于数据结构:排序总结

排序总结插入排序 间接插入排序折半排序希尔排序过程遍历将元素插入后面的有序队列之中在直接插入的根底上改良:通过折半查找找出要插入的地位通过设定步长对步长内的数组进行排序,屡次重复使得数组根本有序,之后应用间接插入排序空间复杂度O(1)O(1)O(1)比拟一个元素工夫复杂度最好(根本有序):O(1)最坏(程序相同):O(n)均匀:O(nlog2n) 总体工夫复杂度O(n^2)O(n^2)最好:O(n^(1/3))最坏:O(n^2)稳定性稳固稳固不稳固适用性顺序存储和链式存储顺序存储和链式存储仅顺序存储特点根本有序时效率最高,最初一趟开始之前所有元素可能都不在最终地位上 组内排序应用间接插入排序替换排序 冒泡排序疾速排序过程每次找出一个最值放到队头或者队尾一直地将待排序表进行划分,右边都比两头小,左边都比两头大直到待排序表根本有序为止空间复杂度O(1)最好状况:O(log2n)最坏状况:O(n)均匀:O(log2n)比拟次数n(n-1)/2 工夫复杂度O(n^2)O(n^2)稳定性稳固不稳固特点元素雷同时不会进行替换保障了稳定性是所有外部排序算法中均匀效率最高的算法抉择排序 简略抉择排序堆排序过程遍历待排序表将最小(大)的放到后面,重复该过程先建设堆(大顶堆或小顶堆),输入堆顶数据后将堆底的数据放到堆顶而后向下调整。对堆进行排序则是从堆的最初一个元素开始进行排序,向上调整之后向下调整。空间复杂度O(1)O(1)比拟次数n(n-1)/2理论状况理论剖析工夫复杂度O(n^2)O(nlog2n)(插入元素与删除元素的工夫复杂度都是)稳定性不稳固稳固特点 调整工夫与树高无关,树越高调整工夫越长,堆能够视为一颗齐全二叉树,采纳顺序存储的形式且对于大顶堆的次大值肯定能在根的下一层,取出堆顶元素之后用堆最初一个元素替换堆顶元素后向下调整,若只是对堆进行排序则应该从最初一个元素开始进行排序。

October 13, 2021 · 1 min · jiezi

关于数据结构:一文搞懂字典树

什么是字典树字典树,是一种空间换工夫的数据结构,又称Trie树、前缀树,是一种树形构造(字典树是一种数据结构),典型用于统计、排序、和保留大量字符串。所以常常被搜索引擎零碎用于文本词频统计。它的长处是:利用字符串的公共前缀来缩小查问工夫,最大限度地缩小无谓的字符串比拟,查问效率比哈希树高。 可能大部分状况你很难直观或者有接触的体验,可能对前缀这个玩意没啥概念,可能做题遇到前缀问题也是暴力匹配蒙混过关,如果字符串比拟少应用哈希表等构造可能也能蒙混过关,但如果字符串比拟长、雷同前缀较多那么应用字典树能够大大减少内存的应用和效率。一个字典树的利用场景:在搜寻框输出局部单词上面会有一些神关联的搜寻内容,你有时候都很神奇是怎么做到的,这其实就是字典树的一个思维。 对于字典树,有三个重要性质: 1:根节点不蕴含字符,除了根节点每个节点都只蕴含一个字符。root节点不含字符这样做的目标是为了可能包含所有字符串。 2:从根节点到某一个节点,路过字符串起来就是该节点对应的字符串。 3:每个节点的子节点字符不同,也就是找到对应单词、字符是惟一的。 设计实现字典树下面曾经介绍了什么是字典树,那么咱们开始设计一个字典树吧! 对于字典树,可能不同的场景或者需要设计上有一些粗疏的区别,但整体来说个别的字典树有插入、查问(指定字符串)、查问(前缀)。 咱们首先来剖析一下简略状况吧,就是字符串中全副是26个小写字母,刚好力扣208实现Trie树能够作为一个实现的模板。 实现 Trie 类: Trie() 初始化前缀树对象。void insert(String word) 向前缀树中插入字符串 word 。boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前曾经插入);否则,返回 false 。boolean startsWith(String prefix) 如果之前曾经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。怎么设计这个字典树呢? 对于一个字典树Trie类,必定是要有一个根节点root的,而这个节点类型TrieNode也有很多设计形式,在这里咱们为了简略放一个26个大小的TrieNode类型数组,别离对应'a'-'z'的字符,同时用一个boolean类型变量isEnd示意是否为字符串开端完结(如果为true阐明)。 class TrieNode { TrieNode son[]; boolean isEnd;//完结标记 public TrieNode()//初始化 { son=new TrieNode[26]; }}用数组的话如果字符比拟多的话可能会耗费一些内存空间,然而这里26个间断字符还好的,如果向一个字典树中增加big,bit,bz 那么它其实是这样的: 那么再剖析一下具体操作: 插入操作:遍历字符串,同时从字典树root节点开始遍历,找到每个字符对应的地位首先判断是否为空,如果为空须要创立一个新的Trie。比方插入big的枚举第一个b时候创立TrieNode,前面也是同理。不过重要的是要在进行的那个TrieNode将isEnd设为true表明这个节点是形成字符串的开端节点。 这部分对应的要害代码为: TrieNode root;/** 初始化 */public Trie() { root=new TrieNode();}/** Inserts a word into the trie. */public void insert(String word) { TrieNode node=root;//长期节点用来枚举 for(int i=0;i<word.length();i++)//枚举字符串 { int index=word.charAt(i)-'a';//找到26个对应地位 if(node.son[index]==null)//如果为空须要创立 { node.son[index]=new TrieNode(); } node=node.son[index]; } node.isEnd=true;//最初一个节点}查问操作: 查问是建设在字典树曾经建好的状况下,这个过程和查问有些相似但不须要创立TrieNode,如果枚举的过程一旦发现该TrieNode未被初始化(即为空)则返回false,如果顺利到最初看看该节点的isEnd是否为true(是否已插入已改字符结尾的字符串),如果为true则返回true。 ...

October 12, 2021 · 2 min · jiezi

关于数据结构:数据结构和算法在流程画布中的实际应用

图灵奖的获得者,Pascal 之父——Niklaus Wirth ,有个经典说法:“算法+数据结构=程序”(Algorithm+Data Structures=Programs)。咱们以这个说法为思路,看在流程画布这个场景中,如何利用数据结构和算法来解决理论的业务需要。 树和图在数据结构中,对树的形容为:树是n(n≥0)个结点的无限集,它或为空树(n=0);或为非空树,对于非空树T: 有且仅有一个称之为根的结点;除根结点以外的其余结点可分为m(m>0)个互不相交的无限集T1, T2, …, Tm,其中每一个汇合自身又是一棵树,并且称为根的子树(SubTree)。由上述形容能够看出,树的构造定义是一个递归的定义,即在树的定义中又用到树的定义,它道出了树的固有个性。树罕用的存储构造有:顺序存储、链式存储。 对图的形容为:图G由两个汇合V和E组成,记为G=(V, E),其中V是顶点的有穷非空集合,E是V中顶点偶对的有穷汇合。V(G)为图的顶点汇合,不能够为空;E(G)为图的边汇合,能够为空。如果边集E(G)为有向边的汇合,则称图为有向图;如果边集E(G)为无向边的汇合,则称图为无向图。 图罕用的存储构造有:邻接矩阵、邻接表、十字链表、邻接多重表。 树和图是画布这个场景中关联度最高的两种数据结构(当然,最根底的还是链表构造)。 G6G6 是一个图可视化引擎。它提供了图的绘制、布局、剖析、交互、动画等图可视化的根底能力,借助 G6 的能力,咱们能够疾速搭建本人的图剖析或图编辑利用。流程画布底层便应用了 antv/g6 来实现图可视化的性能。 依据对其性能应用水平的不同,咱们梳理 G6 的外围概念如下: G6中对图 Graph 接管的参数定义如下: export interface GraphData { nodes?: NodeConfig[]; edges?: EdgeConfig[]; combos?: ComboConfig[]; [key: string]: any;}官网给出的最简略的疾速开始的 demo 代码片段如下: const data = { nodes: [ { id: 'node1', x: 100, y: 200, }, { id: 'node2', x: 300, y: 200, }, ], edges: [ { source: 'node1', target: 'node2', }, ],}; const graph = new G6.Graph({ container: 'mountNode', width: 800, height: 500,});graph.data(data);graph.render();由下面两段代码能够看出,nodes 和 edges 是对图构造中顶点汇合和边汇合的数据表示,同时通过 combos 字段实现了顶点分组的能力。看到此处,咱们能够得出结论:真正的元素绘制局部其实无需关怀,咱们要做的更多是对顶点集和边集数据的治理。 ...

September 2, 2021 · 6 min · jiezi

关于数据结构:优秀程序员必须掌握的-8-种通用数据结构

本文整顿自网络,编辑:逆锋起笔小编数据结构是一种非凡的组织和存储数据的形式,能够使咱们能够更高效地对存储的数据执行操作。数据结构在计算机科学和软件工程畛域具备宽泛而多样的用处。 简直所有已开发的程序或软件系统都应用数据结构。此外,数据结构属于计算机科学和软件工程的根底。当波及软件工程面试问题时,这是一个要害主题。因而,作为开发人员,咱们必须对数据结构有充沛的理解。 在本文中,我将简要解释每个程序员必须晓得的8种罕用数据结构。 1.数组数组是固定大小的构造,能够包容雷同数据类型的我的项目。它能够是整数数组,浮点数数组,字符串数组或什至是数组数组(例如二维数组)。数组已建设索引,这意味着能够进行随机拜访。 数组运算 遍历:遍历所有元素并进行打印。插入:将一个或多个元素插入数组。删除:从数组中删除元素搜寻:在数组中搜寻元素。您能够按元素的值或索引搜寻元素更新:在给定索引处更新现有元素的值数组的利用 用作构建其余数据结构的根底,例如数组列表,堆,哈希表,向量和矩阵。用于不同的排序算法,例如插入排序,疾速排序,冒泡排序和合并排序。2.链表链表是一种程序构造,由互相链接的线性程序我的项目序列组成。因而,您必须程序拜访数据,并且无奈进行随机拜访。链接列表提供了动静集的简略灵便的示意模式。 让咱们思考以下无关链表的术语。您能够通过参考图2来取得一个清晰的主见。 链表中的元素称为节点。每个节点都蕴含一个密钥和一个指向其后继节点(称为next)的指针。名为head的属性指向链接列表的第一个元素。链表的最初一个元素称为尾。 以下是可用的各种类型的链表。 单链列表—只能沿正向遍历我的项目。双链表-能够在后退和后退方向上遍历我的项目。节点由一个称为上一个的附加指针组成,指向上一个节点。循环链接列表—链接列表,其中头的上一个指针指向尾部,尾号的下一个指针指向头。链表操作 搜寻:通过简略的线性搜寻在给定的链表中找到键为k的第一个元素,并返回指向该元素的指针插入:在链接列表中插入一个密钥。插入能够通过3种不同的形式实现; 在列表的结尾插入,在列表的开端插入,而后在列表的两头插入。删除:从给定的链表中删除元素x。您不能单步删除节点。删除能够通过3种不同形式实现; 从列表的结尾删除,从列表的开端删除,而后从列表的两头删除。链表的利用 用于编译器设计中的符号表治理。用于在应用Alt Tab(应用循环链表实现)的程序之间进行切换。3.堆栈堆栈是一种LIFO(后进先出-最初搁置的元素能够首先拜访)构造,该构造通常在许多编程语言中都能够找到。该构造被称为"堆栈",因为它相似于真实世界的堆栈-板的堆栈。 堆栈操作 上面给出了能够在堆栈上执行的2个基本操作。请参考图3,以更好地理解堆栈操作。 Push 推送:在堆栈顶部插入一个元素。Pop 弹出:删除最下面的元素并返回。 此外,为堆栈提供了以下附加性能,以查看其状态。 Peep 窥视:返回堆栈的顶部元素而不删除它。isEmpty:查看堆栈是否为空。isFull:查看堆栈是否已满。堆栈的利用 用于表达式评估(例如:用于解析和评估数学表达式的调车场算法)。用于在递归编程中实现函数调用。4.队列队列是一种FIFO(先进先出-首先搁置的元素能够首先拜访)构造,该构造通常在许多编程语言中都能够找到。该构造被称为"队列",因为它相似于事实世界中的队列-人们在队列中期待。 队列操作 上面给出了能够在队列上执行的2个基本操作。请参考图4,以更好地理解堆栈操作。 进队:将元素插入队列的开端。出队:从队列的结尾删除元素。 队列的利用 用于治理多线程中的线程。用于施行排队零碎(例如:优先级队列)。5.哈希表哈希表是一种数据结构,用于存储具备与每个键相关联的键的值。此外,如果咱们晓得与值关联的键,则它无效地反对查找。因而,无论数据大小如何,插入和搜寻都十分无效。 当存储在表中时,间接寻址应用值和键之间的一对一映射。然而,当存在大量键值对时,此办法存在问题。该表将具备很多记录,并且十分宏大,思考到典型计算机上的可用内存,该表可能不切实际甚至无奈存储。为防止此问题,咱们应用哈希表。 哈希函数 名为哈希函数(h)的非凡函数用于克服间接寻址中的上述问题。 在间接拜访中,带有密钥k的值存储在插槽k中。应用哈希函数,咱们能够计算出每个值都指向的表(插槽)的索引。应用给定键的哈希函数计算的值称为哈希值,它示意该值映射到的表的索引。 h:哈希函数k:应确定其哈希值的键m:哈希表的大小(可用插槽数)。一个不靠近2的准确乘方的素数是m的一个不错的抉择。 1→1→15→5→523→23→363→63→3从下面给出的最初两个示例中,咱们能够看到,当哈希函数为多个键生成雷同的索引时,就会发生冲突。咱们能够通过抉择适合的哈希函数h并应用链接和开放式寻址等技术来解决抵触。 哈希表的利用 用于实现数据库索引。用于实现关联数组。用于实现"设置"数据结构。6.树树是一种层次结构,其中数据按档次进行组织并链接在一起。此构造与链接列表不同,而在链接列表中,我的项目以线性程序链接。 在过来的几十年中,曾经开发出各种类型的树木,以适宜某些利用并满足某些限度。一些示例是二叉搜寻树,B树,红黑树,开展树,AVL树和n元树。 二叉搜寻树 顾名思义,二进制搜寻树(BST)是一种二进制树,其中数据以分层构造进行组织。此数据结构按排序顺序存储值,咱们将在本课程中具体钻研这些值。 二叉搜寻树中的每个节点都蕴含以下属性。 key:存储在节点中的值。left:指向左孩子的指针。右:指向正确孩子的指针。p:指向父节点的指针。二叉搜寻树具备独特的属性,可将其与其余树辨别开。此属性称为binary-search-tree属性。 令x为二叉搜寻树中的一个节点。 如果y是x左子树中的一个节点,则y.key≤x.key如果y是x的右子树中的节点,则y.key≥x.key 树的利用 二叉树:用于实现表达式解析器和表达式求解器。二进制搜寻树:用于许多一直输出和输入数据的搜寻应用程序中。堆:由JVM(Java虚拟机)用来存储Java对象。Trap:用于无线网络。7.堆堆是二叉树的一种非凡状况,其中将父节点与其子节点的值进行比拟,并对其进行相应排列。 让咱们看看如何示意堆。堆能够应用树和数组示意。图7和8显示了咱们如何应用二叉树和数组来示意二叉堆。 堆能够有2种类型。 最小堆-父项的密钥小于或等于子项的密钥。这称为min-heap属性。根将蕴含堆的最小值。最大堆数-父项的密钥大于或等于子项的密钥。这称为max-heap属性。根将蕴含堆的最大值。堆的利用 用于实现优先级队列,因为能够依据堆属性对优先级值进行排序。能够在O(log n)工夫内应用堆来实现队列性能。用于查找给定数组中k个最小(或最大)的值。用于堆排序算法。8.图一个图由一组无限的顶点或节点以及一组连贯这些顶点的边组成。 图的程序是图中的顶点数。图的大小是图中的边数。 如果两个节点通过同一边彼此连贯,则称它们为相邻节点。 有向图 如果图形G的所有边缘都具备批示什么是起始顶点和什么是终止顶点的方向,则称该图形为有向图。 咱们说(u,v)从顶点u入射或来到顶点u,而后入射到或进入顶点v。 自环:从顶点到本身的边。 无向图 如果图G的所有边缘均无方向,则称其为无向图。它能够在两个顶点之间以两种形式流传。 如果顶点未连贯到图中的任何其余节点,则称该顶点为孤立的。 图的利用 ...

August 19, 2021 · 1 min · jiezi

关于数据结构:面试知识点学习7hashMap相关问题

7.1 计算hash值为什么移位运算?为什么是右移16位?7.2 Hash值计算为什么应用&运算?7.3 容量为什么要是2的幂次方?以上三个问题都对应于以下链接中的问答5、6、7 深刻了解 hashcode 和 hash 算法 - 一步之 - 博客园 (cnblogs.com) 7.4 为什么链表长度为8才转为红黑树?首先遍历链表的均匀查找时间复杂度是 O(n),红黑树查找的工夫复杂度管制在 O(log(n)),在n较小的状况O(n) 和 O(log(n)) 差异不大,所以不间接采纳红黑树的形式。 其次,这在ConcurrentHashMap源码中有阐明,大略意思是如果hash值设置的好散布很平均的话,链表个别不会太长(因为达到容量的0.75就扩容了),如果设置转红黑树的链表长度越大转红黑树的概率就越小,通过大量测试当长度为 8 的时候,链表转红黑树的概率仅为 0.00000006,所以采纳了8。 同时也阐明一般来说是很少状况会呈现红黑树结构的,如果呈现了个别状况下咱们就要思考是不是咱们设计的对象的hash办法有问题!

August 7, 2021 · 1 min · jiezi

关于数据结构:Linklist经典题目判断两个链表是否有相交并返回相交起始结点附数学推导

本文内容相交链表问题的解决思路及数学原理推导可执行代码题目内容给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。图例如下: 题目作者:力扣 (LeetCode)链接:https://leetcode-cn.com/leetb...题目起源:力扣(LeetCode)题解作者:WildDuck 解决思路与原理本题目与判断环形链表以及其入口同类型,不仅解决上用到双指针,实质上也同为途程问题 2.可执行代码,双指针构建雷同途程解决问题 /** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */typedef struct ListNode* ListNode_pointer; struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB){ ListNode_pointer temp_A = headA; ListNode_pointer temp_B = headB; while(temp_A != temp_B) { if(temp_A != NULL && temp_B != NULL) { temp_A = temp_A -> next; temp_B = temp_B -> next; } else if(temp_A == NULL) { temp_A = headB; } else if(temp_B == NULL) { temp_B = headA; } } return temp_A;} ...

August 7, 2021 · 1 min · jiezi

关于数据结构:数据结构字符串

数字字符串转成数字 function strNumberToNumber (s) { let i = 0 let res = 0 while (i < s.length && s[i] >= '0' && s[i] <= '9') { // ans = ans * 10 + (s[i++] - '0'); let cur = +s[i] //字符串转整数 res = res * 10 + cur i++ } return res } const s = '4193' const result = strNumberToNumber(s) console.log('result', result) //4193

August 4, 2021 · 1 min · jiezi

关于数据结构:linear-list-线性表的-链式存储方式-C语言实现

基于链式存储形式实现线性表 查问、插入、删除等操作的工夫复杂度剖析 按位查找、按值查找、求单链表长度工夫复杂度均为O(n)设计链表的实现,在链表类中实现这些性能:get(index):获取链表中第 index 个节点的值。如果索引有效,则返回-1。addAtHead(val):在链表的第一个元素之前增加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。addAtTail(val):将值为 val 的节点追加到链表的最初一个元素。addAtIndex(index,val):在链表中的第 index 个节点之前增加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的开端。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。deleteAtIndex(index):如果索引 index 无效,则删除链表中的第 index 个节点。题目作者:力扣 (LeetCode)链接:https://leetcode-cn.com/leetb...题目起源:力扣(LeetCode)题解作者:WildDuck基于链式存储形式实现线性表的代码实现 //有头结点!有头节点!有头节点!//链表中元素的插入为亮点设计,不须要获取待插入地位前一个元素的指针即可实现//以下代码在均在Leetcode中可执行的,带有头结点的单向链表构造#define Elemtype intstruct MyLinkedList{ Elemtype val; struct MyLinkedList* next;};typedef struct MyLinkedList MyLinkedList;//Leetcode中服务器存储的程序主体构造间接应用了MyLinkedList 因而须要再次重命名保障子函数能够正确执行/** Initialize your data structure here. */struct MyLinkedList* myLinkedListCreate(){ struct MyLinkedList* head_pointer = (struct MyLinkedList*)malloc(sizeof(struct MyLinkedList)); if (head_pointer != NULL) { head_pointer->val = 0;//头结点值用于存储链表中元素个数! head_pointer->next = NULL; } return head_pointer;}/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. *///按结点下标 getint myLinkedListGet(struct MyLinkedList* obj, int index){ if (index >= obj->val || index < 0) { return -1; } else { struct MyLinkedList* temp_head = obj; //因为有头结点,所以多走一步 for (int i = 0; i <= index; i++) { temp_head = temp_head->next; } return temp_head->val; }}/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. *///附单向链表的头插法常常用于解决链表逆置问题void myLinkedListAddAtHead(struct MyLinkedList* obj, int val){ struct MyLinkedList* pointer = (struct MyLinkedList*)malloc(sizeof(struct MyLinkedList)); if (pointer != NULL) { pointer->val = val; pointer->next = NULL; } struct MyLinkedList* temp_head = obj->next; obj->next = pointer; pointer->next = temp_head; obj->val = obj->val + 1;}/** Append a node of value val to the last element of the linked list. */void myLinkedListAddAtTail(struct MyLinkedList* obj, int val){ struct MyLinkedList* pointer = (struct MyLinkedList*)malloc(sizeof(struct MyLinkedList)); if (pointer != NULL) { pointer->val = val; pointer->next = NULL; } struct MyLinkedList* temp_head = obj; while (temp_head->next != NULL) { temp_head = temp_head->next; } temp_head->next = pointer; obj->val = obj->val + 1;}/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. *///链表中元素的插入为亮点设计,不须要获取待插入地位前一个元素的指针即可实现void myLinkedListAddAtIndex(struct MyLinkedList* obj, int index, int val){ if (index <= 0) { myLinkedListAddAtHead(obj, val); } else if (index > 0 && index < obj->val) { struct MyLinkedList* temp_head = obj; for (int i = 0; i <= index; i++) { temp_head = temp_head->next; } struct MyLinkedList* pointer = (struct MyLinkedList*)malloc(sizeof(struct MyLinkedList)); pointer->val = temp_head->val; pointer->next = temp_head->next; temp_head->val = val; temp_head->next = pointer; obj->val = obj->val+1; } else if (index == obj->val) { myLinkedListAddAtTail(obj, val); }}/** Delete the index-th node in the linked list, if the index is valid. */void myLinkedListDeleteAtIndex(struct MyLinkedList* obj, int index){ //按下标删除,index不可能大于元素个数 if (index < obj->val && index >= 0) { struct MyLinkedList* temp_head = obj; struct MyLinkedList* temp_next = NULL; //找到index之前的一个元素 for (int i = 0; i < index; i++) { temp_head = temp_head->next; } temp_next = temp_head->next->next; if (temp_head->next != NULL) free(temp_head->next); temp_head->next = temp_next; obj->val = obj->val-1; }}void myLinkedListFree(struct MyLinkedList* obj){ struct MyLinkedList* temp_head = obj; struct MyLinkedList* temp_free = NULL; while (temp_head->next!=NULL) { temp_free = temp_head; temp_head = temp_head->next; free(temp_free); }} ...

August 3, 2021 · 3 min · jiezi

关于数据结构:linear-list-线性表的-顺序存储方式-C语言实现

linear list 线性表的 顺序存储形式实现对顺序存储构造实现线性表查问、插入、删除过程工夫复杂度剖析 插入、删除、按元素值查问推导同理、同后果 按位查问O(1) 基于顺序存储形式实现线性表的代码实现 #include <stdlib.h>#include <stdio.h>#define Elemtype int#define Init_num 20#define Init_cur 10#define Init_value 0//linear list 顺序存储体根本结构(区别于数组形式)//data区域将会取得间断的存储空间,能够间接应用下标拜访(区别于链表,不要看到指针就想到链表)typedef struct { Elemtype *data;//(仅为了实现动态内存空间的调配应用了指针,而不是用指针拜访) int current_max_length; int list_max_length;}linear_list;void initial_list(linear_list &L)//顺序存储构造的初始化办法{ int i = 0; L.data = (Elemtype*)malloc(sizeof(Elemtype)*Init_num-1); for (i=0;i<Init_num;i++) { L.data[i] = Init_value; } //程序表内操作,从零开始计数,所以下标少一个 L.list_max_length = Init_num-1; L.current_max_length = Init_cur-1;//表征能够被间接操作的最初一个元素的下标}int Located_element(linear_list L,Elemtype value){ for (int i=0;i<=L.current_max_length;i++) { if (value == L.data[i]) { printf("\nsearched element in location %d\n",i+1); return i; break; } } printf("\nsearching operation fail,list did not have this element\n"); return -1;//value在程序表中不存在}void delete_element(linear_list &L,int location){ //程序表非空才能够应用删除性能 int temp_loc = location-1; if(L.current_max_length >= 0) { for(temp_loc;temp_loc+1 <= L.current_max_length;temp_loc++) { L.data[temp_loc] = L.data[temp_loc+1]; } L.current_max_length = L.current_max_length-1; printf("delete operation succeed!\n"); for(int i=0 ;i <= L.current_max_length ; i++) { printf("%d ",L.data[i]); } printf("\ndone!\n"); } else { printf("delete operation failed,linear list is empty!\n"); }}//销毁操作是对构造体中的Elemtype类型指针所指向的内存空间进行开释,而不是将构造体L整个进行开释void release_list(linear_list &L){ //free操作非空才无效 if(L.data != NULL) { free(L.data); L.data= NULL; L.current_max_length = -1; L.list_max_length = -1; printf("linear list has been released!\n"); } else { printf("pointer is no defeined!\n"); } }void insert_element(linear_list &L,Elemtype new_element,int location){ //程序表没有多余空位、或者非法输出 location = location -1;//程序表内以0开始计数,线性表逻辑构造中以1开始 if(L.list_max_length == L.current_max_length || location < 0) { printf("insert operation fail!\nno extra space\n"); } else if (L.list_max_length > L.current_max_length) { //应答两类状况 //state_1 间接在程序表开端增加 if(location == L.current_max_length+1) { L.data[location] = new_element; } //state_2 在程序表0-n以内地位插入 else if (location <= L.current_max_length) { int end_marke = L.current_max_length; for(int i=location;i<end_marke;end_marke--) { L.data[end_marke] == L.data[end_marke-1]; } L.data[location] = new_element; } printf("linear_list is added one space!\ninsert operation succeed!\n"); L.current_max_length = L.current_max_length + 1; for(int i=0 ;i <= L.current_max_length ; i++) { printf("%d ",L.data[i]); } }}int main() { bool insert = false; int loc = -2; linear_list L; initial_list(L); for(int i=0 ;i <= L.current_max_length ; i++) { printf("%d ",L.data[i]); } printf("\ndone!\n"); insert_element(L,5,8); loc = Located_element(L,5); delete_element(L,8); release_list(L); release_list(L); return 0;}程序运行后果示例 ...

August 3, 2021 · 2 min · jiezi

关于数据结构:数据结构与算法分析三-线性表

在学习本系列的文章之前,倡议看看本篇的前作《杂感(一)》,该篇探讨了如何进行学习,以及学习策略,置信会对数据结构系列的学习会有所帮忙。前言在写本篇的时候,我想起大学外面,我刚学数据结构这门课程的时候,我过后对这门课是下定决心去听,然而我疏忽的有几点,大学课程是承前启后的,学习数据结构则须要对一门高级语言比拟相熟还须要有肯定的编码量,学习数据结构这门课程,阻碍才不那么的大,然而我对我过后惟一学过的C语言还不那么相熟还没有肯定的编码量,导致我学习这门课程的时候处于一种朦胧的状态,对数据结构这门课程处于一种似懂非懂的状态,起初代码量飞速增长之后,学习数据结构才感觉得心应手一点。所以我的倡议是在学习本系列的文章之前,也倡议对一门高级语言有大抵的理解。 再聊聊文风吧,我不大喜爱写那种书籍的笔记,将书中外围的概念记下来,我更喜爱的是以我本人的形式将这些概念组合起来,退出我本人的了解。 日常生活中的"线性表"排队是咱们日常生活中比拟常见的场景,除了队头和队尾的人,每个人的前后相邻者都只有一个,一个人能够看成一个结点,则称这样的结点之间的关系是一对一,即除了第一个和最初一个数据元素之外,其余数据元素都是首尾相接的。由此咱们就引出了数据结构中的线性表, 在数据结构中,把这种数据元素之间是一对一关系的构造成为线性表,线性表除了第一个和最初一个数据元素之外,其余元素都是首尾相接的。 照之前咱们所探讨的,探讨数据结构个别是探讨逻辑构造、存储构造、基于该构造的运算。咱们首先来探讨线性表的逻辑构造,其实在介绍下面的逻辑构造下面曾经介绍过了,咱们这里来讨论一下线性表的逻辑特色: 存在惟一的一个被称为"第一个"的结点,即开始结点,队头。存在惟一的一个被称为"最初一个"的结点,即终端结点,队尾。除第一个之外,汇合中的每个结点均只有一个前驱,每个排队的人后面只有一个人除最初一个之外,汇合中的每个结点均只有一个后继。从日常生活中的排队来是的话,就是每个排队的人,前面就有一个人 你看咱们将这些形象的数据结构和事实对应的场景做对照,这些绝对形象的货色是不是也不是那么难以了解了。 再议数据结构的三要素咱们这里回顾一下《数据结构与算法剖析(一)》中咱们提到的数据结构的基本概念中的数据结构三要素: 数据的逻辑构造数据的存储构造数据的运算数据的逻辑构造形容的是数据之前的分割,是独立于计算机的,咱们下面探讨的线性表的逻辑构造属于线性逻辑构造,属于"一对一"的,这里所说的一对一指的就是下面所提到的逻辑特色所形容的。数据结构从逻辑构造上划分大抵上就能够分成线性构造和非线性构造,其实还能够做更为粗疏的划分,像上面这样: 那看来当咱们谈起数据结构的时候,次要说的还是数据结构的逻辑构造呀。 接下来咱们来讨论一下数据的存储构造,数据的存储构造又称物理构造,是数据及其逻辑构造在计算机中的示意形式,指数据该如何在计算机中寄存,事实上是内存单元调配,在具体实现时用计算机语言中的数据类型来形容。 咱们思考一下存储数据应该存些什么? 怎么去存,存数据不仅仅要保留数据自身的值,还要保留数据间的分割,这样能力把相干问题的所有信息都存储残缺。保留数据间分割的目标,是能够通过这种分割找到与之相连的元素。其次咱们在计算机存储数据的目标是为了对它们进行解决,如果存进机器了但要用的时候找不到,数据的存储就失去了意义,这里"找到"的含意是一个是可能找到能与之相连的数据元素,所以,数据结构的存储构造的设计应该基于上面两个准则: 存数值,存分割存的进,取的出家喻户晓,咱们的程序在运行之后,是被加载进入内存的,大多数状况下程序运行时的数据都驻留在内存中,那么咱们该如何扫视内存呢,我的想法是咱们应该对内存进行形象,能够将内存了解为一个数组,数组的下标是该内存的地址,有的高级语言一开始就像操作系统申请了一大份内存空间(像Java、C#),而有的语言则是须要了再向操作系统所申请像(C,C++)。 所以咱们能够了解为操作系统向咱们提供的最后的就是顺序存储构造,在这个根底上衍生了链式存储构造、索引存储构造、散列存储构造等。顺序存储构造: 间断程序地存放数据元素,物理内存构造上,数据之间是相邻的。若数据的逻辑构造也是程序(线性的),则逻辑构造和存储构造齐全对立了。间断寄存的数据元素能够很容易地在内存中找到。链式存储构造: 链式存储构造很像火车,将每节车厢视作一个数据元素,那每节车厢还持有指针项(即铁索)。元素在内存中不肯定间断寄存,在元素中附加指针项,通过指针能够找到与之逻辑相连的数据元素的理论地位。索引存储构造: 索引存储办法是存储结点信息的同时,建设一个附加的索引表。索引表中的每一个索引项,索引项的个别模式是: (关键字,地址)散列存储构造: 散列处处形式,以结点的关键字做自变量,通过函数关系F,间接算出该结点的存储地址: 结点地址 = F(关键字),这个有的时候也被称为散列算法。线性表的存储构造—程序表将线性表中的元素顺次放在放在一个间断的存储空间中,这样的线性表叫程序表(Sequential List)。间断的存储空间,想到了什么,就是数组,数组中的元素就是在一个间断的存储空间中,因而能够采纳一维数组来存储线性表的各个结点。依据"存结点存分割"的存储准则,程序表的存储形式尽管并没有间接存储结点之间的逻辑关系,然而结点通过物理地址即存储相邻体现了逻辑相邻。 C语言的话,通常是借助于构造体来体现线性表,实现数据结构的各种运算,比方初始化、插入、删除遍历等。但对于面向对象系的高级语言咱们通常能够借助类来表白。如果你相熟Java中的ArrayList的话,Java中的ArrayList的就是一个实现即好的线性表,存储构造是线性表是数组,所以咱们看源码也是可能增进咱们对数据结构的了解,学习他人是如何设计的。在程序表上的操作通常有: 初始化: 给线性表一些默认参数,一些高级语言会内置一些曾经写好了的数据结构,咱们在学习数据结构的时候能够去参考。求长度: 求线性表元素的个数取元素: 取给定地位的元素值定位: 查找给定元素值的地位插入: 在指定的地位插入给定的值删除: 在指定的地位删除值,删除给定的值线性表的英文是SequenceList,咱们建个类来实现以下: /** * @author XingKe * @date 2021-07-24 15:04 */public class SequenceList{ private int[] elementData; /** * 定义一个变量来返回给调用者 */ private int size; /** * 默认数组大小 */ private int DEFAULT_SIZE = 15; /** * 咱们要不仅仅满足于实现一个简略的数据结构 * 还要设计一个良好的接口、函数。给调用者一个良好的应用体验。 * 发现Java泛型不能间接初始化,这里就间接用int举例了。 * @param initSize */ public SequenceList(int initSize) { if (initSize > 0){ elementData = new int[initSize]; }else { // 抛一个异样进去 或者拒绝执行初始化 } } /** * 这里咱们也给出一个无参的,不强制要求调用者肯定给定出初始大小 */ public SequenceList() { elementData = new int[DEFAULT_SIZE]; } /** * 该办法默认将元素增加至数组已增加的最初一个元素之后 * @param data */ public void add(int data){ // 首先咱们判断元素是否放满了,如果放满了,再放就放不下了 // 咱们执行就须要对数组进行扩容,然而咱们晓得数组一开始就要执行初始化. // 所谓的扩容就是产生一个新数组 // 这里咱们先按扩容两倍来解决,事实上这也是一种衡量 ensureCapacity(size + 1); if (size + 1 > elementData.length){ // 阐明以后须要扩容 elementData = new int[elementData.length * 2]; }else{ elementData[size++] = data; } } /** * 取指定地位的元素 * @param index */ public int get(int index){ rangeCheck(index); return elementData[index]; } /** * 取给定元素的地位,默认返回第一个,不存在则返回-1 * @param data * @return */ public int indexOf(int data){ for (int i = 0; i < elementData.length; i++) { if (data == elementData[i]){ return i; } } return -1; } /** * 向指定地位插入元素 * @param index * @param data */ public void add(int index, int data){ // 先查看地位,有这么多查看地位的,事实上咱们能够做一个通用的办法进去 // 这里我就不做了 // 范畴查看,如果数组元素不够 执行扩容 rangeCheck(index); ensureCapacity(size + 1); // index 到 size - 1 这个地位上的元素都向后平移一位 for (int i = size - 1; i >= index; i--) { elementData[ i + 1 ] = elementData[i]; } elementData[index] = data; ++size; } /** * 查看是否超出了数组的包容 * 超出了执行扩容 * @param minCapacity */ private void ensureCapacity(int minCapacity) { if (minCapacity > elementData.length){ elementData = new int[elementData.length * 2]; } } private void rangeCheck(int index) { if (index < 0 || index > elementData.length){ throw new IndexOutOfBoundsException(); } } /** * 移除指定元素的地位 * @return */ public int remove(int index){ // 先查看地位 rangeCheck(index); int data = elementData[index]; // index+1到size - 1 往前挪动一个地位 for (int i = index + 1; i < size ; i++) { elementData[i] = elementData[++index]; } // 移除后长度-- size--; return data; } /** * 求长度这里,咱们有两种思路,一种是每次都遍历一下,算出长度 * 第二个就是在增加的时候,就对size变量进行++ * 综合上来看,第一种思路更为优良,所以size办法间接返回 * @return */ public int size(){ return size; }}在开端插入和删除开端的工夫复杂度,咱们不再剖析,咱们仅来剖析在两头插入的工夫复杂度,事实上在开端插入也能够蕴含在两头插入这种状况中。这里的问题规模是结点的个数,也就是元素的数量,插入新元素后,结点数变为n+1。该算法的工夫次要破费在结点循环后移上语句上,该语句的执行次数是结点挪动次数为n-k(k为插入地位)。由此能够看出,所需挪动结点的次数不仅依赖于表的长度,而且还须要插入地位无关,不失一般性,假如在表中任何地位(0<=k<=n)上插入结点的机会是均等的: ...

July 31, 2021 · 5 min · jiezi

关于数据结构:数据结构哈希表

哈希表的一种Go语言实现 package mainimport ( "fmt" "os")//链表中的数据的信息type Emp struct { Id int Name string Next *Emp}func (e *Emp) ShowSelf() { fmt.Printf("链表%d 找到了该节点 %d\n", e.Id % 7, e.Id)}//定义链表,该链表不带头结点type EmpLink struct { Head *Emp}//EmpLink的增加办法func (l *EmpLink) Insert (emp *Emp) { //定义两个辅助指针 cur := l.Head var pre *Emp = nil //判断是否为空链表 if cur == nil { //链表头部插入 l.Head = emp //cur = emp //这样会导致节点增加不上,不晓得什么起因 return } //如果不是空链表,给emp找到对应的地位并插入 for { if cur != nil { //链表中部插入 if cur.Id >= emp.Id { pre.Next = emp emp.Next = cur break } pre = cur cur = cur.Next } else { //链表尾部插入 pre.Next = emp emp.Next = cur break } }}func (l *EmpLink)FindId(id int) *Emp{ cur := l.Head for { if cur != nil && cur.Id == id { return cur }else if cur == nil { break } cur = cur.Next } return nil}//定义一个显示链表元素的办法func (l *EmpLink) ShowLink(no int) { if l.Head == nil { fmt.Printf("链表%d 为空\n", no) return } //遍历以后链表并显示数据 cur := l.Head for { if cur != nil { fmt.Printf("链表%d 节点Id= %d 名字=%s -> ", no, cur.Id, cur.Name) cur = cur.Next } else { break } } fmt.Println()}//定义一个hash table,外部含有7条链表type HashTable struct { LinkArr [7]EmpLink}//hashtable的增加办法func (h *HashTable) Insert (emp *Emp) { //应用散列函数,确定将该节点增加到哪个链表 LinkNo := h.HashFun(emp.Id) //应用对应的链表增加 h.LinkArr[LinkNo].Insert(emp)}func (h *HashTable) Find(no int) *Emp { lindNo := h.HashFun(no) return h.LinkArr[lindNo].FindId(no)}//显示hashtable所有节点func (h *HashTable) ShowAll() { for i := 0; i < len(h.LinkArr); i++ { h.LinkArr[i].ShowLink(i) }}func (h *HashTable) HashFun (id int) int { return id % 7 //返回链表下标}func main(){ var hashtable HashTable key := "" id := 0 name := "" for { fmt.Println("====================零碎菜单=====================") fmt.Println("input 增加节点") fmt.Println("show 显示节点") fmt.Println("find 查找节点") fmt.Println("exit 退出零碎") fmt.Println("请输出你的抉择") fmt.Println("请输出你的抉择") fmt.Scanln(&key) switch key { case "input": fmt.Println("输出节点ID") fmt.Scanln(&id) fmt.Println("输出节点Name") fmt.Scanln(&name) emp := &Emp{ Id: id, Name: name, } hashtable.Insert(emp) case "show": hashtable.ShowAll() case "find": fmt.Println("请输出要查找的ID号") fmt.Scanln(&id) emp := hashtable.Find(id) if emp == nil { fmt.Println("id=%d 的节点不存在\n", id) }else { emp.ShowSelf() } case "exit": os.Exit(0) default: fmt.Println("输出谬误") } }}

July 2, 2021 · 2 min · jiezi

关于数据结构:数据结构栈

栈的一种Go语言实现 package mainimport ( "errors" "fmt")type Stack struct { MaxTop int //栈的最大容量 Top int //栈顶 arr [5]int}func (s *Stack) Push(val int) (err error) { //判断栈是否已满 if s.Top == s.MaxTop - 1 { fmt.Println("stack full") return errors.New("stack full") } s.Top++ s.arr[s.Top] = val return}func (s *Stack) Pop() (val int, err error) { //判断栈是否为空 if s.Top == -1 { fmt.Println("stack empty") return -1, errors.New("stack empty") } val = s.arr[s.Top] s.Top-- return val, nil}func (s *Stack) List() { //判断栈是否为空 if s.Top == -1 { fmt.Println("stack empty") return } //从栈顶开始遍历 for i := s.Top; i >= 0; i-- { fmt.Printf("arr[%d]=%d\n", i, s.arr[i]) }}func main(){ stack := &Stack{ MaxTop: 5, Top: -1, } //入栈 stack.Push(1) stack.Push(2) stack.Push(3) stack.Push(4) stack.Push(5) stack.Push(6) //显示 stack.List() //弹出 val, _ := stack.Pop() val, _ = stack.Pop() val, _ = stack.Pop() val, _ = stack.Pop() val, _ = stack.Pop() //val, _ = stack.Pop() fmt.Println("出栈val=", val) stack.List()}利用案例: ...

July 2, 2021 · 3 min · jiezi

关于数据结构:数据结构环形链表

环形链表的一种Go语言实现 package mainimport "fmt"//定义一个环形链表的节点构造体type circleSingleLink struct { id int name string next *circleSingleLink}//插入节点到环形链表func Insert(head, newnode *circleSingleLink) { //判断是否为空链表 if head.next == nil{ //如果为空则把增加的第一个元素给头节点,这里和其余链表有些区别,其余链表头结点是空的 head.id = newnode.id head.name = newnode.name //重点是要让一个节点也造成一个环形,即收尾相接 head.next = head return } //增加非头结点到环形链表 temp := head for{ if temp.next == head { //尾部增加 temp.next = newnode newnode.next = head break } temp = temp.next }}//显示环形链表的所有节点信息func CircleList(head *circleSingleLink){ //判断链表是否为空 if head.next == nil { fmt.Println("链表为空") return } temp := head for { fmt.Printf("[%d %s] -> ", temp.id, temp.name) //留神这里是这样判断终止条件的 if temp.next == head{ break } temp = temp.next }}//删除环形链表的一个节点(难点)func CircleDelete(head, node *circleSingleLink) *circleSingleLink { //之所以有返回值是因为头结点的值和指向要发生变化 //删除思路: //先让temp 指向head //再让helper指向环形链表的最初 //让temp和要删除的节点比拟,如果雷同,则让helper实现删除节点,重点是要思考是否为头结点,因为环形链表的头结点有值 temp := head if temp.next == nil { //链表为空的时候 fmt.Println("链表为空") return head } if temp.next == head { //只有一个头结点的环形链表 temp.next = nil return head } helper := head for { if helper.next == head { break } helper = helper.next //将指针定位到环形链表的尾节点 } //如果有两个及以上的节点 for { if temp.next == head { //阐明到最初一个并且还没判断是否是要删除的节点 if temp.id == node.id { helper.next = temp.next }else { fmt.Println("没有该节点") } break } if temp.id == node.id { //找到了要删除的节点 if temp == head { //如果删除的是头结点 head = head.next } //删除非头结点 helper.next = temp.next //helper始终在temp的后一个 break } temp = temp.next //用作比拟 helper = helper.next //用作操作 } return head}func main(){ //定义一个链表的头 head := &circleSingleLink{} //定义第一个节点 node1 := &circleSingleLink{ id: 1, name : "number1", } node2 := &circleSingleLink{ id: 2, name : "number2", } node3 := &circleSingleLink{ id: 3, name : "number3", } Insert(head, node1) Insert(head, node2) Insert(head, node3) head = CircleDelete(head, node1) CircleList(head)}约瑟夫问题: ...

July 2, 2021 · 3 min · jiezi

关于数据结构:数据结构队列链表实现

队列的一种链表简略实现 package mainimport "fmt"type Queue struct { id int name string next *Queue}//Pushfunc Push(head, node *Queue){ node.next = head.next head.next = node}//Popfunc Pop(head *Queue) (node *Queue){ temp := head for { if temp.next == nil{ return }else if temp.next.next == nil{ node = temp.next temp.next = nil break } temp = temp.next } return}//Listfunc QList(head *Queue){ temp := head if temp.next == nil{ fmt.Println("队列为空") return } for { fmt.Printf("| %d %s | ", temp.next.id, temp.next.name) temp = temp.next if temp.next == nil{ break } }}func main(){ head := &Queue{} node1 := &Queue{ id: 0, name: "number1", next: nil, } node2 := &Queue{ id : 1, name: "number2", next: nil, } Push(head, node1) Push(head, node2) QList(head) fmt.Println() p := Pop(head) fmt.Printf("%d %s\n",p.id, p.name) QList(head)}

July 2, 2021 · 1 min · jiezi

关于数据结构:数据结构双向链表

双向链表的一种Go语言实现 package mainimport "fmt"//定义节点信息type dNode struct { id int name string pre *dNode next *dNode}//尾部插入节点func dInsertTail(head, newNode *dNode) { //定义一个长期游走变量 temp := head for { if temp.next == nil{ break //游走到链表尾部后跳出循环进行插入数据 } temp = temp.next } //尾部增加绝对简略 temp.next = newNode newNode.pre = temp}//头部插入节点func dInsertHead(head, newNode *dNode) { temp := head //先连贯新节点的两条链到链表 newNode.next = temp.next newNode.pre = temp //再拆旧连贯到新连贯上 temp.next = newNode temp.next.pre = newNode}//程序插入节点func dInsertSort(head, newNode *dNode){ temp := head for { if temp.next == nil{ newNode.next = temp.next newNode.pre = temp temp.next = newNode //temp.next.pre = newNode break } else if temp.next.id >= newNode.id { newNode.next = temp.next newNode.pre = temp temp.next = newNode temp.next.pre = newNode break } temp = temp.next }}//头部删除节点func dDeleteHead(head *dNode){ if head.next == nil{ return }else { head.next = head.next.next }}//尾部删除节点func dDeleteTail(head *dNode){ temp := head for { if temp.next == nil { return } else if temp.next.next == nil{ //删除节点 temp.next = nil break } temp = temp.next }}//删除指定节点func dDeleteNode(head, node * dNode){ temp := head for { if temp.next == nil { return }else if temp.next.id == node.id { temp.next = temp.next.next temp.next.pre = temp break } temp = temp.next }}//显示链表元素func dList(head *dNode){ temp := head if temp.next == nil { fmt.Println("链表为空") return } for { //留神这里,我所有的思路都是在链表的以后节点操作下一个节点 fmt.Printf("%d %s --> ", temp.next.id, temp.next.name) temp = temp.next if temp.next == nil{ break } }}func main(){ head := &dNode{} node1 := &dNode{ id: 0, name: "number1", pre: nil, next: nil, } node2 := &dNode{ id: 1, name: "number2", pre: nil, next: nil, } node3 := &dNode{ id: 2, name: "number3", pre: nil, next: nil, } node4 := &dNode{ id: 3, name: "number4", pre: nil, next: nil, } dInsertHead(head, node1) dInsertTail(head, node3) dInsertSort(head, node2) dInsertSort(head, node4) dDeleteHead(head) dDeleteTail(head) dDeleteNode(head, node2) dList(head)}

July 2, 2021 · 2 min · jiezi

关于数据结构:线性表的链式表示双链表

1、和单链表比拟单链表无奈逆向检索双链表可进可退存储构造: typedef struct DNode { //定义双链表节点类型 ElemType data; //数据域 struct DNode *prior, *next; //前驱和后继指针} DNode, *DLinkList;2、插入节点在p节点后插入s节点,留神执行的程序 /** * 在p节点之后插入s节点 * @param p * @param s * @return */bool InsertNextDNode(DNode *p, DNode *s) { if (p == NULL || s == NULL) { //非法参数 return false; } s->next = p->next; if (p->next != NULL) { //如果p节点有后继节点 p->next->prior = s; } s->prior = p; p->next = s; return true;}3、删除节点删除p节点后的q节点 /** * 删除p的后继节点 * @param p * @return */bool DeleteNextNode(DNode *p) { if (p == NULL) { return false; } DNode *q = p->next; //找到p的后继节点q if (q == NULL) { return false; } p->next = q->next; if (q->next != NULL) { //q节点不是最初一个节点 q->next->prior = p; } free(q); //开释空间节点 return true;}

June 23, 2021 · 1 min · jiezi

关于数据结构:数据结构truee

数组转成链表 class TreeNode { constructor(val) { this.val = val; this.left = null; this.right = null; } } function arrayToTree(arr) { function toNode(item) {//转换数组项至节点 if (item === null || item === undefined) { return null } else { return new TreeNode(item) } } let queue = []; const tree = toNode(arr.shift()); queue.push(tree);//入队列第一个元素 while (arr.length > 0) { //当数组里还有项的时候就拿数组的项去填充队列 let current = queue.shift(); current.left = toNode(arr.shift()); current.right = toNode(arr.shift()); if (current.left) { queue.push(current.left) } if (current.right) { queue.push(current.right) } } return tree; } // var arr = [3, 9, 20, null, null, 15, 7] // var arr = [5, 4, 8, 11, null, 13, 4, 7, 2, null, null, null, 1]; // var arr = [3, 1, 4, null, 2] var arr = [5, 3, 6, 2, 4, null, null, 1] var data = new arrayToTree(arr) console.log('data', data);

June 8, 2021 · 1 min · jiezi

关于数据结构:数据结构-知识点

基本概念数据 客观事物的符号示意,在计算机科学中是指所有能输出到计算机中并被计算机程序解决的符号的总称。数据元素 是数据的根本单位,在计算机程序中通常作为一个整体进行思考和解决。数据对象 性质雷同的数据元素的汇合,是数据的一个子集。数据结构 相互之间存在一种或多种特定关系的数据元素的汇合。根本构造 汇合 构造中数据元素之间的关系为属于同一个汇合线性构造 数据元素之间是一对一的关系树形构造 数据元素之间是一对多的关系图状构造或网状结构 数据元素之间是多堆垛的关系线性表

June 3, 2021 · 1 min · jiezi

关于数据结构:线性表的链式表示单链表

1、定义:通过一组任意的存储单元来存储线性表中的数据元素。为了建设数据元素之间的线性关系,对每个链表节点,除了寄存元素本身的信息外,还须要寄存一个指向下一个节点 构造体: typedef int ElemType;typedef struct LNode { //定义单链表节点类型 ElemType data; //每个节点寄存一个数据元素 struct LNode *next; //指针指向下一个节点} LNode, *LinkList; //强调这是一个单链表应用LinkList,强调节点应用LNode*2、创立一个单链表2.1 用头插法建设单链表新节点插入到以后列表的表头: 代码实现: /** * 用头插法建设单链表 * @param L * @return */LinkList List_HeadInsert(LinkList &L) { //逆向建设单链表 LNode *s; int x; L = (LinkList) malloc(sizeof(LNode)); //创立头节点 L->next = NULL; //初始为空链表 scanf("%d", &x); //输出节点的值 while (x != 9999) { //输出9999示意完结 s = (LNode *) malloc(sizeof(LNode)); //创立新节点 s->data = x; s->next = L->next; L->next = s; //将新节点插入表中,L为头指针 scanf("%d", &x); } return L;}采纳头插法建设单链表时,读入数据的程序与生成的链表中的元素的程序式相同的 ...

June 1, 2021 · 2 min · jiezi

关于数据结构:数据结构中含有可变长度字段处理

一、前言在理论零碎中,用户晓得产生了什么事件的流程可能是:底层驱动检测到事件——上抛到对应模块解决——弹窗或者其余形式让用户可见。然而事件很多种,有时候不能确认事件中某一个字段值的长度,而间接申请最大长度的话,如果很大的话比拟节约内存空间。因而可能会应用上面的形式定义事件的相干信息: typedef struct{ ... uint64_t reserved1; //预留 uint64_t reserved2; //预留 uint32_t info_size; uint8_t event_info[0]; }REVENT_INFO,*PREVENT_INFO;event_info字段是不定长度的。这种状况下,不论是设置值还是获取值,咱们都须要申请对应大小的空间,如果 memcpy(event->event_info, pEventInfo, nLen);除非没有其余调用应用到这块内存,否则可能发现意料之外的问题。 二、解决鉴于以上阐明,不论是设置还是获取,咱们最好都先申请一块内存,用来保留事件的信息。以下流程以设置为例进行阐明。 1、申请内存空间CString strEventInfo = Dlg.m_strEventInfo;CStringA strData(strEventInfo);int nLen = strData.GetLength(); //调用输出的内容长度int nEventInfoSize = sizeof(REVENT_INFO) + nLen;char* pEventInfo = new(std::nothrow) char[nEventInfoSize + 1];if (NULL == pEventInfo){ return ;}ZeroMemory(pEventInfo, nEventInfoSize + 1);PREVENT_INFO pEvent = (PREVENT_INFO )pEventInfo;const char* pInfo = strData.GetString();memcpy(pEvent->event_info, pInfo, nLen);pEvent->info_size = nLen;...delete[] pEvent;pEvent = NULL;这里为什么将CString转为CStringA呢?当用户输出的内容蕴含中文或者其余字符时,能正确设置内容;如果不转的话,字符串中含有中文等,非英文字符的状况下,可能用户设置的值和实在设置的值不统一,因为中文字符和英文字符的字节长度不统一。 因为pEvent的内存是new进去,因而在应用完了之后,最好应用delete开释掉,若不开释的话,在频繁调用状况,内存增长很快,到肯定状况下,可能导致其余调用申请内存空间失败。

May 26, 2021 · 1 min · jiezi

关于数据结构:线性表的顺序表示顺序表

1、程序表的定义 程序表是用一组地址间断的存储单元顺次存储线性表中的数据元素。把逻辑上相邻的元素存储在物理地位上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现。顺序存储构造如下图所示: 留神:线性表中的位序是从1开始的,而数组元素的下标是从0开始的。 2、程序表的实现2.1 动态调配#define MaxSize 10 //定义最大长度typedef int ElemType;typedef struct { ElemType data[MaxSize]; //用动态数组存放数据元素 int length; //程序表的以后长度} SqList; //程序表的类型定义/** * 基本操作-初始化一个程序表 * @param L */void InitList(SqList &L) { for (int i = 0; i < MaxSize; ++i) { L.data[i] = 0; //将所有数据设为默认初始值,否则会有脏数据 } L.length = 0; //程序表初始长度为0} 在动态调配时,数组的大小和空间是固定的,无奈更改,一旦空间占满,再退出新的数据就会产生溢出,进而导致程序解体。 思考:如果一开始就申请很大的内存空间呢 ???会存在什么问题 ??? 节约!! 2.2 动态分配#define InitSize 10 //默认的最大长度typedef int ElemType;typedef struct { ElemType *data; //批示动态分配数组的指针 int MaxSize; //程序表的最大容量 int length; //程序表的以后长度} SeqList;void InitList(SeqList &L) { //用malloc函数申请一片间断的内存空间 L.data = (int *) malloc(InitSize * sizeof(int)); L.length = 0; L.MaxSize = InitSize;}用malloc函数申请一片间断的存储空间。malloc函数返回一个指针,须要强制转型为你定义的数据元素类型指针。 ...

May 20, 2021 · 2 min · jiezi

关于数据结构:数据结构与算法之线性表超详细顺序表链表

前言通过后面数据结构与算法基础知识我么晓得了数据结构的一些概念和重要性,那么咱们明天总结下线性表相干的内容。当然,我用本人的了解解分享给大家。 其实说实话,可能很多人仍然分不清线性表,程序表,和链表之间的区别和分割! 线性表:逻辑构造, 就是对外裸露数据之间的关系,不关怀底层如何实现,数据结构的逻辑构造大分类就是线性构造和非线性构造而程序表、链表都是一种线性表。程序表、链表:物理构造,他是实现一个构造理论物理地址上的构造。比方程序表就是用数组实现。而链表用指针实现次要工作。不同的构造在不同的场景有不同的区别。在Java中,大家都晓得List接口类型,这就是逻辑构造,因为他就是封装了一个线性关系的一系列办法和数据。而具体的实现其实就是跟物理构造相干的内容。比方程序表的内容存储应用数组的,而后一个get,set,add办法都要基于数组来实现,而链表是基于指针的。当咱们思考对象中的数据关系就要思考指针的属性。指针的指向和value。 上面用一个图来浅析线性表的关系。可能有些不太确切,然而其中能够参考,并且前面也会依据这个图举例。 线性表根本架构对于一个线性表来说。不论它的具体实现如何,然而它们的办法函数名和实现成果应该统一(即应用办法雷同、达成逻辑上成果雷同,差异的是运行效率)。线性表的概念与Java的接口/抽象类有那么几分类似。最驰名的就是List的Arraylist和LinkedList,List是一种逻辑上的构造,示意这种构造为线性表,而ArrayList,LinkedList更多的是一种物理构造(数组和链表)。 所以基于面向对象的编程思维,咱们能够将线性表写成一个接口,而具体实现的程序表和链表的类能够实现这个线性表的办法,进步程序的可读性,还有一点比拟重要的,记得初学数据结构与算法时候实现的线性表都是固定类型(int),随着常识的提高,咱们该当采纳泛型来实现更正当。至于接口的具体设计如下: package LinerList;public interface ListInterface<T> { void Init(int initsize);//初始化表 int length(); boolean isEmpty();//是否为空 int ElemIndex(T t);//找到编号 T getElem(int index) throws Exception;//依据index获取数据 void add(int index,T t) throws Exception;//依据index插入数据 void delete(int index) throws Exception; void add(T t) throws Exception;//尾部插入 void set(int index,T t) throws Exception; String toString();//转成String输入 }程序表程序表是基于数组实现的,所以所有实现须要基于数组个性。对于程序表的构造应该有一个存储数据的数组data和无效应用长度length. 还有须要留神的是初始化数组的大小,你能够固定大小,然而笔者为了可用性如果内存不够将扩充二倍。 上面着重解说一些初学者容易混同的概念和办法实现。这里把程序表比作一队坐在板凳上的人。 插入操作add(int index,T t) 其中index为插入的编号地位,t为插入的数据,插入的流程为: (1)从后(最初一个有数据位)向前到index顺次后移一位,腾出index地位的空间 (2)将待插入数据赋值到index地位上,实现插入操作 能够看得出如果程序表很长,在靠前的中央如果插入效率比拟低(插入工夫复杂度为O(n)),如果频繁的插入那么复杂度挺高的。 删除操作同理,删除也是十分占用资源的。原理和插入相似,删除index地位的操作就是从index+1开始向后顺次将数据赋值到后面地位上,具体能够看这张图: 代码实现这里我实现一个程序表给大家作为参考学习: package LinerList;public class seqlist<T> implements ListInterface<T> { private Object[] date;//数组存放数据 private int lenth; public seqlist() {//初始大小默认为10 Init(10); } public void Init(int initsize) {//初始化 this.date=new Object[initsize]; lenth=0; } public int length() { return this.lenth; } public boolean isEmpty() {//是否为空 if(this.lenth==0) return true; return false; } /* * * @param t * 返回相等后果,为-1为false */ public int ElemIndex(T t) { // TODO Auto-generated method stub for(int i=0;i<date.length;i++) { if(date[i].equals(t)) { return i; } } return -1; } /* *取得第几个元素 */ public T getElem(int index) throws Exception { // TODO Auto-generated method stub if(index<0||index>lenth-1) throw new Exception("数值越界"); return (T) date[index]; } public void add(T t) throws Exception {//尾部插入 add(lenth,t); } /* *依据编号插入 */ public void add(int index, T t) throws Exception { if(index<0||index>lenth) throw new Exception("数值越界"); if (lenth==date.length)//扩容 { Object newdate[]= new Object[lenth*2]; for(int i=0;i<lenth;i++) { newdate[i]=date[i]; } date=newdate; } for(int i=lenth-1;i>=index;i--)//前面元素后挪动 { date[i+1]=date[i]; } date[index]=t;//插入元素 lenth++;//程序表长度+1 } public void delete(int index) throws Exception { if(index<0||index>lenth-1) throw new Exception("数值越界"); for(int i=index;i<lenth;i++)//index之后元素前挪动 { date[i]=date[i+1]; } lenth--;//长度-1 } @Override public void set(int index, T t) throws Exception { if(index<0||index>lenth-1) throw new Exception("数值越界"); date[index]=t; } public String toString() { String vaString=""; for(int i=0;i<lenth;i++) { vaString+=date[i].toString()+" "; } return vaString; }}链表学习c/c++的时候链表应该是很多人感觉很绕的货色,这个很大起因可能因为指针,Java尽管不间接应用指针然而咱们也要了解指针的原理和使用。链表不同于程序表(数组)它的构造像一条链一样链接成一个线性构造,而链表中每一个节点都存在不同的地址中,链表你能够了解为它存储了指向节点(区域)的地址,可能通过这个指针找到对应节点。 ...

May 13, 2021 · 3 min · jiezi

关于数据结构:改进for循环时间复杂度的一种办法

找到n个数中两个数之和为7的对数//(相比两层for循环工夫复杂度仅为O(N)的改良算法) #include<iostream>#include<algorithm>#include<string>#include<string.h>#include<cstdio>#include<queue>#include<stack> #include<set>#include<map> #include<vector> using namespace std;int main(){ int n;//要输出n个数来找和为7的数的数对 scanf("%d",&n); long long num[20];//定义一个数组去存%7取余后余数为i的个数,20是随便定的,>=7就行,因为任何数对7取余都小于7 for(int i = 0; i < 20; i++){ num[i] = 0;//初始化一下,%7余数为i的个数都是0 } for(int i = 1; i <= n; i++){ int x;//输出n个数 scanf("%d",&x); num[x%7]++;//记录余数为某个数i的个数,更新对应的num[i]的值来记录 } long long sum = 0; sum += (num[0] *(num[0] - 1)/2);//对7取余为0的比拟非凡(因为14+14,7+7等满足条件但却不是一对数(应为不等的一对数)) //故满足条件的数是7,14,21等排列组合失去的个数为n*(n-1)/2 sum += (num[1] * num[6]);//对7取余为1的个数与对7取余为6的个数相乘失去 1和6 总对数(对7取余为1的数与对7取余为6的数相加必定是7的倍数) sum += (num[2] * num[5]);//同理 sum += (num[3] * num[4]);//同理 printf("%lld\n",sum); return 0;} ...

April 28, 2021 · 1 min · jiezi

关于数据结构:数塔经典例题

在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样形容的: 有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则通过的结点的数字之和最大是多少? 曾经通知你了,这是个DP的题目,你能AC吗?Input输出数据首先包含一个整数C,示意测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),示意数塔的高度,接下来用N行数字示意数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。Output对于每个测试实例,输入可能失去的最大和,每个实例的输入占一行。Sample Input1573 88 1 0 2 7 4 44 5 2 6 5Sample Output30 本文用动静布局和改良的深度优先算法(记忆化搜寻)两种方法 //(dp做法,找递归式ans[i][j] = max(ans[i+1][j],ans[i+1][j+1])+自身a[i][j]) //#include<iostream>//#include<algorithm>//#include<string>//#include<string.h>//#include<cstdio>//#include<queue>//#include<stack> //#include<set>//#include<map> //#include<vector> //using namespace std;//int ans[105][105],a[105][105];//int main(){// int t = 0;// cin >> t;// while(t--){// int n = 0;// cin >> n;// memset(ans,0,sizeof(ans));// for(int i=1;i<=n;i++){// for(int j=1;j<=i;j++){// cin >> a[i][j];//输出各行数据 // }// }// for(int i=n;i>=1;i--){// for(int j=1;j<=i;j++){// ans[i][j] = max(ans[i+1][j],ans[i+1][j+1])+a[i][j];//从底层向上,计算各地位可失去的最大值再加上该地位自身的数字值 // }// }// cout << ans[1][1] << endl;//因为是自底向上,所以第一行的第一个数是所求的最大门路 // }// return 0;//} ...

April 28, 2021 · 1 min · jiezi

关于数据结构:拼瓷片和博弈论Theatre-SquareBrave-Game

瓷片相干的题如同都是相似递推,有本人的法则公式,多推导一下 Theatre SquareTheatre Square in the capital city of Berland has a rectangular shape with the size n × m meters. On the occasion of the city's anniversary, a decision was taken to pave the Square with square granite flagstones. Each flagstone is of the size a × a. What is the least number of flagstones needed to pave the Square? It's allowed to cover the surface larger than the Theatre Square, but the Square has to be covered. It's not allowed to break the flagstones. The sides of flagstones should be parallel to the sides of the Square. ...

April 28, 2021 · 2 min · jiezi

关于数据结构:并查集最小生成树How-Many-Tables还是畅通工程

How Many Tables(并查集)Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers. One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table. ...

April 28, 2021 · 4 min · jiezi

关于数据结构:归并排序以及二分法

二分法咱们都晓得,二分的根本过程如下(_merge()见下): //void erfen(int l,int r){//二分递归之后归并 // int mid;// mid = (l + r) / 2;// if(l < r){// erfen(l,mid);// erfen(mid + 1,r);// }// _merge(l,mid,r);//}Points in Segments(二分分治)Given n points (1 dimensional) and q segments, you have to find the number of points that lie in each of the segments. A point pi will lie in a segment A B if A ≤ pi ≤ B. For example if the points are 1, 4, 6, 8, 10. And the segment is 0 to 5. Then there are 2 points that lie in the segment. ...

April 28, 2021 · 3 min · jiezi

关于数据结构:图论含深搜广搜Red-and-Black最短路径

Red and BlackThere is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can't move on red tiles, he can move only on black tiles. Write a program to count the number of black tiles which he can reach by repeating the moves described above.InputThe input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20. ...

April 28, 2021 · 4 min · jiezi

关于数据结构:贪心专题今年暑假不AC迷瘴

往年寒假不AC“往年寒假不AC?”“是的。”“那你干什么呢?”“看世界杯呀,笨蛋!”“@#$%^&*%...” 的确如此,世界杯来了,球迷的节日也来了,预计很多ACMer也会抛开电脑,奔向电视了。作为球迷,肯定想看尽量多的残缺的较量,当然,作为新时代的好青年,你肯定还会看一些其它的节目,比方新闻联播(永远不要遗记关怀国家小事)、十分6+7、超级女生,以及王小丫的《开心辞典》等等,假如你曾经晓得了所有你喜爱看的电视节目的转播时间表,你会合理安排吗?(指标是能看尽量多的残缺节目)Input输出数据蕴含多个测试实例,每个测试实例的第一行只有一个整数n(n<=100),示意你喜爱看的节目的总数,而后是n行数据,每行包含两个数据Ti_s,Ti_e (1<=i<=n),别离示意第i个节目的开始和完结工夫,为了简化问题,每个工夫都用一个正整数示意。n=0示意输出完结,不做解决。Output对于每个测试实例,输入能残缺看到的电视节目的个数,每个测试实例的输入占一行。Sample Input121 33 40 73 815 1915 2010 158 186 125 104 142 90Sample Output5 #include<iostream>#include<algorithm>#include<string>#include<string.h>#include<cstdio>#include<queue>#include<stack> #include<set> using namespace std;//sort函数默认从小到大,但对构造体数组应用sort函数进行排序时,是肯定要用cmp函数的 struct node{ int x,y; }a[105];bool cmp(node u,node v){ return u.y < v.y;//< 示意用cmp依照从小到大排序, > 示意用cmp依照从小到大排序 } int main(){ int n = 0; int ans,temp;//可看的节目数、以后节目下标 while(cin >> n,n != 0){ for(int i = 0; i < n; i++){ cin >> a[i].x >> a[i].y;//输出第i个节目的开始和完结工夫 } sort(a,a + n,cmp);//依照y值即完结工夫从小到大排序(下面曾经定义cmp函数) ans = 1; temp = 0;//先让temp指向第一个节目 for(int i = 1; i <n; i++){//枚举满足条件的节目(从0开始),temp指向0了曾经,第(下)一个比照对象为 i = 1时 if(a[temp].y <= a[i].x){//如果上一节目标完结工夫小于等于下一节目标开始工夫 temp = i;//temp指向第i个节目 ans ++; } } cout << ans << endl; } return 0;} ...

April 28, 2021 · 2 min · jiezi

关于数据结构:递归与递推N皇后问题-素数环

N皇后问题 在N*N的方格棋盘搁置了N个皇后,使得它们不互相攻打(即任意2个皇后不容许处在同一排,同一列,也不容许处在与棋盘边框成45角的斜线上。你的工作是,对于给定的N,求出有多少种非法的搁置办法。 Input共有若干行,每行一个正整数N≤10,示意棋盘和皇后的数量;如果N=0,示意完结。Output共有若干行,每行一个正整数,示意对应输出行的皇后的不同搁置数量。Sample Input1850Sample Output19210 这道题我老是Time Limit,有空还要优化一下,有大佬请帮我看下 ////求N*N的格子摆放皇后的摆法(求有几种摆法而不是能够放多少个皇后)(递归) //#include<iostream>//#include<algorithm>//#include<string>//#include<string.h>//#include<cstdio>//#include<queue>//#include<stack> //#include<set> //using namespace std;//int n,cnt;//int vis[3][50];//vis[3]第一个中括号来辨别不同拜访类型三种类型,0(vis[0][])示意判断 同一列 的格子是否被拜访过////1(vis[1][])示意判断 右斜对角 的格子是否被拜访过,2(vis[2][])示意判断 左斜对角 的格子是否被拜访过////#include<bits/stdc++.h>//万能头文件//void dfs(int row){//把queen所在的行传入dfs // if(row == n + 1 ){//递归进口条件、边界(如果超过第n行了) // cnt++;//递归终止回溯一次,记录一次个数 // return ; // }// for(int i = 1; i <= n; i++){// if(vis[0][i] == 0 && vis[1][n + row - i] == 0 && vis[2][i + row] == 0){// vis[0][i] = vis[1][n + row - i] = vis[2][i + row] = 1;// dfs(row + 1);// vis[0][i] = vis[1][n + row - i] = vis[2][i + row] = 0;//在深搜遍历的时候每一个格子的同行同列、左右对角都被拜访标记了// //然而回溯的时候只是找到了一个适合的格子,(除该以后格子外)其余格子要从新复原为未拜访 // }// }//}//int main(){// memset(vis,0,sizeof(vis));// while(cin >> n,n != 0){//// int cnt = 0;//记录皇后搁置的数量,应定义为全局变量 //// int n;//因为深搜外面也用到该变量,应定义为全局变量// cnt = 0; // dfs(1); // cout << cnt << endl;// }// return 0;//}// 另一种办法,引入check函数,但实质还是递归 ...

April 28, 2021 · 3 min · jiezi

关于二叉树:二叉树创建后如何使用递归和栈遍历二叉树

0. 前言前文【二叉树的概念和原理】次要介绍了树的相干概念和原理,本文次要内容为二叉树的创立及遍历的代码实现,其中包含递归遍历和栈遍历。 1. 二叉树的实现思路1.0. 顺序存储——数组实现后面介绍了满二叉树和齐全二叉树,咱们对其进行了编号——从 0 到 n 的不中断程序编号,而恰好,数组也有一个这样的编号 —— 数组下标,只有咱们把二者联结起来,数组就能存储二叉树了。 那么非满、非齐全二叉树怎么应用数组存储呢? 咱们能够在二叉树中补上一些虚构的结点,结构进去一个满/齐全二叉树来,存储到数组中时,虚构的结点对应的数组元素不存储数据(# 代表虚构的不存在)。如下图: 这样存储的毛病是,数组中可能会有大量空间未用到,造成节约。 1.1. 链式存储——链表实现咱们画树的图时,采纳的都是结点加箭头的形式,结点示意数据元素,箭头示意结点之间的关系,清晰明了。如果你对链表相熟,那么必定能觉察到这是典型的链式构造。链式构造完满解决了程序构造中可能会节约空间的毛病,而且也不会有数组空间限度。 上面来剖析一下结点的构造。 树的结点包含一个数据元素和若干指向其子树分支。二叉树的结点绝对简略,包含: 数据元素左子树分支(结点的左孩子)右子树分支(结点的右孩子)怎么来实现呢?单链表的结点是应用一个指向其后继结点的指针来示意其关系的。同样地,咱们也能够应用指针来示意结点和其左孩子、右孩子的关系。 剖析到这,二叉树的结点就清晰了: 一个存储数据的变量——data一个指向其左孩子结点的指针——left_child一个指向其右孩子结点的指针——right_child 用 C 语言的构造体实现二叉树的结点(为了不便起见,咱们的数据全为字符类型): /*二叉树的结点的构造体*/typedef struct Node { char data; //数据域 struct Node *left_child; //左孩子指针 struct Node *right_child; //右孩子指针} TreeNode;2. 二叉树的发明二叉树的定义是递归的定义,所以如果你想要发明一个二叉树,也能够借助递归去发明。如何递归发明呢?在事实中,一棵树先长根、再长枝干、最初长叶子。咱们用代码发明树时,也恪守这个准则,即先发明根结点,而后左子树,最初右子树。整个过程和先序遍历类似。 我以前写过的文章中有二叉树创立过程的动态图,这里不再赘述。 这里以发明下图中的树为例: 阐明:当咱们看到如左图的二叉树时,要立刻能脑补出对应的右图。#结点是什么? 后面咱们曾经画出了相似的图,过后是 NULL 结点,它的作用是标识某个结点没有孩子,它是咱们虚构进去的。在理论应用 C 语言发明二叉树时,须要应用 #或者什么其余的符号来代替 NULL. 上图的先序遍历程序为:ABDEGCF,如果加上 # 结点,则为:ABD##EG###C#F##. 咱们依照此程序来发明二叉树。 代码如下: /** * 发明一个二叉树 * root: 指向根结点的指针的指针 */void create_binary_tree(TreeNode **root){ char elem; scanf("%c", &elem); if (elem == '#') { *root = NULL; } else { *root = create_tree_node(elem); //发明一个二叉结点 create_binary_tree(&((*root)->left_child)); create_binary_tree(&((*root)->right_child)); }}请留神,函数 create_binary_tree 承受的是一个指向根结点的指针的指针,至于为什么要应用指针的指针,理由在介绍单链表的初始化时曾经解释了。 ...

April 19, 2021 · 2 min · jiezi

关于数据结构:树结构系列三B树B树

文章首发于「陈树义」公众号及集体博客 shuyi.tech 均衡二叉树的查找效率是十分高的,并能够通过升高树的深度来进步查找的效率。然而当数据量十分大,树的存储的元素数量是无限的,这样会导致二叉查找树结构因为树的深度过大而造成磁盘 I/O 读写过于频繁,进而导致查问效率低下。 而 B 树的呈现是为了解决这个问题,其能够一次性读入许多数据。一个节点不再只是存储一个数值,而是存储一个分片的数据。这样就能够防止频繁去读取磁盘数据,造成频繁的 IO 拜访,造成查找速度瓶颈。 B树B-Tree 其实就是 B 树,很多人都会说成 B 减树,其实是错的,要留神。 B 树不要和二叉树混同,B 树不是二叉树,而是一种自均衡树数据结构。 它保护有序数据并容许以对数工夫进行搜寻,程序拜访,插入和删除。B 树是二叉搜寻树的一般化,因为 B 树的节点能够有两个以上的子节点。 与其余自均衡二进制搜寻树不同,B 树非常适合读取和写入绝对较大的数据块(如光盘)的存储系统。它通常用于数据库和文件系统,例如 mysql 的 InnoDB 引擎应用的数据结构就是 B 树的变形 B+ 树。 B 树是一种均衡的多分树,通常咱们说 m 阶的 B 树,它必须满足如下条件: 每个节点最多只有 m 个子节点。每个非叶子节点(除了根)具备至多 ⌈m/2⌉ 子节点。如果根不是叶节点,则根至多有两个子节点。具备 k 个子节点的非叶节点蕴含 k -1 个键。所有叶子都呈现在同一程度,没有任何信息(高度一致)。 B 树的阶,指的是 B 树中节点的子节点数目的最大值。例如在上图的书中,「13,16,19」领有的子节点数目最多,一共有四个子节点(灰色节点)。所以该 B 树的阶为 4,该树称为 4 阶 B 树。在理论利用中,B 树利用于 MongoDb 的索引。 B+树B+ 树是应文件系统所需而产生的 B 树的变形树。B+ 树的特色: ...

April 12, 2021 · 1 min · jiezi

关于数据结构:详细图解学习队列看这一篇就够了

提要钩玄:本文次要介绍队列的构造、基本原理及操作,波及到两种实现:程序队列和链队列。 1. 什么是队列?先举一个日常例子,排队买饭。 大家按先来后到的程序,在窗口前排队买饭,先到先得,买完之后走开,轮到下一位买,新来的人排在队尾,不能插队。 可见,下面的“队”的特点是只容许从一端进入,从另一端来到。 这样的一个队,放在数据结构中就是“队列”。 首先,队列是一个线性表,所以它具备线性表的根本特点。 其次,队列是一个受限的线性表,受限之处为:只容许从一端进入队列,从另一端来到。 依据以上特点,能够画出示意图: 出队元素 1,入队元素 4 之后: 上面是几个相干名词: 入队:进入队列,即向队列中插入元素出队:来到队列,即从队列中删除元素队头:容许出队(删除)的一端队尾:容许入队(插入)的一端队头元素:队列中最先入栈的元素队尾元素:队列中最初入栈的元素咱们能够间接将队头元素看作队头,队尾元素看作队尾。(这些名词概念,有所了解即可,不用细究) 队列的重要个性是在队尾进行入队操作,在队头进行出队操作,所以上图元素的入队程序为:1、2、3,出队程序为:1、2、3,也即,先入队的先出队(First In First Out, FIFO),后入队的后出队(Last In Last Out, LILO). 总结一下,队列是一种只容许在一端进行插入操作,在另一端进行删除操作的先入先出的受限的线性表。 2. 队列的实现思路和栈一样,队列也能够有两种实现形式:数组实现的程序队列和链表实现的链队列。 2.1. 数组实现——程序队列一个用数组实现的程序队列如下图所示: 能够看到,要实现一个程序队列,咱们须要以下构造: 存储数据的数组 —— data[]示意队列的最大容量的值 —— MAXSIZE标识队头端的队头下标 —— front标识队尾端的队尾下标 —— rearfront 和 rear 会随着入队和出队操作而变动,为了不便起见,咱们规定在非空队列中,队尾下标是队尾元素的下一个元素的下标。 理解了构造之后,咱们能够很容易应用 C 语言的构造体实现它: #define MAXSIZE 5 //程序队列的最大存储容量/*程序队列的构造体*/typedef struct { int data[MAXSIZE]; int front; //队头下标 int rear; //队尾下标} QueueArray;2.2. 链表实现——链队列咱们应用带头节点的单链表来实现队列,如下图所示: 能够看到,要实现一个链队列,须要以下构造: 单链表的根本单元结点 —— QueueNode ...

April 6, 2021 · 3 min · jiezi

关于数据结构:树结构系列一从普通树到二叉查找树

文章首发于「陈树义」公众号及集体博客 shuyi.tech 树结构是数据结构中十分重要的一种类型,本文将从最根底的一般树结构入门,延长到二叉树,再延长至二叉查找树。通过这种思路,让大家构建起对于树的最根本的常识链路。 一般树树是一种非线性数据结构,它是数据元素按分支关系组织起来的构造,很像自然界中的树那样。 对于树的官网定义是:一棵树是由 N(N>0)个元素组成的无限汇合,其中: 每个元素称为结点(node)。有一个特定的结点,称为根结点或根(root)。除根结点外,其余结点被分成 m(m>=0)个互不相交的无限汇合,而每个子集又都是一棵树(称为原树的子树)。对于树有几个重要的概念,这里简略做下介绍: 度度即节点的分支数,例如上图树中节点 A 有三个子节点,那么咱们称节点 A 的度是 3。 档次节点的档次,示意节点在书中的地位。根结点的档次为 1,其余结点的档次等于它的父结点的档次数加 1。例如上图中节点 F 的档次为 3。 深度树的深度,即组成该树各节点的最大档次。例如上图中节点的最大档次为 K/L/M,那么树的深度就为 K/L/M 任何一个的档次,即树的深度为 4。 门路对于一棵子树中的任意两个不同的结点,如果从一个结点登程,按档次自上而下沿着一个个树枝能达到另一结点,称它们之间存在着一条门路。可用门路所通过的结点序列示意门路,门路的长度等于门路上的结点个数减 1。 例如上图中 A 到 L 的门路为:A > B > E > L,其门路结点个数为 4,那么其长度为 3。 二叉树二叉树其实就是在一般树的根底上,加上了对树的度限度,即每个节点最多只能有两个子节点。 二叉树有五种根本的状态: 1、空二叉树 —— 如图 a 所示。2、只有一个根结点的二叉树 —— 如图 b 所示。3、只有左子树 —— 如图 c 所示。4、只有右子树 —— 如图 d 所示。5、齐全二叉树 —— 如图 e 所示。 二叉树是一种简略且重要的树,许多其余类型的树都建设在二叉树的根底之上。依据叶子节点的不同情景,咱们能够将二叉树再细分为:完美二叉树、齐全二叉树、满二叉树。 完美二叉树,指的是所有非叶子节点的度都是 2(只有你有孩子,你必然有两个孩子)。 ...

April 6, 2021 · 1 min · jiezi

关于数据结构:几种常见数据结构

二叉树二叉树的特点是,左子树的键值小于根的键值,右子树的键值大于根的键值 然而二叉树在某些场景下,会是这样的构造这种构造的二叉树是非均衡的,层级过高,甚至曾经进化成链表,会导致查问效率过低 均衡二叉树(AVL)在二叉树的根底上,衍生出了均衡的二叉树,均衡二叉树的规定是,任何节点的两个子树的高度差<=1 以上述二叉树为例,在插入1~5的时候,过程如下能够看见每次子树的高度差在大于1的时候,均衡二叉树都会进行旋转,让构造保持平衡 依据失衡前的状态,能够分为4种状态LL:插入或删除一个节点后,发现根节点的左节点的左节点下还有非空节点,导致根节点的左节点的高度比根节点的右节点的高度大2,均衡树失衡,须要进行旋转来保持平衡,示意图如下 LR:插入或删除一个节点后,发现根节点的左节点的右节点下还有非空节点,导致根节点的左节点的高度比根节点的右节点的高度大2,均衡树失衡,须要进行旋转来保持平衡,示意图如下 RR:插入或删除一个节点后,发现根节点的右节点的右节点下还有非空节点,导致根节点的右节点的高度比根节点的左节点的高度大2,均衡树失衡,须要进行旋转来保持平衡,示意图如下 RL:插入或删除一个节点后,发现根节点的右节点的左节点下还有非空节点,导致根节点的右节点的高度比根节点的左节点的高度大2,均衡树失衡,须要进行旋转来保持平衡,示意图如下 均衡二叉树谋求相对均衡,每次插入或删除新节点之后须要旋转的次数不能预知,如果相干的插入和删除操作并不频繁,而是查找操作绝对更频繁,那么就优先选择均衡二叉树树进行实现 红黑树红黑树也是一种均衡的二叉树,不过相比于AVL树,红黑树的均衡没有那么相对,红黑树须要一直的去变色或者旋转来满足以下规定: 节点是红色或彩色。根是彩色。所有叶子都是彩色(叶子是NIL节点)。每个红色节点必须有两个彩色的子节点。(从每个叶子到根的所有门路上不能有两个间断的红色节点。)从任一节点到其每个叶子的所有简略门路都蕴含雷同数目的彩色节点(简称黑高)。Hash表说到hash表,很多人会想到hashmap,hash表是一种基于散列,在关键字key和值value之间建设映射关系f的数据结构,这种映射关系f咱们称为哈希函数,通过计算并存储value的这块间断存储空间称为哈希表,通过计算的key失去的存储地址称为哈希地址。 1.哈希函数哈希函数有多种计算方法 间接定址法:将key的某个线性函数值作为哈希地址数字分析法:选取key中的一部分作为运算参数计算哈希地址平方取中法 :对key进行平方计算,取两头段作为哈希地址折叠法:将key分成长度雷同的几段,计算叠加和,再选取后几位作为哈希地址除留余数法 :对key间接取模(求余数),将值作为哈希地址随机数法:取key的随机函数值作为哈希地址2.哈希碰撞(哈希抵触)在进行哈希函数计算之后,失去的hash值仍然有相等的可能,这种景象称作哈希碰撞,智慧的先驱者们又想出了几种计划来解决这种抵触问题。 3.哈希碰撞解决方案凋谢地址法:发生冲突就去探测寻找下一个空的地址,凋谢地址法有可能会产生多个不同key探测到同一个空的地址并进行竞争的景象,称为沉积再哈希:发生冲突后,采纳另一个哈希函数进行哈希运算,直到失去一个惟一的哈希地址链表地址法:对于每个哈希地址,都保护一个单向链表进行value的存储,链表的每个节点都存储下一个节点的指针地址,hashmap就是用的这种数据结构公共溢出区法:保护两张表,根本表和溢出表,将未发生冲突的数据放入根本表,将发生冲突的数据放入溢出表,查问时先在根本表中查找到对应哈希地址的地位并比拟(揣测应该是在哈希地址雷同的根底上进一步比拟key),如果不相等再去溢出表中查找ps:对于溢出表中多个雷同哈希地址的数据,我也查阅了很多材料,然而这块没有说的比拟清晰的,集体猜想溢出表的构造也是以链表的模式进行存储的B-treeB-tree的构造如图所示,它的所有指针,或者叫索引元素不反复,因而,B-tree的数据在每一阶都有相应的存储数据。另外,它的叶子节点没有指针,只单纯的存储数据。 (相干图例援用自https://blog.csdn.net/a764340703/article/details/82621781) B+treeB+tree的构造如图所示,它和B-tree最大的区别在于: B+tree的非叶子节点会有冗余索引B+tree的所有数据全副存储在叶子节点上,这样操作益处在于同阶条件下,能存储更多的数据B+tree的叶子节点有双向指针,对于范畴查找的效率能大大晋升 更多的B+tree构造在数据库中的利用我会在mysql章节介绍,敬请期待 举荐一个数据结构可视化网站,来自旧金山大学 David Galleshttps://www.cs.usfca.edu/~galles/visualization/Algorithms.html

March 30, 2021 · 1 min · jiezi

关于数据结构:树结构系列开篇聊聊如何学习树结构

文章首发于「陈树义」公众号及集体博客 shuyi.tech 树是一种十分实用的数据结构,最罕用的就是数据库的索引,用于在海量数据查找目标值。举个例子,如果你的表有 1 亿的数据。如果应用链表来存储,那么你最坏状况下须要遍历 1 亿次能力找到目标值。 但如果你应用红黑树来查找,那你最坏状况下的工夫复杂度为 O(logN),即最坏只须要 27 次就能够找到。27 次查找与 1 亿次的查找,这两头相差了 300 万倍,由此可见树结构带来的效率晋升之微小! 那咱们应该如何去学习树这种数据结构呢? 其实树这种数据结构也是有其常识体系的,每一种树结构的诞生都有其起因及理论场景。咱们须要找到这种场景,梳理出一条主线,将这些知识点串起来,造成一个常识体系。 例如咱们所晓得的树结构有: 树二叉树二叉查找树二叉均衡树AVL树红黑树B-树B+树树堆2-3树舒展树Trie树字母树哈夫曼树等等对于树的数据结构不可胜数,让咱们目迷五色。但在这么多树结构中,我梳理出了一条次要的外围链路。这条外围链路涵盖了咱们日常最长应用的树结构,并且它们之间还是关联亲密的,这条链路我取名叫:树结构小道! 树结构小道开始于最根底的树的定义,它最为简略、没有过多的限度。在树定义的根底上,如果咱们限度每个节点最多有两个子树,那么它就变成了二叉树。在二叉树的根底上,如果咱们限度左节点要小于本节点、右节点要大于本节点,那么它就变成了二叉查找树(BST)。 二叉搜寻树在最坏的状况下调演变成链表,即二叉搜寻树的左右子树失衡了(根节点的右边一个节点都没有,所有节点都偏差左边了)。而咱们都晓得树的查找效率取决于树的高度,如果演变成链表,那么查找效率就从 O(logN) 变成了 O(N)。为了管制左右子树的高度,就诞生了均衡二叉树这种构造。均衡二叉树就是用来解决二叉查找树失衡问题的。 咱们最常听见的 AVL 树和红黑树,其实都是均衡二叉树,只不过它们的侧重点不同。AVL树侧重于查问多、增删少的场景,因而其均衡度较高。而红黑树侧重于增删频繁的场景,因而其均衡度较低。 均衡二叉树基本上能解决绝大多数的问题,但此时又有一次问题诞生了。如果咱们的树结构很大,有几十亿的数据,咱们如何去搭建一颗均衡二叉树?最常见的是数据库一个表几十亿的数据,咱们如何对其进行排序?如果间接将数据加载到内存,那么内存势必会爆表,不可行。 此时 B 树(B-Tree)诞生了! B 树采纳磁盘块的形式解决了数据存储空间的问题。简略地说,应用均衡二叉树时咱们的树节点每次只能比拟一个值。如果有几十亿个节点,那咱们就必须往内存中加载几十亿个数字,构建几十亿个树节点。而 B 树的一个节点对应了一个磁盘块,而这一个磁盘块有 4KB 大小的数据,这样咱们所构建的树节点规模就缩减了 4K 倍之巨!B 树通过减少每个节点的数据数量,缩小了加载到内存的数据大小、树的高度,从而解决了均衡二叉树在大数据量查找时的可行性及效率问题。 但 B 树还有一个问题,及它在范畴查找方面性能很差。 此时 B+ 树(B+ Tree)诞生了! B+ 树在 B 树的根底上,在每个根节点增加一个向右的指针,能够间接获取到下一个元素。通过这种形式,咱们只须要找到查找范畴里最小的元素之后,再做一次链表查问就能够了,极大地提高了查问效率。简略地说,B+ 树通过叶节点的指针,解决了 B 树范畴查找效率慢的问题。 看到这里,咱们的「树结构小道」基本上完结了。咱们从最根底的树结构登程,一路通过二叉树、二叉查找树、均衡二叉树(AVL树、红黑树)、B树、B+树。通过它们的诞生背景及利用场景,将它们串了起来。 通过这么一梳理,你会发现咱们所说的内容曾经涵盖大部分日常知识点。那么剩下的知识点怎么办呢?我的倡议是遇到的时候独自理解,而后将其增加到咱们的常识体系中。 到目前为止,我的树结构常识体系如下图所示: 接下来几篇文章,我将具体带大家旅行「树结构小道」,欢送大家关注~

March 22, 2021 · 1 min · jiezi

关于数据结构:LiteOS盘点那些重要的数据结构

摘要:本文会给读者介绍下LiteOS源码中罕用的几个数据结构,包含: 双向循环链表LOS_DL_LIST,优先级队列Priority Queue,排序链表SortLinkList等。在学习Huawei LiteOS源代码的时候,经常会遇到一些数据结构的应用。如果没有把握这它们的用法,浏览LiteOS源代码的时候会很费解、很吃力。本文会给读者介绍下LiteOS源码中罕用的几个数据结构,包含: 双向循环链表LOS_DL_LIST,优先级队列Priority Queue,排序链表SortLinkList等。在解说时,会联合相干的绘图,造就数据结构的立体设想能力,帮忙更好的学习和了解这些数据结构用法。 本文中所波及的LiteOS源码,均能够在LiteOS开源站点https://gitee.com/LiteOS/LiteOS 获取。 咱们首先来看看应用最多的双向循环链表Doubly Linked List。 1、LOS_DL_LIST 双向循环链表双向链表LOS_DL_LIST外围的代码都在kernelincludelos_list.h头文件中,蕴含LOS_DL_LIST构造体定义、一些inline内联函数LOS_ListXXX,还有一些双向链表相干的宏定义LOS_DL_LIST_XXXX。 双向链表源代码、示例程序代码、开发文档如下: kernelincludelos_list.h 双向链表头文件网页获取源码 https://gitee.com/LiteOS/Lite...。 demoskernelapilos_api_list.c 双向链表Demo程序网页获取源码 https://gitee.com/LiteOS/Lite...。 开发指南双向链表文档在线文档https://gitee.com/LiteOS/Lite... 1.1 LOS_DL_LIST 双向链表构造体双向链表构造体LOS_DL_LIST定义如下。看得出来,双向链表的构造非常简单、通用、形象,只蕴含前驱、后继两个节点,负责承前启后的双向链表作用。双向链表不包任何业务数据信息,业务数据信息保护在业务的构造体中。双向链表作为业务构造体的成员应用,应用示例稍后会有讲述。 typedef struct LOS_DL_LIST { struct LOS_DL_LIST *pstPrev; /** 以后节点的指向前驱节点的指针 */ struct LOS_DL_LIST *pstNext; /** 以后节点的指向后继节点的指针 */} LOS_DL_LIST; 从双向链表中的任意一个结点开始,都能够很不便地拜访它的前驱结点和后继结点,这种数据结构模式使得双向链表在查找、插入、删除等操作,对于十分不便。因为双向链表的环状构造,任何一个节点的位置都是平等的。从业务上,能够创立一个节点作为Head头节点,业务构造体的链表节点从HEAD节点开始挂载。从head节点的顺次遍历下一个节点,最初一个不等于Head节点的节点称之为Tail尾节点。这个Tail节点也是Head节点的前驱。从Head向前查找,能够更快的找到Tail节点。 咱们看看LiteOS内核代码中如何应用双向链表构造体的。上面是互斥锁构造体LosMuxCB定义,其中蕴含双向链表LOS_DL_LIST muxList;成员变量: typedef struct { LOS_DL_LIST muxList; /** 互斥锁的双向链表*/ LosTaskCB *owner; /** 以后持有锁的工作TCB */ UINT16 muxCount; /** 持有互斥锁的次数 */ UINT8 muxStat; /** 互斥锁状态OS_MUX_UNUSED, OS_MUX_USED */ UINT32 muxId; /** 互斥锁handler ID*/} LosMuxCB; 双向循环链表能够把各个互斥锁链接起来,链表和其余业务成员关系如下图所示: ...

February 23, 2021 · 6 min · jiezi

关于数据结构:如何设计一个基于对象的链表

前言数据结构,应该大家或多或少都晓得,作为一个前端用的最多的数据结构就是数组了;在写业务中必然离不开数组,因为它岂但存储不便,操作还不便。在不思考效率的状况在,存储数据数组是集体的不二抉择。但数据结构还有一个叫“链表”的货色,“链表”在前端可能用的比拟少,反正作为底层人员,拿命换钱的我,工作了两年多还没用过“链表”,平时都是用数组,尽管可能用不到,但也无妨我学习。“链表”绝对于数组的增,删还是有劣势的,尽管查问比拟“拉胯”。古人云:“择其善者而从之”。链表单向链表是最一般的链表构造了,它是由多个节点组成相似链装的数据结构,样子大略如下: 链表会有一个非凡的节点叫“head”,它寄存第一个节点。每个节点会蕴含一个元素和一个指向下一个元素的“指针(next)”;最初一个节点的next会指向“空(null,undefind)”。 在js中,尽管有“链表”概念,然而它却没有提供内置创立“链表”的构造函数,不像一些后端语言有内置的构造函数。那么在js中,想要用“链表”存储数据,只能本人手动实现一些构造函数和办法。 设计一个链表它应该蕴含以下这些货色: 链表构造函数节点构造函数push办法(增加一个节点到链表最初)insert办法 (增加一个元素到指定的地位)removeAt办法 (删除一个指定地位的节点)removeItem办法(删除一个指定元素的节点)getElementAt办法 (获取一个指定地位的节点)indexOf办法(获取一个元素的节点地位)show办法 (展现所有链表数据)size办法 (链表节点个数)getHead办法 (获取头节点)isEmpty办法 (判断列表是否为空)可能还有其余的,临时没想到需要弄明确了,接下来就一步一步的实现一个链表构造。(为了不便了解,不会对代码进行封装,尽管有比拟多相似代码) 节点构造函数一个一般的节点,只须要一个元素(element)和 一个指针(next)就行,无需其余多余的货色。 class Node { constructor(elememt){ this.elememt = elememt this.next = undefined }}链表构造函数链表构造函数须要一个头节点(head)和保留节点个数的count和下面的那些办法。 class LinkedList { constructor(){ this.count = 0 this.head = new Node('head') }}创立好构造后,就能够实现操作链表的办法了,当初链表的样子如下: push办法实现该办法前,先通过一张图理解一下它的增加逻辑。 push办法是在链表最初增加一个节点,假如,咱们要增加一个“张三”,那么就要通过Node构造函数创立一个叫“张三”的节点,而后,找到该链表的最初一个节点,断开它next指向undefined的链接,并将它指向新节点(如上图)。 逻辑分明,那么实现起来就不费劲了。 push(ele){ // 创立新节点 let newEle = new Node(ele) let current = this.head // 找到最初的那个节点 while(current.next !== undefined){ current = current.next } // 扭转最初一个节点的指针指向 current.next = newEle // 节点个数加1 this.count++}insert办法 ...

January 5, 2021 · 3 min · jiezi

关于数据结构:排序算法的性质

最好工夫复杂度均匀工夫复杂度最坏工夫复杂度空间复杂度稳定性每趟是否能确定一个元素的地位实用于比拟次数与初始序列是否无关间接插入排序O(n)O(n^2)O(n^2)O(1)稳no程序和链式no折半插入排序 O(n^2) O(1)稳no仅程序no希尔排序(放大增量排序) O(n^1.3) O(1)不稳no仅程序与增量序列无关冒泡排序O(n)O(n^2)O(n^2)O(1)稳yes仅程序yes疾速排序(均匀性能最优)O(nlog2n)O(nlog2n)O(n^2)O(log2n)不稳yes仅程序yes,取决于划分操作简略抉择排序O(n^2)O(n^2)O(n^2)O(1)不稳yes程序和链式no堆排序O(nlog2n)O(nlog2n)O(nlog2n)O(1)不稳yes仅程序no2路归并排序O(nlog2n)O(nlog2n)O(nlog2n)O(n)稳no仅程序 基数排序O(d(n+r))O(d(n+r))O(d(n+r))O(r)基你太稳no通常采纳链式no

December 12, 2020 · 1 min · jiezi

关于数据结构:数据结构与抽象Java语言描述pdf

关注“Java后端技术全栈” 回复“面试”获取全套面试材料 什么是数据结构? 数据结构是计算机中存储、组织数据的形式。 通常状况下,精心抉择的数据结构能够带来最优效率的算法。 什么是抽象数据类型? 数据类型,它蕴含了两个货色,一个是“数据对象集”,就是咱们说的“是什么货色”,第二个是“数据汇合相关联的操作集”,对数据的操作。 形象,形象的意思就是“不具体”,就是说,形容数据类型的办法是不依赖于具体的实现的。 这些都是JAVA中十分重要的概念,作为程序员肯定要把握的内容。 最近很多小伙伴问我要一些 数据结构 相干的材料,于是我翻箱倒柜,找到了这本十分经典的电子书——《数据结构与形象:Java语言形容》。 材料介绍 《数据结构与形象:Java语言形容》是一本数据结构的教材,全书介绍了计算机编程中应用的数据结构和算法,包含29章,每章波及一个ADT或其不同实现的规格阐明和用法。本书非常适合作为大学本科生数据结构课程的教材,也可作为计算机钻研与开发人员的参考书。 如何获取? 1.辨认二维码并关注公众号「Java后端技术全栈」; 2.在公众号后盾回复关键字「925」。

December 11, 2020 · 1 min · jiezi

关于数据结构:数据结构与算法分析-分享下载

书籍信息书名: 数据结构与算法剖析原作名: Data Structures and Algorithm Analysis in C:Second Edition作者: [美] Mark Allen Weiss豆瓣评分: 8.9分(1966人评估)标签: 数据结构,算法,计算机,数据结构与算法剖析,算法、数据结构,编程,C,C语言内容简介本书是《Data Structures and Algorithm Analysis in C》一书第2版的简体中译本。原书曾被评为20世纪顶尖的30部计算机著述之一,作者Mark Allen Weiss在数据结构和算法剖析方面卓有建树,他的数据结构和算法剖析的著述尤其滞销,并受到宽泛好评.已被世界500余所大学用作教材。在本书中,作者更加精炼并强化了他对算法和数据结构方面翻新的解决办法。通过C程序的实现,着重论述了抽象数据类型的概念,并对算法的效率、性能和运行工夫进行了剖析。全书特点如下:●专用一章来探讨算法设计技巧,包含贪心算法、分治算法、动静布局、随机化算法以及回溯算法●介绍了以后风行的论题和新的数据结构,如斐波那契堆、斜堆、二项队列、跳跃表和舒展树●安顿一章专门探讨摊还剖析,考查书中介绍的一些高级数据结构●新开拓一章探讨高级数据结构以及它们的实现,其中包含红黑树、自顶向下舒展树。treap树、k-d树、配对堆以及其余相干内容●合并了堆排序均匀状况剖析的一些新后果本书是国外数据结构与算法剖析方面的规范教材,介绍了数据结构(大量数据的组织办法)以及算法剖析(算法运行工夫的估算)。本书的编写指标是同时讲授好的程序设计和算法剖析技巧,使读者能够开发出具备最高效率的程序。 本书可作为高级数据结构课程或研究生一年级算法剖析课程的教材,应用本书需具备一些中级程序设计常识,还须要离散数学的一些背景常识。作者简介Mark Allen Weiss,1987年在普林斯顿大学取得计算机科学博士学位,师从Robert Sedgewick (师从Knuth),现任美国佛罗里达国内大学计算与信息科学学院传授。他已经负责全美AP(Advanced Placement)考试计算机学科委员会的主席(2000-2004)。他的次要钻研方向是数据结构、算法和教育学。下载地址https://590m.com/file/1876512...

December 9, 2020 · 1 min · jiezi

关于数据结构:数据结构与算法系列之散列表一GO

对于散列表的代码实现及下边实际局部的代码实现均可从我的Github获取(欢送star^_^) 散列思维概念散列表(Hash Table),也能够叫它哈希表或者Hash表 散列表用的是数组反对依照下标随机拜访数据的个性,所以散列表其实就是数组的一种扩大,由数组演变而来。能够说,如果没有数组,就没有散列表 举例假如全校有1000名学生,为了不便记录他们的期末问题,会给每个学生一个编号,编号从1~1000。当初如果要实现通过编号来找到具体的学生 能够把这1000个学生的信息放在数组里。编号为1的学生,放到数组中下标为1的地位;编号为2的学生,放到数组中下标为2的地位。以此类推,编号为k的学生放到数组中下标为k的地位 因为编号跟数组下标一一对应,当咱们须要查问编号为x的学生的时候,只须要将下标为x的数组元素取出来就能够了,工夫复杂度就是O(1) 实际上,这个例子曾经用到了散列的思维。在这个例子里,编号是自然数,并且与数组的下标造成一一映射,所以利用数组反对依据下标随机拜访的个性,查找的工夫复杂度是O(1) ,就能够实现疾速查找编号对应的学生信息 然而,上边这个例子用到的散列思维不够显著,改一下 假如编号不能设置得这么简略,要加上年级、班级这些更具体的信息,所以这里把编号的规定略微批改了一下,用8位数字来示意。比方05110067,其中,前两位05示意年级,两头两位11示意班级,最初四位还是原来的编号1到1000。这个时候该如何存储学生信息,才可能反对通过编号来疾速查找学生信息呢? 思路还是跟后面相似。只管不能间接把编号作为数组下标,但能够截取编号的后四位作为数组下标,来存取学生信息数据。当通过编号查问学生信息的时候,用同样的办法,取编号的后四位,作为数组下标,来读取数组中的数据 这就是典型的散列思维。其中,学生的编号叫作键(key)或者关键字。用它来标识一个学生。把学生编号转化为数组下标的映射办法就叫作散列函数(或“Hash函数”“哈希函数”),而散列函数计算失去的值就叫作散列值(或“Hash 值”“哈希值”) 总结散列表用的就是数组反对依照下标随机拜访的时候,工夫复杂度是O(1)的个性。通过散列函数把元素的键值映射为下标,而后将数据存储在数组中对应下标的地位。当依照键值查问元素时,用同样的散列函数,将键值转化数组下标,从对应的数组下标的地位取数据 散列函数概念散列函数,顾名思义,它是一个函数。能够把它定义成hash(key) ,其中key示意元素的键值,hash(key)的值示意通过散列函数计算失去的散列值 在上边的例子中,编号就是数组下标,所以hash(key)就等于 key。革新后的例子,写成散列函数就像下边这样 func hash(key string) int { hashValue,_ := strconv.Atoi(key[len(key)-4:]) return hashValue}散列函数根本要求上边的的例子,散列函数比较简单,也比拟容易想到。然而,如果学生的编号是随机生成的6位数字,又或者用的是a到z之间的字符串,这种状况,散列函数就会简单一些 散列函数设计的根本要求 散列函数计算失去的散列值是一个非负整数如果key1 = key2,那hash(key1) == hash(key2)如果key1 ≠ key2,那hash(key1) ≠ hash(key2)第一点了解起来应该没有任何问题。因为数组下标是从0开始的,所以散列函数生成的散列值也要是非负整数。第二点也很好了解。雷同的key,通过散列函数失去的散列值也应该是雷同的 第三点了解起来可能会有问题。这个要求看起来荒诞不经,然而在实在的状况下,要想找到一个不同的key对应的散列值都不一样的散列函数,简直是不可能的。即使像业界驰名的MD5、SHA、CRC等哈希算法,也无奈完全避免这种散列抵触。而且,因为数组的存储空间无限,也会加大散列抵触的概率 所以,简直无奈找到一个完满的无抵触的散列函数,即使能找到,付出的工夫老本、计算成本也是很大的,所以针对散列抵触问题,须要通过其余路径来解决 散列抵触凋谢寻址法凋谢寻址法的核心思想是,如果呈现了散列抵触,就从新探测一个闲暇地位,将其插入。从新探测一个闲暇地位的办法有好几个,这里以线性探测举例 当往散列表中插入数据时,如果某个数据通过散列函数散列之后,存储地位曾经被占用了,就从以后地位开始,顺次往后查找,看是否有闲暇地位,直到找到为止。看下图 从图中能够看出,散列表的大小为10,在元素x插入散列表之前,曾经有6个元素插入到散列表中。x通过hash算法之后,被散列到下标为7的地位,然而这个地位曾经有数据了,所以就产生了抵触。于是就程序地往后一个一个找,看有没有闲暇的地位,遍历到尾部都没有找到闲暇的地位,于是再从表头开始找,直到找到闲暇地位2,于是将其插入到这个地位 在散列表中查找元素的过程相似插入过程。通过散列函数求出要查找元素的键值对应的散列值,而后比拟数组中下标为散列值的元素和要查找的元素。如果相等,则阐明就是咱们要找的元素;否则就程序往后顺次查找。如果遍历到数组中的闲暇地位,还没有找到,就阐明要查找的元素并没有在散列表中 散列表和数组一样,也反对插入、查找、删除操作,然而对于线性探测办法解决散列抵触,在进行删除操作时比拟非凡,不能单纯地把要删除的元素设置为空 上边在说散列表的查找操作时,通过线性探测的形式找到一个闲暇地位,而后就认定散列表中不存在该数据。如果说这个闲暇地位是起初删除的,就会导致原来的线性探测有问题,可能原本存在的数据,认为不存在了 这种状况下,就须要将删除的元素加一个删除的标记。在进行线性探测的时候,如果遇到删除标记的元素,则持续向下探测 小伙伴必定曾经看出问题了,当散列表中插入的数据越来越多时,散列抵触产生的可能性就会越来越大,闲暇地位会越来越少,线性探测的工夫就会越来越久。极其状况下,可能须要探测整个散列表,所以最坏状况下的工夫复杂度为O(n)。同理,在删除和查找时,也有可能会线性探测整张散列表,能力找到要查找或者删除的数据 对于凋谢寻址抵触解决办法,除了线性探测办法之外,还有另外两种比拟经典的探测办法,二次探测(Quadratic probing)和双重散列(Double hashing) 二次探测二次探测跟线性探测很像,线性探测每次探测的步长是1,它探测的下标序列就是hash(key)+0,hash(key)+1,hash(key)+2……而二次探测探测的步长就变成了原来的“二次方”,它探测的下标序列是hash(key)+0,hash(key)+1^2,hash(key)+2^2,hash(key)+3^2…… 双重散列双重散列意思就是不仅要应用一个散列函数。应用一组散列函数 hash1(key),hash2(key),hash3(key)……先用第一个散列函数,如果计算失去的存储地位曾经被占用,再用第二个散列函数,顺次类推,直到找到闲暇的存储地位 凋谢寻址法总结不论采纳哪种探测办法,当散列表中闲暇地位不多的时候,散列抵触的概率就会大大提高。为了尽可能保障散列表的操作效率,个别状况下,会尽可能保障散列表中有肯定比例的闲暇槽位。用装载因子(load factor)来示意空位的多少 装载因子的计算公式 散列表的装载因子 = 填入表中的元素个数 / 散列表的长度装载因子越大,阐明闲暇地位越少,抵触越多,散列表的性能会降落 链表法链表法是一种更加罕用的散列抵触解决办法,相比凋谢寻址法,它比较简单。在散列表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值雷同的元素都放到雷同槽位对应的链表中 当插入的时候,只须要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的工夫复杂度是O(1)。当查找、删除一个元素时,同样通过散列函数计算出对应的槽,而后遍历链表查找或者删除 对于查找和删除操作,工夫复杂度跟链表的长度k成正比,也就是 O(k)。对于散列比拟平均的散列函数来说,实践上讲,k=n/m,其中n示意散列中数据的个数,m示意散列表中“槽”的个数 ...

December 9, 2020 · 1 min · jiezi

关于数据结构:图解数据结构开篇

图解数据结构作者:天行加入了 lucifer 的 91 天学算法流动,人不知;鬼不觉中曾经一月无余。从自觉地做到有目标、有套路地去做。 在 lucifer 的 91 课程中,从根底到进阶到专题,在这个月中,经验了根底篇的洗礼,不论在做题思路,还是做题速度都有了很大的晋升,这个课程,没什么好说的,点赞点赞再点赞。也意识到学习好数据结构有多重要,不仅是思维形式的扭转,还是在工程上的利用。 对一个问题应用画图、举例、合成这 3 种办法将其化繁为简,造成清晰思路再入手写代码,一张好的图可能更好地帮忙去了解一个算法。因而本次分享如何应用画图同时联合经典的题目的办法去论述数据结构。 <!-- more --> 数据结构与算法有用么?这里我摘录了一个知乎的高赞答复给大家做参考: 集体认为数据结构是编程最重要的基本功没有之一!学了程序表和链表,你就晓得,在查问操作更多的程序中,你应该用程序表;而批改操作更多的程序中,你要应用链表;而单向链表不不便怎么办,每次都从头到尾好麻烦啊,怎么办?你这时就会想到双向链表 or 循环链表。 学了栈之后,你就晓得,很多波及后入先出的问题,例如函数递归就是个栈模型、Android 的屏幕跳转就用到栈,很多相似的货色,你就会第一工夫想到:我会用这货色来去写算法实现这个性能。 学了队列之后,你就晓得,对于先入先出要排队的问题,你就要用到队列,例如多个网络下载工作,我该怎么去调度它们去取得网络资源呢?再例如操作系统的过程(or 线程)调度,我该怎么去分配资源(像 CPU)给多个工作呢?必定不能全副一起领有的,资源只有一个,那就要排队!那么怎么排队呢?用一般的队列?然而对于那些优先级高的线程怎么办?那也太共产主义了吧,这时,你就会想到了优先队列,优先队列怎么实现?用堆,而后你就有疑难了,堆是啥玩意?本人查吧,敲累了。 总之好好学数据结构就对了。我感觉数据结构就相当于:我塞牙了,那么就要用到牙签这“数据结构”,当然你用指甲也行,只不过“性能”没那么好;我要拧螺母,必定用扳手这个“数据结构”,当然你用钳子也行,只不过也没那么好用。学习数据结构,就是为了理解当前在 IT 行业里搬砖须要用到什么工具,这些工具有什么利弊,利用于什么场景。当前用的过程中,你会发现这些根底的“工具”也存在着一些缺点,你不满足于此工具,此时,你就开始本人在这些数据结构的根底上加以革新,这就叫做自定义数据结构。而且,你当前还会造出很多其余利用于理论场景的数据结构。。你用这些数据结构去造轮子,人不知;鬼不觉,你成了又一个轮子哥。既然这么有用,那咱们怎么学习呢?我的倡议是先把常见的数据结构学个大略,而后开始装置专题的模式冲破算法。这篇文章就是给大家疾速过一下一部分常见的数据结构。 从逻辑上分,数据结构分为线性和非线性两大类。 线性数据结构包含数组、链表、栈、队列。非线性构造包含树、哈希表、堆、图。而咱们罕用的数据结构次要是数组、链表、栈、树,这同时也是本文要讲的内容。 数据结构一览数组数组的定义为寄存在间断内存空间上的雷同类型数据的汇合。因为内存空间间断,所以能在 O(1)的工夫进行存取。 剑指 offer03.数组中的反复的数字题目形容: 在一个长度为 n 的数组 nums 里的所有数字都在 0 ~ n-1 的范畴内。数组中某些数字是反复的,但不晓得有几个数字反复了,也不晓得每个数字反复了几次。请找出数组中任意一个反复的数字。剖析: 反复象征至多呈现两次,那么找反复就变成了统计数字呈现的频率了。那如何统计数字频率呢?(不应用哈希表),咱们能够开拓一个长度为 n 的数组 count_nums,并且初始化为 0,遍历数组 nums,应用 nums[i]为 count_nums 赋值. 图解: (留神:数组下标从 0 开始) 剑指 offer21. 调整数组程序使奇数位于偶数后面题目形容: 输出一个整数数组,实现一个函数来调整该数组中数字的程序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半局部。剖析: 依据题目要求,须要咱们调整数组中奇偶数的程序,那这样的话,咱们能够从数组的两端同时开始遍历,左边遇到奇数的时候停下,右边遇到偶数的时候停下,而后进行替换。 1122.数组的绝对排序题目形容: 给你两个数组,arr1 和  arr2,arr2  中的元素各不相同arr2 中的每个元素都呈现在  arr1  中对 arr1  中的元素进行排序,使 arr1 中项的绝对程序和  arr2  中的绝对程序雷同。未在  arr2  中呈现过的元素须要依照升序放在  arr1  的开端。示例输出:arr1 = [2,3,1,3,2,4,6,7,9,2], arr2 = [2,1,4,3,9,6]输入:[2,2,2,1,4,3,3,9,6,7]剖析: ...

December 3, 2020 · 3 min · jiezi

关于数据结构:数据结构与算法系列之跳表GO

具体理解跳表前边的一篇文章中分享了二分查找算法,里边有说到二分查找算法依赖数组的随机拜访个性,只能用数组来实现。如果数据存储在链表中就没法用二分查找算法了 本篇文章分享的「跳表」,能够实现相似二分的查找算法,工夫复杂度也是「O(logn)」 假如有一个有序的单链表,如果想从该链表中查找某一个数据,只能从头到尾的遍历查找,它的工夫复杂度是O(n)。如何进步查找的效率? 假如我像下图这样,在原来的单链表上减少一级索引,每两个节点取一个结点到「索引层」。其中down为指向下一级节点的指针 假如当初须要查找【16】这个节点。能够先遍历索引层节点,当遍历到【13】这个节点时,发现下一个节点为【17】,那么咱们要找的节点【16】必定就在它们两头。此时,通过索引节点【13】的down指针,降落到原始链表层中持续遍历,此时只须要再遍历两个节点就找到了咱们要查找的【16】。如果间接在原始链表中查找【16】,须要遍历10个结点,当初只须要遍历7个结点 从上边能够发现,加了一层索引之后,查找一个节点须要遍历的节点数缩小了,也就意味着查找的效率进步了,如果咱们减少更多的索引层,效率会不会进步更多? 假如当初在原来的一级索引上边按同样的办法,再减少一层索引,如果再查【16】这个节点,只须要遍历6个结点了。查找一个节点须要遍历的节点又缩小了 上边的例子中,原始链表的节点数量并不多,每减少一层索引,查问效率进步的并不显著,下边假如原始链表中有64个节点,建设5级索引 从图中能够看出,原来没有索引的时候,查找【62】这个节点须要遍历62个节点,当初只须要遍历11个节点,速度进步了很多。当链表的长度n比拟大时,比方1000、10000的时候,在构建索引之后,查找效率的晋升就会非常明显。「这种链表加多级索引的构造,就是跳表」 跳表的工夫复杂度如果链表里有n个结点,能够有多少级索引? 依照上边例子中的那种形式,每两个结点会抽出一个结点作为上一级索引的结点,那第一级索引的结点个数大概就是n/2,第二级索引的结点个数大概就是n/4,第三级索引的结点个数大概就是n/8,顺次类推,也就是说,第k级索引的结点个数是第k-1级索引的结点个数的1/2,那第k级索引结点的个数就是 n/(2^k) 假如索引有h级,最高级的索引有2个结点。通过下面的公式,能够失去n/(2^h)=2,从而求得 h=log2n-1(2为底)。如果蕴含原始链表这一层,整个跳表的高度就是log2n(2为底)。「在跳表中查问某个数据的时候,如果每一层都要遍历m个结点,那在跳表中查问一个数据的工夫复杂度就是O(m*logn)」 这个m的值是多少?依照上边的例子中这种索引构造,咱们每一级索引都最多只须要遍历3个结点,也就是说m=3,解释一下为啥是3 假如要查找的数据是x,在第k级索引中,咱们遍历到y结点之后,发现x大于y,小于前面的结点z,所以咱们通过y的 down 指针,从第k级索引降落到第k-1 级索引。在第k-1级索引中,y和 z之间只有3个结点(蕴含 y 和 z),所以,咱们在k-1级索引中最多只须要遍历3个结点,顺次类推,每一级索引都最多只须要遍历3个结点 通过下面的剖析,失去m=3,所以「在跳表中查问任意数据的工夫复杂度就是O(logn)」。这个查找的工夫复杂度跟二分查找是一样的。换句话说,其实是基于单链表实现了二分查找。不过,天下没有收费的午餐,这种查问效率的晋升,前提是建设了很多级索引,是一种空间换工夫的思路 跳表的空间复杂度剖析假如原始链表大小为n,那第一级索引大概有n/2个结点,第二级索引大概有n/4个结点,以此类推,每回升一级就缩小一半,直到剩下2个结点。如果咱们把每层索引的结点数写进去,就是一个等比数列 `n/2, n/4, n/8,...,8, 4, 2` 这几级索引的结点总和就是n/2+n/4+n/8…+8+4+2=n-2。所以,「跳表的空间复杂度是 O(n)」。也就是说,如果将蕴含n个结点的单链表结构成跳表,咱们须要额定再用靠近n个结点的存储空间 有没有方法升高索引占用的内存空间呢? 升高跳表空间复杂度的办法后面都是每两个结点抽一个结点到下级索引,如果咱们每三个结点或五个结点,抽一个结点到下级索引,就不必那么多索引结点了 从图中能够看出,第一级索引须要大概n/3个结点,第二级索引须要大概n/9个结点。每往上一级,索引结点个数都除以3。为了不便计算,假如最高一级的索引结点个数是1。咱们把每级索引的结点个数都写下来,也是一个等比数列 `n/3, n/9, n/27, ... , 9, 3, 1` 通过等比数列求和公式,总的索引结点大概就是n/3+n/9+n/27+…+9+3+1=n/2。只管空间复杂度还是 O(n),但比下面的每两个结点抽一个结点的索引构建办法,要缩小了一半的索引节点存储空间 实际上,在平时开发中,不用太在意索引占用的额定空间。在数据结构和算法中,习惯性地把要解决的数据看成「整数」,然而在理论的开发中,原始链表中存储的有可能是「很大的对象」,而索引结点只须要「存储要害值」和几个指针,并不需要存储对象,「所以当对象比索引结点大很多时,那索引占用的额定空间就能够疏忽了」 跳表操作跳表是个「动静数据结构」,不仅反对查找操作,还反对动静的插入、删除操作,而且插入、删除操作的工夫复杂度也是「O(logn)」 插入在单链表中,一旦定位好要插入的地位,插入结点的工夫复杂度是很低的,就是 O(1)。然而,为了保障原始链表中数据的有序性,须要先找到要插入的地位,这个查找操作就会比拟耗时 对于纯正的单链表,须要遍历每个结点,来找到插入的地位。然而,对于跳表来说,查找某个节点的工夫复杂度是 O(logn),所以这里查找某个数据应该插入的地位,办法也是相似的,工夫复杂度也是O(logn) 删除「如果要删除的结点在索引中有呈现,除了要删除原始链表中的结点,还要删除索引中的」。因为单链表中的删除操作须要拿到要删除结点的前驱结点,而后通过指针操作实现删除。所以在查找要删除的结点的时候,肯定要获取前驱结点。当然,如果用的是双向链表,就不须要思考这个问题了 索引动静更新当不停地往跳表中插入数据时,如果不更新索引,就有可能呈现某2个索引结点之间数据十分多的状况。极其状况下,跳表还会进化成单链表 作为一种动静数据结构,它须要某种伎俩来保护索引与原始链表大小之间的均衡,也就是说,如果链表中结点多了,索引结点就相应地减少一些,防止复杂度进化,以及查找、插入、删除操作性能降落 「后边会分享到的红黑树、AVL树这样的均衡二叉树,它们是通过左右旋的形式放弃左右子树的大小均衡」,而跳表是通过「随机函数」来保护后面提到的“平衡性” 当咱们往跳表中插入数据的时候,能够抉择同时将这个数据插入到局部索引层中。如何抉择退出哪些索引层,就是随机函数要干的事件 通过一个随机函数,来决定将这个结点插入到哪几级索引中,比方随机函数生成了值K,那就将这个结点增加到「第一级到第K级」这K级索引中 随机函数的抉择很有考究,从概率上来讲,可能保障跳表的索引大小和数据大小平衡性,不至于性能适度进化 跳表的利用「为什么Redis要用跳表来实现有序汇合,而不是红黑树?」 Redis中的有序汇合是通过跳表来实现的,严格点讲,其实还用到了「散列表」。后边的文章会分享到散列表,所以当初暂且疏忽这部分。如果你理解Redis中的有序汇合,它反对的外围操作次要有上面这几个 插入一个数据;删除一个数据;查找一个数据;依照区间查找数据(比方查找值在 [100, 356] 之间的数据);迭代输入有序序列其中,插入、删除、查找以及迭代输入有序序列这几个操作,红黑树也能够实现,工夫复杂度跟跳表是一样的。然而,「依照区间来查找数据这个操作,红黑树的效率没有跳表高」 对于依照区间查找数据这个操作,跳表能够做到O(logn)的工夫复杂度定位区间的终点,而后在原始链表中程序往后遍历就能够了。这样做十分高效 Redis之所以用跳表来实现有序汇合,还有其余起因,比方,跳表更容易代码实现。尽管跳表的实现也不简略,但比起红黑树来说还是好懂、好写多了,而简略就意味着可读性好,不容易出错。还有,跳表更加灵便,它能够通过扭转索引构建策略,无效均衡执行效率和内存耗费 不过,跳表也不能齐全代替红黑树。因为红黑树比跳表的呈现要早一些,很多编程语言中的Map类型都是通过红黑树来实现的。做业务开发的时候,间接拿来用就能够了,不必吃力本人去实现一个红黑树,然而跳表并没有一个现成的实现,所以在开发中,如果你想应用跳表,必须要本人实现

December 3, 2020 · 1 min · jiezi

关于数据结构:HashMap源码

HashMap是一个数组+链表+红黑树的构造 字段属性public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { //序列化和反序列化时,通过该字段进行版本一致性验证 private static final long serialVersionUID = 362498820763181265L; //默认 HashMap 汇合初始容量为16(必须是 2 的倍数) static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //汇合的最大容量,如果通过带参结构指定的最大容量超过此数,默认还是应用此数 static final int MAXIMUM_CAPACITY = 1 << 30; //默认的填充因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; //当桶(bucket)上的结点数大于这个值时会转成红黑树(JDK1.8新增) static final int TREEIFY_THRESHOLD = 8; //当桶(bucket)上的节点数小于这个值时会转成链表(JDK1.8新增) static final int UNTREEIFY_THRESHOLD = 6; /**(JDK1.8新增) * 当汇合中的容量大于这个值时,表中的桶能力进行树形化 ,否则桶内元素太多时会扩容, * 而不是树形化 为了防止进行扩容、树形化抉择的抵触,这个值不能小于 4 * TREEIFY_THRESHOLD */ static final int MIN_TREEIFY_CAPACITY = 64; //保留数据的数组 transient Node<K,V>[] table; //HashMap中的动态外部类,用于存储数据 static class Node<K,V> implements Map.Entry<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; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }put1.HashMap在初始化的时候没有初始化table,在第一次插入时须要初始化table ...

November 29, 2020 · 4 min · jiezi

关于数据结构:构建二叉排序树及查找删除结点递归二叉链表

#include<iostream>using namespace std;#define ENDFLAG '#'typedef struct ElemType{ char key;}ElemType;typedef struct BSTNode{ ElemType data; BSTNode *lchild,*rchild; }BSTNode,*BSTree;//查找BSTree SearchBST(BSTree T,char key) { if((!T)|| key==T->data.key) return T; else if (key<T->data.key) return SearchBST(T->lchild,key); else return SearchBST(T->rchild,key); }//插入void InsertBST(BSTree &T,ElemType e ) { if(!T) { BSTree S = new BSTNode; S->data = e; S->lchild = S->rchild = NULL; T =S; } else if (e.key< T->data.key) InsertBST(T->lchild, e ); else if (e.key> T->data.key) InsertBST(T->rchild, e); }//结构二叉排序树void CreateBST(BSTree &T ) { T=NULL; ElemType e; cin>>e.key; while(e.key!=ENDFLAG){ InsertBST(T, e); cin>>e.key; } }//删除void DeleteBST(BSTree &T,char key) { BSTree p=T;BSTree f=NULL; BSTree q; BSTree s; while(p){ if (p->data.key == key) break; f=p; if (p->data.key> key) p=p->lchild; else p=p->rchild; }if(!p) return; if ((p->lchild)&& (p->rchild)) { q = p; s = p->lchild; while (s->rchild) {q = s; s = s->rchild;} p->data = s->data; if(q!=p){ q->rchild = s->lchild; } else q->lchild = s->lchild; delete s; }else{ if(!p->rchild) { q = p; p = p->lchild; } else if(!p->lchild) { q = p; p = p->rchild; } if(!f) T=p; else if (q==f->lchild) f->lchild = p; else f->rchild = p; delete q; }}//中序遍历void InOrderTraverse(BSTree &T){ if(T){ InOrderTraverse(T->lchild); cout<<T->data.key; InOrderTraverse(T->rchild); }}int main(){ BSTree T; cout<<"输出若干字符 #完结:"; CreateBST(T); cout<<"有序二叉树中序遍历:"; InOrderTraverse(T); cout<<endl; char key; cout<<"待查找字符:"; cin>>key; BSTree result=SearchBST(T,key); if(result) {cout<<"找到字符"<<key<<endl;} else {cout<<"未找到字符"<<key<<endl;} cout<<"待删除字符:"; cin>>key; DeleteBST(T,key); cout<<"有序二叉树中序遍历:"; InOrderTraverse(T);} ...

November 28, 2020 · 1 min · jiezi

关于数据结构:归并排序递归

#include <iostream>using namespace std;#define MAXSIZE 20 typedef struct{ int key; char *otherinfo;}RedType;typedef struct{ RedType *r; int length;}SqList; void Create_Sq(SqList &L){ int i,n; cout<<"数据个数:"; cin>>n; cout<<"待排序的数据:"; for(i=1;i<=n;i++){ cin>>L.r[i].key; L.length++; }}//相邻两个有序子序列归并void Merge(RedType R[],RedType T[],int low,int mid,int high){ int i,j,k; i=low; j=mid+1;k=low; while(i<=mid&&j<=high){ if(R[i].key<=R[j].key) T[k++]=R[i++]; else T[k++]=R[j++]; } while(i<=mid) T[k++]=R[i++]; while(j<=high) T[k++]=R[j++]; }void MSort(RedType R[],RedType T[],int low,int high){ int mid; RedType *S=new RedType[MAXSIZE]; if(low==high) T[low]=R[low]; else{ mid=(low+high)/2; MSort(R,S,low,mid); MSort(R,S,mid+1,high); Merge(S,T,low,mid,high); }}//归并排序 void MergeSort(SqList &L){ MSort(L.r,L.r,1,L.length); }void show(SqList L){ int i; for(i=1;i<=L.length;i++) cout<<L.r[i].key<<" ";}int main(){ SqList R; R.r=new RedType[MAXSIZE+1]; R.length=0; Create_Sq(R); MergeSort(R); cout<<" 归并排序 :"; show(R);} ...

November 28, 2020 · 1 min · jiezi