数据结构初阶---二叉树---堆

一、树

1.树的概念

树是一种非线性的数据结构,由n(n≥0)个有限结点组成的一个有层次关系的集合。形状类似一棵倒挂的树,根朝上,分支向下。

根结点没有前驱结点,可以有n(n≥0)个后继结点。

其余结点被分为M个互不相交的集合,每一个集合都是一棵子树,每棵子树的根节点均有1个前驱结点,n(n≥0)个后继结点。因此树是递归定义的。

注:除根外的结点被分为互不相交的集合,如果相交,就不是树,而是图了。

子树之间存在交集,不属于树形结构,某结点存在多个父结点即它的前驱结点>1,也不属于树形结构。

2.与树有关的概念

节点的度:一个节点含有的子树的个数称为该节点的度; 
叶节点(终端节点):度为0的节点称为叶节点;(重要)
分支节点(非终端节点):度不为0的节点;
父节点(双亲结点):若一个节点含有子节点,则这个节点称为其子节点的父节点;(重要)
子节点(孩子节点):一个节点含有的子树的根节点称为该节点的子节点;(重要)
兄弟节点:具有相同父节点的节点互称为兄弟节点;
树的度:一棵树中,最大的节点的度称为树的度;
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;(重要)
树的高度或深度:树中节点的最大层次;(重要)
堂兄弟节点:双亲在同一层的节点互为堂兄弟;
节点的祖先:从根到该节点所经分支上的所有节点;(重要)
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。                                                          森林:由m(m>0)棵互不相交的树的集合称为森林;

注:关于树的层次,一般而言是从1开始,即根所在层次为1,如果题目给定从0开始,那就从0开始,否则默认从1开始层次,因为对于无结点树而言,层次刚好是0,从0开始那么无结点树层次就为-1。

3.树的表示

如果我们使用数组来表示树,那么我们一般采用数组ArrayChild来存储孩子结点

#define N 6 //假设已知树的度为6
struct TreeNode
{TreeNodeTypeData val;struct TreeNode* ArrayChild[N];
};

但是,如果这样来表示树,那么相当于每一个分支结点,都有N个数组空间(N是树的度的值),但是树的分支结点不一定都有N个子结点,这样的表示就太过于浪费空间了。

如果用顺序表来表示树型结构,

struct TreeNode
{TreeNodeTypeData val;SeqList Childlist;
};

那么每个孩子结点都是一个顺序表,但是需要我们去构建一个顺序表,在C语言阶段也是非常费心费力的。

左孩子右兄弟表示法

树形结构的最优表示方法是左孩子右兄弟表示法

树形结构中创建两个指针,左孩子指针LeftChild与右兄弟指针RightBrother

struct TreeNode
{TreeNodeTypeData val;struct TreeNode* LeftChild;struct TreeNode* RightBrother;
};

我们通过每一次的根节点访问左孩子,就可以通过左孩子结点访问右兄弟指针找到该层次的其他结点;如果想访问下一层次,那么现在的左孩子结点访问它的左孩子结点,通过它的左孩子结点访问右兄弟指针能够找到其他所有结点。

上图中左孩子右兄弟表示形式里,未标识出来的左孩子LeftChild指针都应该指向NULL。

由此我们只需要访问上一层次某一棵子树的根节点的左孩子结点就可以遍历该层次属于该子树的所有结点。

如A的左孩子为B,A结点访问成员LeftChild得到B结点,B结点访问成员RightBrother就能够访问到同层次属于A孩子的C与D结点。如B的左孩子是E,那么通过B访问到E结点就可以遍历E、F结点。

4.树的实际运用

Linux树状目录结构。

Windows文件目录多盘形式---森林。

二、二叉树

1.概念

二叉树的结点的度均≤2(即每个结点的子结点不超过2个),同时二叉树区分左右子树(即区分左右结点),是有序树。

二叉树是特殊的树。

2.特殊的二叉树

①满二叉树

二叉树的所有结点的度都是2,那么就是满二叉树。对于满二叉树而言,每一层的结点数都达到了最大值。

假设满二叉树的深度为h,结点数为N,那么存在关系:2^h - 1 = N <==> h = log(N+1) (以2为底)

