算法整理

合并排序(Merge Sort)

两个已经排序的序列合并成一个序列,具体过程如下:

  1. 申请空间,使其大小为两个已经排序序列之和,然后将待排序数组复制到该数组中;
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
  3. 比较复制数组中两个指针所指向的元素,选择相对小的元素放入到原始待排序数组中,并移动指针到下一位置;
  4. 重复步骤3直到某一指针达到序列尾;
  5. 将另一序列剩下的所有元素直接复制到原始数组末尾。
    合并排序
    合并排序
//将有二个有序数列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;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值