算法导论学习笔记三——堆排序,快速排序
本文是机械工业出版社出版的《算法导论(原书第三版)》的学习笔记的第三篇,对应原书第6,7章——堆排序,快速排序
算法1.堆排序
堆的基本概念:
如下图,堆可以看成一个数组,可以看成一个近似的完全二叉树,对于一个节点i,其父节点为flor(i/2),其左孩子为2i,右孩子为2i+1。
最大堆与最小堆:
最大堆的性质为除了根节点之外,所有节点都满足:
A
[
p
a
r
e
n
t
(
i
)
]
≥
A
[
i
]
A[parent(i)] \ge A[i]
A[parent(i)]≥A[i]
最小堆同理
堆排序:
初始时候,堆排序算法利用build_max_heap将输入数组A[1,2,…,n]建成最大堆。因为数组中的最大元素总在根结点A[1]中,通过把它与A[n]进行互换,我们可以让该元素放到正确的位置。 这时候,如果我们从堆中去掉结点n,剩余的结点中,原来根的孩子结点仍然是最大堆,而新的根结点可能会违背最大堆的性质。 为了维护最大堆的性质,我们要做的是调用max_heapify(A,1) , 从而在A[1,2,…,n-1]上构造一个新的最大堆。堆排序算法会不断重复这一过程,直到堆的大小从n —1 降
到 2。
算法时间复杂度为O(nlogn)
代码:
void heapsort(int* arr, int n)
{
build_max_heap(arr, n);//先构建一个最大堆
int heap_size = n;
for (int i = n - 1; i > 0; i--)
{
swap(&arr[i], &arr[0]);//将最大堆的根节点一定是堆中最大的节点,将其放到堆最后面
heap_size--;//堆的长度减一
max_heapify(arr, heap_size, 0);
}
}
void build_max_heap(int* arr,int n)
{
//创建最大堆
//这里floor(n / 2) - 1之后的节点都是叶子节点,所以从这里往前开始即可
for (int j = floor(n / 2) - 1; j >= 0; j--)
{
max_heapify(arr, n, j);
}
}
void max_heapify(int* arr, int n, int i)
{
//维护最大堆的性质
//假设i的左孩子与右孩子都已经是最大堆,那么只要
//比较当前节点与左右孩子的大小,将当前节点与较大
//的孩子交换即可。但是这样会破坏子节点的最大堆性质,
//所以需要递归的计算。
int left, right,idx=i;
left = floor(2*i+1);
right = floor(2 * i + 2);
if (left < n - 1 && arr[left]>arr[i])
{
swap(&arr[left], &arr[i]);
idx = left;
}
if (right < n - 1 && arr[right]>arr[i])
{
swap(&arr[right], &arr[i]);
idx = right;
}
if (idx != i)
{
max_heapify(arr, n, idx);
}
算法2.快速排序
算法思想
快速排序也是一种分治算法,把一个A[p,…,r]的数组划分为A[p,…,q-1],A[q]和A[q+1,…,r],使得A[p,…,q-1]<=A[q]<=A[q+1,…,r],之后再递归对A[p,…,q-1]和A[q+1,…,r]排序。
核心在于A[p,…,q-1],A[q]和A[q+1,…,r]的划分方法:
首先我们把A[r]看成主元,将A[p,…,r-1]中所有元素都跟其比较,使用两个指针i和j,A[p,…,i]存储小于主元的元素,A[i+1,…,j]存储大于主元的元素,具体见下图:
代码
void quicksort(int* arr, int n, int p, int r)
{
if (p < r)
{
int q = patition(arr, n, p, r);
std::cout << q<<std::endl;
quicksort(arr, n, p, q-1);
quicksort(arr, n, q+1, r);
}
}
int patition(int* arr, int n, int p, int r)
{
int x = arr[r];//主元
int i = p - 1;
for (int j = p; j < r; j++)
{
if (arr[j] <= x)
{
i = i + 1;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[r]);
return i + 1;
}