【完全二叉树魔法:顺序结构实现堆的奇象】

本章重点

  • 二叉树的顺序结构
  • 堆的概念及结构
  • 堆的实现
  • 堆的调整算法
  • 堆的创建
  • 堆排序
  • TOP-K问题

1.二叉树的顺序结构

        普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

访问结点的规律:

//访问孩子节点
leftchild = parent*2+1
rightchild = parent*2+2//访问父亲结点
parent = (child-1)/2

2.堆的概念及结构

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。
  • 大堆:任何父亲节点 >= 孩子结点
  • 小堆:任何父亲节点 <= 孩子结点

1.下列关键字序列为堆的是:()。

A 100,60,70,50,32,65

B 60,70,65,50,32,100

C 65,100,70,32,50,60

D 70,65,100,32,50,60

E 32,50,100,70,65,60

F 50,100,70,65,60,32

解析:

        堆(Heap)是一种特殊的树形数据结构,它通常有两种类型:小堆(Min Heap)和大堆(Max Heap)。在小堆中,父节点的值小于或等于其子节点的值,而在大堆中,父节点的值大于或等于其子节点的值。

        要判断一个序列是否是堆,需要检查该序列是否满足堆的性质。我们发现A符合大堆的性质父节点的值大于或等于其子节点的值。

2.已知小根堆为8,15,10,21,34,16,12,删除关键字 8 之后需重建小堆,在此过程中,关键字之间的比较次数是()。

A 1

B 2

C 3

D 4

解析:

        在一个小根堆中删除根节点后,需要重新构建小根堆。删除根节点后,通常会将堆的最后一个元素移动到根的位置,然后通过与其子节点的比较来逐级下移,以确保小根堆的性质得以恢复。

        给定的小根堆是:8, 15, 10, 21, 34, 16, 12。

        首先删除根节点8后,将最后一个元素12移到根的位置,得到:12, 15, 10, 21, 34, 16。

        然后,我们需要逐级下移12,直到小根堆性质得以恢复。在这个过程中,我们将12与其子节点进行比较,选择较小的子节点来交换位置。

        第一次比较:12与15比较,不需要交换。

        第二次比较:12与10比较,需要交换。

        第三次比较:12与16比较,不需要交换。

        因此,关键字之间的比较次数是3次。

3.一组记录排序码为(5 11 7 2 3 17),则利用堆排序方法建立的初始堆为()。

A(11 5 7 2 3 17)

B(11 5 7 2 17 3)

C(17 11 7 2 3 5)

D(17 11 7 5 3 2)

E(17 7 11 3 5 2)

F(17 7 11 3 2 5)

        堆排序是一种基于堆数据结构的排序算法,通常会建立一个最大堆(Max Heap)或最小堆(Min Heap)来进行排序。在这里,我们需要建立一个最大堆。

        初始堆的建立过程通常是从数组的末尾开始,逐步将元素向上移动,以满足堆的性质。对于给定的排序码数组(5 11 7 2 3 17),初始堆的建立步骤如下:

4.最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是()。

A[3,2,5,7,4,6,8]

B[2,3,5,7,4,6,8]

C[2,3,4,5,7,8,6]

D[2,3,4,5,6,7,8]

3.堆的实现

        

        这里的堆是使用数组实现的,博主重点介绍堆的删除和插入接口,其他接口同顺序表相同,这里就不过多赘述了。

typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;// 堆的初始化
void HeapInit(HP* php);
// 堆的打印
void HeapPrint(HP* php);
// 堆的销毁
void HeapDestroy(HP* php);
//堆的创建
void HeapInitArray(HP*php, int* a, int n);
// 堆的插入
void HeapPush(HP* php, HPDataType x);
// 堆的删除
void HeapPop(HP* php);
// 取堆顶的数据
HPDataType HeapTop(HP* php);
// 堆的数据个数
int HeapSize(HP* php);
// 堆的判空
bool HeapEmpty(HP* php);

