算法导论习题(9)

练习(持续更新...)

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 题目太长。。。

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值