算法导论2-9章补充几道题

本篇博文意在对前几章中遗漏的,本人觉得有意思的习题当独拿出来练练手。

1、习题2-4,求逆序对,时间复杂度要求Θ(nlgn)

定义:对于一个有n个不同的数组A, 当i<j时,存在A[i]>A[j],则称对偶(i, j)为A的一个逆序对。

譬如:<2,3,8,6,1>有5个逆序对。

解题思路:归并排序的思想:逆序对的数量=左区间的逆序对+右区间的逆序对+合并的逆序对

代码如下:

 1 #include <iostream>
 2 #include <vector>
 3 using namespace std;
 4
 5 int MergeInversion(int arr[], int nLeft, int nRight);
 6 int Merge(int arr[], int p, int q, int r);
 7
 8 //用归并排序找逆序对
 9 int MergeInversion(int arr[], int nLeft, int nRight)
10 {
11     int nInversion = 0;
12     if (nLeft < nRight) {
13         int nMid = (nLeft+nRight) >> 1;
14         nInversion += MergeInversion(arr, nLeft, nMid);
15         nInversion += MergeInversion(arr, nMid+1, nRight);
16         nInversion += Merge(arr, nLeft, nMid, nRight);
17     }
18     return nInversion;
19 }
20
21 int Merge(int arr[], int p, int q, int r)
22 {
23     int n1 = q - p + 1;
24     int n2 = r - q;
25     int *left = new int[n1];
26     int *right = new int[n2];
27
28     int i,j;
29     for (i = 0; i < n1; i ++)
30         left[i] = arr[p+i];
31     for (j = 0; j < n2; j ++)
32         right[j] = arr[q+1+j];
33
34     i = 0;
35     j = 0;
36     int k = p;
37     int nInverCount = 0; //逆序对的数目
38
39     while (i < n1 && j < n2) {
40         if (left[i] <= right[j])
41             arr[k++] = left[i++];
42         else {//假如左边子序列的数大于右子序列,则逆序对数为n1 - i;
43             arr[k++] = right[j++];
44             nInverCount += (n1 - i);
45         }
46     }
47     while (i < n1)
48         arr[k++] = left[i++];
49     while (j < n2)
50         arr[k++] = right[j++];
51
52     delete left;
53     left = NULL;
54     delete right;
55     right = NULL;
56
57     return nInverCount;
58 }
59
60 int main()
61 {
62     int arr[] = {2,3,8,6,1};
63     int len = sizeof(arr)/sizeof(arr[0]);
64
65     int *arrT = new int[len];
66
67
68     cout << MergeInversion1(arr, 0, 4, arrT) << endl;
69     for (int i = 0; i < 5; i++)
70         cout << arrT[i] << " ";
71     return 0;
72 }

2、习题9.3-7,设计一个O(n)时间的算法,对于一个给定的包含n个互异元素的集合S和一个正整数 k<=n,该算法能够确定S中最接近中位数的k个元素。

譬如数组:<5,7,10,3,6,2,8,9,1,11,12>,中位数为6,如果k=3,最接近6的3个元素是<5,7,4>或<5,7,8>;如果k=4,则最接近6的4个元素是<5,7,4,8>。

解题思路:

1)通过Select()得到A的中位数 --->O(n)

2)计算A中每个数到中位数的差值作为数组D ,并增加D的一个副本D‘ --->O(n)

3)通过Select()得到D’中第k大的数 ---> O(n)

4)依次遍历D中的数,判断比k小的k个数,即为所求 ---> <O(n)

代码如下:

 1 #include <iostream>
 2 #include <cassert>
 3 #include <cmath>
 4 using namespace std;
 5
 6 int* K_Select(int arr[], int nLen, int k);
 7 int Select(int arr[], int nLeft, int nRight, int nMin);
 8 int Partition(int arr[], int p, int r);
 9 void Swap(int &n, int &m);
