从0开始的算法大师
标签(空格分隔):《算法导论》
常言道:《算导》是本看不下去的好书
第二部分:排序和顺序统计量
《数据结构》已经学过排序内容
第6章: 堆排序
用数组A表示堆,根节点为A[1],A[0]代表A.heap-size
属性 | 说明 |
---|---|
A.length | 数组元素个数 |
A.heap-size | 堆的有效元素A.heap-size<=A.length |
伪代码使用的三个函数
PARENT(i)
return[i/2]
LEFT(i)
return 2i
RIGHT(i)
return 2i+1
对于大牛来说,2i一般都是用左移实现的。
在堆排序的实现中,我们用宏或者内联函数实现上面三个函数。这样比较快。
6.2 维护堆的性质
话不多说,上代码(我们用完全二叉树构建最大堆为例。)
- MAX-HEAPIFY过程
MAX-HEAPIFY过程是维护最大堆性质的重要过程。就是告诉你一个A[i]不满足最大堆,叫你把它变成最大堆。
#include<cstdio>
#include<iostream>
using namespace std;
typedef int maxheap;
inline int Left(int i){return i<<1;}
inline int Right(int i){return (i<<1)+1;}
inline int Parent(int i){return i/2;}
void max_heapify(maxheap * A, int i)
{
int l = Left(i);
int r = Right(i);
int largest = i;
if(l <= A[0] && A[l] > A[i])
largest = l;
if(r <= A[0] && A[r] > A[largest])
largest = r;
if(largest != i)
{
int temp = A[i];
A[i] = A[largest];
A[largest] = temp;
max_heapify(A, largest);
}
}
int main()
{
maxheap a[102] = {5, 2, 5, 3, 4, 1};
max_heapify(a, 1);
for(int i = 1; i <= a[0]; i++)
{
printf("%d ", a[i]);
}
return 0;
}
我们假设需要T(n)的时间,经过一次操作我们把问题通过递归变为该结点子树的子问题。由于这是一棵完全二叉树,而子树的结点数最多为原来的2/3【
(2h−1−1)/2+k2h−1+k(0<=k<=2h−1)
(
2
h
−
1
−
1
)
/
2
+
k
2
h
−
1
+
k
(
0
<=
k
<=
2
h
−
1
)
求最大】。于是:
根据主定理:
就是说MAX-HEAPIFY过程复杂度为O(h),h为结点高度。
6.3 建堆
建堆我们采用自低向上的递归方法。用到了MAX-HEAPIFY过程,意思是从最底层的一个由三个结点构成的堆开始一步一步调整为最大堆,而每次调整只有一个结点不满足最大堆性质,因此用MAX-HEAPIFY过程解决。
当然,基于数组的堆其实只要排一下序就可以了,但是这不需要,多比较了同一层的数的大小。后面我们还会接触插入建堆。
- BUILD-MAX-HEAP过程
void build_max_heap(maxheap * A)
{
//巧妙的A[0]/2
for(int i = A[0]/2; i > 0; i--)
max_heapify(A, i);
}
P88页计算时间复杂度O(n)。看不懂,貌似用到几何级数,博主学过忘了,qwq。
完整示例(懒人版)
#include<cstdio>
#include<iostream>
using namespace std;
typedef int maxheap;
inline int Left(int i){return i<<1;}
inline int Right(int i){return (i<<1)+1;}
inline int Parent(int i){return i/2;}
void max_heapify(maxheap * A, int i)
{
int l = Left(i);
int r = Right(i);
int largest = i;
if(l <= A[0] && A[l] > A[i])
largest = l;
if(r <= A[0] && A[r] > A[largest])
largest = r;
if(largest != i)
{
int temp = A[i];
A[i] = A[largest];
A[largest] = temp;
max_heapify(A, largest);
}
}
void build_max_heap(maxheap * A)
{
for(int i = A[0]; i > 0; i--)
max_heapify(A, i);
}
int main()
{
maxheap a[102] = {9, 8, 1, 9, 5, 4, 6, 3, 2, 7};
build_max_heap(a);
for(int i = 1; i <= a[0]; i++)
{
printf("%d ", a[i]);
}
return 0;
}
6.4 堆排序算法
堆排序算法是一个巧妙的算法,因为最大堆拥有一个性质:根节点为最大的值。
- HEAPSORT过程
void heap_sort(maxheap * A)
{
// build_max_heap(A);
int len = A[0];
for(int i = A[0]; i >= 2; i--)
{
//根节点的与最后的结点互换
int temp = A[1];
A[1] = A[i];
A[i] = temp;
//最大已经取出,放到最后
A[0]--;
//重新调整,只有新根结点不满足最大堆
max_heapify(A, 1);
}
A[0] = len;
}
结果得出从小到大排序(最大堆从小到大,最小堆从大到小)
我们调用max_heapify()了n次,每次复杂度近似为O(lgn),于是复杂度为O(nlgn)
完整示例(懒人版)
#include<cstdio>
#include<iostream>
using namespace std;
typedef int maxheap;
inline int Left(int i){return i<<1;}
inline int Right(int i){return (i<<1)+1;}
inline int Parent(int i){return i/2;}
void max_heapify(maxheap * A, int i)
{
int l = Left(i);
int r = Right(i);
int largest = i;
if(l <= A[0] && A[l] > A[i])
largest = l;
if(r <= A[0] && A[r] > A[largest])
largest = r;
if(largest != i)
{
int temp = A[i];
A[i] = A[largest];
A[largest] = temp;
max_heapify(A, largest);
}
}
void build_max_heap(maxheap * A)
{
for(int i = A[0]; i > 0; i--)
max_heapify(A, i);
}
void heap_sort(maxheap * A)
{
// build_max_heap(A);
int len = A[0];
for(int i = A[0]; i >= 2; i--)
{
int temp = A[1];
A[1] = A[i];
A[i] = temp;
A[0]--;
max_heapify(A, 1);
}
A[0] = len;
}
int main()
{
maxheap a[102] = {9, 8, 1, 9, 5, 4, 6, 3, 2, 7};
build_max_heap(a);
heap_sort(a);
for(int i = 1; i <= a[0]; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
6.5 优先队列
优先队列是一种用来维护由一组元素构成的集合S的数据结构,其中的每一个元素都有一个相关值,称为关键字。(P90 书本摘抄)
最大堆可以实现最大优先队列S。
操作 | 说明 |
---|---|
insert(S, x) | 把x插入S |
maximum(S) | 返回S中最大的元素 |
extract_max(S) | 去掉S中最大的元素 |
increase_key(S,x,k) | 将元素x关键字增加k |
每个函数实现如下:
- maximum———-O(n)
int maximum(maxheap A)
{
return A[1];
}
- extract_max———-O(lgn)
void extract_max(maxheap * A)
{
A[1] = A[i];
A[i] = 0;
A[0]--;
max_heapify(A, 1);
}
- heap_increase_key
我们增加一个结点的key,然后不断与它的父结点比较,交换。
void heap_increase_key(maxheap * A, int i, int key)
{
if(key < 0)
{
return ;
}
A[i]+=key;
while(i > 1 && A[Parent(i)] < A[i])
{
int temp = A[i];
A[i] = A[Parent(i)];
A[Parent(i)] = temp;
i = Parent(i);
}
}
完整示例(懒人版)
#include<cstdio>
#include<iostream>
using namespace std;
typedef int maxheap;
inline int Left(int i){return i<<1;}
inline int Right(int i){return (i<<1)+1;}
inline int Parent(int i){return i/2;}
void max_heapify(maxheap * A, int i)
{
int l = Left(i);
int r = Right(i);
int largest = i;
if(l <= A[0] && A[l] > A[i])
largest = l;
if(r <= A[0] && A[r] > A[largest])
largest = r;
if(largest != i)
{
int temp = A[i];
A[i] = A[largest];
A[largest] = temp;
max_heapify(A, largest);
}
}
void build_max_heap(maxheap * A)
{
for(int i = A[0]; i > 0; i--)
max_heapify(A, i);
}
void heap_sort(maxheap * A)
{
// build_max_heap(A);
int len = A[0];
for(int i = A[0]; i >= 2; i--)
{
int temp = A[1];
A[1] = A[i];
A[i] = temp;
A[0]--;
max_heapify(A, 1);
}
A[0] = len;
}
void heap_increase_key(maxheap * A, int i, int key)
{
if(key < 0)
{
return ;
}
A[i]+=key;
while(i > 1 && A[Parent(i)] < A[i])
{
int temp = A[i];
A[i] = A[Parent(i)];
A[Parent(i)] = temp;
i = Parent(i);
}
}
int main()
{
maxheap a[102] = {9, 8, 1, 9, 5, 4, 6, 3, 2, 7};
build_max_heap(a);
// heap_sort(a);
for(int i = 1; i <= a[0]; i++)
{
printf("%d ", a[i]);
}
printf("\n");
heap_increase_key(a, 4, 8);
for(int i = 1; i <= a[0]; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
- heap_insert
void heap_insert(maxheap * A, int key)
{
A[0]++;
A[A[0]] = 0;
heap_increase_key(A, A[0], key);
}
第7章: 快速排序
7.1 描述
快速排序用到分治的思想。书本上把数组分为如下结构:
我们取数组最后一个值x作为基准点,不断比较对前面的所有数进行变换,最后把<=x与>=x的部分进行递归。
变换操作规则如下:
话不多说,上代码!
#include<cstdio>
#include<iostream>
using namespace std;
void quick_sort(int * a, int l, int r)
{
if(l < r)
{
int x = a[r];
int i = l - 1;
for (int j = l; j < r; j++)
{
if (a[j] <= x)
{
i++;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
int temp = a[i + 1];
a[i + 1] = a[r];
a[r] = temp;
quick_sort(a, l, i);
quick_sort(a, i+2, r);
}
}
int main()
{
int a[] = {4,6,9,7,2,5,1,3,8};
int n = sizeof(a)/4;
quick_sort(a, 0, n-1);
for(int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
复杂度O(nlgn)
7.2快速排序的性能
7.3快速排序的随机化版本
7.4快速排序分析
这些章节先跳过了
另外:推荐一个链接
第8章: 线性时间排序
书上之前学过的算法复杂度下界为 Ω(nlgn) Ω ( n l g n ) ,这意味着无论如何优化都不可能到达 Θ(n) Θ ( n ) 。
证明:对于一棵每个排列都作为一个可达叶结点出现的决策树,根据前面的讨论即可确定其高度。考虑一棵高度为h的、具有L个可达叶结点的决策树,它对应于对n个元素所做的比较排序。因为n个输入元素共有n!种排列,每一种都作为一个叶子出现在树中,故有
n!≤L
n
!
≤
L
。又由于在一棵高为h的二叉树中,叶子的数目不多于
2h
2
h
,则有
对该式取对数,得到
分析之前学过的算法,发现它们都有一个共同点,就是需要进行大小比较,以比较结果确定数组的位置。书上用决策树来形象化描述这一发现。
为了让大家对线性时间排序有个初步认识,
我先贴一个链接:啊哈算法的桶排序
8.2 计数排序
为了实现线性时间排序,我们必不可少需要更大的储存空间。
变量 | 说明 |
---|---|
A[] | 原数组 |
B[] | 排序后数组 |
C[] | 辅助数组 |
我们用C[]的数组的下标表示需要排序的数,数组值为几表示该数出现几次。【其实知道了C[]就可以遍历C[]打印出排序结果了-。-,不知道为啥要计数排序hh】
既然我们已经知道每个数有多少个,如图【a】,那就知道这个数前面有多少个数,如图【b】,前面有多少个数就可以确定这个数的大小。
#include<cstdio>
#include<iostream>
using namespace std;
const int mmax = 10002;//个人写法不要介意
void counting_sort(int * a, int n)
{
int c[mmax];
int b[n];
memset(c, 0, sizeof(c));
for(int i = 0; i < n; i++)
{
b[i] = a[i];
c[a[i]] += 1;
}
for(int i = 1; i < mmax; i++)
c[i] = c[i] + c[i-1];
for(int i = 0; i < n; i++)
{
a[c[b[i]]-1] = b[i];
c[b[i]] = c[b[i]]-1;
}
}
int main()
{
int a[] = {4,6,9,7,2,5,1,3,8};
int n = sizeof(a)/4;
counting_sort(a, n);
for(int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
复杂度O(n)
8.3 基数排序
基数排序按照与人的常数相反的顺序来排序。先比较个位,再比较十位。。。
这个顺序其实和从高到低没有什么区别,效果是一样的。
第9章: 中位数和顺序统计量
顺序统计量是该集合中第i小的元素。
中位数是n/2的整除结果所代表的数。
9.1 最小值与最大值
找到最小值与找到最大值需要进行比较n-1次,但同时找到最小值和最大值只需要比较3(n-1)/2次。
看代码
#include<cstdio>
#include<iostream>
using namespace std;
const int INF = 99999999;
int main()
{
int a[] = {4,6,9,7,2,5,1,3,8};
int n = sizeof(a)/4;
int i,min,max;
if(n%2==0)
{
min = -INF;
max = INF;
i = 0;
}
else
{
min = a[0];
max = a[0];
i = 1;
}
for(; i < n; i+=2)
{
if(a[i] > a[i+1])
{
if(a[i] > max)max = a[i];
if(a[i+1] < min)min = a[i+1];
}
else
{
if(a[i] < min)min = a[i];
if(a[i+1] > max)max = a[i+1];
}
}
printf("max:%d\nmin:%d\n", max, min);
return 0;
}
9.2 期望为线性时间的选择算法
我们要找到第i小的数,用快排进行排序时可以只递归调用存在第i小的数的那部分。
RANDOMIZED-SELECT算法
#include<cstdio>
#include<iostream>
using namespace std;
int quick_sort(int * a, int l, int r, int k)
{
if(l < r)
{
int x = a[r];
int i = l - 1;
for (int j = l; j < r; j++)
{
if (a[j] <= x)
{
i++;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
int temp = a[i + 1];
a[i + 1] = a[r];
a[r] = temp;
if(i > k)
return quick_sort(a, l, i, k);
else if(i < k)
return quick_sort(a, i+2, r, k);
else
return k;
}
}
int main()
{
int a[] = {4,6,9,7,2,5,1,3,8};
int n = sizeof(a)/4;
int k;
scanf("%d", &k);
int res = quick_sort(a, 0, n-1, k);
printf("%d", res);
return 0;
}
复杂度O(n)