数据结构笔记:堆
文章目录
优先级队列:本质是堆
什么是完全二叉树?
一棵树必定是满的,如果不满也一定是最后一层的右孩子不满。
连续的数组可以被认为是一个完全二叉树:
堆:
- 是完全二叉树
- 大根堆:每个子树的最大值都是头节点
- 小根堆:每个子树的最小值都是头节点
如何把一个数插入大根堆?
程序怎么写?
void heapinsert( int arr[] , int index) {
while (arr[index] > arr[(index - 1) / 2]) {
int tmp = arr[index];
arr[index] = arr[(index - 1) / 2];
arr[(index - 1) / 2]=tmp;
index = (index - 1) / 2;
}
}
如何得到堆中最大的值并删除
程序实现:
void heapify(int arr[], int index, int heapsize) {
int left = index * 2 + 1;
while (left < heapsize) {//有左孩子,有没有右孩子
int large = (left + 1 < heapsize) && (arr[left] < arr[left + 1]) ? left + 1 : left;
int largest = arr[large] > arr[index] ? large : index;
if (largest == index)
break;
int tmp = arr[index];
arr[index] = arr[largest];
arr[largest] = tmp;
index = largest;
left = index * 2 + 1;
}
}
int remove(int arr[], int& heapsize) {
if (heapsize == 0)
return NAN;
int max = arr[0];
arr[0] = arr[heapsize-1];
heapsize--;
heapify(arr, 0, heapsize-1);
return max;
}
修改了堆中的某个值如何调整堆?
改完以后顺序调用heapinsert()和heapify()函数。(可以换顺序)
这两个函数只会有一个真正起作用。
大根堆小根堆转换
思路:重写比较器
测试一下以上函数:
堆排序
完全二叉树的高度:O(logn)
heapinsert():只和父节点pk,复杂度都是log(n)
heapify(),往下沉只向一侧沉,复杂度也是log(n)
因为堆排序的过程中,假设一个无序数组,我们要先调整成大根堆(小根堆)
每次比较大小只和父节点比较
例如:无序数组为:[1,2,4,6,0,3]
此时heapsize=0;
heapinsert 1
heapinsert 2 …以此类推得到大根堆:
[643102] heapsize=6
第二步:最后的数和根节点交换,heapsize–
得到[2 4 3 1 0] 6,heapify调整该数组
重复第二步,直到都交换完,数组排序完成。
代码实现:
void swap(int arr [],int index1, int index2) {
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
void heapsort1(int arr[],int length) {
int heapsize = length;
for (int i = 0; i < length ; i++) {
heapinsert(arr, i);
}
while (heapsize > 0) {
swap(arr, 0, heapsize - 1);
heapsize--;
heapify(arr,0,heapsize);
}
for (int i = 0; i < length; i++) {
printf("%d", arr[i]);
}
printf("\n");
}
主函数测试上述功能:
int main() {
/* transmit buffer and receive buffer */
int arr[10];
int heapsize = 0;
for (int j = 0; j < 10; j++) {
arr[j] = j;
heapinsert(arr, j);
heapsize++;
}
for (int j = 0; j < heapsize ; j++) {
printf("%d", arr[j]);
}
int max=remove(arr, heapsize);
printf("\n");
for (int j = 0; j < heapsize; j++) {
printf("%d", arr[j]);
}
printf("\nmax%d\n",max);
int arr2[10];
for (int j = 0; j < 10; j++) {
arr2[j] = j;
}
heapsort1(arr2,10);
for (int j = 0; j <10; j++) {
printf("%d", arr2[j]);
}
return 0;
}
堆排序的复杂度计算
建堆的复杂度是多少?
N
∗
l
o
g
N
N*logN
N∗logN? 但是直觉上感觉一开始的高度是不到logN的,只有最后一层的数加进去的时候数的高度是logN,因此每一步的logN不是一个固定的值,而是一个不断变化的值。
如何证明就是NlogN?可以利用一种极限的两边夹法则。
假设数据量是
2
N
2N
2N ,那么后面N个数的复杂度是大于
O
(
n
l
o
g
N
)
O(nlogN)
O(nlogN)的前N个数的上复杂度上限是O(NlogN)因此总复杂度是O(NlogN)
另外在堆排序里面,建堆和后面调堆的复杂度都是
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)因此整体的复杂度是
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)
建堆方法的进一步优化
从上往下建立堆复杂度是
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN),如果是从下往上建,可以达到
O
(
N
)
O(N)
O(N)。下面介绍该方法:
方法思路:
首先有一个数组,我们从最底层的子树开始进行heapify操作
然后依次往上一层重复heapify操作
代码实现:
void heapsort2(int arr[], int length) {
if (arr == nullptr || length < 2)
return;
int heapsize = length;
for (int i = length - 1; i >= 0; i--)
heapify(arr, i, heapsize);
while (heapsize != 0) {
swap(arr, 0, heapsize - 1);
heapsize--;
heapify(arr, 0, heapsize);
}
}
为什么这种方法可以收敛到
O
(
N
)
O(N)
O(N)?
C++ STL大根堆小跟堆
比较器
C++內部自带的比较器:
在 STL 中,greater 是一个函数对象,用于比较两个整数的大小。在使用 priority_queue 容器时,如果需要实现小根堆,则可以使用 greater 作为比较函数对象;如果需要实现大根堆,则可以使用 less 作为比较函数对象。
greater 和 less 都定义在 头文件中,它们是 C++ STL 提供的函数对象之一。这些函数对象都实现了 () 运算符,因此可以像函数一样调用它们,将它们作为比较函数对象传递给容器或算法中使用。
greater 用于按照从小到大的顺序比较两个整数的大小,它实际上是一个结构体,内部实现了一个重载了函数调用运算符的 operator() 函数,代码如下:
struct greater {
bool operator()(const int& a, const int& b) const {
return a > b;
}
};
template <typename T>
struct less {
bool operator()(const T& x, const T& y) const {
return x < y;
}
};
我们也可以用自己定义的比较器替代该比较器:
class MyLess {
public:
bool operator()(const int& a, const int& b) const {
return a < b;
}
};
class Mylarger {
public:
bool operator()(const int& a, const int& b)const{
return a > b;
}
};
实现大根堆:
int main() {
/* transmit buffer and receive buffer */
priority_queue<int, vector<int>, less<int> >maxheap;
//或者:priority_queue<int, vector<int>, MyLess >maxheap;
minheap.push(0);
minheap.push(9);
minheap.push(-1);
while (!minheap.empty()) {
int min = minheap.top();
minheap.pop();
cout << min << endl;
}
return 0;
}
实现小根堆:
int main() {
/* transmit buffer and receive buffer */
priority_queue<int, vector<int>, greater<int> >minheap;
//或者priority_queue<int, vector<int>, Mylarger >maxheap;
minheap.push(0);
minheap.push(9);
minheap.push(-1);
while (!minheap.empty()) {
int min = minheap.top();
minheap.pop();
cout << min << endl;
}
return 0;
}
堆排序相关小例题
已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k,并且k相对于数组长度来说是比较小的。请选择一个合适的排序策略,对这个数组进行排序。
思路:
0~k-1里面的最小值排完以后肯定在0位置
1.把0~k-1放在小根堆里面,弹出最小值放在0位置
2.把k位置放进小根堆,弹出最小值放1位置
3.把k+1位置放进小根堆,弹出最小值放2位置
…
直到都放完
复杂度:
O
(
n
∗
l
o
g
k
)
O(n*logk)
O(n∗logk)
代码实现:
vector<int> randommovearr(int length, int maxval, int k) {
vector <int> arr(length);
for (int i = 0; i < arr.size(); i++) {
arr[i] = (rand() % (maxval + 1));
}
sort(arr.begin(), arr.end());
//change
vector<bool>isswapped(length);
for (int i = 0; i < arr.size(); i++) {
int j = i;
int newindex = i + rand() % (k + 1);
if (newindex> (arr.size() - 1))
j = (arr.size() - 1);
else
j= newindex;
if (!isswapped[i] && !isswapped[j]) {
isswapped[i] = true;
isswapped[j] = true;
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
return arr;
}
vector<int> copyArray(vector<int>& arr) {
vector<int> res(arr.size());
for (int i = 0; i < arr.size(); i++) {
res[i] = arr[i];
}
return res;
}
int main() {
/* transmit buffer and receive buffer */
srand(time(0));
int testtimes = 100000;
int maxsize = 100;
int maxval = 100;
for (int i = 0; i < testtimes; i++) {
int length= (rand()%(maxsize))+1;
int k = (rand() % (maxsize))+1;
while(k>=length)
k = (rand() % (maxsize))+1;
vector<int>arr = randommovearr(length, maxval,k);
vector <int> arr1 = copyArray(arr);
vector <int> arr2 = copyArray(arr);
priority_queue<int, vector<int>, greater<int> >minheap;
int ii = 0;
for (ii = 0; ii <= k; ii++) {
minheap.push(arr[ii]);
}
for (int jj = 0; jj < arr.size();jj++) {
arr1[jj] = minheap.top();
minheap.pop();
if(ii<arr.size())
minheap.push(arr[ii++]);
}
sort(arr2.begin(), arr2.end());
for (int iii = 0; iii < arr.size(); iii++) {
if (arr1[iii] != arr2[iii])
{
cout << "error";
return 0;
}
}
}
return 0;
}