10
11 int* K_Select(int arr[], int nLen, int k)
12 {
13     assert(k <= nLen);
14
15     int *pDis = new int[nLen-1]; //the distance of every bit and median
16     int *pDis_cpy = new int[nLen-1];
17     int *pKArr = new int[k]; //return k_array
18
19     int nMedian = Select(arr, 0, nLen-1, nLen/2); //get the median of arr
20     int iMedian;
21     int nDLen = 0;
22     for (int i = 0; i < nLen; i ++) {
23         if (arr[i] != nMedian)
24             pDis[nDLen++] = abs(arr[i]-nMedian); //get the distance using abs()
25         else
26             iMedian = i; //get the index of median
27     }
28     memcpy(pDis_cpy, pDis, sizeof(int)*(nLen-1)); //copy the distance
29     int nKMedian = Select(pDis_cpy, 0, nDLen-1, k); //get the k-th minimum distance between median and every bit
30
31     delete pDis_cpy;
32     pDis_cpy = NULL;
33
34     int ik = 0;
35     for (int i = 0; ik < k && i < nDLen; i ++) {
36         if (pDis[i] <= nKMedian) {
37             if (i < iMedian) //judge the index of array and the index of median
38                 pKArr[ik++] = nMedian - pDis[i];
39             else
40                 pKArr[ik++] = nMedian + pDis[i];
41         }
42     }
43
44     delete pDis;
45     pDis = NULL;
46
47     return pKArr;
48 }
49
50 int Select(int arr[], int nLeft, int nRight, int nMin)
51 {
52     assert(nLeft <= nRight);
53     assert(nMin <= nRight-nLeft+1);
54
55     if (nLeft == nRight)
56         return arr[nLeft];
57     int nMid = Partition(arr, nLeft, nRight);
58     int k = nMid - nLeft + 1;
59     if (k == nMin)
60         return arr[nMid];
61     else if (k > nMin)
62         return Select(arr, nLeft, nMid-1,nMin);
63     else
64         return Select(arr, nMid+1, nRight, nMin-k);
65 }
66
67 int Partition(int arr[], int p, int r)
68 {
69     assert(p <= r);
70
71     int nTemp = arr[r];
72     int i = p - 1, j = p;
73     while(j < r) {
74         if (arr[j] <= nTemp) {
75             Swap(arr[i+1], arr[j]);
76             i ++;
77         }
78         j ++;
79     }
80     Swap(arr[i+1], arr[r]);
81     return i+1;
82 }
83
84 void Swap(int &n, int &m)
85 {
86     int t = n;
87     n = m;
88     m = t;
89 }
90
91 int main()
92 {
93     int arr[] = {5,7,10,3,6,2,8,9,4,1,11,12};
94     int *karr = K_Select(arr, 12, 4);
95     for (int i=0; i < 4; i ++)
96         cout << karr[i] << " ";
97     return 0;
98 }

3、习题9.3-8,求两个有序数组的中位数?

题目:设X[1...n]和Y[1...n]为两个数组,每个都包含n个有序的元素。请设计一个O(lgn)时间的算法来找出数组X 和Y中所有2n个元素的中位数。

譬如:X:<1,2,3,4,7,9>; Y:<2,5,6,7,10,11>; 中位数为数组Y中的6.

解题思路:二分查找思想--->O(lgn)

1)如果两个数组均只有一个元素,则返回数组中的较小值。

2)两个数组有2n个元素,则中位数位于n index,小于中位数的元素个数为n-1;

3)如果当前找到中位数在数组X中的标号为 K,则在数组Y的标号为:n-K-2 < index <= n-K-1;

4)如果当前在数组X中找到的中位数 X[K]<= Y[n-K-2],说明该数小了,应该在X的右区间[mid+1, right]继续找,反之,如果X[K] > Y[n-K-1],说明该数大了,应该在X的左区间[left, mid-1]继续找。

5)如果重复该过程,没有在X中找到该中位数,则采用同样的方法在Y中找。

