算法导论习题(9)

来源:互联网 发布:海贼王887 知乎 编辑:程序博客网 时间:2024/06/08 16:00

练习(持续更新...)

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 1int 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 2int 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 medianint random_select(int l, int r, int i) {if (l >= r) return A[l];// promise not the worest conditionint 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 sortvoid quick_sort(int l, int r) {// find medianif (l >= r) return;int median = random_select(l, r, (r - l + 1) / 2);// quick sort: leftint p = special_partition(l, r, median);quick_sort(l, p - 1);quick_sort(p + 1, r);}// show sorted resultvoid 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) {// divideint 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 mediansint median_param = SELECT_GENERAL(B, 0, group_num, group_num / 2);delete[] B;// find the median in Afor (int i = 0; i*5 < 33; i++) {if (A[i*5 + 3] == median_param) {swap(A[i*5 + 3], A[32]);break;}}// real selectint 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 repeatvoid 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--;}}// partitionint 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 satint 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 medianint 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 satvoid 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-Avoid show() {for (int i = 0; i < 30; i++) printf("%d%c", A[i], (i == 29 ? '\n' : ' '));}// solutionvoid 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();// solutionint 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];// initializevoid init(int* arr, int start, int step) {for (int i = 0; i < 30; i++) {arr[i] = start + step*i;}}// binary selectint 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 题目太长。。。

简而言之就是在中位数区间取值都是可以的,这里的区间说明一下,左右数值可以相等,这个时候表示数组的个数为奇数,区间只有一个中位数


1 0
原创粉丝点击