数组算法的总结

这篇博客总结了数组相关的经典算法,包括数组循环右移、一维和二维数组的子数组之和最大值问题、最长递增子序列的求解、查找长数组中的最小K个数的多种方法,以及ushort类型数组排序和判断两个递增数组中是否存在相同数字的算法。通过实例解析和伪代码展示了各种算法的实现过程和思路。

数组相关算法的学习。

1、数组循环移位

设计:把含有N个元素的数组循环右移K位,要求时间复杂度0(N),且只允许使用两个附加变量。


假设原数组序列 abcdef1234  ,要求变换成的数组序列为 1234abcdef,即循环右移了4位。相比之后,其中有两段的顺序是不变的:

1234和 abcdef,可把这两段看成两个整体。右移K位的过程就是把数组的两部分交换一下。交换的过程通过以下步骤完成:

1)逆序排序 abcdef:    abcdef1234  -->fedcba1234;
2)逆序排序1234:   fedcba1234 -->fedcba4321;
3)全部逆序1234:   fedcba4321-->1234abcdef;


伪代码:

Reverse(int *arr,int b ,int e)//翻转一小段数组
{
	for(;b < e;b++,e--)
	{
		int temp = arr[e];//使用了一个辅助变量
		arr[e] = arr[b];
		arr[b] = temp;
	}
}

RightShift(int * arr,int N, int K)
{
	K  %= N;//右移的位置
	Reverse(arr,0 ,N -K -1); //翻转前N-K个数
	Reverse(arr,N - K ,N - 1);//翻转后K个数
	Reverse(arr,0,N -1);//翻转前N个数(所有的数)
}

可以在线性时间内实现右移操作。


2、求数组的子数组之和的最大值

(1)一维数组

一个有N个整数的一维数组 (A[0],A[1],.......,A[n-2],A[n-1],),这个数组当然有很多的子数组,那么子数组之和的最大值是什么?

设计思考:

数组的开始和末尾的下标分别为i和j

1)当0 = i = j时,元素A[0]本身构成和最大的一段  

2)当0 =i < j 时,和最大的一段以A[0]开始  

3)当0 < i时,元素A[0]跟和最大的一段没有关系    

由以上三种情况看出,可以将一个大问题(N个元素数组)转化为一个较小的问题(n - 1个元素的数组):

(A[1],..A[n-1])中的最大的一段之和为All[1]

(A[1],..A[n-1])中包含A[1]的和最大是一段为Start[1]。

根据上述分析的三种情况,(A[0],..A[n-1])中的问题的解All[0]是三种情况的最大值max{A[0],  A[0]+start[1], All[1]}.可用动态规划方式解决。

#define  max(x,y)      ((x>y)?x:y)

int MaxSum(int * A,int n)
{
	Start[n - 1] = A[n-1];//最后的Start数组成员(包含i的最大段)和All数组成员(i之后的最大段)
	All[n - 1] = A[n - 1];
	for(i = n - 2 ; i >= 0;i--)//依次往前计算
	{
		Start[i] = max(A[i], A[i] + Start[i + 1]);// Start[i]是包含A[i]的和最大段
		All[i] = max(Start[i],All[i + 1]);//All[i]是i到n-1中的和最大段
	}
	return All[0];//遍历完数组,All[0] 中存放结果
}

复杂度是O(N)

(2)二维数组

一个有N个整数的二维数组,那么子数组之和的最大值是什么?(需要的是每个一维数组中的子数组的前后下标一致,如起始和终结都为 i和j)
其实这个问题是一维的,可以把每一列中第a行和第c行之间的元素看成一个整体。即求数组(BC[1],..,BC[M])中的最大的一段,其中
BC[i] = B[a][i] + ...+ B[c][i].
这样,我们枚举矩形上下边界,就可以得到二维问题的解。

时间复杂度是O(N平方 *M)
图如下中的灰色部分:

伪代码如下:
 
int MaxSum(int *A,int n, int m)
{
	maximum = -INF;
	for(a = 1; a <= n;a++)
	{
		for (c = a ; c <= n;c++)
		{
			All  = Start = BC(a,c,m);//第m列(a到c行)数组的和
			for (i = m -1 ;i >= 1;i--)//从m-1 列算到1列
			{
				if (Start <0  ) Start = 0;//如果从后往前的计算的和是负数的就是不是需求的(否则越加越小了)
				
				Start += BC(a,c,i);//第i列(a到c行)数组的和
				if (Start > All)All = Start;
			}
			if (All > maximum )maximum  = All;
		}
	}
	return maximum  ;
}

BC(a,c,i),表示在第a行和第c行之间的第i列的所有元素的和

3、求数组中的最长递增子序列

求一个一维数组(N个元素)中的最长递增子序列的长度。
如:在序列 1,-1,2,-3,4,-5,6,-7 其最长的递增子序列为1,2,4,6

对于前面的i个元素的任何一个递增子序列,如果这个子序列的最大的元素比array[i+1]小,那么就可以将array[i + 1] 加在这个子序列后面,构成一个新递增子序列。
比如当i = 4的时候,目标序列为: 1,-1,2,-3,4,-5,6,-7 最长递增序列为:(1,2),(-1,2)。那么,只要4 > 2 ,就可以把4直接增加到前面的子序列中形成一个新的递增子序列。