反过来说,如果一个二叉树的层数为K,它的结点数为2^K-1,那么这个二叉树就是满二叉树。

②完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。

对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。满二叉树是一种特殊的完全二叉树。

通俗来讲,除去最后一层的其它层结点数达到最大值,最后一层结点数不确定但是从左到右一定有序的二叉树就是完全二叉树。

对于完全二叉树来说,由于最后一层的结点有序,但是不确定最后一层结点个数,因此总的结点数N是一个范围,最小的时候最后一层只有1个结点,最大的时候是满二叉树。

那么N的范围为:[2^(h-1)-1+1 , 2^(h) -1]  化简--->[2^(h-1) , 2^(h) -1]

注意,对于完全二叉树来说,最后一层的结点一定是有序的!结点一定是从左至右增加的。

3.二叉树的性质

4.二叉树的存储结构

二叉树一般可以使用两种结构进行存储,顺序结构和链式结构。

①顺序存储

顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

顺序存储--->使用数组存储,只适用于完全二叉树。如果用数组存储的方式对任意二叉树,那么由于每个结点的度不同,会有许多数组空间的浪费,因此数组存储只适用于完全二叉树。

将完全二叉树的结点一层一层的存入数组中,

我们会发现,父子结点的下标是存在联系的。

②链式存储

对于不是完全二叉树和满二叉树的树形结构,我们采取链式结构来进行数据存储。

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所 在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面学到高阶数据结构如红黑树等会用到三叉链。

5.堆

如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1, 2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:①堆中某个节点的值总是不大于或不小于其父节点的值;②堆总是一棵完全二叉树。

简单的说,如果一个数组的内容满足堆的性质,那么堆就把这个数组数据看做一棵完全二叉树。

堆只分为小(根)堆和大(根)堆,小堆表示所有的父结点的数据都小于等于其子结点;大堆表示所有的父结点的数据都大于等于其子结点。除此之外都不是堆。

有序数组是不是堆?是,但是堆不一定是有序数组。

跟之前数据结构讲的栈不同于内存中的栈类似,

数据结构的堆是一种管理数据的结构,是完全二叉树;

而操作系统中的堆,指的是一个内存区域,这个区域中的空间可以通过malloc、calloc开辟、realloc扩容使用,最后free释放。

堆的物理结构是数组;逻辑结构是完全二叉树。

堆的意义:

1.堆排序---时间复杂度O(N*logN)

2.top k问题

对于堆的实现,我们想象自己操纵的是完全二叉树,但是我们实际上操纵的是数组。

数组实现堆

结构如下,我们来实现一下大(根)堆:

//堆是由读取规定的---与栈和队列一样,堆顶开始读取然后删除
//大堆---父节点始终大于等于子节点
#define HeapDataType int
typedef struct Heap
{HeapDataType* a;int size;int capacity;
}Heap;

接口函数

①初始化HeapInit

void HeapInit(Heap* php);

//初始化
void HeapInit(Heap* php)
{assert(php);php->a = NULL;php->capacity = 0;php->size = 0;
}
②销毁HeapDestroy

void HeapDestroy(Heap* php);

//销毁
void HeapDestroy(Heap* php)
{assert(php);free(php->a);php->a = NULL;php->capacity = 0;php->size = 0;
}
③插入HeapPush---O(logN)

void HeapPush(Heap* php, HeapDataType x);

在一个堆中插入一个新的数据x,那么我们就必须得将该数据移动到合适的位置,确保插入后新的数组仍然是一个大堆。

首先先写出扩容判断的代码以及插入的代码:

	//检测容量---不用单独开接口,因为只有一个插入if (php->size == php->capacity){int NewCapacity = php->capacity == 0 ? 4 : php->capacity * 2;HeapDataType* tmp = (HeapDataType*)realloc(php->a, sizeof(HeapDataType) * NewCapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = NewCapacity;}//插入数据php->a[php->size] = x;php->size++;

那么就差最后一步,判断是否需要更换位置:

我们知道,对于大堆而言,父节点的数据始终大于等于子节点的数据,对于目前插入在最后一层的新结点而言,我们需要将其与它的父辈、祖辈,与它的祖先进行比较,如果新结点的数据大于父节点,那么就需要将新结点与父节点交换,然后接着向上比较:

需要交换数据,那么我们封装为一个Swap函数用来交换数据:

//交换函数
void Swap(HeapDataType* p1, HeapDataType* p2)
{HeapDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}

所以对于插入的值的比较,我们需要通过该结点找到其父结点,根据前面学到的堆的父子结点关系,我们知道,子节点下标child,(child-1)/2就能够得到父节点下标parent

最后的比较要到什么时候终止呢?如果最慢的情况,插入的数据比原根节点都要大,那么就要换到A结点处--->因此循环的终止条件应为child == 0 。那么我们将判断和移动数据的过程封装成一个函数UpAdjust(向上调整)。

向上调整算法UpAdjust

void UpAdjust(Heap* php, int child);

    //向上调整算法UpAdjust(php, php->size - 1);

传入的child是插入结点的下标,由于前面插入后size自增1了,所以这个地方传进来的应该是size-1。

//向上调整算法---最坏情况调整深度h次,而完全二叉树的h与logN相关---因此时间复杂度logN
void UpAdjust(Heap* php, int child)
{assert(php);int parent = (child - 1) / 2;//父节点下标为子节点-1再除2while (child > 0){if (php->a[parent] < php->a[child]){Swap(&php->a[parent], &php->a[child]);child = parent;parent = (parent - 1) / 2;}elsebreak;}
}
④删除HeapPop---O(logN)

void HeapPop(Heap* php);

在堆的实现中的删除操作,删除的是堆顶元素---即根结点元素。

但是我们又不能够直接进行覆盖操作,如果直接进行覆盖,一会导致原来的这个父子下标关系改变,摧毁了这个堆;二是数组的头删挪位覆盖时间复杂度为O(N)。

所以就有一种方法:我们先交换堆顶元素与最后一个元素,然后进行尾删--->就删除了堆顶元素。

对于数组结构而言,尾删是非常方便的,size自减1即可。然后这个时候,除了堆顶元素改变,其他位置上的元素均没有变化,其实类似于向上调整---我们实现一个向下调整算法DownAdjust。

//删除堆顶结点---时间复杂度O(logN)
void HeapPop(Heap* php)
{assert(php);Swap(&php->a[0], &php->a[php->size - 1]);//交换--->尾删原堆顶php->size--;//尾删原堆顶//向下调整算法---O(logN),要将交换上去的结点向下调整DownAdjust(php->a, php->size, 0);
}

DownAdjust向下调整算法内,我们将堆顶元素与它的子节点元素中较大的进行判断,如果堆顶元素大,那么符合大堆,不用进行交换元素,反之,堆顶元素小于较大的子结点元素,那么交换二者,将交换后的处于子节点处的该元素继续与这个位置上它的子结点的较大元素进行比较。

向下调整算法DownAdjust

 void DownAdjust(HeapDataType* a, int size, int parent);

//向下调整---时间复杂度O(logN)
void DownAdjust(HeapDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){if ( child+1 < size && a[child] < a[child + 1])//小心越界{child++;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = 2 * child + 1;}elsebreak;}
}

为了防止if else判断两个子节点谁大的代码冗长,我们选择使用了假设判断法,先将其中的左节点下标给child,然后再if判断如果左节点数据小于右孩子结点,那么将右节点下标给child,实际上就与child自增1没有区别。 极端情况下,当我们判断两者大小时,可能出现child+1的越界访问的问题。 因此需要在if的条件中多加上一条child+1<size

找到更大的孩子节点、然后进行比较,这个整体是一个循环过程,因为交换到堆顶的数据最多需要进行深度h次交换,如果该数据足够小的话。循环进行条件就是chiild<size,即下标不能越界。在发现了该节点处的数据大于等于较大子节点的数据时,就满足了大堆性质,直接跳出循环即可。

⑤获取堆顶元素HeapTop

HeapDataType HeapTop(Heap* php);

//获取堆顶元素
HeapDataType HeapTop(Heap* php)
{assert(php);assert(php->size);return php->a[0];
}

有关于获取堆顶元素的接口,我们一般与删除堆顶元素联合起来使用,大堆的堆顶元素读取然后删除,执行k次,就能够找到这个堆中前k大的数据。

同时如果我们也可以这样获取降序的全部数据--->大堆就能够获取降序,小堆能够获取升序数据。

