短时间理解堆排序

本篇文章是我通过自身实践总结出来的一种简单学习堆排序或者是其他排序算法的方法。如有不足,请大家指正,毕竟是学习,主要是理解,主要是网络中大神们的话太过专业、不怎么好懂,字也多、我这篇应该算好懂(如不好懂,请指出,我会修改)。
#内容包括:
1.堆的分类。
2.如何构造堆(大根堆,以及代码实现,小根堆同理)。
3.真正堆排序。

#一、堆的分类
咱可以把堆(Heap)看成完全二叉树(叶节点出现在最下层和次下层,最下面一层都连续集中在最左边的若干二叉树)。
######形如这样(下面分析也会结合此图):
短时间理解堆排序_第1张图片
好的,就这样别的废话不说。(注意,上图已经是一个小根堆了,非终端节点值不大于其左子树和右子树,接下来的构造堆则是将它变为大根堆的过程)

#二、如何构造堆(将上图小根堆变为大根堆)
######先分析
如何将小根堆变为大根堆呢?其实就是将元素中最大的值放放到堆顶(即将0索引位置的黑色1变为9索引位置的黑色10),那如何做呢?我们开始。
###首先:咱们先进行建堆得第一步,先分析调整一个节点,最后使整个堆构成一个大根堆
既然我们要用到程序中,自然不能单纯的想,不过也不能没有抽象概念。

在程序中我们怎么把现在的完全二叉树表示出来呢,数组是一个不错的表示方法,好那我们就先定义一下这个数组。

上图中我们的灰色数字就是数组的索引,所以我们的数组可以定义为:

int num[] = {1,2,3,4,5,6,7,8,9,10};

然后我们通过语言来模拟节点的移动,先调整一个节点,咱们看看效果怎么样,开始咯!

在这之前我们在想一下,那从第几个节点开始调整呢?想一下,如果从黑色10节点(灰色数字9索引除)调整,它没有子节点,显然如果把它当做一个堆得话,它既是最大的元素也是最小的元素,调整它显然没有意义;在想一下如果调整黑色5节点,咱们也把它当做一个堆,它有一个子节点,就是黑色10,显然子节点比这个黑5要大,所以这个需要调整,就是互换他们;综上所述,我们要想构建大根堆,我们显然没必要从没有子节点的节点开始调整。这样我们就可以得到最后一个非叶子节点就是黑色5,也就是节点数/2,由于程序中我们是用索引来访文数组的,所以还需要减去1,就是节点数/2-1,这样通过循环就能调整到整个完全二叉树非叶子节点,黑色1、2、3、4、5。我们先调整一个吧,从最后一个非叶子节点调整就是黑色5。

好的我们开始,我们想像有一个黑盒子,这个盒子很厉害,假设这个堆只有三个元素,根节点和两个子节点,但是它不是一个大根堆,但是把这个根节点扔进这个黑盒子之后,就能把它变为最大堆,是不是很神奇。而我们写的这个构造堆的函数,就是这个黑盒子,好了,开始。

我们先给这个盒子起个名字吧,它的作用是构造堆,我们最好起一个外文的名字,暂时叫HeapStruct(堆构造)…这个盒子肯定得接收点东西,才能有相应的输出是吧,我们既然是要调整上面那个num[]数组,所以盒子肯定要这个数组,也就是盒子接收的参数一定得有这个数组,好,这样一个参数确定了;然后这个盒子是不是也得知道从第几个元素调整呢,是的,毕竟叶子节点没必要调整,那第二个参数自然是在这个数组中要调整的元素索引了。如果这个盒子再知道这个数组有多少个元素就更好了,那第三个参数就是数组的长度了。好吧,先这样,如果有需要我们在添加。那我们用c语言模拟这个盒子的话,就成下面这样了:

/**
* array[] 接收要调整的数组
* i 要调整的元素索引
* nlength 数组的长度
**/
void HeapStruct(int array[],int i,int nlength){
    // 具体构造大根堆的过程
}

######现在把这个堆(数组num[])扔进盒子(HeapStruct)中,上面提到我们要从length/2-1索引的节点构造,所以也将length/2-1(4)传给盒子,最后把数组长度传入。
这时候调用函数成为:

HeapStruct(num,length/2-1,length);

