练习(持续更新...)
9.3-3 假设所有元素都是互异的,说明在最坏情况下,如何才能使快速排序的运行时间为O(nlgn)?
快速排序的最坏情况就是选取的主元要不是当前范围的最大值,要不就是最小值,只有避免这种情况,才能够使得快排达到期望值。之前有写过一个在O(n)复杂度等级的随机选择,目的是可以在O(n)内计算出指定第几个最小或者最大的值。这里可以借鉴:
1)先计算出中位数,将中位数和原本的主元互换;
2)对新的数据进行快排;
加入问题的规模是n,那么时间为:T(n) = O(n) + T(k) + T(n-k),计算的结果是:T(n) = O(nlgn)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <ctime>
using namespace std;
int A[30] = {0};
// find partition 1
int general_partition(int l, int r) {
int val = A[r], i = l - 1, j = l;
while (j < r) {
if (A[j] < val) swap(A[++i], A[j]);
j++;
}
swap(A[++i], A[r]);
return i;
}
// find partition 2
int special_partition(int l, int r, int val) {
for (int i = l; i < r; i++) {
if (A[i] == val) {
swap(A[i], A[r]);
break;
}
}
return general_partition(l, r);
}
// find median
int random_select(int l, int r, int i) {
if (l >= r) return A[l];
// promise not the worest condition
int q = general_partition(l, r);
int k = l - q + 1;
if (i == k) {
return A[q];
} else if (i < k) {
return random_select(l, q - 1, i);
} else {
return random_select(q + 1, r, i - k);
}
}
// quick sort
void quick_sort(int l, int r) {
// find median
if (l >= r) return;
int median = random_select(l, r, (r - l + 1) / 2);
// quick sort: left
int p = special_partition(l, r, median);
quick_sort(l, p - 1);
quick_sort(p + 1, r);
}
// show sorted result
void show() {
puts("Sorted Result:");
for (int i = 0; i < 30; i++) printf("%d%c", A[i], (i == 29 ? '\n' : ' '));
}
int main(int argc, char* argv[]) {
srand(time(NULL));
for (int i = 0; i < 30; i++) {
A[i] = 30 - i;
printf("%d ", A[i]);
}
quick_sort(0, 29);
show();
return 0;
}
9.3-5 假设有一个最坏情况下是线性时间的用于求解中位数的“黑箱”子程序,设计一个能在线性时间内解决任意顺序统计量的选择问题算法
考虑最坏情况为线性时间的选择算法,其实最关键还是要找到一个合适的主元,防止最坏情况发生时,导致退化成一般的快排或者插入排序。那么问题的关键变成找到一个合适的中位数(因为这个中位数不一定要求是真正的中位数,接近且能够解决问题就好):
1)分组,对原数据分组,同时对每组进行插入排序,其实是一步桶排序,保证了运行时间为O(n);
2)对以上结果拿出每一组的中位数;
3)采用选择算法处理这些中位数,目的是筛选出中位数的中位数;
4)将3)中得到的结果作为主元使用相似的选择算法进行选择,选出第i个最大或最小值;
采用的选择算法保证了期望时间为O(n)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
int A[33] = {0};
int INSERTION_SORT(int l, int r) {
int beg = l + 1;
while (beg <= r) {
for (int i = beg - 1; i >= 0; i--) {
if (A[i] > A[beg]) swap(A[i + 1], A[i]);
else {
swap(A[i + 1], A[beg]);
break;
}
}
beg++;
}
return A[(l + r) / 2];
}
int PARTITION(int l, int r) {
int val = A[r], i = l - 1, j = l;
while (j < r) {
if (A[j] < val) swap(A[++i], A[j]);
j++;
}
swap(A[++i], A[j]);
return i;
}
int SELECT(int l, int r, int i) {
if (l >= r) return A[l];
int q = PARTITION(l, r);
int k = q - l + 1;
if (k == i) {
return A[q];
} else if (i < k) {
return SELECT(l, q - 1, i);
} else {
return SELECT(q + 1, r, i - k);
}
}
int SELECT_GENERAL(int* T, int l, int r, int i) {
if (l >= r) return T[l];
int q = PARTITION(l, r);
int k = q - l + 1;
if (k == i) {
return T[q];
} else if (i < k) {
return SELECT_GENERAL(T, l, q - 1, i);
} else {
return SELECT_GENERAL(T, q + 1, r, i - k);
}
}
void PROCEED(int val) {
// divide
int group_num = 33 / 5;
int* B = new int[group_num];
while (group_num >= 0) {
int l = group_num*5;
B[group_num] = INSERTION_SORT(l, l + 4);
group_num--;
}
// select the median of medians
int median_param = SELECT_GENERAL(B, 0, group_num, group_num / 2);
delete[] B;
// find the median in A
for (int i = 0; i*5 < 33; i++) {
if (A[i*5 + 3] == median_param) {
swap(A[i*5 + 3], A[32]);
break;
}
}
// real select
int result = SELECT(0, 32, val);
cout << "RESULT: " << result << endl;
}
void SHOW() {
for(int i = 0; i < 33; i++) {
printf("%d%c", A[i], (i == 32 ? '\n' : ' '));
}
}
int main(int argc, char* argv[]) {
for (int i = 0; i < 33; i++) {
A[i] = 33 - i;
printf("%d%c", A[i], (i == 32 ? '\n' : ' '));
}
PROCEED(5);
SHOW();
return 0;
}
9.3-6 (暂略)
9.3-7 设计一个O(n)的算法,对于一个给定的包含n个互异元素的集合S和一个正整数 k <= n ,该算法能够确定接近中位数的k个元素。
单纯的使用选择算法,没有办法将和中位数相近的数放置在它的周围,但是可以在查找之前可以遍历一遍数组,将每个数和中位数的差的绝对值纪录下来,同时这个绝对值应当和原始数据下标对应,再对这组数据使用选择算法处理,即选择第k个数,那么最近的k个数的下标可以在O(n)的时间范围内找到
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <ctime>
#include <utility>
using namespace std;
typedef pair<int, int> P;
int A[30];
P sat[30];
// produce a random array, no repeat
void init() {
int temp[30], len = 30;
for (int i = 0; i < 30; i++) temp[i] = i;
for (int i = 0; i < 30; i++) {
int index = rand() % len;
A[i] = temp[index];
swap(temp[index], temp[len - 1]);
len--;
}
}
// partition
int partition(int l, int r) {
int val = A[r], i = l - 1, j = l;
while (j < r) {
if (A[j] < val) swap(A[++i], A[j]);
j++;
}
swap(A[++i], A[r]);
return i;
}
// partition for sat
int partition_2(int l, int r) {
P val = sat[r];
int i = l - 1, j = l;
while (j < r) {
if (sat[j].first < val.first) {
i++;
P temp = sat[i];
sat[i] = sat[j];
sat[j] = temp;
}
j++;
}
i++;
P temp = sat[i];
sat[i] = sat[j];
sat[j] = temp;
return i;
}
// select median
int select_median(int l, int r, int i) {
if (l >= r) return A[l];
int q = partition(l, r);
int k = q - l + 1;
if (k == i) return A[q];
else if (i < k) return select_median(l, q - 1, i);
else return select_median(q + 1, r, i - k);
}
// select for sat
void select_sat(int l, int r, int i) {
if (l >= r) return ;
int q = partition_2(l, r);
int k = q - l + 1;
if (k == i) return ;
else if (i < k) select_sat(l, q - 1, i);
else select_sat(q + 1, r, i - k);
}
// show array-A
void show() {
for (int i = 0; i < 30; i++) printf("%d%c", A[i], (i == 29 ? '\n' : ' '));
}
// solution
void solution(int k) {
int median = select_median(0, 29, 15);
for (int i = 0; i < 30; i++) {
sat[i] = make_pair(abs(median - A[i]), i);
}
select_sat(0, 29, k);
for (int i = 0; i < k; i++) printf("%d ", A[sat[i].second]);
printf("\n");
}
int main(int argc, char* argv[]) {
srand(time(NULL));
init();
show();
// solution
int k = 0;
puts("输入k(最接近中位数的k个元素):");
scanf("%d", &k);
solution(k);
show();
return 0;
}
9.3-8 设X[1...n] 和 Y[1...n]为两个有序数组,每个都包含n个有序的元素,设计一个O(lgn)的算法,来找出数组X和Y中所有2n个元素的中位数
单个数组的中位数,我们可以立即确定,那么可以分成以下三种情况:
1)X的中位数和Y的中位数相同,说明中位数为其中任意一个;
2)X的中位数小于Y的中位数,那么中位数在X的后半部和Y的前半部之间确定;
3)Y的中位数小于X的中位数,那么中位数在Y的后半部和X的前半部之间确定;
以上我默认X、Y都是从大到小的顺序,可以使用二分,正好在O(lgn)的范围内找到
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <ctime>
#include <cmath>
using namespace std;
int A[30], B[30];
// initialize
void init(int* arr, int start, int step) {
for (int i = 0; i < 30; i++) {
arr[i] = start + step*i;
}
}
// binary select
int select(int* a, int* b, int len) {
int mid = len / 2;
if (len == 0) return (a[len] > b[len]) ? b[len] : a[len];
else if (a[mid] < b[mid]) {
return select(a + (len + 1) / 2, b, mid);
} else if (a[mid] > b[mid]) {
return select(a, b + (len + 1) / 2, mid);
}
return a[mid];
}
int PARTITION(int* c, int l, int r) {
int val = c[r], i = l - 1, j = l;
while (j < r) {
if (c[j] < val) swap(c[++i], c[j]);
j++;
}
swap(c[++i], c[r]);
return i;
}
int SELECT(int* c, int l, int r, int i) {
if (l >= r) return c[l];
int q = PARTITION(c, l, r);
int k = q - l + 1;
if (k == i) return c[q];
else if (i < k) return SELECT(c, l, q - 1, i);
else return SELECT(c, q + 1, r, i - k);
}
void solution() {
int* C = new int[60];
for (int i = 0; i < 30; i++) {
C[i] = A[i];
C[i + 30] = B[i];
}
int result = select(A, B, 29);
int res = SELECT(C, 0, 59, 30);
printf("%d %d -- %s\n", result, res, (res == result ? "TRUE" : "FALSE"));
delete[] C;
}
void show(string name, int* a, int len) {
cout << name << endl;
for (int i = 0; i < len; i++) printf("%d%c", a[i], (i == len - 1 ? '\n' : ' '));
printf("\n");
}
int main(int argc, char* argv[]) {
srand(time(NULL));
int start = rand() % 45;
init(A, start, rand() % 3 + 1);
start = rand() % 45;
init(B, start, rand() % 3 + 1);
show("Array-A", A, 30);
show("Array-B", B, 30);
solution();
return 0;
}
9.3-9 题目太长。。。
简而言之就是在中位数区间取值都是可以的,这里的区间说明一下,左右数值可以相等,这个时候表示数组的个数为奇数,区间只有一个中位数