算法笔记五:利用堆结构来对数据进行排序
2014-10-17 17:34
525 查看
算法思想:
//将数组build成最大堆格式,这样,数组的第一个元素一定是最大的
//有了这个前提,就可以从后往前迭代:将最后一个元素与第一个元素交换,这样最大值就排到了最后面
//然后对第一个元素开头的堆结构进行重建堆,使其依然满足第一个元素为最大的元素
//关键就在于建堆的逻辑
//堆数据结构:给定一个节点的下标i,其左子节点的位置为2*i+1(i右移一位+1),右子节点的位置为2*i加2(右移一位+2)
//最大堆:父节点的值data[i]一定不小于子节点的值
//涉及的几个操作:
//堆调整:给定一个节点,假设其左子节树和右子树都满足最大堆的性质,那么,如果当前节点和左右子节点不满足堆性质,就要进行调整,调整时,要么和左子节点进行交换,要么和右子节点进行交换
//一旦发生交换后,就会破坏被交换的那个子节点的堆性质,然后需要做的就是递归对子节点进行同样的堆调整操作
//建堆:这里,我们可以利用堆调整的操作,自底而上的构建堆,由子节点位置的计算公式可以得出,数组的n/2处,后面的一定都是叶子节点,所以我们从n/2处开始往回走,直到第一个元素,对这些每个非叶子节点,自下而上的进行堆性质维护,因为自下而上,所以每次都满足我们建堆的假设条件:左右子树都满足堆的性质,所以最终就完成了整个堆的构建
//有了堆,排序就好办,数组的第一个元素是最大的,将他和数组的最后一个元素调换,这样最大的就放到最后去了,然后对堆的根节点重新进行调整
//其他的几个操作:
//增大一个节点的值:因为是增大该节点,所以,其节点本身的堆属性并不会受影响
//而唯一需要做的就是,连续向上和父节点进行比较,如果比父节点大,则交换,直到其比父节点的值要小
//插入一个节点
//也很简单,先扩容,然后插入一个无穷小的值到最后,这时候一定是不违反堆性质的,然后执行上面的增大值的操作将这个无穷大的值增大到实际值即可
//其实就是扩容-->赋值-->执行该节点的增大操作
这里的一个问题就是,其是对原有问题的分解,不属于分治思想,但我们依然可以分析其执行的代价:
整个排序由两部分组成:
1、建堆
2、取最大值(取完之后又要对未取完的数据进行建堆)
其中,取最大值的代价,就是两个元素进行交换的代价,是一个常量值,而且最多执行n次,所以我们可以忽略它
所以,其根本还是取决于建堆的代价
堆调整:其执行的次数,最大值就是要调整树的高度的次数,其值等于lgn = h(树的高度)
建堆:假定每个要调整的子树,其最坏调整代价都是lgn(除根节点处的所有子树,都会小于这个值),
那么一共会有少于总数n的有限个子树需要调整,总代价为n*lgn ,这里的数学公式比较的复杂,但是可以告诉大家其答案为n,也就是不会超过整棵树的元素的个数,读者可以多画几棵树来实际推测验证下。
取最大值:最大值,是遍历数组最后一个元素,与第一个元素交换,然后再重建调整堆,直到交换到数组只剩2个元素,所以,一共的交换的次数为n-1,而每次交换后的调整堆的代价最坏也就是lgN,所以总代价就是(n-1)*lgn,再加上我们前面分析的建堆的代价为lgn,得出最坏的代价为(n-1)*lgn + n
结论:其最坏的代价为(n-1)*lgn + n(这里教科书上将其渐进的等于nlgn)
空间代价:
空间上只有在调整堆时,对两个元素交换使用了一个临时存储空间,所以空间代价还是非常小的
算法实现:
算法总结:
时间代价表现优异,n + n*lgn
空间代价上,仅仅在需要进行调整时,开辟一个用来存储两个元素交换的临时的一个存储空间,代价非常小。
还有一个结论就是,在一个已经建好的堆上执行所有的优先级队列的操作,其时间代价均为lgn!比如:取出一个最大值,插入一个值,调整一个值的大小==
//将数组build成最大堆格式,这样,数组的第一个元素一定是最大的
//有了这个前提,就可以从后往前迭代:将最后一个元素与第一个元素交换,这样最大值就排到了最后面
//然后对第一个元素开头的堆结构进行重建堆,使其依然满足第一个元素为最大的元素
//关键就在于建堆的逻辑
//堆数据结构:给定一个节点的下标i,其左子节点的位置为2*i+1(i右移一位+1),右子节点的位置为2*i加2(右移一位+2)
//最大堆:父节点的值data[i]一定不小于子节点的值
//涉及的几个操作:
//堆调整:给定一个节点,假设其左子节树和右子树都满足最大堆的性质,那么,如果当前节点和左右子节点不满足堆性质,就要进行调整,调整时,要么和左子节点进行交换,要么和右子节点进行交换
//一旦发生交换后,就会破坏被交换的那个子节点的堆性质,然后需要做的就是递归对子节点进行同样的堆调整操作
//建堆:这里,我们可以利用堆调整的操作,自底而上的构建堆,由子节点位置的计算公式可以得出,数组的n/2处,后面的一定都是叶子节点,所以我们从n/2处开始往回走,直到第一个元素,对这些每个非叶子节点,自下而上的进行堆性质维护,因为自下而上,所以每次都满足我们建堆的假设条件:左右子树都满足堆的性质,所以最终就完成了整个堆的构建
//有了堆,排序就好办,数组的第一个元素是最大的,将他和数组的最后一个元素调换,这样最大的就放到最后去了,然后对堆的根节点重新进行调整
//其他的几个操作:
//增大一个节点的值:因为是增大该节点,所以,其节点本身的堆属性并不会受影响
//而唯一需要做的就是,连续向上和父节点进行比较,如果比父节点大,则交换,直到其比父节点的值要小
//插入一个节点
//也很简单,先扩容,然后插入一个无穷小的值到最后,这时候一定是不违反堆性质的,然后执行上面的增大值的操作将这个无穷大的值增大到实际值即可
//其实就是扩容-->赋值-->执行该节点的增大操作
这里的一个问题就是,其是对原有问题的分解,不属于分治思想,但我们依然可以分析其执行的代价:
整个排序由两部分组成:
1、建堆
2、取最大值(取完之后又要对未取完的数据进行建堆)
其中,取最大值的代价,就是两个元素进行交换的代价,是一个常量值,而且最多执行n次,所以我们可以忽略它
所以,其根本还是取决于建堆的代价
堆调整:其执行的次数,最大值就是要调整树的高度的次数,其值等于lgn = h(树的高度)
建堆:假定每个要调整的子树,其最坏调整代价都是lgn(除根节点处的所有子树,都会小于这个值),
那么一共会有少于总数n的有限个子树需要调整,总代价为n*lgn ,这里的数学公式比较的复杂,但是可以告诉大家其答案为n,也就是不会超过整棵树的元素的个数,读者可以多画几棵树来实际推测验证下。
取最大值:最大值,是遍历数组最后一个元素,与第一个元素交换,然后再重建调整堆,直到交换到数组只剩2个元素,所以,一共的交换的次数为n-1,而每次交换后的调整堆的代价最坏也就是lgN,所以总代价就是(n-1)*lgn,再加上我们前面分析的建堆的代价为lgn,得出最坏的代价为(n-1)*lgn + n
结论:其最坏的代价为(n-1)*lgn + n(这里教科书上将其渐进的等于nlgn)
空间代价:
空间上只有在调整堆时,对两个元素交换使用了一个临时存储空间,所以空间代价还是非常小的
算法实现:
#ifndef __p1__HeapSortV2__ #define __p1__HeapSortV2__ #include <stdio.h> //将数组build成最大堆格式,这样,数组的第一个元素一定是最大的 //有了这个前提,就可以从后往前迭代:将最后一个元素与第一个元素交换,这样最大值就排到了最后面 //然后对第一个元素开头的堆结构进行重建堆,使其依然满足第一个元素为最大的元素 //关键就在于建堆的逻辑 //堆数据结构:给定一个节点的下标i,其左子节点的位置为2*i+1(i右移一位+1),右子节点的位置为2*i加2(右移一位+2) //最大堆:父节点的值data[i]一定不小于子节点的值 //涉及的几个操作: //堆调整:给定一个节点,假设其左子节树和右子树都满足最大堆的性质,那么,如果当前节点和左右子节点不满足堆性质,就要进行调整,调整时,要么和左子节点进行交换,要么和右子节点进行交换 //一旦发生交换后,就会破坏被交换的那个子节点的堆性质,然后需要做的就是递归对子节点进行同样的堆调整操作 //建堆:这里,我们可以利用堆调整的操作,自底而上的构建堆,由子节点位置的计算公式可以得出,数组的n/2处,后面的一定都是叶子节点,所以我们从n/2处开始往回走,直到第一个元素,对这些每个非叶子节点,自下而上的进行堆性质维护,因为自下而上,所以每次都满足我们建堆的假设条件:左右子树都满足堆的性质,所以最终就完成了整个堆的构建 //有了堆,排序就好办,数组的第一个元素是最大的,将他和数组的最后一个元素调换,这样最大的就放到最后去了,然后对堆的根节点重新进行调整 //其他的几个操作: //增大一个节点的值:因为是增大该节点,所以,其节点本身的堆属性并不会受影响 //而唯一需要做的就是,连续向上和父节点进行比较,如果比父节点大,则交换,直到其比父节点的值要小 //插入一个节点 //也很简单,先扩容,然后插入一个无穷小的值到最后,这时候一定是不违反堆性质的,然后执行上面的增大值的操作将这个无穷大的值增大到实际值即可 //其实就是扩容-->赋值-->执行该节点的增大操作 class HeapSortV2 { public: //获取父节点 int getParentNode(int i){ return ( i - 1 ) >> 1; } //获取左子节点 int getLeftChildNode(int i){ return (i << 1) + 1; } //获取右子节点 int getRightChildNode(int i){ return (i << 1) + 2; } void changeTwoItems(int * data,int i,int j){ int tmp = *(data + i); *(data + i) = *(data + j); *(data + j) = tmp; } //求父节点和两个子节点的最大的那个节点 int getLargestNode(int * data,int node,int size){ int leftNode = getLeftChildNode(node); int rightNode = getRightChildNode(node); //node没有左子节点 if(leftNode >= size){ return node; } //有左子树,没有右子树 if(rightNode >= size){ if(*(data + node) >= *(data + leftNode)){ return node; }else{ return leftNode; } } //左子树和右子树都有,则求哪个节点是最大的节点 int largsetNode = node; if(*(data + node) >= *(data + leftNode)){ if(*(data + node) >= *(data + rightNode)){ largsetNode = node; }else{ largsetNode = rightNode; } }else{ if(*(data + leftNode) >= *(data + rightNode)){ largsetNode = leftNode; }else{ largsetNode = rightNode; } } return largsetNode; } //调整给定节点的堆的性质,假定其左子树和右子树都已经满足堆的性质 void adjustHeap(int * data,int node,int size){ //求得最大的节点 int largsetNode = getLargestNode(data,node,size); if(largsetNode != node){ //交换 changeTwoItems(data,node,largsetNode); //继续调整那个被改变了的节点 adjustHeap(data,largsetNode,size); } } //将数组build成最大堆 void buildHeap(int * data,int size){ int middle = size / 2 ; for (; middle >=0 ; middle--) { adjustHeap(data,middle,size); } } //增大一个节点的值 void increaseNode(int * data,int node){ if(node == 0){ return; } int parentNode = getParentNode(node); if(*(data + node) > *(data + parentNode)){ changeTwoItems(data,node,parentNode); increaseNode(data, parentNode); } } //增大一个节点的值 void increateNodeValue(int * data,int node,int newValue){ *(data + node) = newValue; increaseNode(data, node); } //执行堆排序 void sortByHeap(int * data,int size){ //建堆 buildHeap(data,size); //增大值的测试:第80个元素的值增大1002 increateNodeValue(data,8,*(data + 8) + 1002); //有了堆,排序就好办,数组的第一个元素是最大的,将他和数组的最后一个元素调换,这样最大的就放到最后去了,然后对堆的根节点重新进行调整 for (int i= size -1; i>0; i--) { changeTwoItems(data,i,0); adjustHeap(data,0,i ); } } //排序入口 void do_sorting(int * data,int size){ sortByHeap(data, size); } }; #endif /* defined(__p1__HeapSortV2__) */
算法总结:
时间代价表现优异,n + n*lgn
空间代价上,仅仅在需要进行调整时,开辟一个用来存储两个元素交换的临时的一个存储空间,代价非常小。
还有一个结论就是,在一个已经建好的堆上执行所有的优先级队列的操作,其时间代价均为lgn!比如:取出一个最大值,插入一个值,调整一个值的大小==
相关文章推荐
- 【数据结构与算法】 利用哈夫曼树进行文件压缩 (部分借鉴网上内容)
- 数据结构与算法学习笔记之 适合大规模的数据排序
- 数据结构笔记--排序总结
- 数据结构笔记--排序
- 数据结构笔记--排序
- 数据结构于算法分析整理笔记1
- 数据结构笔记--排序
- 数据结构与算法之二分法插入排序
- 数据结构与算法整理之排序(一)
- 【数据结构与算法基础】单链表及其应用基数排序 / Singly Linked List and radix sort
- [Silverlight学习笔记]关于利用WCF RIA Service进行通信并在客户端获取数据
- 在SQL2005中利用DENSE_RANK()排名函数对现有数据进行排序改造
- 数据结构中排序和查找的算法
- Excel学习笔记002-002:工作表内及工作表间、工作簿间单元格数据的复制、剪切、粘贴;如何进行成绩排序。
- 数据结构笔记--排序
- 我的数据结构、算法笔记
- 数据结构与算法之C#插入排序
- gridview利用 DataView 对象进行排序和过滤数据
- 数据结构与算法笔记
- 数据结构笔记-算法时间复杂度分析