3.1堆的插入:void HeapPush(HP* php, HPDataType x)

  1. 先将元素插入到堆的末尾,即最后一个孩子之后。
  2. 插入之后如果堆的性质遭到破坏,将信新插入节点顺着其双亲往上调整到合适位置即可,即向上调整
  3. 向上调整结束的条件是child等于0,parent等于-1,但是我们写的循环结束条件是child大于0,因为parent的值不会是-1,而是0,这里可以去看我的另外一篇文章,里面介绍了c语言取整规则:链接

void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType temp = *p1;*p1 = *p2;*p2 = temp;
}//向上调整
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 = (parent - 1) / 2;}else{break;}}
}// 堆的插入
void HeapPush(HP* php, HPDataType x)
{assert(php);if(php->size == php->capacity){int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* temp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);if (temp == NULL){perror("realloc fail");exit(-1);}php->a = temp;php->capacity = newCapacity;}php->a[php->size] = x;php->size++;AdjustUP(php->a, php->size - 1);
}

3.2堆的删除:void HeapPop(HP* php)

  1. 将堆顶元素与堆中最后一个元素进行交换。
  2. 删除堆中最后一个元素。
  3. 将堆顶元素向下调整到满足堆特性为止。
  4. 向下调整的结束条件是child等于叶子结点。
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType temp = *p1;*p1 = *p2;*p2 = temp;
}//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{int child = parent * 2 + 1;//parent到叶子结点就结束while (child < n){//可能不存在右孩子if (child + 1 < n && a[child] > a[child + 1]){child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}
// 堆的删除
void HeapPop(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);
}

4.堆的调整算法

4.1堆向下调整算法

        现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

int array[] = {27,15,19,18,28,34,65,49,25,37};

//向下调整
void AdjustDown(HPDataType* a, int n, int parent)
{int child = parent * 2 + 1;//parent到叶子结点就结束while (child < n){//可能不存在右孩子if (child + 1 < n && a[child] > a[child + 1]){child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}

4.2堆向上调整算法

        现在我们给出一个数组,前n-1个数已经是堆了,现在再添加一个数要让其满足堆的性质。我们通过从最后一个叶子结点向上调整算法可以把它调整成一个小堆。向上调整算法有一个前提:前面的数据必须是一个堆,才能调整。

//向上调整
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;}else{break;}}
}

5.堆的创建

方法一:向上调整插入的思想

        下面我们给出一个数组,利用上面push函数的思路,将数组a中的元素依次插入向上调整,把第一个数当成堆,满足堆向上调整的前提,可以调整成堆。

int a[] = {1,5,3,2,8};

//建堆
//向上调整:前提是前面的数据是堆
// 思路:第一个数据当作堆,后面数据依次插入,向上调整
//时间复杂度O(N*logN)
for (int i = 1; i < n; i++)
{AdjustUp(a, i);
}

        所以这里我们就可以给堆结合实现一个创建堆的接口:使用向上调整的思路。

void HeapInitArray(HP* php, int* a, int n)
{assert(php);assert(a);php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);if (php->a == NULL){perror("malloc fail");exit(-1);}php->size = php->capacity = n;memcpy(php->a, a, sizeof(HPDataType) * n);for (int i = 0; i < n; i++){AdjustUp(php->a, i);}
}

        因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

因此:建堆的时间复杂度为O(N*logN)。

方法二:倒数第一个非叶子结点向下调整的思想

        下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。如果根节点左右子树是堆,我们可以直接向下调整即可,但是此时根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树与其叶子结点开始向下调整,调整完直接下标减一就是倒数的第二个非叶子节点,一直调整到根节点的树,就可以调整成堆。

int a[] = {1,5,3,8,7,6}; //倒数第一个非叶子结点:(最后一个叶子结点-1)/2 

//建堆
//向下调整建堆
//找到倒数第一个非叶子结点
//时间复杂度O(N)
for (int i = (n - 1 - 1)/2; i >= 0; i--)
{AdjustDown(a, n, i);
}

        因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

因此:建堆的时间复杂度为O(N)。

6.堆排序

1. 排序如何建堆

  • 升序:建大堆
  • 降序:建小堆

        为什么升序是建大堆呢?按照我们的常理,我们先建小堆,然后再取出堆顶的数据,这样就取得了最小的数据,这样数据不就有序了,为什么要去建大堆呢???

        取出堆顶的数据,这样就取得了最小的数据,然后再选次小的数,此时我们只能将剩下的数看做堆,但是剩下的数据还是堆嘛?

        此时就要重新建堆,然后再取堆顶数据,再建堆...每次建堆的时间复杂度N*logN,一共有N个数据,所以总的排序时间复杂度就是N * logN * N,那还不如直接遍历一遍排序来的快呢!!!

