对二叉树堆排序的升级&TOPK问题(跑路人笔记)

简介: 对二叉树堆排序的升级&TOPK问题(跑路人笔记)

前言

对堆排序进行一下升级,我们之前使用堆需要先实现一下整个堆的函数而且还要将数据导入到堆结构单独开辟的空间内,非常没必要所以我们为什么不直接将我们的原数组内的元素直接当做堆来进行堆排序的所需呢?

说搞就搞


堆的应用

简易实现堆

之前我们通过实现堆的代码然后用堆代码来实现我们的堆排序,这样的堆排序的


空间复杂度O(N)


时间复杂度为O(N)


代码如下:


void HeapSort(int* a, int size)
{
  Heap hp;
  HeapCreat(&hp);
  for (int i = 0; i < size; ++i)
  {
  HeapPush(&hp, a[i]);
  }
  size_t j = 0;
  while (!HeapEmpty(&hp))
  {
  a[j] = HeapTop(&hp);
  j++;
  HeapPop(&hp);
  }
  HeapDestory(&hp);
}



而且我们想使用这样的堆排序甚至要专门搞个堆来实现.非常痛苦.


其实我们完全可以通过稍微更改一下向上和向下调整函数来实现堆排序


调整后的函数我通过注释的方式进行讲解吧=-=.毕竟之前就讲过