⑥获取堆有效元素个数HeapSize

int HeapSize(Heap* php);

//获取堆有效元素个数
int HeapSize(Heap* php)
{assert(php);return php->size;
}
⑦判断堆是否为空HeapEmpty

bool HeapEmpty(Heap* php);

//判断堆是否为空
bool HeapEmpty(Heap* php)
{assert(php);return php->size == 0;
}

堆的应用

1.堆排序问题

时间复杂度O(N*logN)。

上面通过数组实现了堆,那么给定一个数组,我们当然可以将数组的数据插入的堆结构中,进行向上调整,然后通过获取堆顶数据,删除堆顶向下调整的小套餐来打印排序后的数据,但是这样的操作有两个缺点:①需要一个完整的堆结构②原数组其实没有改变

那么实际上不需要如上操作进行排序,堆排序可以对该数组重新排序。

升序--->①建立大堆②进行排序

大堆父结点大于子节点,为什么会升序?===>这就涉及到伪删除,我们利用到堆接口函数删除堆顶元素的实现,本质是将尾部数据与堆顶数据互换再删除堆顶数据,那么其实此时堆顶数据在末尾,只要我们不删除,就能够实现每一次的伪删除会将最大的数据移动到数组尾部。每一次互换之后我们需要进行向下调整,当然调整不会包括互换后的尾结点数据。

降序--->①建立小堆②进行排序

降序为什么建立小堆同上。

下面演示升序:

①升序演示---建立大堆

建堆有两种方式:我们可以通过将每个数组数据从起始开始进行向上调整;也可以从尾结点的父结点开始进行向下调整。目的都是为了将数组数据排放成堆的形式。

从起始开始向上调整:时间复杂度O(N*logN)

	//数组建堆---//方法一:第一个数据成堆,其他数据依次入堆然后向上调整 O(N*logN)for (int i=1; i < n; i++){UpAdjust(a, i);//数据依次向上调整成大堆}

从尾结点的父结点开始向下调整:时间复杂度O(N)

	//方法二:从最后一个结点的父结点开始,倒着对所有非叶子结点进行向下调整 O(N)for (int i = (n - 1 - 1) / 2; i >= 0; i--){DownAdjust(a, n, i);}

起始我们也能看出来时间复杂度差在哪里,对于方式二而言,最后一层结点数最多,但是对于方式二却不用对它们进行任何次数的调整,因此时间复杂度比方式一要小。

②升序演示---伪删除/选数

将堆顶与尾部数据交换,然后传入DownAdjust向下调整的end每次遍历减1。第一次交换后传入的size应该是减1之后的===>即下面代码的end = n-1。

	//伪删除---堆顶数据与尾部交换,size--,向下调整===>那么数组尾部就是最大数据 O(NlogN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);DownAdjust(a, end, 0);//依次向下调整end--;}

一个数据在向下调整最多层数h次,N个数据===>N*logN。

选数操作的时间复杂度O(N*logN)。 

推排序升序数组:

//堆排序
//堆排序是将给出的数组数据先排序成堆,然后再进行排序
//而不是将数据依次插入实现好的堆中
void HeapSort()//测试---堆排序
{int a[] = { 1,5,3,6,8,2,1,5,8 };int n = sizeof(a) / sizeof(a[0]);//数组建堆---//方法一:第一个数据成堆,其他数据依次入堆然后向上调整 O(N*logN)for (int i=1; i < n; i++){UpAdjust(a, i);//数据依次向上调整成大堆}//方法二:从最后一个结点的父结点开始,倒着对所有非叶子结点进行向下调整 O(N)for (int i = (n - 1 - 1) / 2; i >= 0; i--){DownAdjust(a, n, i);}//伪删除---堆顶数据与尾部交换,size--,向下调整===>那么数组尾部就是最大数据 O(NlogN)int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);DownAdjust(a, end, 0);//依次向下调整end--;}for (int i = 0; i < n; i++){printf("%d ", a[i]);}
}

2.top k问题
思路一