2. 利用堆删除思想来进行排序

        所以此时我们可以建大堆,将堆顶的数据和最后一个叶子结点交换,由于此时的堆结构没有破坏,左子树和右子树仍然是堆,使用堆的向下调整去调整堆,然后在缩小下次向下调整的范围,也就是把最大的那个数不算做堆的范围了,这样最大的数据就保存在了下标最大的位置处,满足了升序的要求。每次向下调整的时间复杂度是logN,一共有N个数据,所以总的排序时间复杂度就是N * logN。

#define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>void Swap(int* p1, int* p2)
{int temp = *p1;*p1 = *p2;*p2 = temp;
}
//向上调整
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;}else{break;}}
}
//向下调整
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;//parent到叶子结点就结束while (child < n){//可能不存在右孩子if (child + 1 < n && a[child] < a[child + 1]){child++;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}}
void HeapSort1(int a[],int n)
{//建堆//向上调整:前提是前面的数据是堆// 思路:第一个数据当作堆,后面数据依次插入,向上调整//O(N*logN)for (int i = 1; i < n; i++){AdjustUp(a, i);}//升序建大堆//O(N*logN)//向下调整:前提是左右子树是堆int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}
void HeapSort2(int a[],int n)
{//建堆//向下调整:前提是左右子树是堆// 思路:找到倒数第一个非叶子结点,与最后一个叶子结点进行向下调整,直至根节点//O(N)for (int i = (n - 1 - 1)/2; i >= 0; i--){AdjustDown(a, n, i);}//升序建大堆//O(N*logN)//向下调整:前提是左右子树是堆int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDown(a, end, 0);end--;}
}
int main()
{int a[] = { 3,17,4,20,16,5 };//HeapSort1(a,sizeof(a)/sizeof(a[0]));HeapSort2(a,sizeof(a)/sizeof(a[0]));int i = 0;for (i = 0; i < sizeof(a) / sizeof(a[0]); i++){printf("%d ", a[i]);}return 0;
}

运行结果:

7.TOP-K问题

        TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

        比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

        对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

1. 用数据集合中前K个元素来建堆

  • 前k个最大的元素,则建小堆

这里不能用大堆,如果第一个数据就是最大的,放在堆顶,其余数据就无法入堆,所以要用小堆,最大的前k个数肯定比堆顶大,此时该数替换堆顶的数入堆,入完k个后就找到前k个最大的元素。

  • 前k个最小的元素,则建大堆

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

  • 将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

3.复杂度

  • 时间复杂度:O(N*logK)
  • 空间复杂度:O(K)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
void Swap(int* p1, int* p2)
{int temp = *p1;*p1 = *p2;*p2 = temp;
}
//向下调整
void AdjustDown(int* a, int n, int parent)
{int child = parent * 2 + 1;//parent到叶子结点就结束while (child < n){//可能不存在右孩子if (child + 1 < n && a[child] > a[child + 1]){child++;}if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
void PrintTopK(const char *filename, int k)
{// 1. 建堆--用a中前k个元素建堆FILE* fout = fopen(filename, "r");if (fout == NULL){perror("fopen fail");exit(-1);}int *Minheap = (int*)malloc(sizeof(int) * k);if (Minheap == NULL){perror("malloc fail");exit(-1);}//读文件for (int i = 0; i < k; i++){fscanf(fout, "%d", &Minheap[i]);}//向下调整建小堆for (int i = (k-2)/2; i >= 0; --i){AdjustDown(Minheap, k, i);}// 2. 将剩余n-k个元素依次与堆顶元素交换,不满则则替换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);
}
void CreatNData()
{//造数据int n = 10000;srand((unsigned int)time(0));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen fail");exit(-1);}for (int i = 0; i < n; ++i){int x = rand() % 1000000;fprintf(fin, "%d\n", x);}fclose(fin);
}
int main()
{//CreatNData();PrintTopK("data.txt",10);return 0;
}

运行结果:

但是我们怎么知道这几个数据就是前k个最大的呢?我们可以在文件中手动创造10个最大的值,看看输出是不是我们刚刚手动创造10个最大的值。

1000001;1000002;1000003;10000041000005;

1000006;1000007;1000008;1000009;1000009。

这样就完成了我们的TOP-K问题!!!

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

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

相关文章

kafka消费者多线程开发

目录 前言 kafka consumer 设计原理 多线程的方案 参考资料 前言 目前&#xff0c;计算机的硬件条件已经大大改善&#xff0c;即使是在普通的笔记本电脑上&#xff0c;多核都已经是标配了&#xff0c;更不用说专业的服务器了。如果跑在强劲服务器机器上的应用程序依然是单…

pom.xml中解决“vulnerable dependency maven:org.yaml:snakeyaml:1.33“警告问题

问题 当我们引入依赖的时候&#xff0c;pom文件会有这样的提示&#xff0c;其大概的意思就是 maven:org.yaml:snakeyaml:1.30"表示通过Maven引入了一个潜在的安全漏洞依赖项"org.yaml:snakeyaml:1.30" 解决办法 其实我们就是要更改这个依赖的版本&#xff0c…

【李沐深度学习笔记】按特定轴求和

课程地址和说明 线性代数实现p4 本系列文章是我学习李沐老师深度学习系列课程的学习笔记&#xff0c;可能会对李沐老师上课没讲到的进行补充。 这节就算之前内容的复习&#xff0c;后面以截图形式呈现 这节课就简单说明以下&#xff0c;axis为0是行&#xff0c;1是列&#xf…

解决方案:TSINGSEE青犀+智能分析网关助力智慧仓储智能化监管

为全面保障物流仓储的安全性与完整性&#xff0c;解决仓库管理难题&#xff0c;优化物流仓储方式&#xff0c;提升仓储效率&#xff0c;降低人工成本&#xff0c;旭帆科技推出智慧仓储AI视频智能分析方案&#xff0c;利用物联网、大数据、云计算等技术&#xff0c;对仓储管理进…

Date类的学习笔记-超级详细

Date 的定义, 在开始研究这个之前我们首先要能够明白一点&#xff0c;这个 Date 其实本质上是一个对象&#xff0c;我们通过这个对象可以去构建变量&#xff0c;知道这个之后就可以开展后续的研究了 JDK 通用 Date 类的构造方法 测试 获取当前的时间 // 构造这个日期对象Date…

【@PostConstruct、 @Autowired与构造函数的执行顺序】

PostConstruct、 Autowired与构造函数的执行顺序 一、PostConstruct介绍二、Spring框架中在bean初始化和销毁时候执行实现方式三、项目验证1.MyServiceImpl2.测试结果3. 项目源码 最近对同事代码进行codeReview时候发现用PostConstruct注解&#xff0c;特地对此注解执行顺序进行…

IDEA2023新UI回退老UI

idea2023年发布了新UI&#xff0c;如下所示 但是用起来真心不好用&#xff0c;各种位置也是错乱&#xff0c;用下面方法可以回退老UI

【C刷题】day3

一、选择题 1、已知函数的原型是&#xff1a; int fun(char b[10], int *a); &#xff0c;设定义&#xff1a; char c[10];int d; &#xff0c;正确的调用语句是&#xff08; &#xff09; A: fun(c,&d); B: fun(c,d); C: fun(&c,&d); D: fun(&c,d); 【答案…

07_ElasticSearch:倒排序索引与分词Analysis

07_ElasticSearch&#xff1a;倒排序索引与分词Analysis 一、 倒排索引是什么&#xff1f;1.1 通过示例&#xff0c;简单理解下1.2 核心组成 二、倒排索引是怎么工作的&#xff1f;2.1 创建倒排索引2.2 倒排索引搜索 三、Analysis 进行分词3.1 Analyzer 由三部分组成3.2 Analyz…

【JS】—垃圾回收机制

一、指令材料 1.定义 JavaScript&#xff08;JS&#xff09;的垃圾回收机制是一种自动管理内存的过程&#xff0c;它有助于释放不再使用的内存&#xff0c;以避免内存泄漏和提高程序的性能。 JavaScript的垃圾回收机制是一种自动管理内存的方式&#xff0c;以确保不再被引用的…

Linux Shell 实现一键部署podman

podman 介绍 使用 Podman 管理容器、Pod 和映像。从本地环境中无缝使用容器和 Kubernetes&#xff0c;Podman 提供与 Docker 非常相似的功能&#xff0c;它不需要在你的系统上运行任何守护进程&#xff0c;并且它也可以在没有 root 权限的情况下运行。 Podman 可以管理和运行…

Hive 的函数介绍

目录 ​编辑 一、内置运算符 1.1 关系运算符 1.2算术运算符 1.3逻辑运算符 1.4复杂类型函数 1.5对复杂类型函数操作 二、内置函数 2.1数学函数 2.2收集函数 2.3类型转换函数 2.4日期函数 2.5条件函数 2.6字符函数 三、内置的聚合函数 四、内置表生成函数 五、…

Android Jetpack组件架构:Lifecycle的使用 和 原理

Android Jetpack组件架构&#xff1a;Lifecycle的使用和原理 导言 作为Jetpack中关于生命周期管理的核心组件&#xff0c;Lifecycle组件是其他比如LiveDate和ViewModel等组件的基础&#xff0c;本篇文章主要就将介绍关于Lifecycle的使用和它的运作原理。 Lifecycle的使用 我…

MyBatis 中的插件可以拦截哪些操作

MyBatis 中的插件可以拦截哪些操作 MyBatis 是一个优秀的持久化框架&#xff0c;在实际项目开发中广泛应用。MyBatis 的插件机制可以方便地对 MyBatis 的各个环节进行扩展和定制。在本文中&#xff0c;我们将详细介绍 MyBatis 中的插件机制&#xff0c;并探讨插件可以拦截哪些…

C语言大佬的必杀技---宏的高级用法

C语言大佬的必杀技—宏的高级用法 目录: 字符串化标记的拼接宏的嵌套替换多条语句防止一个文件被重复包含宏和函数的区别 可能大家在学习的时候用得比较少&#xff0c;但是在一些代码量比较大的时候&#xff0c;这样使用&#xff0c;可以大大的提高代码的可读性&#xff0c;…

Dependency ‘org.redisson:redisson:‘ not found解决方法 三种刷新Maven项目的方法

报错情况 在pom中导入redisson包 <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId> </dependency> 爆红&#xff0c;还显示Dependency org.redisson:redisson: not found。 由于报错已经解决&#xff0c;…

002-第一代硬件系统架构确立及产品选型

第一代硬件系统架构确立及产品选型 文章目录 第一代硬件系统架构确立及产品选型项目介绍摘要硬件架构硬件结构选型及设计单片机选型上位机选型扯点别的 关键字&#xff1a; Qt、 Qml、 信号采集机、 数据处理、 上位机 项目介绍 欢迎来到我们的 QML & C 项目&#xff…

【视觉SLAM入门】8. 回环检测,词袋模型,字典,感知,召回,机器学习

"见人细过 掩匿盖覆” 1. 意义2. 做法2.1 词袋模型和字典2.1.2 感知偏差和感知变异2.1.2 词袋2.1.3 字典 2.2 匹配(相似度)计算 3. 提升 前言&#xff1a; 前端提取数据&#xff0c;后端优化数据&#xff0c;但误差会累计&#xff0c;需要回环检测构建全局一致的地图&…

【AI视野·今日Sound 声学论文速览 第十期】Fri, 22 Sep 2023

AI视野今日CS.Sound 声学论文速览 Fri, 22 Sep 2023 Totally 1 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers Performance Conditioning for Diffusion-Based Multi-Instrument Music Synthesis Authors Ben Maman, Johannes Zeitler, Meinard M lle…

【新版】系统架构设计师 - 案例分析 - 架构设计<架构风格和质量属性>

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 案例分析 - 架构设计&#xff1c;架构风格和质量属性&#xff1e;例题1例题2例题3例题4例题5例题6 架构 - 案例分析 - 架构设计&#xff1c;架构风格和质量属性&#xff1e; 例题1 某软件公司为…