百度2012实习生校园招聘笔试题
数组al[0,mid]和al[mid+1,num-1]是各自有序的,对数组al[0,num-1]的两个子有序段进行merge,得到al[0,num-1]整体有序。要求空间复杂度为O(1)。注:al[i]元素是支持'<'运算符的。
1、用temp数组作为中间变量,保存两个有序子数组的合并结果数组,再复制回原数组。
//将有二个有序数列a[first...mid]和a[mid...last]合并。
void mergearray(int a[], int first, int mid, int last, int temp[])
{
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i <= m && j <= n)
{
if (a[i] <= a[j])
temp[k++] = a[i++];
else
temp[k++] = a[j++];
}
while (i <= m)
temp[k++] = a[i++];
while (j <= n)
temp[k++] = a[j++];
for (i = 0; i < k; i++)
a[first + i] = temp[i];
}
2、没有中间变量temp,要求空间复杂度为O(1),通过交换+移位实现
空间复杂度O(1),时间复杂度最坏为O(N*N)
void swap( int *a,int *b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
void MergeSortedArray02(int *a,int first,int mid,int last) //不使用中间数组temp,通过数组元素交换实现,空间复杂度O(1),时间复杂度最坏为O(N^2)
{
int temp;
int i=first,j=mid+1;
while(i<=mid&&j<=last)
{
if(a[i]<=a[j])
{
i++;
}
else
{
swap(&a[i],&a[j]); //交换位置
for(int k=j-1;k>i;k--)
{
swap(&a[k],&a[k+1]); //从i+1到j的子数组循环右移一位,使得
}
i++;
j++;
}
}
}
3、优化第二种方法,交换+循环移位(不用中间变量,通过交换反转实现),第二种方法中,每次移动的单位是一个数,效率低,可以采用块循环移动(单位是连续几个数)进行优化
空间复杂度O(1),时间复杂度应该比O(m+n)要小!
void Reverse(int *a , int begin , int end ) //反转
{
for(; begin < end; begin++ , end--)
swap(a[begin] , a[end]);
}
void RotateRight(int *a , int begin , int end , int k) //循环右移,a[begin:end]段向右循环移位k位
{
int len = end - begin + 1; //数组的长度
k %= len;
Reverse(a , begin , end - k);
Reverse(a , end - k + 1 , end);
Reverse(a , begin , end);
}
// 将有序数组a[begin...mid] 和 a[mid+1...end] 进行合并
void Merge(int *a , int begin , int end )
{
int i , j , k;
i = begin;
j = 1 + ((begin + end)>>1); //位运算的优先级比较低,需要加一个括号
while(i <= end && j <= end && i<j)
{
while(i <= end && a[i] < a[j])
i++;
k = j; //暂时保存指针j的位置
while(j <= end && a[j] < a[i]) // 找到连续的j索引列都小于a[i],进行块循环移动
j++;
if(j > k)
RotateRight(a , i , j-1 , j-k); //数组a[i...j-1]循环右移j-k次,a[i..j-1]始终有序
i += (j-k+1); //第一个指针往后移动,因为循环右移后,数组a[i....i+j-k]是有序的
}
}
4、标准参考算法
1 /*
2 数组a[begin, mid] 和 a[mid+1, end]是各自有序的,对两个子段进行Merge得到a[begin , end]的有序数组。 要求空间复杂度为O(1)
3 方案:
4 1、两个有序段位A和B,A在前,B紧接在A后面,找到A的第一个大于B[0]的数A[i], A[0...i-1]相当于merge后的有效段,在B中找到第一个大于A[i]的数B[j],
5 对数组A[i...j-1]循环右移j-k位,使A[i...j-1]数组的前面部分有序
6 2、如此循环merge
7 3、循环右移通过先子段反转再整体反转的方式进行,复杂度是O(L), L是需要循环移动的子段的长度
8 */
9 #include<iostream>
10 using namespace std;
11
12 void Reverse(int *a , int begin , int end ) //反转
13 {
14 for(; begin < end; begin++ , end--)
15 swap(a[begin] , a[end]);
16 }
17 void RotateRight(int *a , int begin , int end , int k) //循环右移
18 {
19 int len = end - begin + 1; //数组的长度
20 k %= len;
21 Reverse(a , begin , end - k);
22 Reverse(a , end - k + 1 , end);
23 Reverse(a , begin , end);
24 }
25
26 // 将有序数组a[begin...mid] 和 a[mid+1...end] 进行归并排序
27 void Merge(int *a , int begin , int end )
28 {
29 int i , j , k;
30 i = begin;
31 j = 1 + ((begin + end)>>1); //位运算的优先级比较低,外面需要加一个括号,刚开始忘记添加括号,导致错了很多次
32 while(i <= end && j <= end && i<j)
33 {
34 while(i <= end && a[i] < a[j])
35 i++;
36 k = j; //暂时保存指针j的位置
37 while(j <= end && a[j] < a[i])
38 j++;
39 if(j > k)
40 RotateRight(a , i , j-1 , j-k); //数组a[i...j-1]循环右移j-k次
41 i += (j-k+1); //第一个指针往后移动,因为循环右移后,数组a[i....i+j-k]是有序的
42 }
43 }
44
45 void MergeSort(int *a , int begin , int end )
46 {
47 if(begin == end)
48 return ;
49 int mid = (begin + end)>>1;
50 MergeSort(a , begin , mid); //递归地将a[begin...mid] 归并为有序的数组
51 MergeSort(a , mid+1 , end); //递归地将a[mid+1...end] 归并为有序的数组
52 Merge(a , begin , end); //将有序数组a[begin...mid] 和 a[mid+1...end] 进行归并排序
53 }
54
55 int main(void)
56 {
57 int n , i ;
58 // int a[] = {1, 5,6,7,15,66, 2,3,4,8,33,35};
59 int a[] = {1 ,6 ,9 , 2 ,3,4 ,15};
60
61 n = sizeof(a)/sizeof(int);
62
63 MergeSort(a , 0 , n - 1);
64
65 for(i = 0 ; i < n ; ++i)
66 cout<<a[i]<<" ";
67
68 return 0;
69 }