数组相关算法的学习。
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)二维数组
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 ;
}3、求数组中的最长递增子序列
如:在序列 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个数
(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类型长数组排序
使用到图的方式,操作上使用数组 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;
}
这篇博客总结了数组相关的经典算法,包括数组循环右移、一维和二维数组的子数组之和最大值问题、最长递增子序列的求解、查找长数组中的最小K个数的多种方法,以及ushort类型数组排序和判断两个递增数组中是否存在相同数字的算法。通过实例解析和伪代码展示了各种算法的实现过程和思路。

2757

被折叠的 条评论
为什么被折叠?