需要前i个元素中的一个最长的递增子序列,使得这个递增子序列的元素比array[i + 1]小。则将array[i+1]加在该递增子序列后,便可找到以array[i+1]为最大元素的最长递增子序列。

算法设计:
1) MaxV数组记录递增序列的最大元素,下标为递增子序列长度
2)假设在数组的前i个元素中,以array[i]为最大元素的最长递增子序列的长度为LIS[i]
3)nMaxLIS 记录数组最长递增子序列的长度

获取数组的最长递增子序列的长度,代码如下:
int LIS(int *array,int Length)
{
	//在数组的前i个元素中,以array[i]为最大元素的最长递增子序列的长度为MaxLengthElementI[i]
	int* MaxLengthElementI = new int[Length];
	// 长度为MaxLengthElementI[i]的递增子序列的最大元素的最小值为 MaxElements[MaxLengthElementI[i]]
	int* MaxElements = new int [Length + 1];  
	MaxElements[1] = array[0];//数组中的第一个值 
	MaxElements[0] = Min(array) -1;//数组中的最小值 
	for(int i = 0; i < Length;i++)
	{
		MaxLengthElementI[i] = 1;
	}
	int nMaxLIS = 1;//数组最长递增子序列的长度
	int high ,low;
	int j;
	for (int i = 1;i < array.Length;i++)
	{
 		//MaxLengthElementI和MaxElement单调递增,二分查找array[i]的合适位置
		high = i - 1;
		low = 1;
		j = (high + low)/2;
		while (high> low) 
		{
			j = MaxLengthElementI[(high + low)/2];
			if(MaxElements[j+ 1] > array[i] && array[i] > MaxElements[j])//array[i]符合条件,加入子序列
			{
				MaxLengthElementI[i] = j + 1;//以array[i]为最长子序列的末尾,子序列长度为j+1
				break;
			}
			if(array[i] <= MaxElements[j]) 
			{
				low = (high + low)/2+1;
			}
			else
			{
				high = (high + low)/2-1;
			}
		} 
		if(MaxLengthElementI[i] > nMaxLIS) //更新最长递增子序列的长度
		{
			nMaxLIS = MaxLengthElementI[i];
	 		MaxElements[MaxLengthElementI[i]] = array[i];//更新递增子序列最大元素的最小值
		}
		else if (MaxElements[j] < array[i] && array[i] < MaxElements[j + 1])//更新递增序列的最大元素的最小值
		{
			MaxElements[j + 1] = array[i];
		}
	}
	free(MaxElements);
	free(MaxLengthElementI);
	return nMaxLIS;
}

复杂度为O(N * lnN) 

4、查找长数组中的最小的K个数 

假设数组成员个数N为一亿,K远小于N

(1)有序数组法

构造数组是使用折半插入方式由小到大排好顺序(O(NlnK )),数组最大长度是K,插入时需要移动数组成员(O(k*N))。

总复杂度是O(N(lnK + K) ) 

查询是直接获取前k个数。

总复杂度是O(1)。

构造+查询  :N(lnK + K) +1

(2)大根堆法

构造数组是使用大根堆构造(数组结构的),数组最大长度是K。当数组成员还没达到K时,放到数组最后,然后调整大根堆。当数组成员个数达到K时,每次增加一个新的数就直接跟堆头成员比较,较小则覆盖堆头,交换堆头和和最后一个成员并调整大根堆,否则不做任何操作。 

总复杂度是O(NlnK )。

查询是直接获取前k个数。

总复杂度是O(1)。

若要排好顺序就采用快速排序。

总复杂度是O(KlnK)。

构造+查询   :NlnK + 1 (或者Klnk)

总的来说方式2综合效率较好

