2-1 在合并排序中对小数组采用插入排序
由题意可知,程序的设计如下所示:
#include "stdafx.h"
#include <string.h>
#include <iostream>
using namespace std;
void Merge(int* arr, int head, int mid, int tail);
void InsertSort(int* arr, int k, int head, int tail) // 进行k个大小的数组插入排序
{
if (head + k - 1< tail) {
int mid = (head + tail) / 2;
InsertSort(arr, k, head, mid);
InsertSort(arr, k, mid + 1, tail);
Merge(arr, head, mid, tail);
}
else {
int key, i, j;
for (i = head + 1; i <= tail; i++)
{
key = arr[i];
for (j = i - 1; j >= head && key < arr[j]; j--)
arr[j+1] = arr[j];
arr[j + 1] = key;
}
}
}
void Merge(int* arr, int head, int mid, int tail)
{
int n1 = mid - head + 1;
int n2 = tail - mid;
int* arr1 = new int[n1 + 1];
int* arr2 = new int[n2 + 1];
int i, j, k;
for(int i = 0; i < n1; i++)
arr1[i] = arr[head + i];
for(int i = 0; i < n2; i++)
arr2[i] = arr[mid + 1 + i];
arr1[n1] = 1000000000; //哨兵
arr2[n2] = arr1[n1];
i = j = 0;
k = head;
while(k <= tail) {
if (arr1[i] <= arr2[j]) {
arr[k] = arr1[i];
i++;
k++;
}
else {
arr[k] = arr2[j];
j++;
k++;
}
}
delete[] arr1;
delete[] arr2;
}
测试后通过。关键代码是InsertSort函数中的“if (head + k - 1< tail)”,这句决定了可以进行多大范围的插入排序。
那么a).每次插入排序最坏情况下显然为O(n^2),共进行了n/k次插入排序,显然是在O(nk)时间内完成;
b).由于有n/k个子列表,那么共有log(n/k)+1层。每一层的代价是O(n),因此总共的时间复杂度为O(nlog(n/k));
c).标准的合并算法时间复杂度为O(nlog(n)),要使修改后的算法具有与标准合并排序算法一样的渐进运行时间,k的最大渐进值是logn,原来时间复杂度为O(nk + nlog(n/k)),现在变为了O(nlogn + nlog(n/logn)),忽略nlog(n/logn)的影响,这样即为O(nlogn);
d).在满足插入排序比合并排序更快的前提下,k取最大值。
2-2 冒泡排序算法的正确性
a)A'中的值全部来自A;
b)起始:A数组的初始状态为A[1]、A[2]、A[3]、……A[n],对于for的第一个元素,即j=n,A[n]来说,其元素本身对于只有该元素的数组来说已排好序;
保持:当i=k时,对于j = k-1个元素来说,已经是排好的最小的k-1个元素,当j=n时,A[n]需要与A[n-1]比较,当j为k+1时,A[k+1]是A[n]~A[k]中次小或者最小的数,交换后A[k]变为第k个最小的元素,此时也已经排好序;
终止:当i=n-1时,我们已经排好n-2个最小的元素,j=n时,A[j]只需和A[j-1]比较一次即排好序。
c)起始:A'数组的初始状态为空
保持:若i=k,则A'[1]、A'[2]、……A'[i-1]已经排好序,由b)可知,排序后A'[1]、A'[2]、……、A‘[i]为排好序的;
终止:i=n时,所有数都已经排好序了。
d)O(n^2),最坏情况下也是Σ(n-i)
2-3 霍纳规则的正确性
a)霍纳规则的代码渐进运行时间是n?;
b)朴素多项式?伪代码是:
void Ploynomial()
{
int t;
sum = a[0];
for (i = 1; i < n; i++)
{
sum += a[i]*x;
x *= x;
}
}
那么运行时间应该是2n吧?与上述相比是2倍时间长度;
c)起始:因为y初始状态为0,i为n,因此在第一轮迭代的开始时有,这时y正好为0;
保持:利用数学归纳法,若i = k+1时,迭代前为,此时成立;那么对于下一轮迭代,i=k,迭代前有
,此时
,必然成立。
终止:终止时i=-1,那么此时,与原多项式相同,证明成立
2-4 逆序对
a)列出5个逆序对:(2,1),(3,1),(8,6),(8,1),(6,1)。so easy~~
b)怎样的数组含有最多的逆序对?它们包含多少个逆序对?
从大到小排列,为(n,n-1,n-2,...,1),共(n-1)n/2个。
c)相等。插入排序的思想是,若已经排好序的数为n,待排序的数为第n+1个,需与第n个数比较,若逆序,交换后,再与第n-1个数比较;若不逆序,停止比较。否则直到第1个数与当前数比较完毕。比较的次数和逆序的次数是一样多的。
d) 算法如下所示:
#include "stdafx.h"
int count = 0;
void Merge(int *arr, int head, int mid, int tail)
{
int n1 = mid - head + 1;
int n2 = tail - mid;
int* arr1 = new int[n1 + 1];
int* arr2 = new int[n2 + 1];
int i, j, k;
for (i = 0; i < n1; i++)
arr1[i] = arr[head + i];
for (i = 0; i < n2; i++)
arr2[i] = arr[mid + i + 1];
arr1[n1] = arr2[n2] = 1000000000;
i = j = 0;
k = head;
while (k <= tail) {
if (arr1[i] < arr2[j]) {
arr[k] = arr1[i];
i++;
}
else {
arr[k] = arr2[j];
count++;
j++;
}
k++;
}
delete [] arr1;
delete [] arr2;
}
void MergeSort(int* arr, int head, int tail)
{
if (head < tail)
{
int mid = (head + tail) / 2;
MergeSort(arr, head, mid);
MergeSort(arr, mid + 1, tail);
Merge(arr, head, mid, tail);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
int a[5] = {2,3,8,6,1};
MergeSort(a, 0, 4);
printf("%d", count);
return 0;
}
部分参考http://blog.youkuaiyun.com/powerrock/article/details/2478700