- 本周的学习内容包括:
1.堆排序
主要包括完全二叉树概念及其数组表示、堆的概念与堆的建立实现、堆排序、优先级队列
2.快速排序
主要包括快速排序的算法设计思路、快速排序的递归实现、快速排序的优化
3.归并排序
主要归并排序的设计思路、递归实现、循环实现
4.STL中排序sort
**********堆排序*********
本文介绍:完全二叉树概念及其数组表示、堆的概念与堆的建立实现、堆排序、优先级队列
- 正文:
1,完全二叉树的概念
对于一颗深度为h的二叉树,除第h层以外,其他所有层二叉树节点个数达到最大个数的二叉树,且第h层节点均靠左排序。
图1 完全二叉树
2,完全二叉树的数组表示
数组建立完全二叉树:
对一颗完全二叉树进行横向遍历,对于第k个节点,与其左子节点中间共相隔k个节点。(如:节点5与其左子节点11,中间相差5个节点)
a、对于节点K,其左子树为2K+1.(如:节点5的左子树为11,11=2*5+1)
b、右子树为2K+2(如:节点5的右子树为12,12=2*5+2)
c、对于节点K,如果他是非叶子节点,那么左子树2K+1一定存在。
d、对最后一个非叶子节点可知,非叶子节点个数 = 总节点数/2。(如:6是最后一个非叶子节点,所以非叶子节点个数7 = 14/2,对于图1,4是最后一个非叶子节点,所以非叶子节点个数 5 = 10/2)
3、大顶堆和小顶堆的概念
堆是具有以下性质的二叉树:
每个节点的值都大于或等于其左右孩子节点的值,称为大顶堆;
每个节点的值都小于或等于其左右孩子节点的值,称为小顶堆;
大顶堆数组分析:
对于大顶堆,完全二叉树对节点a[i],其左右子节点为a[2i+1]和a[2i+2],所以:a[i]>=a[2I+1] && a[i] >=a[2i +2]
4、大顶堆的实现思路
对每一个非叶子节点node,将node的值与其两个子节点比较大小,将较大的子节点交换到父节点位置,自下而上以此调整,调整后再对子节点中调整过的部分向下重调。
假如:对 4 9 1 3 5 7 6 2 8无序序列进行大顶堆实现
大顶堆的代码实现:
void max_heap(int* a, int pos, int size) {
int pos_max = pos, swap_item;
if (pos * 2 + 1 < size && a[pos * 2 + 1] > a[pos_max])//如果比自己的左子树节点少,将pos_max位置换给左子树节点
{
pos_max = pos * 2 + 1;
}
if (pos * 2 + 2 < size && a[pos * 2 + 2] > a[pos_max])//如果比自己的右子树节点少,将pos_max位置换给右子树节点
{
pos_max = pos * 2 + 2;
}//找出对于节点pos及其子节点,值最大的节点
if (pos != pos_max)//如果父节点不是pos_max,则调换父节点与最大值节点的值
{
swap_item = a[pos];
a[pos] = a[pos_max];
a[pos_max] = swap_item;
max_heap(a, pos_max, size);//pos与pos_max位置交换使得pos_max的子树结构混乱,重新调整pox_max的结构
}
}
//构建大顶堆
void bulid_heap(int *a, int size)
{
int i;
for (i = size / 2 - 1; i >= 0; i--)
{
max_heap(a, i, size);//从最后一个非叶子节点
}
}
5、堆排序
有了大顶堆的实现以及调整方式后实现利用大顶堆的排序就非常容易了。
a、对初始数组构建大顶堆
b、将大顶堆根节点的值与最后一个节点数值交换,且将最后一个节点移除大顶堆(相当于把根节点从大顶堆移出)
c、从根节点调整大顶堆
d、循环以上步骤,直至所有元素移出大顶堆
堆排序的一个示意图:如4 6 8 5 9这个二叉树
先进行大顶堆实现,然后用堆排序进行排序,得到4 5 6 8 9。
堆排序的代码实现:
void max_heap(int* a, int pos, int size) {
int pos_max = pos, swap_item;
if (pos * 2 + 1 < size && a[pos * 2 + 1] > a[pos_max])//如果比自己的左子树节点少,将pos_max位置换给左子树节点
{
pos_max = pos * 2 + 1;
}
if (pos * 2 + 2 < size && a[pos * 2 + 2] > a[pos_max])//如果比自己的右子树节点少,将pos_max位置换给右子树节点
{
pos_max = pos * 2 + 2;
}//找出对于节点pos及其子节点,值最大的节点
if (pos != pos_max)//如果父节点不是pos_max,则调换父节点与最大值节点的值
{
swap_item = a[pos];
a[pos] = a[pos_max];
a[pos_max] = swap_item;
max_heap(a, pos_max, size);//pos与pos_max位置交换使得pos_max的子树结构混乱,重新调整pox_max的结构
}
}
//构建大顶堆
void bulid_heap(int *a, int size)
{
int i;
for (i = size / 2 - 1; i >= 0; i--)
{
max_heap(a, i, size);//从最后一个非叶子节点
}
}
void heap_sort(int *a, int size) {
int i, swap_item;
bulid_heap(a, size);//初始数组构建大顶堆
for (i = size; i > 0; i--)
{
//将堆顶元素与最后一个元素调换
swap_item = a[0];
a[0] = a[i - 1];
a[i - 1] = swap_item;
max_heap(a, 0, i - 1);//将堆顶元素与最后一个元素调换以后从堆顶调整堆
}
}
这个代码的实现步骤:
先输入 a数组 ,进行大顶堆,然后进行排序,完成了堆排序!
//头文件
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <iostream>
#include <stdlib.h> //该头文件为malloc必须
using namespace std;
//
void max_heap(int* a, int pos, int size) {
int pos_max = pos, swap_item;
if (pos * 2 + 1 < size && a[pos * 2 + 1] > a[pos_max])//如果比自己的左子树节点少,将pos_max位置换给左子树节点
{
pos_max = pos * 2 + 1;
}
if (pos * 2 + 2 < size && a[pos * 2 + 2] > a[pos_max])//如果比自己的右子树节点少,将pos_max位置换给右子树节点
{
pos_max = pos * 2 + 2;
}//找出对于节点pos及其子节点,值最大的节点
if (pos != pos_max)//如果父节点不是pos_max,则调换父节点与最大值节点的值
{
swap_item = a[pos];
a[pos] = a[pos_max];
a[pos_max] = swap_item;
max_heap(a, pos_max, size);//pos与pos_max位置交换使得pos_max的子树结构混乱,重新调整pox_max的结构
}
}
//构建大顶堆
void bulid_heap(int *a, int size)
{
int i;
for (i = size / 2 - 1; i >= 0; i--)
{
max_heap(a, i, size);//从最后一个非叶子节点
}
}
void heap_sort(int *a, int size) {
int i, swap_item;
bulid_heap(a, size);//初始数组构建大顶堆
for (i = size; i > 0; i--)
{
//将堆顶元素与最后一个元素调换
swap_item = a[0];
a[0] = a[i - 1];
a[i - 1] = swap_item;
max_heap(a, 0, i - 1);//将堆顶元素与最后一个元素调换以后从堆顶调整堆
}
}
int main()
{
//int a[105] = { 4,9,1,3,7,6,2,8 };
int len;
int *a;
cout << "请输入开辟动态数组的长度:" << endl;
cin >> len;
//长度乘以int的正常大小,才是动态开辟的大小
a = (int*)malloc(len * sizeof(int));
cout << "请逐个输入动态数组成员:" << endl;
for (int i = 0; i<len; ++i)
{
//此处不可以写成:cin>>*p[i]
cin >> a[i];
}
cout << "您输入的动态数组为:" << endl;
for (int i = 0; i<len; ++i)
{
cout << a[i] << " ";
}
heap_sort(a, len);
cout << "输入的数组进行堆排序:" << endl;
for (int i = 0; i < len; i++)
{
printf("%d", a[i]);
}
printf("\n");
free(a);//时刻记住:有malloc就要有free
return 0;
}
6、堆排序的复杂度分析
初始化建堆过程时间复杂度:O(n logn)
取出最大元素后更新调整大顶堆更新堆结构时间复杂度:O(logn)
以上过程循环执行n次
综上所述时间复杂度为O(n logn)
空间复杂度,由于操作在原空间上,没有开辟新空间,故O(1)
关于时间复杂度的问题,我个人觉得我不懂, 或者总是很模糊,明显知道的是就是估计运算次数而已。疑惑!!!!
7、优先级队列
当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出(first in,largest out)的行为特征。
主要操作:
- 插入操作:将一个新的元素插入优先队列
- 删除操作:弹出队列中优先级最高的元素
- 查询操作:查询队列中优先级最高的元素
举例:
如何实现呢?分析一下:
利用堆的基础,直接构建大顶堆,构建优先队列且实现优先队列所有操作。
就是根据定义的优先级构建大顶堆,堆顶元素肯定是优先级最高的元素。操作堆顶元素即可完成对优先队列的所有操作。
- 插入操作:将元素插入末尾后进行堆调整,调整至大顶堆。
void priority_queue_push(struct priority_queue *queue, int val)
{
queue->a[queue->size++] = val;
build_heap(queue->a, queue->size);
}//将元素val插入优先队列
- 删除操作:将顶端元素从堆中删除,调整至大顶堆
实现方法:先将堆顶的元素与最后一个元素交换,然后删除最还有一个元素,然后堆调整。
void priority_queue_pop(struct priority_queue *queue)
{
queue->a[0] = queue->a[queue->size - 1];//将堆内最后一个元素换成堆顶
queue->size--;//删除最后一个元素
max_heap(queue->a, 0, queue->size);//调整堆
}
- 查询操作:查询堆顶元素
int priority_queue_top(struct priority_queue *queue)
{
return queue->a[0];
}//返回队列堆顶元素
- 求队列元素个数:
int priority_queue_size(struct priority_queue *queue)
{
return queue->size;
} //返回优先队列元素的个数
- 判断队列是否为空
int priority_queue_is_empty(struct priority_queue *queue)
{
return queue->size > 0 ? 0 : 1;
}//当优先队列元素个数为非0,返回队列为0
题外话,还记得如果定义队列吗?
struct priority_queue
{
int a[10000];
int size;
};
初始化 优先队列
typedef struct priority_queue priority_queue;//有这句后其余struct priority_queue 都可以换成 priority_queue;
struct priority_queue* priority_queue_init()
{
struct priority_queue* queue = (struct priority_queue*)malloc(sizeof(struct priority_queue));//动态分配队列空间
queue->size = 0;
return queue;
}
测试代码:
#include<stdio.h>
#include<malloc.h>
#include<string.h>
void max_heap(int *a, int pos, int size)
{
int pos_max = pos, swap_item;
if (pos * 2 + 1 < size && a[pos * 2 + 1] > a[pos_max])
{
pos_max = pos * 2 + 1;
}
if (pos * 2 + 2 < size && a[pos * 2 + 2] > a[pos_max])
{
pos_max = pos * 2 + 2;
}
if (pos != pos_max)
{
swap_item = a[pos];
a[pos] = a[pos_max];
a[pos_max] = swap_item;
max_heap(a, pos_max, size);
}
}
void build_heap(int *a, int size)
{
int i;
for (i = size / 2 - 1; i >= 0; i--)
{
max_heap(a, i, size);
}
}
const int N = 10000;
struct priority_queue
{
int a[10000];
int size;
};
void priority_queue_push(struct priority_queue *queue, int val)
{
queue->a[queue->size++] = val;
build_heap(queue->a, queue->size);//大顶堆的实现,此句就是用来实现是以什么为优先级别的条件的,大顶堆为大的为优先级。
}//将元素val插入优先队列
void priority_queue_pop(struct priority_queue *queue)
{
queue->a[0] = queue->a[queue->size - 1];//将堆内最后一个元素换成堆顶
queue->size--;//删除最后一个元素
max_heap(queue->a, 0, queue->size);//调整堆
}
int priority_queue_top(struct priority_queue *queue)
{
return queue->a[0];
}//返回队列堆顶元素
int priority_queue_size(struct priority_queue *queue)
{
return queue->size;
} //返回优先队列元素的个数
int priority_queue_is_empty(struct priority_queue *queue)
{
return queue->size > 0 ? 0 : 1;
}//当优先队列元素个数为非0,返回队列为0
void print(struct priority_queue *queue)
{
int i;
printf("Priority Queue:\n");
for (i = 0; i < queue->size; i++)
{
printf("[%d] ", queue->a[i]);//->指针专用,符号前面是指针则用->
}
}
typedef struct priority_queue priority_queue;//有这句后其余struct priority_queue 都可以换成 priority_queue;
struct priority_queue* priority_queue_init()
{
struct priority_queue* queue = (struct priority_queue*)malloc(sizeof(struct priority_queue));//动态分配队列空间
queue->size = 0;
return queue;
}
int main()
{
struct priority_queue *pri_que = priority_queue_init();
priority_queue_push(pri_que, 1);
priority_queue_push(pri_que, 5);
priority_queue_push(pri_que, 4);
print(pri_que);
printf("\nPriority Queue front value is: %d\n", priority_queue_top(pri_que));
printf("Priority size is: %d\n", priority_queue_size(pri_que));
printf("Priority empty is: %d\n\n", priority_queue_is_empty(pri_que));
priority_queue_pop(pri_que);
print(pri_que);
printf("\nPriority Queue front value is: %d\n", priority_queue_top(pri_que));
printf("Priority size is: %d\n", priority_queue_size(pri_que));
printf("Priority empty is: %d\n\n", priority_queue_is_empty(pri_que));
priority_queue_pop(pri_que);
priority_queue_push(pri_que, 7);
print(pri_que);
printf("\nPriority Queue front value is: %d\n", priority_queue_top(pri_que));
printf("Priority size is: %d\n", priority_queue_size(pri_que));
printf("Priority empty is: %d\n\n", priority_queue_is_empty(pri_que));
priority_queue_pop(pri_que);
priority_queue_pop(pri_que);
print(pri_que);
printf("\nPriority size is: %d\n", priority_queue_size(pri_que));
printf("Priority empty is: %d\n\n", priority_queue_is_empty(pri_que));
return 0;
}
对号入座,结果看看:
8、STL中的优先级队列
标准STL中的优先级队列priority_queue中,含有5种基本操作:
#include<stdio.h>
#include<queue> //priority_queue的头文件
using namespace std;
int main()
{
std::priority_queue<int> big_heap; //默认构造最大堆
//std::priority_queue<int, std::vector<int>, std::greater<int> > small_heap;
std::priority_queue<int, std::vector<int>, std::less<int> > big_heap2;//最大堆构造方法
if (big_heap.empty()) {
printf("big_heap is empty!\n");
}
int test[] = { 6, 10, 1, 7, 99, 4, 33 };
for (int i = 0; i < 7; i++) {
big_heap.push(test[i]);
}
printf("big_heap.top = %d\n", big_heap.top());
big_heap.push(1000);
printf("big_heap.top = %d\n", big_heap.top());
for (int i = 0; i < 3; i++) {
big_heap.pop();
}
printf("big_heap.top = %d\n", big_heap.top());
printf("big_heap.size = %d\n", big_heap.size());
return 0;
}
end
彩蛋:
为什么呢?
big_heap为何是如此排列的呢?