在此次学习的过程中,有个数学问题一直困扰着我。
就是为什么2^0+2^1+2^2=2^3-1
。网上找的答案说是这个
S=2^0+2^1+2^2+2^3+.+2^(n-1)
2S=2^1+2^2+2^3+...+2^(n-1)+2^n
两式相减,
2S-S=2^n-2^0
S=2^(n)-1
但是这只是推导过程,还是没说为什么会这样。
然后我仔细想了想,想出来了哈哈哈!!!
首先2^0+2^1+2^2=2^3-1
可以变为2^0+2^1+2^2=2^3-2^0
,然后移项,变为
2^0+2^0+2^1+2^2=2^3
,因为2^0+2^0=2^1
,所以公式变为2^1+2^1+2^2=2^3
,就等于2^2+2^2=2^3
就等于2^3=2^3
,推到完成!!麻蛋初中的知识都忘了,哼唧~
在学习堆排序之前有必要了解一下完全二叉树的结构原理,因为堆排序是基于完全二叉树的。
https://blog.csdn.net/qq_22642239/article/details/80774013 可以参考下这篇文章学习一下树。
一,先说二叉树:
名词解释:
1.层次:顶级节点是0层,依次往下递增。
2.高度:指顶级节点到最远子节点的边长。什么都没有树高为-1;顶级节点树高为0。
比如如下图(右),为二叉树,左为完全二叉树:
一共有3个层次;树高就是最长的边,树高为3.
完美二叉树(左)
二叉树(右)
二叉树特点:
(1)若二叉树的层次从0开始,则在二叉树的第i层至多有2^i个结点(i>=0)。
(2)高度为k的二叉树最多有2^(k+1) - 1个结点(k>=-1)。 (空树的高度为-1)
(3)对任何一棵二叉树,如果其叶子结点(度为0)数为m, 度为2的结点数为n, 则m = n + 1。
上面所说最多的意思就是指代完美二叉树。
我们现在拿完美二叉树推导二叉树的特性为什么是这样。
1.特点三:完美二叉树每一层结点个数为2^0,2^1,2^2,2^3.......2^n.
,自己可以看图参考下,因为我们最上面已经证明了2^0+2^1+2^2=2^3-1
,所以对于当二叉树为完美二叉树时,特点三可以这样理解,看图完美二叉树,叶子结点(度为0)m=2^3,度为2的结点数n为2^0+2^1+2^2
,就是所有非叶子结点数量.因为2^0+2^1+2^2=2^3-1
,即n=m-1,也就是m=n+1.以此类推可以得到完美二叉树都符合这个特性。
2.特点二:依然符合我们的2^0+2^1+2^2=2^3-1
公式,看这句话:高度为k的完美二叉树的结点数是高度为k+1的完美二叉树的叶子节点数-1.看图,完美二叉树,前三排的结点总数,是第四排的节点数-1;我们虚构一个第五排,那前四排的结点总数是第五排的节点数-1。所以,高度为k的完美二叉树最多有2^(k+1) - 1个结点。
3.特点一:这个就简单了,完美二叉树每层结点都是2^n-1.
以上用完美二叉树,推导二叉树的特性,属于特殊情况,二叉树的推导,以后有时间再看吧。
本次学习的猪脚来了:完全二叉树!
完全二叉树从根结点到倒数第二层满足完美二叉树,最后一层可以不完全填充,其叶子结点都靠左对齐。
copy~:
堆排序是将数据看成是完全二叉树、根据完全二叉树的特性来进行排序的一种算法
最大堆要求节点的元素都要不小于其孩子,最小堆要求节点元素都不大于其左右孩子
那么处于最大堆的根节点的元素一定是这个堆中的最大值
完全二叉树有个特性:左边子节点位置 = 当前父节点的两倍 + 1,右边子节点位置 = 当前父节点的两倍 + 2
那么对一个数组而言,如果把数组看做逻辑上的堆,最后一个父结点下标就是arr.length/2-1,为什么呢?
解释:设最后一个父结点在数组中的位置为i,
则如果只有一个子节点,则子节点为左子节点,左子节点为:2i+1,左子节点在数组中最后一个位置,则:2i+1=arr.length-1;
如果有父结点有两个子节点,最后一个子节点为右子结点:2i+2,右子结点在数组中最后一个位置,则:2i+2=arr.length-1;
两个公式移项得:i=arr.length/2-1;或者i=arr.length/2-3/2;3/2向下取整为1,所以也是i=arr.length/2-1。
下面看代码:
public static void main(String[] args) {
int[] arr = new int[10]; //构建一个空的一维数组
for (int i = 0; i < arr.length; i++) {
int temp = (int) (Math.random() * 1000) + 1;//随机产生一个 1~10 的整数
arr[i] = temp;//将产生的数添加到数组
}
long a = System.currentTimeMillis();
heapSort(arr);
long b = System.currentTimeMillis();
System.out.println(a - b + "毫秒");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static int[] heapSort(int[] arr) {
// 调整堆,从最开始的三个数形成的小堆,一直扩展到整个数组形成的大堆。 这是自底向上构建堆
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
//排序
for (int j = arr.length - 1; j > 0; j--) {
//把数组起始位置的数放到最后,因为经过调整堆之后,数组下标为0的数据是最大的,把最大的数据放在数组最后面。
swap(arr, 0, j);
// 数组交换位置后,堆得结构需要重新调整,使得下一个最大值存放到数组起始位置,这是自顶向下调整堆,最多比较次数是树高
adjustHeap(arr, 0, j);
}
return arr;
}
/**
* 交换元素
*
* @param arr
* @param a 元素的下标
* @param b 元素的下标
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
// 调整堆
public static void adjustHeap(int[] array, int i, int length) {
int temp = array[i];
// 这一步的意思是判断当前结点是否有左子节点,k=2*k+1的意思是如果左子结点下面还有左子节点,那么继续进行判断比较是否符合堆规则
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {
// 这一步的意思是判断是否有右子结点,并且右子结点数据比左子节点大,则取右子结点
if (k + 1 < length && array[k] < array[k + 1]) {
k++;
}
// 取得左右子结点中较大的那一个和父节点比较,如果比父节点大,则交换
if (array[k] > temp) {
// 交换三角堆中数据,大的值作为父节点
swap(array, i, k);
// 这一部很重要,我就卡着了,顺便吐槽一下百度百科的java堆排序,没有这一步,那个排序是错的
//这步的意义就是:三角堆中,子结点和父结点的数据交换后,判断新形成的三角堆,以子结点为父结点的下方的三角堆是否满足大顶堆,调整。
i = k;
} else {
// 如果不大,则证明此堆符合规则,则退出循环,
break;
}
}
}
以上是堆排序,我研究的时候想的不周全,以为每次重建堆,都会把数组中所有的数据重新排列建堆,所以想对堆排序进行一些改进,然后下面是我研究堆排序得到的一个算法,可惜效率不高,本质是选择排序。
看代码,我把它命名为堆选择排序:
public static void main(String[] args) {
int[] arr = new int[10]; //构建一个空的一维数组
for (int i = 0; i < arr.length; i++) {
int temp = (int) (Math.random() * 1000) + 1;//随机产生一个 1~10 的整数
arr[i] = temp;//将产生的数添加到数组
}
long a = System.currentTimeMillis();
heapSort1(arr);
long b = System.currentTimeMillis();
System.out.println(a - b + "毫秒");
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
public static int[] heapSort1(int[] arr) {
//取得数组中最大值
heapChange(arr, 0);
for (int j = arr.length - 1; j > 0; j--) {
// 把最大值和末尾数据交换
swap(arr, 0, j);
// 从剩余数据中取得最大值
heapChange(arr, arr.length - j);
}
return arr;
}
private static void heapChange(int[] array, int j) {
int n = (array.length - j) / 2 - 1;
// 这一步的意思是判断当前结点是否有左子节点,k=2*k+1的意思是如果左子结点下面还有左子节点,那么继续进行判断比较是否符合堆规则
for (int k = 2 * n + 1; k < array.length - j && n >= 0; k = 2 * n + 1) {
// 这一步的意思是判断是否有右子结点,并且右子结点数据比左子节点大
if (k + 1 < array.length - j && array[k] < array[k + 1]) {
k++;
}
// 取得左右子结点中较大的那一个和父节点比较,如果比父节点大,则交换
if (array[k] > array[n]) {
swap(array, n, k);
}
// 比较下一个父结点
n--;
}
}
/**
* 交换元素
*
* @param arr
* @param a 元素的下标
* @param b 元素的下标
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
我这个排序,是从数组中取得最大值,放到数组最后面,然后再从数组中取得剩余数据的最大值,放到最后面,本质是选择排序,效率没有堆排序高,原因是,堆排序一次建堆后,每次最多比较树高次就可以重新建堆,效率高,而我的,效率低。。
经此研究,发现堆排序效率还是可以的,但没有归并排序效率高好像