成都诗和远方网站建设,网页数据抓取,怎么建立一个免费网址,做公司网站哪个好主页#xff1a;醋溜马桶圈-CSDN博客 专栏#xff1a;数据结构_醋溜马桶圈的博客-CSDN博客 gitee#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.树概念及结构
1.1 树的概念
2.2 树的相关概念
1.3 树的表示
1.4 树在实际中的运用
2.二叉树的概念及结构
2.1 二叉树的概念… 主页醋溜马桶圈-CSDN博客 专栏数据结构_醋溜马桶圈的博客-CSDN博客 giteemnxcc (mnxcc) - Gitee.com 目录 1.树概念及结构
1.1 树的概念
2.2 树的相关概念
1.3 树的表示
1.4 树在实际中的运用
2.二叉树的概念及结构
2.1 二叉树的概念
2.2 特殊的二叉树
编辑2.3 二叉树的性质
2.4 二叉树的存储结构
2.5 二叉树的实现
3.堆的概念及结构
3.1 举例说明
3.2 堆数据结构与堆内存的区别
3.3 堆的意义
3.4 堆的实现
3.4.1 堆向下调整算法
3.4.2 堆的创建
3.4.2.1 创建
3.4.2.2 初始化
3.4.2.3 销毁
3.4.3 建堆时间复杂度
3.4.4 堆的插入
3.4.4.1 插入
3.4.4.2 向上调整
3.4.5 堆的删除
3.4.5.1 删除
3.4.5.2 向下调整
3.4.6 返回堆顶元素
3.4.7 判空
3.4.8 返回数据个数
3.4.9 访问
3.4.10 实现代码
3.4.10.1 Heap.h
3.4.10.2 Heap.c
3.5 堆的TOP-K问题
3.5.1 问题描述
3.5.2 算法思路
3.5.2.1 创建数据到文件中
3.5.2.2 并构建一个k个数的小堆
3.5.2.3 读取文件剩下的值
3.5.3 解决代码 1.树概念及结构
1.1 树的概念
树是一种非线性的数据结构它是由nn0个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树也就是说它是根朝上而叶朝下的 有一个特殊的结点称为根结点根节点没有前驱结点除根节点外其余结点被分成M(M0)个互不相交的集合T1、T2、……、Tm其中每一个集合Ti(1 i m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱可以有0个或多个后继因此树是递归定义的 注意树形结构中子树之间不能有交集否则就不是树形结构 2.2 树的相关概念 节点的度一个节点含有的子树的个数称为该节点的度 如上图A的为6叶节点或终端节点度为0的节点称为叶节点 如上图B、C、H、I...等节点为叶节点非终端节点或分支节点度不为0的节点 如上图D、E、F、G...等节点为分支节点双亲节点或父节点若一个节点含有子节点则这个节点称为其子节点的父节点 如上图A是B的父节点孩子节点或子节点一个节点含有的子树的根节点称为该节点的子节点 如上图B是A的孩子节点兄弟节点具有相同父节点的节点互称为兄弟节点 如上图B、C是兄弟节点树的度一棵树中最大的节点的度称为树的度 如上图树的度为6节点的层次从根开始定义起根为第1层根的子节点为第2层以此类推树的高度或深度树中节点的最大层次 如上图树的高度为4堂兄弟节点双亲在同一层的节点互为堂兄弟如上图H、I互为堂兄弟节点节点的祖先从根到该节点所经分支上的所有节点如上图A是所有节点的祖先子孙以某节点为根的子树中任一节点都称为该节点的子孙。如上图所有节点都是A的子孙森林由mm0棵互不相交的树的集合称为森林
1.3 树的表示
树结构相对线性表就比较复杂了要存储表示起来就比较麻烦了既要保存值域也要保存结点和结点之间的关系实际中树有很多种表示方式如双亲表示法孩子表示法、孩子双亲表示法以及孩子兄弟表示法等
我们这里就简单的了解其中最常用的孩子兄弟表示法 1.4 树在实际中的运用
表示文件系统的目录树结构 2.二叉树的概念及结构
2.1 二叉树的概念
一棵二叉树是结点的一个有限集合该集合:
或者为空由一个根节点加上两棵别称为左子树和右子树的二叉树组成 从上图可以看出
二叉树不存在度大于2的结点二叉树的子树有左右之分次序不能颠倒因此二叉树是有序树
注意对于任意的二叉树都是由以下几种情况复合而成的 现实中的二叉树 2.2 特殊的二叉树
满二叉树一个二叉树如果每一个层的结点数都达到最大值则这个二叉树就是满二叉树。也就是说如果一个二叉树的层数为K且结点总数是2^k-1则它就是满二叉树 如果一个树是满二叉树结点总数是N那它的高度hlog2(N1)每一层都是满的完全二叉树完全二叉树是效率很高的数据结构完全二叉树是由满二叉树而引出来的。对于深度为K的有n个结点的二叉树当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树 总结点数范围是[2^(h-1)2^h-1]前h-1层是满的最后一层不一定满但是最后一层从左到右都是连续的
2.3 二叉树的性质
若规定根节点的层数为1则一棵非空二叉树的第i层上最多有2^(i-1)个结点.若规定根节点的层数为1则深度为h的二叉树的最大结点数2^h-1对任何一棵二叉树, 如果度为0其叶结点个数为n0 , 度为2的分支结点个数为n2 ,则有n0n21若规定根节点的层数为1具有n个结点的满二叉树的深度h log2(n1). (long2(n1)是log以2为底n1为对数)对于具有n个结点的完全二叉树如果按照从上至下从左至右的数组顺序对所有节点从0开始编号则对于序号为i的结点有 1. 若i0i位置节点的双亲序号(i-1)/2i0i为根节点编号无双亲节点 2. 若2i1n左孩子序号2i12i1n否则无左孩子 3. 若2i2n右孩子序号2i22i2n否则无右孩子 2.4 二叉树的存储结构
二叉树一般可以使用两种结构存储一种顺序结构一种链式结构
1. 顺序存储
顺序结构存储就是使用数组来存储一般使用数组只适合表示完全二叉树因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储二叉树顺序存储在物理上是一个数组在逻辑上是一颗二叉树
只有满二叉树或者完全二叉树才适合这种存储 父子节点间下标有一个规律关系 leftchild parent*21rightchild parent*22parent (child-1)/2 2. 链式存储
二叉树的链式存储结构是指用链表来表示一棵二叉树即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成数据域和左右指针域左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链。 2.5 二叉树的实现
二叉树链式结构的实现参考此文章【数据结构】二叉树链式结构-CSDN博客
3.堆的概念及结构 堆的性质
堆中某个节点的值总是不大于或不小于其父节点的值堆总是一棵完全二叉树 3.1 举例说明
堆一般是把数组数据看做是一棵完全二叉树
小堆要求任意一个父亲孩子大堆要求任意一个父亲孩子
比如 我们分别分析一下这个题选A 3.2 堆数据结构与堆内存的区别
我们数据结构中学的堆和C语言操作系统中学的堆不是一个东西他们只是名字相同而已
数据结构的堆是一棵特殊的完全二叉树操作系统的堆是一个内存区域的划分
3.3 堆的意义
堆排序 O(N*logN)top k问题Top K算法分析_基于向量交集的topk搜索-CSDN博客......
3.4 堆的实现
3.4.1 堆向下调整算法
现在我们给出一个数组逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆
向下调整算法有一个前提左右子树必须是一个堆才能调整
3.4.2 堆的创建
下面我们给出一个数组
现在我们通过算法把它构建成一个堆
一个非叶子节点的子树开始调整一直调整到根节点的树就可以调整成堆
3.4.2.1 创建
我们用一个动态顺序表来实现堆创建一个结构体封装顺序表 3.4.2.2 初始化 3.4.2.3 销毁 3.4.3 建堆时间复杂度
3.4.4 堆的插入
先插入一个10到数组的尾上再进行向上调整算法直到满足堆
3.4.4.1 插入
这里我们以小堆为例父亲节点小于儿子节点 以这棵树为例
在逻辑结构上是一棵二叉树
而在物理结构上是顺序表即数组
如果我们分别插入102030 3.4.4.2 向上调整
具体的流程如图 这里的算法思路是插入到数组如果child小于parent则交换child和parent的值child的坐标调整到parentparent则调整到(parent-1)/2继续进行比较交换直到child调整到0位置结束这就是向上调整的思路
向上调整的时间复杂度是O(logN) 3.4.5 堆的删除
删除堆是删除堆顶的数据将堆顶的数据根最后一个数据一换然后删除数组最后一个数据再进行向下调整算法
3.4.5.1 删除
删除我们规定删除堆顶的值即删除根节点的值
要求删除根节点之后依然是一个堆
我们的思路是
第一个节点和最后一个节点交换尾删掉最后一个节点然后从根节点开始向下调整 交换之后左右子树依旧是小堆 3.4.5.2 向下调整
向下调整算法的思路是
找左右child节点左右child节点比较和较小的child节点交换继续向下调整调整到叶子节点就结束 向下调整的时间复杂度是O(logN)
具体的思路是
找小节点先找左节点如果有右节点则比较左右节点没有就直接是左节点
交换如果child节点小于parent节点则交换child和parent的值然后parent走到childchild走到(parent*21)
如果走到叶子节点或者child大于parent节点就跳出循环 3.4.6 返回堆顶元素 3.4.7 判空 3.4.8 返回数据个数 3.4.9 访问 3.4.10 实现代码
堆总是一棵完全二叉树
3.4.10.1 Heap.h
#pragma once
#include stdio.h
#include stdlib.h
#include stdbool.h
#include assert.h
typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;
//初始化
void HPInit(HP* php);
//销毁
void HPDestroy(HP* php);
//交换
void Swap(HPDataType* p1, HPDataType* p2);
//向上调整
void AdjustUp(HPDataType* a, int child);
//插入(小堆)
void HPPush(HP* php, HPDataType x);
//向下调整
void AdjustDown(HPDataType* a, int size, int parent);
//删除(根节点)
void HPPop(HP* php);
//返回堆顶数据
int HPTop(HP* php);
//判空
bool HPEmpty(HP* php);
//返回数据个数
int HPSize(HP* php);
3.4.10.2 Heap.c
#define _CRT_SECURE_NO_WARNINGS 1
#include Heap.h\
//初始化
void HPInit(HP* php)
{assert(php);php-a NULL;php-size 0;php-capacity 0;
}
//销毁
void HPDestroy(HP* php)
{assert(php);free(php-a);php-a NULL;php-size 0;php-capacity 0;
}
//交换
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp *p1;*p1 *p2;*p2 tmp;
}
//向上调整
void AdjustUp(HPDataType* a, int child)
{int parent (child - 1) / 2;while (child 0){if (a[child] a[parent]){Swap(a[child], a[parent]);child parent;parent (child - 1) / 2;}elsebreak;}
}
//插入(小堆)
void HPPush(HP* php, HPDataType x)
{assert(php);if (php-size php-capacity){int newcapacity php-capacity 0 ? 4 : php-capacity * 2;HPDataType* tmp (HPDataType*)realloc(php-a, sizeof(HPDataType) * newcapacity);if (tmp NULL){perror(realloc fail);exit(-1);}php-a tmp;php-capacity newcapacity;}php-a[php-size] x;php-size;AdjustUp(php-a, php-size - 1);
}
//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{int child parent * 2 1;while (child size){if ((child 1) size a[child 1] a[child])child;if (a[child] a[parent]){Swap(a[child], a[parent]);parent child;child parent * 2 1;}elsebreak;}
}
//删除(根节点)
void HPPop(HP* php)
{assert(php);assert(php-size 0);Swap(php-a[0], php-a[php-size - 1]);php-size--;AdjustDown(php-a, php-size, 0);
}
//返回堆顶数据
int HPTop(HP* php)
{assert(php);assert(php-size 0);return php-a[0];
}
//判空
bool HPEmpty(HP* php)
{assert(php);return php-size 0;
}
//返回数据个数
int HPSize(HP* php)
{assert(php);return php-size;
}
3.5 堆的TOP-K问题
3.5.1 问题描述
TOP-K问题即求数据结合中前K个最大的元素或者最小的元素一般情况下数据量都比较大
比如专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等
对于Top-K问题能想到的最简单直接的方式就是排序但是如果数据量非常大排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决基本思路如下
1. 用数据集合中前K个元素来建堆
前k个最大的元素则建小堆前k个最小的元素则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后堆中剩余的K个元素就是所求的前K个最小或者最大的元素
3.5.2 算法思路
大致的实现代码是这样 数据量非常非常大的时候比如在文件中有1000000000个值找出最大的前十个
这时我们不可能建大堆去pop 10次太消耗内存了
我们的思路是假如TopK
创建数据到文件中读取文件前k个值构建一个k个数的小堆读取文件剩下的值与堆顶的数比较如果比堆顶数值大那就替换他并向下调整打印前k个数据
3.5.2.1 创建数据到文件中 这里我们创建数据的时候%了10000000保证数据都是在10000000以内的
我们创建的文件就在文件夹中 3.5.2.2 并构建一个k个数的小堆 3.5.2.3 读取文件剩下的值 3.5.3 解决代码
#define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include stdlib.h
#include time.h
void Swap(int* p1, int* p2)
{int tmp *p1;*p1 *p2;*p2 *p1;
}
void AdjustUp(int* a, int child)
{int parent (child - 1) / 2;while (child 0){if (a[child] a[parent]){Swap(a[child], a[parent]);child parent;parent (child - 1) / 2;}elsebreak;}
}
void AdjustDown(int* a, int size, int parent)
{int child parent * 2 1;while (child size){if (child 1 size a[child 1] a[child]){child;}if (a[child] a[parent]){Swap(a[child], a[parent]);parent child;child parent * 2 1;}elsebreak;}
}
void CreatNDate()
{//造数据int n 10000000;srand(time(0));const char* file data.txt;FILE* fin fopen(file, w);if (fin NULL){perror(fopen error);return;}for (int i 0; i n; i){int x (rand() i) % 10000000;fprintf(fin, %d\n, x);}fclose(fin);
}
void PrintTopK(const char* file, int k)
{FILE* fout fopen(file, r);if (fout NULL){perror(fopen error);return;}//建一个k个数的小堆int* minheap (int*)malloc(sizeof(int) * k);if (minheap NULL){perror(malloc fail);return;}//读取前k个数for (int i 0; i k; i){fscanf(fout, %d, minheap[i]);//建小堆AdjustUp(minheap, i);}//读文件剩下的值int x 0;while (fscanf(fout, %d, x) ! EOF){//与堆顶进行比较if (x minheap[0]){//替代堆顶的值minheap[0] x;//向下调整AdjustDown(minheap, k, 0);}}for (int i 0; i k; i){printf(%d , minheap[i]);}printf(\n);fclose(fout);
}
int main()
{//CreatNDate();PrintTopK(data.txt, 5);return 0;
}
结果我们就可以找出前k个值了