合并排序(Merge Sort)
两个已经排序的序列合并成一个序列,具体过程如下:
- 申请空间,使其大小为两个已经排序序列之和,然后将待排序数组复制到该数组中;
- 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
- 比较复制数组中两个指针所指向的元素,选择相对小的元素放入到原始待排序数组中,并移动指针到下一位置;
- 重复步骤3直到某一指针达到序列尾;
- 将另一序列剩下的所有元素直接复制到原始数组末尾。
//将有二个有序数列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];
}
void mergesort(int a[], int first, int last, int temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
mergesort(a, first, mid, temp); //左边有序
mergesort(a, mid + 1, last, temp); //右边有序
mergearray(a, first, mid, last, temp); //再将二个有序数列合并
}
}
bool MergeSort(int a[], int n)
{
int *p = new int[n];
if (p == NULL)
return false;
mergesort(a, 0, n - 1, p);
delete[] p;
return true;
}
位图表示
可使用一个20位的位串,表示<20的的非负整数集合。
例如:{1, 2, 3, 5, 8, 14}可表示为
即:0x 812E
同理,可用一个1千万位的位串,表示<10,000,000的非负整数集合,内存大小为 10 000 000/8/1024/1024=1.19MB
可分两次读取,用500万位的位串,第一次对1- 5,000,000之间的数排序,之后再对5,000,001-10,000,000之间的数排序,内存大小为 5,000,000/(810241024) = 0.596MB
伪代码:
/* phase 1: initialize set to empty */
for i = [0,n)
bit[i] = 0
/* phase 2: insert present element into the set */
for each i in the input file
bit[i] = 1
/* phase 3: write sorted outout */
for i = [0,n)
if bit[i] == 1
write i on the output file
位图数据
C语言:
所申请的int数组如下:
字节位置=数据/32;
(采用位运算即右移5位)
位位置=数据%32;
(采用位运算即跟0X1F进行与操作)
位图排序
#include <stdio.h>
#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];
void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK)); }
void clr(int i) { a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int test(int i){ return a[i>>SHIFT] & (1<<(i & MASK)); }
int main(){ int i;
for (i = 0; i < N; i++) clr(i);
/*Replace above 2 lines with below 3 for word-parallel init
int top = 1 + N/BITSPERWORD;
for (i = 0; i < top; i++)
a[i] = 0;
*/
while (scanf("%d", &i) != EOF)
set(i);
for (i = 0; i < N; i++)
if (test(i))
printf("%d\n", i);
return 0;
}
产生初始待排序序列:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAXN 2000000
int x[MAXN];
int randint(int a, int b){return a + (RAND_MAX * rand() + rand()) % (b + 1 - a);}
int main(int argc, char *argv[]){
int i, k, n, t, p;
srand((unsigned) time(NULL));
k = atoi(argv[1]);
n = atoi(argv[2]);
for (i = 0; i < n; i++)
x[i] = i;
for (i = 0; i < k; i++) {
p = randint(i, n-1);
t = x[p]; x[p] = x[i]; x[i] = t;
printf("%d\n", x[i]);
}
return 0;
}
原则
仔细分析小问题可以带来巨大的实际好处。
普遍原则
位图数据结构
多趟算法
时间和空间的权衡,两者不可偏废
简单的设计
二分查找
在编程中,二分查找最常见应用就是在排序数组中查找某个元素。如果正在查找的元素是50,那么算法将进行如下探测:
int n, k[MAX_N]; //输入
bool binary_search (int x) {
int s = 0, e = n; //x的存在范围是k[s], k[s+1], …, k[e-1].
while( e - s >= 1) {
int s = (s + e) / 2;
if (k[ i ] == x) return true; //找到x
else if (k[ i ] < x) s = i + 1 ;
else e = i;
}
}
时间复杂度分析:
若 n = 2 ,执行 1 = log2(2) 次找到
若 n = 4 ,执行 2 = log2(4)次找到
若 n = 8 ,执行 3 = log2(8)次找到
··· ···
若 n,执行 log2(n)
O(log n)
分块查找
将n个数据元素"按块有序"划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须"按块有序";即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素,······ 。
typedef struct {
int value;
int index; } INDEX;
typedef struct {
int key; } ELEMTYPE;
int BlockSearch(INDEX index[],ELEMTYPE list[], int low, int high, int key, int m, int n)
{
int mid = 0;
while (low<=high) {
mid = (low+high)/2;
if (index[mid].value < key) low = mid +1;
else high = mid-1;
}
int position = high+1;
int i = index[position].index;
for( ; i<=index[position].index+m-1&&i<n; i++)
if ( list[i].key == key) break;
if (i<=index[position].index+m-1&&i<n) return i;
else return -1;
}
int main()
{
Index index[] = {{10,0},{20,5},{30,10},{40,15}};
ElemType list[] = {{1},{5},{6},{9},{10},
{11},{15},{16},{19},{20},
{21},{25},{26},{29},{30},
{31},{35},{36},{39},{40}};
for(int i=0;i<20;i++)
printf(“%d”, BlockSearch(index,list,0,3,list[i].key,5,20));
return 0;
}
向量旋转
请将一个具有n个元素的一维向量向左旋转i个位置。例如,假设n=8, i=3, 那么向量abcdefgh旋转之后得到向量defghabc。
简单的代码使用一个n元的中间向量在n步内完成该工作。你能否仅使用数十个额外字节的存储空间,在正比于n的时间内完成向量的旋转?
解法1:简单编码
如果不考虑时间要求为O(n),那么可以每次整体左移一位,一共移动i次。只使用O(1)的空间的条件下,一共要进行元素交换O(n*i)次;
void Rotate(char *a, int length, int i)
{
for (int k=0; k<I; k++) {
char t = a[0];
for (int j=0; j<length; j++)
a[j] = a[j+1];
a[j] = t;
}
}
如果不考虑空间要求为O(1),
那么可以把前i个存入临时数组,剩下的左移i位,再把临时数组里的内容放入后i个位置中。
void Rotate(char *a, int length, int i){
char tmp[10]; //缓存区
int step= i % length; //需要移动的次数
int j=0;
if (step == 0) return;
//移出
for(j=0; j<step; j++)
tmp[j] = a[j];
//前移
for(j=step; j<length; j++)
a [j-step]=a [j];
//移回
j = 0;
while((a[length-step+j]=tmp[j]) != ‘\0’)
j++;
}
解法2:杂耍算法
T A[0]
A[0] A[3] [0] [ i mod n]
A[3] A[6] [i mod n] [ 2i mod n]
A[6] A[1] [2i mod n] [3i mod n]
A[1] A[4] [3i mod n] [4i mod n]
A[4] A[7] [4i mod n] [5i mod n]
A[7] A[2] [5i mod n] [6i mod n]
A[2] A[5] [6i mod n] [7i mod n]
A[5] T [7i mod n] T
void rotate(char* a, int n, int i) {
int temp = a[0];
int current = 0;
while(1) {
int next = (current + i) % n;
if (next == 0) break;
a[current] = a[next];
current= next;
}
a[current] = temp;
}
}
基础
【定义1】同余
如果两个整数(a,b)除以同一个整数m,如果得到相同的余数k。则称a,b对于模m同余。记作:a ≡ b (mod m)
【定义2】同余类
指以某一特定的整数(如m)为模,按照同余的方式对全体整数进行的分类。对给定的模m,有且恰有m个不同的模m的同余类。即:0 (mod m),1 (mod m),…,m-1 (mod m)。
【定义3】完全剩余类
所有的整数以m为模可以划分为m个没有交集的集合。从每个集合中取一个整数组成一个集合,则这个集合中的m个整数就不存在同余的整数,这个集合就叫做完全剩余类。
原理
如果i和n互质,那么序列:0 ; i mod n ; 2i mod n ; 3i mod n ; …… ;(n-1)*i mod n
就包括了集合 {0,1,2,……n-1} 的所有元素。
前提条件
对于模n来说,序列0,1,2,……,n-1本身就是一个完全剩余类(即两两互不同余)
证明步骤
1)从此序列中任取两个数字xk,xl(0 <=k, l <= n-1),则有xk≠ xl (mod n),
2)由于i和n是互质的,所以 xk * i ≠ xl * i (mod n)
=》这就说明,xi从0开始一直取值到n-1,得到的序列0 * i,1 * i,2 *i,……(n-1)*n就是一个完全剩余类。即集合0,1,2,……n-1}
T A[0]
A[0] A[3] [0] [ i mod n]
A[3] A[6] [i mod n] [ 2i mod n]
A[6] A[1] [2i mod n] [3i mod n]
A[1] A[4] [3i mod n] [4i mod n]
A[4] A[7] [4i mod n] [5i mod n]
A[7] A[2] [5i mod n] [6i mod n]
A[2] A[5] [6i mod n] [7i mod n]
A[5] T [7i mod n] T
以上的赋值操作,赋值操作符的两边都得到了一个完全剩余类,也就是说所有的0 ~ n-1的所有位置都被移动过了
请注意第二个操作,X[0] = X[i mod n]。该操作决定了整体的导向,该操作将i mod n位置的值移动到了最开始的位置。
由于i,2i,……之间的偏移量是相同的,所以整个操作实际上就是讲序列向左移动i个位置(超过了开始位置的接到最右边去)
i和n不互质的情况
void rotate(char* a, int n, int i) {
int step = gcd(n, i);
for(int j = 0; j < step; j++) {
int temp = a[j];
int current = j;
while(1) {
int next = (current + i) % n;
if (next == j) break;
a[current] = a[next]; current= next;
}
a[current] = temp;
}
}