前面接口函数中提到了top k问题的解决方法,我们可以通过将这些数据以大堆的形式全部插入到堆中,那么再循环k次来删除k次堆顶数据并获取前k个最大的数据。但是如果数据基数N非常大,那么我们需要为这个大堆开辟的空间是非常非常大的,举个例子,N=100亿--->100亿个整型--->约为400亿字节--->约为40G的内存大小,现如今普通人家的电脑内存大小,16-32G,无法完成,而且就算能够完成,也不适用。

思路二---最优解

在top k问题中,如果要在一个非常非常大的N个数据中,找出k个最大的数据,那么我们可以通过这样的办法实现:

创建一个k个数据的小堆,先将最开始的k个数据存入小堆中,然后在剩余的数据依次与小堆堆顶元素比较,如果大于堆顶元素,那么覆盖堆顶元素并向下调整,使其依然满足小堆。那么到最后,小堆中就是最大的k个数据了。这种方法是非常便捷的,时间复杂度为O(N*logk)即O(N),但是空间复杂度只有O(1),而思路一的空间复杂度达到了O(N),而且基本上难以实现。

时间复杂度O(N*logk)==O(N)。

那么通过最优解,就可以从N个数据中找k个最大的数据,假设我们在文件中保存1000000个随机值,然后通过堆来找出k个最大的值:

首先我们在文件中写入N=1000000个随机值:

//TopK问题---在N个数据中找到K个最大的数据
void CreatNData()//在文件中创建1000000个数据
{FILE* fin = fopen("data.txt", "w");if (fin == NULL){perror("fopen fail");return;}int n = 10000000;srand(time(0));while (n--){int x = (rand() + n) % 1000000;//数据都是小于1000000的fprintf(fin, "%d\n", x);}fclose(fin);
}

这里写入的数据模了1000000方便我们后续测试是否成功取出k个最大数据。

我们执行一次CreatNdatra函数,那么就能够创建这个data.txt文件并在其中写入1000000个数据。

由于我们需要取较大数据,那么就需要用小堆===>即原来适用于大堆的向上调整与向下调整算法需要进行细微的修改。

下面是小堆的向上向下调整算法(其实只需调整几个大于小于号)

//小堆的向上向下调整算法
//向上调整算法---最坏情况调整深度h次,而h大概率等于logN相关---因此时间复杂度logN
void UpAdjust_lowheap(HeapDataType* a, int child)
{int parent = (child - 1) / 2;//父节点下标为子节点-1再除2while (child > 0){if (a[parent] > a[child]){Swap(&a[parent], &a[child]);child = parent;parent = (parent - 1) / 2;}elsebreak;}
}
//向下调整---时间复杂度O(logN)---小堆
void DownAdjust_lowheap(HeapDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){if (child + 1 < size && a[child] > a[child + 1])//小心越界{child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = 2 * child + 1;}elsebreak;}
}

写一个函数PrintTopK,解决topk问题:

void PrintTopK(FILE* file, int k);

创建一个minheap数组存储k个数据,VS不支持变长数组,那么我们使用malloc开辟k个数据大小的空间给minheap。

int* minheap = (int*)malloc(sizeof(int) * k);

第一步将该文件中的前k个数据存入小堆进行向上调整;

	//读前k个数据建小堆for (int i = 0; i < k; i++){fscanf(fout, "%d", &minheap[i]);UpAdjust_lowheap(minheap, i);}

第二步将文件中剩余的N-k个数据依次遍历与堆顶数据比较,大于等于堆顶数据则替换堆顶数据然后进行向下调整使之依然成小堆。

	//剩下N-K个数据依次遍历与堆顶数据比较,大于等于堆顶数据进入堆int x = 0;while (fscanf(fout, "%d", &x) != EOF)//先读到x中,若x大于等于堆顶,放入堆顶并向下调整{if (x >= minheap[0]){minheap[0] = x;DownAdjust_lowheap(minheap, k, 0);}}

fscanf读取结束返回EOF,那么循环条件可以直接写作如上图所示,将每次遍历的文件中的数据存入x中,x与堆顶数据进行判断,若大于等于堆顶数据,替换堆顶数据并向下调整。

注:scanf函数以及fscanf等函数,默认遇到空格或者换行\n终止。 

最后我们可以打印minheap来观察数据,记得最后需要释放堆中开辟的空间以及关闭文件。

	for(int i=0;i<k;i++){printf("%d ",minheap[i]);}free(minheap);minheap = NULL;fclose(fout);

前面手动添加了取模1000000,我们可以通过手动修改data.txt中的5个数据使其大于1000000,再次执行程序来观察最大值的变化,从而判断程序是否正确无误。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/63014.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言 字符串输入输出函数、scanf(“%[^\n]“,)可输入空格 、fgets删除换行符

字符串输入函数&#xff1a; scanf&#xff08;"%s"&#xff0c;数组名&#xff09; gets&#xff08;数组名&#xff09; fgets&#xff08;&#xff09; --- 文件流输入函数 函数原型&#xff1a; int scanf( const char *format, ...…

深度学习camp-第J4周:ResNet与DenseNet结合探索

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 本周任务&#xff1a; 探索ResNet和DenseNet的结合可能性本周任务较难&#xff0c;我们在chatGPT的帮助下完成 一、网络的构建 设计一种结合 ResNet 和 Den…

「iOS」通过CoreLocation Framework深入了解MVC架构

「iOS」通过CoreLocation Framework重新了解多界面传值以及MVC架构 文章目录 「iOS」通过CoreLocation Framework重新了解多界面传值以及MVC架构前言CoreLocation了解根据需求建模设计属性方法设计协议传值Block传值KVONotification通知方式 总结参考文章 前言 在这个学期的前…

ArrayList源码分析、扩容机制面试题,数组和List的相互转换,ArrayList与LinkedList的区别

目录 1.java集合框架体系 2. 前置知识-数组 2.1 数组 2.1.1 定义&#xff1a; 2.1.2 数组如何获取其他元素的地址值&#xff1f;&#xff08;寻址公式&#xff09; 2.1.3 为什么数组索引从0开始呢&#xff1f;从1开始不行吗&#xff1f; 3. ArrayList 3.1 ArrayList和和…

【C++】- 掌握STL List类:带你探索双向链表的魅力

文章目录 前言&#xff1a;一.list的介绍及使用1. list的介绍2. list的使用2.1 list的构造2.2 list iterator的使用2.3 list capacity2.4 list element access2.5 list modifiers2.6 list的迭代器失效 二.list的模拟实现1. list的节点2. list的成员变量3.list迭代器相关问题3.1…

Docker--Docker Container(容器) 之容器实战

对docker容器的前两篇文章 Docker–Docker Container(容器) 之 操作实例 Docker–Docker Container(容器&#xff09; Mysql容器化安装 我们可以先在Docker Hub上查看对应的Mysql镜像,拉取对应的镜像&#xff1a; 拉取mysql5.7版本的镜像&#xff1a; docker pull mysql:5.7…

【汇编语言】内中断(二) —— 安装自己的中断处理程序:你也能控制0号中断

文章目录 前言1. 编程处理0号中断1.1 效果演示1.2 分析所要编写的中断处理程序1.2.1 引发中断1.2.2 中断处理程序1.2.3 中断处理程序do0应该存放的位置1.2.4 中断向量表的修改1.2.5 总结 1.3 程序框架1.4 注意事项1.5 从CPU的角度看中断处理程序1.6 一些问题的思考与解答 2. 安…

VS2019中无法跳转定义_其中之一情况

我习惯了使用VS2019看stm的代码&#xff1b; 遇到的问题&#xff0c;在导入代码后&#xff0c;发现有些函数调用不能跳转到定义&#xff1b; 问题描述步骤 1、导入代码 2、跳转&#xff0c;无法跳转 1、中文路径 2、删除.vs文件 和网上查的都没办法解决 最后发现是VS不支持 …

让 Win10 上网本 Debug 模式 QUDPSocket 信号槽 收发不丢包的方法总结

在前两篇文章里&#xff0c;我们探讨了不少UDP丢包的解决方案。经过几年的摸索测试&#xff0c;其实方法非常简单, 无需修改代码。 1. Windows 下设置UDP缓存 这个方法可以一劳永逸解决UDP的收发丢包问题&#xff0c;只要添加注册表项目并重启即可。即使用Qt的信号与槽&#…

Elasticsearch:ES|QL 中的全文搜索 - 8.17

细心的开发者如果已经阅读我前两天发布的文章 “Elastic 8.17&#xff1a;Elasticsearch logsdb 索引模式、Elastic Rerank 等”&#xff0c;你就会发现在 8.17 的发布版中&#xff0c;有一个重要的功能发布。那就是 ES|QL 开始支持全文搜索了。在今天的文章中我们来尝试一下。…

SQL和Python 哪个更容易自学?

SQL和Python不是一个物种&#xff0c;Python肯定更难学习。如果你从事数据工作&#xff0c;我建议先学SQL、有余力再学Python。因为SQL不光容易学&#xff0c;而且前期的投入产出比更大。 SQL是数据查询语言&#xff0c;场景限于数据查询和数据库的管理&#xff0c;对大部分数据…

【unity】从零开始制作平台跳跃游戏--界面的认识,添加第一个角色!

在上一篇文章中&#xff0c;我们已经完成了unity的环境配置与安装⬇️ 【Unity】环境配置与安装-CSDN博客 接下来&#xff0c;让我们开始新建一个项目吧&#xff01; 新建项目 首先进入unityHub的项目页面&#xff0c;点击“新项目”&#xff1a; 我们这个系列将会以2D平台…

怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev

本文引用怎么禁用 vscode 中点击 go 包名时自动打开浏览器跳转到 pkg.go.dev 在 vscode 设置项中配置 gopls 的 ui.navigation.importShortcut 为 Definition 即可。 "gopls": {"ui.navigation.importShortcut": "Definition" }ui.navigation.i…

Unity3D实现抽象类的应用场景例子

系列文章目录 unity知识点 文章目录 系列文章目录👉前言👉一、示例👉二、使用步骤👉三、抽象类和接口的区别👉3-1、抽象类👉3-2、接口类👉壁纸分享👉总结👉前言 假设我们正在制作一个游戏,游戏中有多种不同类型的角色,这些角色都有一些共同的行为(比如移…

数据仓库工具箱—读书笔记01(数据仓库、商业智能及维度建模初步)

数据仓库、商业智能及维度建模初步 记录一下读《数据仓库工具箱》时的思考&#xff0c;摘录一些书中关于维度建模比较重要的思想与大家分享&#x1f923;&#x1f923;&#x1f923; 博主在这里先把这本书"变薄"~有时间的小伙伴可以亲自再读一读&#xff0c;感受一下…

docker启动一个helloworld(公司内网服务器)

这里写目录标题 容易遇到的问题&#xff1a;1、docker连接问题 我来介绍几种启动 Docker Hello World 的方法&#xff1a; 最简单的方式&#xff1a; docker run hello-world这会自动下载并运行官方的 hello-world 镜像。 使用 Nginx 作为 Hello World&#xff1a; docker…

基于IEEE 802.1Qci的时间敏感网络(TSN)主干架构安全分析及异常检测系统设计

中文标题&#xff1a;基于IEEE 802.1Qci的时间敏感网络&#xff08;TSN&#xff09;主干架构安全分析及异常检测系统设计 英文标题&#xff1a;Security Analysis of the TSN Backbone Architecture and Anomaly Detection System Design Based on IEEE 802.1Qci 作者信息&…

怎样提升企业网络的性能?

企业网络的稳定性和高效性直接影响员工的工作效率。以下从多维度分析了一些有效策略&#xff0c;帮助公司提升网络性能&#xff0c;营造更高效的办公环境。 1. 升级网络设备 采用性能更高的网络硬件是优化网络体验的重要基础。选择支持高吞吐量、低延迟的设备&#xff08;如企业…

力扣239.滑动窗口最大值

文章目录 一、前言二、单调队列 一、前言 力扣239.滑动窗口最大值 滑动窗口最大值&#xff0c;这道题给定一个数组&#xff0c;以及一个窗口的长度&#xff0c;这个窗口会往后滑动&#xff0c;直到数组最后一个元素。 要求每个滑动窗口的中的最大值。对于这道题&#xff0c;我…

mac 安装CosyVoice (cpu版本)

CosyVoice 介绍 CosyVoice 是阿里研发的一个tts大模型 官方项目地址&#xff1a;https://github.com/FunAudioLLM/CosyVoice.git 下载项目&#xff08;非官方&#xff09; git clone --recursive https://github.com/v3ucn/CosyVoice_for_MacOs.git 进入项目 cd CosyVoic…