mooc_03_排序
来源:互联网 发布:数码兽网络侦探下载pc 编辑:程序博客网 时间:2024/05/22 01:43
代码地址:https://github.com/AlbinZhang/Mooc_DataAlgorithm/tree/master/04_heapSort
1. 二叉堆
要说堆排序,首先要说下数据结构中的二叉堆,有最大堆和最小堆。
二叉堆的定义: 二叉堆是完全二叉树或者是近似完全二叉树。二叉堆满足二个特性: 1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。 2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
二叉堆一般用数组来表示, 例如上图的例子,红字标识数组下标,空出下标为0的, 根节点的下标是1,l两个子节点就是2,3第n个位置的子节点分别是2n,2n+1, 父节点是n/2取整上图的数组形式: -, 100. 34, 22, 25, 31, 19, 17, 20, 11
实现一个二叉堆(最大堆)
要求: 最大的元素在顶端(最大的元素在顶端) 每个元素都比它的父节点小,或者和父节点相等主要逻辑: 插入: 先把带插入的点放到数组的最后,然后和他的父节点比较,谁大谁在上面 弹出: 弹出顶端元素,把最后一个元素放到顶端,然后与两个子节点中大的比较,谁大谁在上面
#include <iostream>#include <stdio.h>#include <time.h>#include <assert.h>using namespace std;template <typename T>class MaxHeap{public: MaxHeap(int capacity){ m_capacity = capacity; m_data = new T[capacity + 1]; m_next = 1; } ~MaxHeap(){ delete m_data; } void insert(T node) { assert(m_next <= m_capacity); int index = m_next; m_data[index] = node; shiftUp(index); m_next++; } T pop(){ assert(m_next > 1); int index = 1; T ret = m_data[index]; m_data[index] = m_data[--m_next]; shiftDown(index); return ret; } bool empty(){ return m_next == 1; }private: void shiftUp(int index){ while (index > 1 && m_data[index] > m_data[index / 2]){ swap(m_data[index], m_data[index / 2]); index = index / 2; } } void shiftDown(int index) { while (index * 2 < m_next) { int maxIndex = index * 2; int rightNode = index * 2 + 1; if (rightNode < m_next && m_data[maxIndex] < m_data[rightNode]){ maxIndex = rightNode; } if (m_data[index] >= m_data[maxIndex]) break; swap(m_data[index], m_data[maxIndex]); index = maxIndex; } }private: int m_capacity; int m_next; //只想下一个可以存储的数组下标 T *m_data;};int main(int argc, char *argv[]){ int arr[100]; srand(time(NULL)); for (int i = 0; i < 10; i++){ //maxheap.push(rand() % 100); arr[i] = rand() % 100; } MaxHeap<int> maxheap(arr, 10); while (!maxheap.empty()){ cout << maxheap.pop() << endl; } return 0;}
shiftUp: 是检查当前index元素是否比他的父节点大,如果大,就交换,然后继续和父节点比shiftDown: 用来检查当前节点是否比他的两个子节点大,如果比子节点小,就和自己节点中最大的交换,然后继续跟子节点比较
2. 堆排序
因为二叉堆(最大堆)根节点是最大的,所以每次弹出根节点,都是当前堆中最大的,那我们就使用二叉堆进行堆排序
template <typename T>void heapSort1(T arr[], int n){ MaxHeap<T> maxheap = MaxHeap<T>(n); int i = 0; for (i = 0; i < n; i++){ maxheap.insert(i); } for (i = n - 1; i >= 0; i--) { arr[i] = maxheap.pop(); }}//number = 1000000MergeSort :0.279398sQuitSort3Way :0.269952sHeapSort1 :0.455481s
Heapify
如果直接使用一个数组生成二叉堆,可以使用另一个方法,看下图,所有的叶节点(红色的)都可以看做是要给一个完整的二叉堆,那节点4,就可以看作是这个新的二叉堆的根节点,使用shiftdown,重新使得这个二叉堆变成一个最大堆然后再是节点3,2,1 之后 整个堆就是一个最大堆了那么怎么找到最后一个非叶节点的呢? 在根节点是数组下标1的情况下,这个数组的长度为n,那么最后后一个非叶子节点是n/2通过这个方法我们重新创建一个MaxHeap的构造函数
MaxHeap(T arr[], int n){ m_capacity = n; m_data = new T[n + 1]; for (int i = 0; i < n; i++) m_data[i + 1] = arr[i]; m_next = n+1; for (int i = (m_next - 1) / 2; i >= 1; i--) shiftDown(i);}//新的heapSortvoid heapSort2(T arr[], int n){ MaxHeap<T> maxheap = MaxHeap<T>(arr, n); int i = 0; for (i = n - 1; i >= 0; i--) { arr[i] = maxheap.pop(); }}//////// 效果还是很明显的number = 1000000MergeSort :0.264709sQuitSort3Way :0.257416sHeapSort1 :0.447973sHeapSort2 :0.364310s
3. 优化堆排序
在上面介绍的堆排序中,都是先把元素拷贝到MaxHeap中,然后进行排序,最后再pop出来,重新添加到数组中这要就需要额外的开辟缓存,元素的拷贝,但是通过之前最MaxHeap的理解,完全可以不用这样我们现在已有一个数组,我们可以直接把这个数组通过MaxHeap(T arr[], int n)这里的方法使其成为一个最大堆然后堆顶的元素就是当前数组中最大的元素了, 把第一个和最后一个交换,这样最大值就已经放到末尾了然后再把前n-1个元素重新变成一个最大堆,再放到倒数第二的位置 以此类推
之前我们都是以下标1开始的,当根结点是0的时候,各个点之间的关系如下
template <typename T>void __shiftDown(T arr[], int n, int index){ while (index * 2+1 < n) { //如果还有子节点 int leftIndex = 2 * index + 1; int rightIndex = 2 * index + 2; if (rightIndex < n && arr[leftIndex] < arr[rightIndex]) { leftIndex = rightIndex; } if (arr[index] >= arr[leftIndex]) { break; } swap(arr[index], arr[leftIndex]); index = leftIndex; }}template <typename T>void heapSort3(T arr[], int n){ for(int i = (n-1)/2; i >=0; i--){ __shiftDown(arr, n, i); } for(int i = n-1; i > 0; i--){ swap(arr[0], arr[i]); __shiftDown(arr, i, 0); }}//--------------------number = 1000000MergeSort :0.350852sQuitSort3Way :0.249016sHeapSort1 :0.428990sHeapSort2 :0.323061sHeapSort3 :0.295751s
根据排序的结果可以看到,在完全随机的情况下,原地堆排序是略优于其他堆排序的
4. 索引堆
在上面的最大堆实现中,我们把数组中的元素的位置都进行了交换,但是在实际工作中,原数组的顺序可能也是一项重要的信息,值得保存下来所以为了保留元素组的顺序信息,我们不直接对原数组进行最大堆的计算,我们可以通过额外维护一个数组列表,用来保存原数组经过最大堆计算后的下标的顺序,用来实现最大堆,这个就是索引堆为了符合用户的使用习惯,索引数组是从下标0开始使用的
//从下标0开始的最大堆#ifndef IndexMAXHEAP_H#define IndexMAXHEAP_H#include <cassert>#include <string.h>#include <stdio.h>using namespace std;template <typename T>class IndexMaxHeap{ public: IndexMaxHeap(int capacity) { m_capacity = capacity; m_data = new T[capacity]; m_next = 0; } ~IndexMaxHeap() { delete m_data; } void insert(T node) { assert(m_next < m_capacity); m_data[m_next] = node; shiftUp(m_next); m_next++; } T extractMaxItem() { assert(m_next > 0); int index = 0; T ret = m_data[index]; m_data[index] = m_data[m_next]; m_next--; shiftDown(index); return ret; } bool empty() { return m_next == 0; } private: /* 跟父节点比较,如果大于父节点,交换,然后继续跟父节点比较 n 的父节点 。(n-1)/2 */ void shiftUp(int index) { while(index > 0 && m_data[index] > m_data[(index-1)/2]) { swap(m_data[index], m_data[(index-1)/2]); index = (index-1)/2; } } /* 跟子节点比较,如果小于 子节点中的最大值,就交换,然后继续跟子节点比较 n left_node = 2*n+1 right_node = 2*n+2 */ void shiftDown(int index) { while ((index*2 +1) < m_next) { //确保有子节点 int lhs = index * 2 + 1; if((lhs+1) < m_next && m_data[lhs] < m_data[lhs+1]) { lhs = lhs+1; } if(m_data[index] >= m_data[lhs]) { break; } swap(m_data[index], m_data[lhs]); index = lhs; } } private: int m_capacity; int m_next; //指向下一个可以存储的数组下标 T *m_data;};#endif //IndexMAXHEAP_H
然后我们再次基础上,增加一个 m_indexes数组,用于存储原数组排序后的索引,同时插入的时候,用户可以指定插入的位置,所以原来的insert要增加一个参数int i来指定插入的位置因为现在我们通过m_indexes存储实际数组的下标,所以shiftUp和shiftDown进行比较数值大小时,也需要通过m_indexes获取下标但是在我们进行shiftUp,shiftDown交换操作时,我们不能直接交换m_data中的值,而是m_indexes中的索引,不然我们要他何用并且我们增加了3个函数 extractMaxIndex 获取最大值的索引 getItem 通过索引获取原数组的元素 change 指定更改一个数组中的元素,并且进行最大堆的维护,使m_indexes还是一个最大堆索引
#ifndef IndexMAXHEAP_H#define IndexMAXHEAP_H#include <cassert>#include <string.h>#include <stdio.h>using namespace std;template <typename T>class IndexMaxHeap{ public: IndexMaxHeap(int capacity) : m_capacity(capacity), m_next(0) { m_data = new T[capacity]; m_indexes = new int[capacity]; } ~IndexMaxHeap() { delete m_data; delete m_indexes; } void insert(int i, T node) { assert(m_next < m_capacity); assert(i < m_capacity); m_data[i] = node; m_indexes[m_next] = i; shiftUp(m_next); m_next++; //因为shiftUp是向上比较,所以m_next++,在前或在后都可以 } T extractMaxItem() { assert(m_next > 0); int index = 0; T ret = m_data[m_indexes[index]]; m_indexes[index] = m_indexes[m_next]; m_next--; shiftDown(index); //shiftDown是向下比较,所以边界就需要正确,所以m_next--要在之前, return ret; } int extractMaxIndex() { assert(m_next > 0); int index = 0; int ret = m_indexes[index]; m_indexes[index] = m_indexes[m_next]; m_next--; shiftDown(index); //shiftDown是向下比较,所以边界就需要正确,所以m_next--要在之前, return ret; } T getItem(int i) { return m_data[i]; } /* 修改一个已经存在的数组元素 */ void change(int i, T newItem){ assert(i < m_next); m_data[i] = newItem; //此时我们要找到i,这个元素在m_indexes中的位置 //之后在shiftUp shifiDown,看当前元素能否上下移动 for(int j = 0; j < m_next; j++){ if(indexes[j] == i){ shiftUp(j); shiftDown(j); return ; } } } bool empty() { return m_next == 0; } private: /* 跟父节点比较,如果大于父节点,交换,然后继续跟父节点比较 n 的父节点 。(n-1)/2 */ void shiftUp(int index) { while(index > 0 && m_data[m_indexes[index] > m_data[m_indexes[(index-1)/2]]) { swap(m_indexes[index], m_indexes[(index-1)/2]); index = (index-1)/2; } } /* 跟子节点比较,如果小于 子节点中的最大值,就交换,然后继续跟子节点比较 n left_node = 2*n+1 right_node = 2*n+2 */ void shiftDown(int index) { while ((index*2 +1) < m_next) { //确保有子节点 int lhs = index * 2 + 1; if((lhs+1) < m_next && m_data[m_indexes[lhs]] < m_data[m_indexes[lhs+1]]) { lhs = lhs+1; } if(m_data[m_indexes[index]] >= m_data[m_indexes[lhs]]) { break; } swap(m_indexes[index], m_indexes[lhs]); index = lhs; } } private: int m_capacity; int m_next; //指向下一个可以存储的数组下标 T *m_data; int *m_indexes;};#endif //IndexMAXHEAP_H
优化change方法
change说了,是指定更改一个数组中的元素,并且进行最大堆的维护,使m_indexes还是一个最大堆索引为了维护最大堆,我们需要找到元素索引在m_indexes中的位置,之前我们使用的是循环遍历的方法,因为循环是n,shiftUp,shiftDown的复杂度是log(n),这样时间复杂度就变成了n+log(n),也就是O(n)但是我们的堆这种结构,之前的插入和删除复杂度都是log(n)级别的,现在change是O(n),整个把时间复杂度拉低了,所以我们需要进行优化这里我们的优化方式是通过反向查找的方式,来提高change的效率简单来说就是在创建一个数组,使用这个数组m_reverse来维护,m_indexes中,m_data的元素在什么位置至此我们一共有了三个数组结构,分别是m_data,m_indexes,m_reverse他们分别的作用是: m_data: 按照用户指定下标的方式,存储用户的数据 m_indexes: 为了不破环用户指定的下标,额外建立一个数组用于存储,m_data最大堆运算之后的 各个元素的位置,使用下标的方式存储 m_reverse:为了方便通过m_data下标,直接找到m_indexes中当前下标的位置,所以使用此数组来存储当前数组下标在m_indexes中的位置 例: m_data[j] 经过在最大堆中的位置是k,那么 m_indexes[k] = j; m_reverse[m_indexes[j]] = k;
#ifndef IndexMAXHEAP_H#define IndexMAXHEAP_H#include <cassert>#include <string.h>#include <stdio.h>using namespace std;template <typename T>class IndexMaxHeap{ public: IndexMaxHeap(int capacity) : m_capacity(capacity), m_next(0) { m_data = new T[capacity]; m_indexes = new int[capacity]; m_reverse = new int[capacity]; memset(m_reverse, 0, sizeof(int)*capacity); } ~IndexMaxHeap() { delete m_data; delete m_indexes; delete m_reverse; } void insert(int i, T node) { assert(m_next < m_capacity); assert(i < m_capacity); m_data[i] = node; m_indexes[m_next] = i; m_reverse[i] = m_next; shiftUp(m_next); m_next++; //因为shiftUp是向上比较,所以m_next++,在前或在后都可以 } T extractMaxItem() { assert(m_next > 0); int index = 0; T ret = m_data[m_indexes[index]]; m_indexes[index] = m_indexes[m_next]; m_reverse[m_indexes[index]] = index; m_reverse[m_indexes[m_next]] = 0; m_next--; shiftDown(index); //shiftDown是向下比较,所以边界就需要正确,所以m_next--要在之前, return ret; } int extractMaxIndex() { assert(m_next > 0); int index = 0; int ret = m_indexes[index]; m_indexes[index] = m_indexes[m_next]; m_reverse[m_indexes[index]] = index; m_reverse[m_indexes[m_next]] = 0; m_next--; shiftDown(index); //shiftDown是向下比较,所以边界就需要正确,所以m_next--要在之前, return ret; } bool contain(int i) { assert (i >= 0 && i< m_capacity); return m_reverse[i] == 0; } T getItem(int i) { assert(contain(i)); return m_data[i]; } /* 修改一个已经存在的数组元素 */ void change(int i, T newItem) { assert(contain(i)); m_data[i] = newItem; //此时我们要找到i,这个元素在m_indexes中的位置 //之后在shiftUp shifiDown,看当前元素能否上下移动 /* for(int j = 0; j < m_next; j++){ if(indexes[j] == i){ shiftUp(j); shiftDown(j); return ; } } */ int j = m_reverse[i]; shiftUp(j); shiftDown(j); } bool empty() { return m_next == 0; } private: /* 跟父节点比较,如果大于父节点,交换,然后继续跟父节点比较 n 的父节点 。(n-1)/2 */ void shiftUp(int index) { while(index > 0 && m_data[m_indexes[index] > m_data[m_indexes[(index-1)/2]]) { swap(m_indexes[index], m_indexes[(index-1)/2]); m_reverse[m_indexes[index]] = index; m_reverse[m_indexes[(index-1)/2]] = (index-1)/2; index = (index-1)/2; } } /* 跟子节点比较,如果小于 子节点中的最大值,就交换,然后继续跟子节点比较 n left_node = 2*n+1 right_node = 2*n+2 */ void shiftDown(int index) { while ((index*2 +1) < m_next) { //确保有子节点 int lhs = index * 2 + 1; if((lhs+1) < m_next && m_data[m_indexes[lhs]] < m_data[m_indexes[lhs+1]]) { lhs = lhs+1; } if(m_data[m_indexes[index]] >= m_data[m_indexes[lhs]]) { break; } swap(m_indexes[index], m_indexes[lhs]); m_reverse[m_indexes[index]] = index; m_reverse[m_indexes[lhs]] = lhs; index = lhs; } } private: int m_capacity; int m_next; //指向下一个可以存储的数组下标 T *m_data; int *m_indexes; int *m_reverse;};#endif //IndexMAXHEAP_H
阅读全文
0 0
- mooc_03_排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- 排序
- PHP之文件处理
- SQL与NOSQL的区别
- C++ 中 \ 的含义
- JZOJ2137.2017.05.20【usaco2017_Mar Bronze & Silver】C组T5Bovine Genomics
- 使用原码, 反码和补码
- mooc_03_排序
- 双基回文数
- 小米金融初轮技术面试题目
- Spring+Quartz 实现自动作业调度
- as2.3的安装容易忽视的问题
- Mac系统下向liunx服务器导入本地数据库
- golang解析数字证书
- svn 提示comment must start in the first column
- java-jvm-架构