代码如下:

 1 #include <iostream>
 2 #include <cassert>
 3 using namespace std;
 4
 5 int SelectMedian(int *arrA, int *arrB, int left, int right)
 6 {
 7     assert(NULL != arrA);
 8     assert(NULL != arrB);
 9
10     int nLen = right-left+1;
11
12     while (left <= right) {
13         int mid = (left+right)/2;
14
15         if (mid == nLen-1 && arrA[mid] <= arrB[0]) //only one element
16             return arrA[mid];
17         else if (mid < nLen-1) {
18             if(arrA[mid] <= arrB[nLen-mid] && arrA[mid] > arrB[nLen-1-mid]) //K = nLen-1-K note:from 0 index
19                 return arrA[mid];
20             else if (arrA[mid] <= arrB[nLen-1-mid]) //if the element is small, find it in right region.
21                 left = mid + 1;
22             else         //if the element is big, find it in left region.
23                 right = mid - 1;
24         }
25     }
26     return -1;
27 }
28
29 //find the median of two array: arrA and arrB
30 //return the median of two array
31 void TwoArrMedian(int *arrA, int *arrB, int nLen)
32 {
33     assert(NULL != arrA);
34     assert(NULL != arrB);
35
36     int nMedian;
37     if (SelectMedian(arrA, arrB, 0, nLen-1) == -1) //not find
38         nMedian = SelectMedian(arrB, arrA, 0, nLen-1);
39
40     cout << nMedian << endl;
41 }
42
43 int main()
44 {
45     int a[] = {1,2,3,4,8,9,13};
46     int b[] = {2,5,6,7,10,11,12};
47
48     TwoArrMedian(a, b, 7);
49     return 0;
50 }
时间: 2024-10-05 07:00:53

算法导论2-9章补充几道题的相关文章

算法导论 第13章 红黑树

二叉查找树的基本操作包括搜索.插入.删除.取最大和最小值等都能够在O(h)时间复杂度内实现,因此能在期望时间O(lgn)下实现,但是二叉查找树的平衡性在这些操作中并没有得到维护,因此其高度可能会变得很高,当其高度较高时,而二叉查找树的性能就未必比链表好了,所以二叉查找树的集合操作是期望时间O(lgn),最坏情况下为O(n). 红黑树也是一种二叉查找树,它拥有二叉查找树的性质,同时红黑树还有其它一些特殊性质,这使得红黑树的动态集合基本操作在最坏情况下也为O(lgn),红黑树通过给节点增加颜色和其它

算法导论 第6章 堆排序

堆数据结构实际上是一种数组对象,是以数组的形式存储的,但是它可以被视为一颗完全二叉树,因此又叫二叉堆.堆分为以下两种类型: 大顶堆:父结点的值不小于其子结点的值,堆顶元素最大 小顶堆:父结点的值不大于其子结点的值,堆顶元素最小 堆排序的时间复杂度跟合并排序一样,都是O(nlgn),但是合并排序不是原地排序(原地排序:在排序过程中,只有常数个元素是保存在数组以外的空间),合并排序的所有元素都被拷贝到另外的数组空间中去,而堆排序是一个原地排序算法. 1.在堆排序中,我们通常使用大顶堆来实现,由于堆在

算法导论 第6章 堆排序(简单选择排序、堆排序)

堆数据结构实际上是一种数组对象,是以数组的形式存储的,可是它能够被视为一颗全然二叉树,因此又叫二叉堆.堆分为下面两种类型: 大顶堆:父结点的值不小于其子结点的值,堆顶元素最大 小顶堆:父结点的值不大于其子结点的值,堆顶元素最小 堆排序的时间复杂度跟合并排序一样,都是O(nlgn),可是合并排序不是原地排序(原地排序:在排序过程中,仅仅有常数个元素是保存在数组以外的空间),合并排序的全部元素都被复制到另外的数组空间中去,而堆排序是一个原地排序算法. 1.在堆排序中,我们通常使用大顶堆来实现,因为堆

算法导论 第8章 线性时间排序