好了装进盒子了,盒子要进行怎样的运作呢?
首先假设黑5有两个子节点(因为毕竟是二叉树,一个节点也最多有两个子节点),如何判断两个子节点和父节点谁大谁小进行比较呢?那肯定得先获取到黑5的两个子节点。
######这里有一个概念,对于二叉树,左子节点的索引(比如黑10)9是父节点索引(黑5)4的两倍多一(24+1),右子节点就简单了,是左子节点加1((24+1)+1),所以知道了一个节点的索引也就很清楚知道左子节点和右子节点了。

知道了这些我们开始调整:
我们将黑5的左节点索引定义成nChild,那么右子节点的索引自然就是nChild+1了,那么我们先获得左子节点元素值,现在黑子就变成。

/**
* 这个是具体执行函数,参数与上面的调用函数一一对应
*/
void HeapStruct(int array[],i,length){
  int nChild; // 定义左子节点的索引值nChild
  nChild = 2 * i + 1; // 那么左子节点就是2倍的这个元素的索引+1了 
}

这个时候已经获取到左子节点了。
######现在说个新问题,怎么判断一个元素的两个子节点谁大谁小呢?那肯定是要获取这个元素的右子节点,没错是的。

######这里有个这样的逻辑,就是如果是左子节点比右子节点元素值大,那么直接调换左子节点与父节点即可这样就构造了一个三个节点的大根堆,但是如果右子节点比左子节点大,如何呢?那么调换父节点和右子节点即可了;这样为了运算简便,右子节点是左子节点索引+1,为了我们选择子节点最大值,我们的执行函数就变成:

/**
* 这个是具体执行函数,参数与上面的调用函数一一对应
*/
void HeapStruct(int array[],i,length){
  int nChild; // 定义左子节点的索引值nChild
  nChild = 2 * i + 1; // 那么左子节点就是2倍的这个元素的索引+1了 
  
  /**
  * nChild < length-1先判断一下当前子节点是不是在整个数组长度中,不在的话没必要看了
  * array[nChild + 1] > array[nChild ]继续判断对应在数组中的元素是否右子节点大于左子节点,如果大于,则将子节点值nChild = nChild + 1
  */
  if(nChild < length-1 && array[nChild + 1] > array[nChild ]){
    ++nChild;
  }
}

######下面开始替换子节点中元素值大的到父节点,既然要替换,首先得有个东西暂时保存父节点值,暂时叫他nTemp吧,这样我们的执行函数变为:

/**
* 这个是具体执行函数,参数与上面的调用函数一一对应
*/
void HeapStruct(int array[],i,length){
  int nChild; // 定义左子节点的索引值nChild
  nChild = 2 * i + 1; // 那么左子节点就是2倍的这个元素的索引+1了 
  
  /**
  * nChild < length-1先判断一下当前子节点是不是在整个数组长度中,不在的话没必要看了
  * array[nChild + 1] > array[nChild ]继续判断对应在数组中的元素是否右子节点大于左子节点,如果大于,则将子节点值nChild = nChild + 1
  */
  if(nChild < length-1 && array[nChild + 1] > array[nChild ]){
    ++nChild;
  }

  int nTemp; // 定义一个暂存变量
  if(array[i] < array[nChild]){ // 如果父节点小于子节点最大的值,则替换
    nTmep = array[i]; // 先保存父节点值到nTemp
    array[i] = array[nChild]; // 将父节点替换为子节点最大值
    array[nChild] = nTemp; // 子节点则换为保存在变量中的原父节点值
  }else{
    // 什么都不做
  }
}

#####好了,咱们走一下上面的流程,看一下黑5和黑10会不会调换
我的分析步骤

1、传入数组,索引,数组长度HeapStruct(num,length/2-1,length)
2、nChild = 2 * 4 + 1 = 9;
3、第一个判断:if:nChild < length - 1 ==> 9 < 9 (否,不执行)
4、第二个判断:if:array[4] < array[9] ==> 5 < 10 (是,执行) 
      nTemp = array[4] ==> nTemp = 5;
      array[4] = array[9] ==> array[4] = 10;
      array[9] = nTemp ==> array[9] = 5;
5、好的,现在是不是黑5和黑10就替换成功了,完成了我们的调整一个节点的任务。

未完,待续。。。

你可能感兴趣的:(排序,算法,排序,堆排序)