void Swap(HPDataType* a, HPDataType* b)
{
  HPDataType tmp = *a;
  *a = *b;
  *b = tmp;
}
void AdjustDown(HPDataType* a, size_t size, size_t root)//root就是我们要调整的父亲节点.
{
  size_t parent = root;
  size_t child = parent * 2 + 1;//公式得到的child是parent的左孩子
  while (child < size)
  {
    // 1、根据我们需要的情况去调整我们的大于小于号
    if (child + 1 < size && a[child + 1] < a[child])
    {
      ++child;
    }
    // 2、孩子与父亲比较为真则交换,并继续往下调整
    if (a[child] < a[parent])
    {
      Swap(&a[child], &a[parent]);
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}
void AdjustUp(HPDataType* a, size_t child)
{
  size_t parent = (child - 1) / 2;//公式无论是左孩子还是右孩子都可以得到我们的parent
  while (child > 0)
  {
    if (a[child] < a[parent])
    {
      Swap(&a[child], &a[parent]);
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}

我们得到调整后的函数后我们就可以将传来的数组调整成堆形式然后通过一个手段(会讲的啦)来得到我们想要的排序结果.


我们主要讲一下我们用向下调整和向上调整形成堆的时间复杂度.


堆的排序我们要使用向下调整的方式来实现.


首先我们先得到我们的堆可以通过向上调整和向下调整两种方式得到.


首先我们先将一下向上调整的方式的代码和时间复杂度讲一下.


for (int i = 1; i < n; i++)
  {
  AdjustUp(a, i);
  }


向上调整的函数直接将所有的元素都检查一下并将所有位置都进行调整,下面这种的调整也许大家更容易理解


for (int i = n; i >0; i--)
  {
  AdjustUp(a, i);
  }



我们来讲一下他的时间复杂度.


假设我们每次的AdjustUp都是最坏的情况及从尾一直走到头,及执行了他树高度的次数,我们知道他的N值通过公式的h = log2(N+1)(以2为底的log).


再因为for循环每次循环次数从n~1而每层节点数如下.

image.png



我们以满二叉树为例,因为我们的cpu的运算能力就算少哪几个节点或多几个节点对时间影响不大.


所以我们可以列出公式如下:


image.png


这就是很经典的等比数列乘以等差数列了,我们直接用错位相减的方式可得:


image.png


然后又有N= 2h -1


所以我们的时间复杂度就为O(N*logN).


向下调整的函数的时间复杂度我们也稍微讲一下.


先看看我们使用的方式吧.


for (int i = (n - 1 - 1) / 2; i >= 0; --i)
  {
  AdjustDown(a, n, i);
    //void AdjustDown(HPDataType* a, size_t size, size_t root)//root就是我们要调整的父亲节点.
  }


相信大家注意到了我们这个int i = (n-1-1)/2这个怪怪的表达式了.


我们这个是需要从最后一个非叶节点开始调整的,n的第一次-1是为了找到最后一个节点,再-1是因为我们要找父节点的公式所以可以解读长((n-1)-1)/2,其中的n-1就是为了得到最后一个节点的下标.


既然读懂了这个步骤的操作我们就来看看他的时间复杂度吧.


类似于我们的向上调整这个时间的复杂度我们也用满二叉树来讲解.


image.png


我们的向下调整建堆就比向上调整建堆减少了一层.


所以根据最坏次数得到的时间复杂度如下公式


假设有n个元素h层


image.png


依旧是错位相减即可(高中知识啊).


最后得

image.png



所以时间复杂度为O(N)


堆排序

OK.现在我们所有的方式的建堆都得到了.


现在我们想用这些堆来实现堆排序我们需要做什么呢.


这时候我们要通过向下调整来得到我们的排序结果.


使用向下调整的原因也很简单.因为向上调整函数会破坏我们已经创建好的堆结构.而向下调整不会.


不过我们不能普普通通的使用AdjustDown函数我们需要更改结尾位置


int end = n - 1;
  while (end > 0)
  {
  Swap(&a[0], &a[end]);
  AdjustDown(a, end, 0);
  end--;
  }



为什么我们要end–呢?


其实很简单我们要通过更改end的位置将根节点(a[0])传输到从数值的倒数第一的位置再到倒数第二的位置…这样我们的排序就完成了.🤪


TOPK问题

TOPK问题分为两种,


第一种总体N和K的数值差距不大 如: 从50个人里找到前10.


第二种总体N和K的数值相差巨大 如:从10亿个人里找到前十


第一种我们直接通过排序或者建堆然后用HeapPop函数来得到我们的值


不过如果是第二种情况呢?


总不能建立一个10亿的堆吧.我们这10亿个数据都存储在文件中的时候我们如何得到前K的数据呢?


其实我们可以建一个只有K个数值的小堆,然后我们遍历那个10亿的数据发现比小堆根节点根节点大的就将其进行调换并重新将堆格式进行调整.


这样我们就得到了K个最大的值,如果还想进行排序就简单多了.


结尾

下一篇我的计划是将分治思想.也不知道会有多大的篇幅.🤪


相关文章
|
10月前
|
算法 测试技术 Android开发
LeetCode 周赛上分之旅 #45 精妙的 O(lgn) 扫描算法与树上 DP 问题
学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度也更复杂。在这个专栏里,小彭与你分享每场 LeetCode 周赛的解题报告,一起体会上分之旅。
44 2
LeetCode 周赛上分之旅 #45 精妙的 O(lgn) 扫描算法与树上 DP 问题
|
2月前
|
算法
【错题集-编程题】腐烂的苹果(多源 BFS + 最短路)
【错题集-编程题】腐烂的苹果(多源 BFS + 最短路)
|
25天前
|
C++
【洛谷 P1618】三连击(升级版)题解(深度优先搜索+位集合)
`三连击(升级版)` 是一道编程题,要求将数字 $1$ 到 $9$ 分成三组,构成三个三位数,其比例为 $A:B:C$。给定 $A$, $B$, $C$,程序应找到所有可能的组合并按首位升序输出。输入为 $A$, $B$, $C$,输出是满足比例的三位数或&quot;No!!!&quot;(当无解时)。解决方案涉及全排列搜索和比例验证。提供的AC代码使用C++,通过位集记录数字使用情况,递归实现全排列。
8 0
|
10月前
|
机器学习/深度学习 存储 算法
【数据结构与算法篇】手撕八大排序算法之快排的非递归实现及递归版本优化(三路划分)
【数据结构与算法篇】手撕八大排序算法之快排的非递归实现及递归版本优化(三路划分)
72 0
|
算法 搜索推荐
【第四天】算法图解 之 快速排序
【第四天】算法图解 之 快速排序
【第四天】算法图解 之 快速排序
|
存储 机器学习/深度学习 算法
【第二天】算法图解 之 选择排序
【第二天】算法图解 之 选择排序
|
算法 搜索推荐
算法笔记(二)——快排,归并算法(做成模板题)
算法笔记(二)——快排,归并算法(做成模板题)
算法笔记(二)——快排,归并算法(做成模板题)
|
存储 算法
学会二叉树不知道干啥?二叉树的深度优先搜索和广度优先搜索,我要打十个乃至二十个(打开你的LeetCode撸起来)学练并举
学会二叉树不知道干啥?二叉树的深度优先搜索和广度优先搜索,我要打十个乃至二十个(打开你的LeetCode撸起来)学练并举
学会二叉树不知道干啥?二叉树的深度优先搜索和广度优先搜索,我要打十个乃至二十个(打开你的LeetCode撸起来)学练并举
|
算法 搜索推荐
# 每日一问——什么是快速排序?如何优化?
快速排序是从冒泡排序演变而来的算法,但是其比冒泡排序要高效,所以叫做快速排序,简单理解如下。
75 0
|
算法 程序员 索引
【算法合集】学习算法第二天(二分与排序篇)
哈喽,大家好,我是程序猿追,通过上一篇算法文的私信,有小伙伴留言说,什么时候更新呀?这不?今天它就来了。
106 0
【算法合集】学习算法第二天(二分与排序篇)