网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
接下来我们即将进入一个全新的空间,对代码有一个全新的视角~
以下的内容一定会让你对数据结构
有一个颠覆性的认识哦!!!
❗以下内容以C语言
的方式实现,对于数据结构
来说最重要的是思想
哦❗
以下内容干货满满,跟上步伐吧~
作者介绍:
🎓 作者: 热爱编程不起眼的小人物🐐
🔎作者的Gitee:代码仓库
📌系列文章&专栏推荐: 《刷题特辑》、 《C语言学习专栏》、《数据结构_初阶》📒我和大家一样都是初次踏入这个美妙的“元”宇宙🌏 希望在输出知识的同时,也能与大家共同进步、无限进步🌟
📌导航小助手📌
💡本章重点
- 堆排序
- Top-K问题
🍞堆的应用
**🥐Ⅰ.**堆排序
💡堆排序:
- 本质为选择排序的一种
- 堆排序便是利用
堆
这种数据结构的思想进行排序
✊如果有同学不太熟悉堆
,可点击⌈跳转⌋补充知识再出发呀
🔥 思想:
- 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
❓思考: 如何利用堆
进行排序
- 假如排
升序
,我们利用小堆
进行排序的话: - 我们建好一个小堆后,因为小堆的特性为
最小
的数在根节点处(最上面),我们便排好升序
中的第一个数字 - 此时我们再继续排剩下的数字,便需要在
建堆
的时候剔除已经排好的数字,继而将剩下的重新的建堆
,选最小
的数字 - 重复上述步骤即可
❗特别注意:
- 如果按照上述方法进行排序的话,有两个问题:
- 🔴第一次建好堆后,在选出最小的数字放到第一个位置紧接着要选出次小的(即进行第二次建堆选数,谁去做新的根节点),如何选?
Eg:
-
🟠假如按数组的下标顺下去,让下一个数作为新的根结点(即指上图中的
5
)的话,那树原来的结构就被打乱了,需要重新建堆,才能再次选出最小的- 建堆的时间复杂度为
O
(
N
)
O(N)
O(N),那整体排序下来时间复杂度便为
O
(
N
2
)
O(N^2)
O(N2)
-
🟡最终发现如果按照这种方式进行排序的话,相当于
建堆
的作用就是选数
,还不如直接遍历数组选数进行排序,那次是堆排序
就没有意义了
⭐但事实真的是这样吗?并不!
➡️方法:
- 1️⃣建堆
假如我们调转一下思路,排升序
不用小堆
,而是:
- 升序:建
大堆
- 降序:建
小堆
Eg:
- 2️⃣利用
堆删除
的思想进行排序
- 将
堆顶
的数据与堆尾
数据进行交换,这样就达到排好最大值的效果
- 将排好的数字不再纳入
建堆
中【即建堆
的目的就是选出最大值
,所以已经选出来的,就无需要再参加】 - 并将交换后产生新的树进行重新
建堆
【因为此时只有根节点发生变化,而左、右子树的关系并没有改变,所以可以利用向下调整算法
进行建堆】
- 重复上述步骤即可,直至
堆
里只剩下一个元素,才截止
✊动图示例: 完整过程
✊综上:堆排序的代码实现【时间复杂度:
O
(
N
∗
l
o
g
N
)
O(N*logN)
O(N∗logN)】
void ADjustDown(int \* a, int n, int parent)
{
//大堆
int child = parent \* 2 + 1;
while (child < n)
//当 孩子的下标 超出 数组的范围,则说明不存在
{
//1.选出左右孩子中,较小的一个
//child -- 左孩子下标;child+1 -- 右孩子下标
if (child + 1 < n && a[child + 1] > a[child])
{
//想象的时候:默认左孩子是比右孩子小
//如果大的话,child就走到右孩子下标处
child++;
}
//2.交换
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent \* 2 + 1;
}
else
{
//满足的情况
break;
}
}
}
void HeadSort(int\* a,int n)
{
//建堆 --- 时间复杂度:O(N)
//大堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
ADjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
//end = 0的时候就截止:即 长度为0就截止
{
Swap(&a[0], &a[end]);
//选出次大值
ADjustDown(a, end, 0);
end--;
}
}
**🥐Ⅱ.**Top-K问题
💡Top - K:
- 意思就是:求数据中
前K个
最大的元素或者最小的元素 - 比如:在高考出成绩时,需要对全省考生的成绩进行排序并取出前50名考生的成绩进行封锁,此时就涉及
Top-K
问题了
👉我们便以题目去理解它吧~
🏷️最小的K个数【难度:简单】
🔍题目传送门:
剑指 Offer 40. 最小的k个数 |
---|
输入整数数组 arr
,找出其中最小的 k
个数
例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4
- 示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
- 示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
💡解题关键: 找出前K
个最小的元素
➡️ 思路一:堆排序
- 利用我们刚刚所学的
堆排序
,对数组进行排序,然后取出前K
个即可
❗特别注意:
- 我们需要自己先实现一个
堆排序
,再进行使用 - 在用完
堆
后,记得销毁,否则会造成内存泄露
👉实现:
int\* getLeastNumbers(int\* arr, int arrSize, int k, int\* returnSize)
{
int\* returnArray = (int\*)malloc(k \* sizeof(int)) ;
HeadSort(arr,arrSize);
for(int i = 0; i < k; i++)
{
returnArray[i] = arr[i];
}
\*returnSize = k;
return returnArray;
}
💫但目前
堆排序
的效率为:O
(
N
∗
l
o
g
N
)
O(N*logN)
O(N∗logN),是否还能优化呢?
⭐答案是可以的
➡️ 思路二: 建堆
- 根据题意,我们只需要取出最小的前
K
个数即可,那我们便可以不对数组进行排序,而是利用小堆
的特性:每个父亲结点的值总是≤
孩子结点的值 - 只需要对这个数组进行
建堆
(建成小堆)即可,然后每次都取出根节点
(最小值),一共取K
次,便是最小的前K
个元素
❗特别注意:
- 我们需要自己先实现一个
堆
,再进行使用
👉实现:
1️⃣实现堆
typedef int HPDataType;
typedef struct Heap
{
HPDataType\* a;
int size;
int capacity;
}HP;
void Swap(HPDataType\* p1, HPDataType\* p2);
void AdjustDwon(HPDataType\* a, int size, int parent);
void AdjustUp(HPDataType\* a, int child);
void HeapDestroy(HP\* php);
void HeapPop(HP\* php);
HPDataType HeapTop(HP\* php);
void Swap(HPDataType\* p1, HPDataType\* p2)
{
HPDataType tmp = \*p1;
\*p1 = \*p2;
\*p2 = tmp;
}
void HeapInit(HP\* php, HPDataType\* a, int n)
{
assert(php);
php->a = (HPDataType\*)malloc(sizeof(HPDataType)\*n);
if (php->a == NULL)
{
printf("malloc fail\n");
exit(-1);
}
//1.拷贝
memcpy(php->a, a, sizeof(HPDataType)\*n);
php->size = n;
php->capacity = n;
//2.建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDwon(php->a, php->size, i);
}
}
void HeapDestroy(HP\* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->size = php->capacity = 0;
}
void AdjustUp(HPDataType\* a, int child)
{
int parent = (child - 1) / 2;
//while (parent >= 0)
while (child > 0)
{
//if (a[child] < a[parent])
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//建小堆
void AdjustDwon(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;
}
else
{
break;
![img](https://img-blog.csdnimg.cn/img_convert/7aed594ea8bbf23540629865c60a7e87.png)
![img](https://img-blog.csdnimg.cn/img_convert/db0152a90202f87e5f6b39631950d44a.png)
![img](https://img-blog.csdnimg.cn/img_convert/69e266498d0afc63cc705473a303b694.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
\* 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;
}
else
{
break;
[外链图片转存中...(img-CKR2iaDJ-1715055108646)]
[外链图片转存中...(img-FrM9LJZU-1715055108646)]
[外链图片转存中...(img-ZqkAva9E-1715055108647)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**