合并排序和堆排序的时间复杂度为O(nlgn),插入排序和冒泡排序的时间复杂度为O(n^2),快速排序的时间复杂度在平均情况下是O(nlgn),这些排序算法都是通过对元素进行相互比较从而确定顺序的,因此都叫比较排序. 比较排序可以看做是决策树(一个满二叉树),因为每一次比较都是一个分支.n个元素的序列,其排序的结果有 n! 种可能(n个元素的全排),所以这个决策树有 n! 个叶子结点,假设树的高度为h,则有:n! <= 2^h,所以h >= lg(n!) = Ω(nlgn).一次比较排序就是从决

算法导论 第7章 高速排序

高速排序在最坏情况下的时间复杂度为O(n^2),尽管在最坏情况下执行时间比較差,可是高速排序一般是用于排序的最佳选择.由于其平均性能相当好,期望的执行时间为O(nlgn),且在O(nlgn)的记号中隐含的常数因子非常小. 高速排序和合并排序有相似之处,都是须要划分序列,在合并排序中.划分的过程非常easy.直接选择元素序列的中间位划分位置,排序是在合并的过程中实现的,所以合并排序的合并过程非常重要.相比合并排序,高速排序就没有合并的过程.仅仅有划分,高速排序的划分过程非常重要,排序是在划分的过程

算法导论第7章___快速排序

快速排序本质上是插入排序,但是它在这个基础上增强了算法. 下面我们来分析一下快速排序: 有了前面的分析基础,我们在来看排序算法也就容易多了. public class Quick_Sort { private void quick_Sort(int []A,int left,int right){ if(left<right){ //划区比较,这个partition 第一次!得到的就是我们刚才说的2. int partition=partition(A, left, right); //实现第一

算法导论 第9章 中位数和顺序统计学

/* * 算法导论 第九章 中位数和顺序统计学 * 线性时间选择元素 */ #include <iostream> #include <ctime> using namespace std; int minimum(int *arr, int len); int randomizedSelect(int *arr, int p, int r, int i); int randomizedPartition(int *arr, int p, int r); void exchange

算法导论 第7章 快速排序

快速排序在最坏情况下的时间复杂度为O(n^2),虽然在最坏情况下运行时间比较差,但是快速排序通常是用于排序的最佳选择,因为其平均性能相当好,期望的运行时间为O(nlgn),且在O(nlgn)的记号中隐含的常数因子很小. 快速排序和合并排序有相似之处,都是需要划分序列,在合并排序中,划分的过程很简单,直接选择元素序列的中间位划分位置,排序是在合并的过程中实现的,所以合并排序的合并过程很重要:相比合并排序,快速排序就没有合并的过程,只有划分,快速排序的划分过程很重要,排序是在划分的过程中实现的. /

算法导论 第2章

本章主要是算法知识的基础讲解,介绍了循环不变式,几个简单的排序算法,递归分治算法等内容. 1.循环不变式 循环不变式主要用来说明算法的正确性,那么什么是循环不变式呢,其实就是在循环过程中,一些元素数据必须保持的一些性质,例如在插入排序中,数组为A,必须保证三个性质: (1) 初始化:在循环开始之前,循环不变式是成立的,即:A[0]是有序的,A[1...n-1]是无序的. (2) 保持:在循环的某一次迭代开始之前,循环不变式是成立的,那么在此次迭代结束后依然应该是成立的,即:A[0...i]是有序

算法导论_第二章(1)

年前的时候去逛书店,久仰算法导论这本书的大名看见后也就买了下来.回家看了一段时间,发现看书的进度真的是极慢,书里的课后题很多,那些不会的问题也是通过网上搜别人的答案才得以解决的.所以,我就想把我看这本书的心得连带课后的解答分享给大家.同时也是给我坚持把算法导论这本书看完的一个动力 ^_^ 因为本书的第一章相当于一个导论就直接跳过了,那么,从第二章开始! 第二章主要介绍了插入排序和归并排序: 所谓的插入排序就像是一局扑克刚开始时的摸牌阶段,你对手中的扑克所做的整理排序一样.开始时,我们的左手为空并