(3)快速二分法(根据编程之美

寻找N个数中最大的前K数
   假设N个数存储在数组S中,我们从数组S中随机选出一个元素X,把数组分为两部分Sa和Sb.Sa中的元素都大于X,Sb中的元素都小于等于X,这时,有两种可能性:
1.Sa中元素的个数小于K,Sa中所有的数和Sb中最大的K-|Sa|个数(|Sa|指Sa中元素的个数)就是数组S中最大的K个数。
2.Sa中元素的个数大于或等于K,则直接返回Sa中最大的K个元素。
平均时间复杂度O(N*log2 K)
伪代码如下:

Kbig(S,K)     
   if(k<=0):  
      return []  
   if(length S<=K):  
      return S  
   //分成两部分,前一部分小于数组中的某个数,其余的在后一部分(后一批数大于前一批数)
   (Sa,Sb)=Partition(s)  
   //获取前k个最大的数(|Sa|>k则只从Sa中获取,否则就是Sa加Sb中的前k-|Sa|个最大的数)
   if (length Sa == k)return Sa
   else if (length Sa > k)return Kbig(Sa,k)
   return Sa.Append(Kbig(Sb,k-length Sa)  

Partition(S):  
    Sa=[];  
    Sb=[];  
    Swap(s[0],S[length S /2])  //交换第一个和数组中随机一个数
    p=S[1]  
    //把大于第一个数的放到Sa中,其他的放到Sb中
    for i in [1:(length S) -1]:  
        s[i] > p ? Sa.Append(s[i]):Sb.Append(S[i])  
    //把第一个数放到较小的数组中(使两批数尽量均分)
    length Sa<length Sb ? Sa.Append(p):Sb.Append(p)  
    return (Sa,Sb) 


代码实现如下:

#include <stdio.h>  
#include <tchar.h>  
#include <iostream>  
#include <vector>  
#include <stdlib.h>  
#include <time.h>  
using namespace std;  

struct RetVec
{
	vector<int> sa;
	vector<int> sb;
	void clear()
	{
		sa.clear();
		sb.clear();
	}
};

void Kbig(vector<int>& origin,int k,struct RetVec &retvec)  ;  
void Partition(vector<int> &origin,struct RetVec &retvec)  ; 

		
int main()  
{  
    struct RetVec retvec;

    vector<int> datas(10000,0); 
    retvec.clear();
    int k;  
    cout <<"datas array:"<<endl; 
    int i; 
    for (i = 0;i < 10000;i++)  
    {  
        datas[i] = rand() % 10001; 
        printf("%d ",datas[i]);
    }   
    printf("\n请输入你想找的最大的数的个数:\n "); 
    scanf("%d",&k);  
     
    Kbig(datas,k,retvec);  
    
    printf("\n最大的%d个数是:\n ",k); 
    k = origin.size();
    int *datasL = datas.data();
    for(i = 0;i < k;i++)
    	printf("%d ",datas[i]); 
    
    return 0;  
}  
  
void Kbig(vector<int>& origin,int k,struct RetVec &retvec)  
{   
    if(k <= 0 ||origin.size() <= k) return; 
    //把 origin分成两部分到retvec
    Partition(origin,retvec);  
    origin = retvec.sa;
		
    int saSize = origin.size();
    
    if (saSize == k)return;//直接返回(origin就是需要的)
    
    if (saSize > k)//获取sa的前k个大数
    {
    	Kbig(origin,k,retvec);  
    } 
    else  
    {  
    	vector<int> sb = retvec.sb;// 获取sb中的前k - saSize大的数
        Kbig(sb,k - saSize,retvec);  
        for (vector<int>::iterator iter = sb.begin(); iter != sb.end();++ iter)  
        {  
            origin.push_back(*iter);  
        }  
    }  
}  
  
void Partition(vector<int> &origin,struct RetVec &retvec)  
{   
  retvec.clear();
  vector<int> &sa = retvec.sa;
  vector<int> &sb = retvec.sb;
  int p;  
  swap(origin[0],origin[origin.size()/2]);  
  p = origin[0];  
  for (vector<int>::iterator iter = origin.begin() + 1;iter != origin.end();++iter)  
      *iter > p ? sa.push_back(*iter) : sb.push_back(*iter); 
       
  sa.size() < sb.size() ? sa.push_back(p) : sb.push_back(p);  
}    


6、给ushort类型长数组排序 

假设数组成员个数N为一亿,类型为ushort.
因为数组成员都是ushort类型,所以,数的大小在0-65535之间
使用到图的方式,操作上使用数组 ushort arraysCounter[65535]来计数,复杂度为O(n)

typedef unsigned short ushort;

//一亿个数的数组
#define arrMax (100000000)
ushort array[arrMax];

void sortBigArray(ushort* arr,int n,int* arraysCounter)//需要排序的数组在arr,个数为n,计数数组为arraysCounter
{
	for(int i = n-1;i>-1;--i)
	{
		++arraysCounter[arr[i]];//计算数的个数(数为下标)
	}
}

int main(char** args,int argn)
{
	int arraysCounter[65536] = {0};
	sortBigArray(array,sizeof(array)/sizeof(array[0]),arraysCounter);
	//输出排序后的数组所有成员(从大到小)
	for(int i = sizeof(arraysCounter)/sizeof(arraysCounter[0])-1;i >-1;--i)
	{
		for(int j = arraysCounter[i]-1;j>-1;--j)
			printf("%d,"i+1)
	}
	return 0;
}


7、判断两个递增数组中是否存在相同的数字

给定两个递增的数组,怎么高效得判断这两个数组中存在相同的数字.

因为两个数组都是排序好的,所以只要一次遍历就行了,首先设两个下标,分别初始化为两个数组的起始地址,依次向前推进,推进的规则是比较两个数组中的数字,小的那个数组的下标各前推进一步,直到任何一个数组的下标到达数组末尾时,如果这时还没碰到相同的数字,说明数组中没有相同的数字。算法复杂度O(n)

代码如下:  

bool findEqual(int a[], int size1,int b[],int size2){  
    int i = 0, j = 0;  
    while(i<size1 && j<size2){  
        if(a[i]==b[j])  
            return true;  
        
        if(a[i]>b[j])  
            ++j;//j为b数组下标   
        if(a[i]<b[j])  
            ++i;//i为a数组下标   
    }  
      
    return false;  
}  




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值