1.优先级队列
优先级队列是0个或多个元素的集合,每个元素都有一个优先权,对优先级队列执行的操作有(1)查找(2)插入一个新元素 (3)删除 一般情况下,查找操作用来搜索优先权最大的元素,删除操作用来删除该元素 。
优先级队列是不同于先进先出队列的另一种队列。每次从队列中取出的是具有最高优先权的元素。
优先队列插入元素的复杂度都是O(lgn),删除元素的复杂度是O(1),所以很快。
代码实现:
“Heap.h”
#pragma once
#include <vector>
template <class T>
struct Less
{
bool operator()(const T&l, const T&r) const
{
return l < r;
}
};
template <class T>
struct Greater
{
bool operator()(const T&l, const T&r) const
{
return l > r;
}
};
//大堆
template <class T, class Compare = Greater<T>>
class Heap
{
public:
Heap()//无参构造函数
{}
Heap(T* a, size_t n)//构造函数
{
_a.reserve(n);
for (size_t i = 0; i < n; i++)
{
_a.push_back(a[i]);
}
for (int i = (_a.size() - 2) / 2; i >= 0; i--)
{
_AdjustDown(i);
}
}
void Push(const T& x)//插入数据
{
_a.push_back(x);
_AdjustUp(_a.size() - 1);
}
void Pop()//删除数据
{
assert(!_a.empty());
swap(_a[0], _a[_a.size() - 1]);
_a.pop_back();
_AdjustDown(0);
}
const T& Top() const
{
return _a[0];
}
size_t Size()
{
return _a.size();
}
bool Empty()
{
return _a.empty;
}
protected:
void _AdjustDown(int root)//向下调整算法用于创建堆
{
Compare comFunc;
int parent = root;
int child = root * 2 + 1;
while (child < _a.size())
{
if ((child+1<_a.size()) && comFunc(_a[child + 1],_a[child]))
{
++child;
}
//到这里了,在大堆里child就是最大的那个孩子,小堆相反
if (child<_a.size() && comFunc(_a[child] ,_a[parent]))//大堆里,孩子比父亲大
{
swap(_a[child], _a[parent]);//交换
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void _AdjustUp(int child)//向上调整算法用于插入数据
{
Compare comFunc;
int parent = (child - 1) / 2;
while (child > 0)
{
if (comFunc(_a[child] , _a[parent]))
{
swap(_a[child], _a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
protected:
vector<T> _a;
};
void TestHeap()
{
int a[] = { 10, 11, 13, 12, 16, 18, 15, 17, 14, 19 };
Heap<int> hp1(a, sizeof(a) / sizeof(a[0]));
hp1.Push(25);
hp1.Pop();
}
"PriorityQueue.h"
#pragma once
#include "Heap.h"
template <class T,class Compare = Greater<T>>//大的数优先级高
class PriorityQueue
{
public:
PriorityQueue(T* a,size_t n)//构造函数
:_hp(a,n)
{}
void Push(const T& x)//插入
{
_hp.Push(x);
}
void Pop()//删除
{
_hp.Pop();
}
const T& Top()//优先级最高的数
{
return _hp.Top();
}
size_t Size()//大小
{
return _hp.Size();
}
bool Empty()//是否为空
{
return _hp.Empty();
}
protected:
Heap<T, Compare> _hp;
};
void TestPriorityQueue()
{
int a1[] = { 3, 7, 9, 2, 6, 1 };
PriorityQueue<int> tp(a1, sizeof(a1) / sizeof(a1[0]));
tp.Push(8);
tp.Pop();
tp.Pop();
tp.Pop();
tp.Pop();
tp.Pop();
tp.Pop();
tp.Top();
}
"Test.cpp"
#include <iostream>
#include <assert.h>
using namespace std;
#include "Heap.h"
#include "PriorityQueue.h"
int main()
{
TestPriorityQueue();
return 0;
}
2.Topk问题
1.在1000个数中取10个数。(n中取k个数)
对于这问题的解决:
(1)、将这1000个数进行排序(升序或降序),然后取(后10个或前十个),用冒泡排序的时间复杂度O(n^2)
(2)、将这1000个数建大堆,依次取出前10个数。时间复杂度O(nlgn)
2、在10000000亿个数中取50个数。
如果这个用上面两种方法显然不能实现,因为内存中就存不下那么多数据。
这也叫海量数据处理。
这个也有两种方法:
(1)、建一个k大小的数组,然后进行排序。将剩余的数依次和数组中最小的数进行比较,如果比数组中最小的大,就进行交换。每次交换完后进行重新排序。时间复杂度O(nk)。
(2)、建一个K大小的小堆。将剩余的数依次和堆顶数据进行比较,如果比堆顶数据大就进行交换。每次交换完再调整为大堆。时间复杂度O(nlgk)。
如果k小一点,两个算法差不多。如果k大一点,显然建堆的时间复杂度小。
我写了建k大小的堆。
代码实现:
"TopK.h"
#pragma once
void AdjustDown(int* heap, size_t k, size_t parent)//向下调整算法
{
size_t child = parent * 2 + 1;
while (child < k)
{
if (child + 1<k && heap[child + 1] < heap[child])
{
++child;
}
if (heap[child] < heap[parent])
{
swap(heap[parent], heap[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void TopK(int* a, int n, int k)//在N个数中找最大K个数
{
assert(n > k);
int* heap = new int[k];//申请一个k大小的数组
for (size_t i = 0; i < k; ++i)//在N中取前k个数,赋值给heap
{
heap[i] = a[i];
}
//找大的数建小堆,找小的数建大堆
for (int i = (k - 2) / 2; i >= 0; --i)
{
AdjustDown(heap, k, i);
}
for (size_t j = k; j < n; ++j)
{
if (a[j] > heap[0])
{
heap[0] = a[j];
AdjustDown(heap, k, 0);
}
}
for (size_t i = 0; i < k; ++i)
{
cout << heap[i] << " ";
}
cout << endl;
delete[] heap;
}
void TestTopK()
{
const int N = 1000;
const int K = 8;
int a[N];
for (size_t i = 0; i < 1000; ++i)
{
a[i] = i;
}
a[20] = 10000;
a[30] = 1010;
a[400] = 2000;
TopK(a, N, K);
}
"Test.cpp"
#include <iostream>
#include <assert.h>
using namespace std;
#include "TopK.h"
int main()
{
TestTopK();
return 0;
}
----------
堆排序
堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆性质:即子结点的值总是小于(或者大于)它的父节点。
堆排序是一种选择排序,是利用堆这种二叉树的性质进行的排序。
堆排序进行两步:第一步建堆,第二步选数据。
初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树)。所谓的建堆就是,如果排升序就建大堆,排降序就建小堆。选数据就是如果排升序,建好大堆后,取出堆顶数据就是n个数的最大数,和数组最后一个数进行交换。然后调整堆使之变为大堆,再取堆顶元素,得到n 个元素中次小(或次大)的元素,和数组第n-1个数进行交换。依次类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。
代码实现:
"HeapSort.h"
#pragma once
void AdjustDown(int* a, size_t n, size_t parent)//向下调整算法
{
size_t child = parent * 2 + 1;
while (child < n)
{
if (child + 1 < n && a[child + 1] > a[child])
{
++child;
}
if (child<n && a[child] > a[parent])
{
swap(a[parent], a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, size_t n)
{
assert(a);
//排升序建大堆,排降序建小堆
for (int i = (n - 2) / 2; i >= 0; --i)
{
AdjustDown(a, n, i);
}
//选数据
size_t end = n - 1;
while (end > 0)
{
swap(a[0], a[end]);
AdjustDown(a, end, 0);
--end;
}
}
void Print(int* a, size_t n)
{
for (size_t i = 0; i < n; ++i)
{
cout << a[i] << " ";
}
cout << endl;
}
void TestHeapSort()
{
int a[8] = { 6, 1, 9, 3, 4, 0, 2, 7 };
HeapSort(a, sizeof(a) / sizeof(a[0]));
Print(a, sizeof(a) / sizeof(a[0]));
}
"Test.cpp"
#include <iostream>
#include <assert.h>
using namespace std;
#include "HeapSort.h"
int main()
{
TestHeapSort();
return 0;
}