//算法思想:搜索整个顺序表,查找最小值元素并记住其位置,搜索结束后用最后一个元素填补其位置
bool Del_Min(sqList &L,ElemType &value){
//删除顺序表L中最小元素的结点,并用引用型参数value返回其值
//若删除成功,则返回true,否则返回false
if(L.length==0)
return false;//空表,中止操作返回
value=L.data[0];//从第1个元素开始遍历
int pos=0;//记录位置
for(int i = 1 ; i < L.length; i++)
{
if(L.data[i]>value)
value = L.data[i]; pos = i ;//让value记忆当前具有最小值的元素,pos记录当前位置
//同时value也会将最小值带出函数
}
L.data[pos]=L.data[L.length-1];//用最后一个元素填补最小值位置
L.length--;
return true;
}
算法思想:扫描L的前半部分元素,对于元素L.data[i](0<=i<L.length/2),将其对应的后半部分元素L.data[L.length-1-i]与其交换
如果顺序表长为偶数,正好交换完,如果是奇数,正好中间的元素不参与运算,也应为整数除法向下取整,不会被操作到
void Reserve(SqList &L){
for(int i = 0 ; i < L.length/2; i++)
{
ElemType temp = L.data[i];
L.data[i] = L.data[L.length-1-i];
L.data[L.length-1-i]=temp;
}
}
//本题需要记住一点是,与前半部分L.data[i]对应的后半部分元素是L.data[L.length-1-i]
//这道题刚拿到的时候,我的思路是,遍历一遍,如果当前数是第一个x,就把他和最后一个元素交换,如果是第二个x,就让他和倒数第二个元素交换,也就是双指针
bool Del_X(sqList &L,ElemType x){
if(L.length == 0 ) return false;
int flag = L.length - 1;//记录需要与x交换的位置
int count = 0 ;//记录x的数量
for(int i = 0; i < L.Length; i++){
if(L.data[i]==x){
ElemType temp = L.data[i];
L.data[i] = L.data[flag];
L.data[flag] = temp;
flag--;
count++;
}
}
L.length-=count;//删除后面的元素,后面元素都是x
return true;
}
//但是实际上这种思路是错的,这样的思路虽然在题目要求的时空复杂度之间,但是改变的顺序表的顺序,所以我们看接下来的两种思路
解法一:用k记录顺序表L中不等于x的元素个数(即需要保存到最后的元素的个数),边扫描L边统计k
并将不等于x的元素向前移动k个元素,最后修改L的长度,多说无益,直接上代码可能更好理解
void Del_x_1(sqList &L,ElemType x){
int k = 0 ;
for(int i =0;i<L.length;i++)
{
if(L.data[i]!=x)
L.data[k]=L.data[i];
k++;
}
L.length = k;//最后只保留k个元素
}
就相当于扫描时遇到x就跳过 ,重新给当前这个数组赋一遍值
解法二:用k记录顺序表L中等于x的个数,思路其实和上一个一样,还是扫描一遍数组,重新赋值一遍,遇到x就跳过
void Del_x_2(sqList &L,ElemType x){
int k = 0 ;
for(int i = 0 ; i < L.length;i++)
{
if(L.data[i]==x) k++;
else L.data[i-k]=L.data[i];//重新给值
}
L.length-=k;//顺序表减值
}
本题和上一题的区别,因为是有序表,所以删除的元素必然是相连的整体。
算法思想:先寻找大于等于s的第一个元素(第一个要删除的元素),然后寻找值大于t的第一个元素(最后一个删除的元素的下一个元素),要将这一段删除,只需将后面的元素前移。
bool Del_s_t(sqList &L,ElemType s,ElemType t){
int i,j;
if(s>=t || L.length==0) return false;
for(i = 0 ;i<L.length&&L.data[i]<s;i++)
//循环条件设置为小于s,即当循环结束后,i就是当前需要删除的片段的起始值
if(i>=L.length) return false;//所有元素值均小于s,就返回
for(j=i;j<L.length&&L.data[j]<t;j++);//寻找值大于t的第一个元素
//接下来只要把j后面的值续到i后面就行了
while(j<L.length)
L.data[i++] = L.data[j++];//有点归并排序最后合并的内味
L.length = i;
return true;
}
算法思想:从前向后扫描顺序表L,用K记录下元素值在s和t之间的元素的个数(初始k=0)
对于当前扫描的元素,若其值不在s和t之间则前移K个位置,和第三题很像
bool Del_s_t2(sqList &L,ElemType s,ElemType t){
int i = 0 , k = 0;
if(L.length==0&&s>=t) return false;
for(i=0;i<L.length;i++){
if(L.data[i]>=s&&L.data[i]<=t)
k++;
else
L.data[i-k] = L.data[i];
}
L.length-=k;
return true;
}
第一眼看这个题,我第一反应使,有序表的话,如果一个数等于前一个数,那么就是重复的元素,但是为了提高效率,我们可以放置一个跟随我们移动的小尾巴
void Del_Same(sqList &L){
int flag = L.data[0];
int k=1;
for(int i = 1 ; i< L.length;i++)
{
if(flag != L.data[i])
{
L.data[k++]=L.data[i];
flag = L.data[i];
}
}
L。length = k;
}
这道题答案的思路是这样的:注意是有序的顺序表,值相同的元素一定在连续的位置上
用类似直接插入排序的思想,初始时将第一个元素视为非重复的有序表。之后依次判断
后面的元素是否与前面非重复的有序表的最后一个元素相同,若相同继续向后判断,若
不同则插入到前面的非重复有虚表的最后,直到判断到表尾为止
bool Delete_Same(SqList &L){
if(L.length == 0 ) return false;
int i ,j;
for(i=0,j=1;j<L.length;j++){
if(L.data[i]!=L.data[j])
L.data[++i]=L.data[j];
}
L.length = i+1;
return true;
//其实和我的思路一模一样,答案写的不直观
}
这道题,如果学过归并排序就知道,就是个Merge函数罢了哈哈,可以看我之前的博客详细介绍了归并排序
bool Merge(sqList A, sqList B, sqList &c)//c带值出去
{
if(A.length+B.length>c.Maxsize) return false;
int i=0,j=0,k=0;
while(i<A.length&&j<B.length){
if(A.data[i]<=B.data[j])
c.data[k++]=A.data[i];
else
c.data[k++]=B.data[j];
}
//还剩一个没有比完的顺序表
while(i<A.length) c.data[k++]=A.data[i++];
while(j<B.length) c.data[k++]=B.data[j++];
c.length = k ;//不要忘了给length赋值,这是个整体
return true;
}
这道题我看到后第一反应就是前m个数和后n个数互换,但是就只能想到这
参考了一下答案,才发现此题女少口阿
算法思想:先将数组A[m+n]中的全部元素(a1,a2,...,am,b1,b2,...,bn)原地逆置为(bn,bn-1,...,b1,am,am-1,...,a1),再对前n个元素和前m个元素分别使用逆置算法,即可得到(b1,b2,...,bn,a1,a2,...,am),从而实现顺序表的位置互换
//摘取第二题的resrve算法,思路和第二题一模一样
void Reverse(int A[],int left, int right, int arrarSize){
if(left>=right||right>=arraySize)
return ;
int mid = (left + right)/2;
for(int i = 0 ; i < = mid - left; i++){
int temp = A[left+i];
A[left+i]=A[right - i ];
a[right - i] = temp;
}
}
void Exchange(int A[], int m, int n, int arraySize){
Reverse(A,0,m+n-1,arraySize);
Reverse(A,0,n-1,arraySize);
Reverse(A,n,m+n-1,arrrySize);
}
有序递增的话,最快的查找方法,对于有序来说,肯定是折半查找
int Search_Exchange_INsert(ElemType A[],ElemType x){
int low = 0, high = n-1,mid;//low和high指向顺序表下届和上界的下标
while(low < high){
mid = (low + high)/2;//找中间位置
if(A[mid] == x) return A[mid];
else if (A[mid]<x) low= mid+1;//到中点mid的右半部分去查
else high = mid -1;//到点mid的左半部分去查
}
//两种情况,找到了和没找到
if(A[mid]==x&&mid!=n-1)//找到了并且其不是最后一个元素,其有后继元素
{
int t = A[mid];
A[mid] = A[mid+1];
A[mid+1]=t;
}
//没找到则插入到表中
if(low> high){
for(int i= n-1; i>high;i--)
A[i+1] = A[i];//往high这个位置插入
A[i+1]=x;
}
}
(1).给出算法思想
(2).根据设计思想,采用C/C++/Java语言描述算法、关键之处给出注释
(3).说明你所设计算法的时空复杂度
算法的设计思想:可将这个问题视为把数组ab转换为数组ba,(a代表前p个元素,b代表后n-p个元素),这个算法我们第8题刚刚讲过,就是先整体逆置,再分别逆置,其实也可以先分别逆置,再整体逆置。
void Reverse(int R[],int from,int to){
int i, temp;
for(int i =0;i<(to - from +1)/2;i++){
temp = R[from+i];
R[from+i] = R[to-i];
R[to-i] = temp;
}
}
void Converse(int R[],int n,int p){
Reverse(R,0,p-1);
Reverse(R,p,n-1);
Reverse(R,0,n-1);
}
上述算法中三个Reverse函数的时间复杂度分别是O(p/2)、O((n-p)/2)、O(n/2),故所设计的算法的时间复杂度为O(n),空间复杂度O(1)
另外,此题也可以通过辅助数组来实现,算法思想:
创建大小为p的辅助数组s,将R中前p个整数依次先暂存到S中,完了再把R中的数整体前移,再把S中的数重新放回R就行
(1).这道题,思路就是我们之前做过的第7题,难点在于把两个序列合并为一个序列,合并成功后很容易找到中位数
但是这种方法有个弊端,就是空间复杂度将会变为O(A.length+B.length)
标准答案为:分别求A和B的中位数、设为a和b、求序列A、B的中位数过程如下
1.若a=b,即a or 就是中位数、算法结束
2.若a<b,则舍弃A中较小的一半和B中较大的一半,要求两次舍弃的长度想等
3.若a>b,则舍弃B中较小的一半和A中较大的一半,要求两次舍弃的长度想等
在保留的两个升序序列中,重复过程1,2,3,**直到两个序列中均只有一个元素时为止**,较小者即为所求的中位数
(2).
int M_Search(int A[],int B[],int n){
int s1=0,d1=n-1,m1, s2=0,d2=n-1,m2;
//分别表示序列A和B的首位数、末位数和中位数
while(s1!=d1||s2!=d2){//直到两个序列中均只剩一个元素
m1=(s1+d1)/2;
m2=(s2+d2)/2;
if(A[m1] == B[m2])
return A[m1]; //满足条件1
if(A[m1]<B[m2]) //满足条件2
{
if((s1+d1)%2==0){//若数组长为奇数
s1=m1; //舍弃A中点以前的部分并保留中间点
d2=m2; //舍弃B中点以后的部分并保留中间点
}
else//若数组长度为偶数
{
s1= m1+1;
d2 =m2;
}
}
else{
if((s2+dd)%2==0){//若数组长为奇数
d1=m1; //舍弃A中点以后的部分并保留中间点
s2=m2; //舍弃B中点以前的部分并保留中间点
}
else//若数组长度为偶数
{
d1= m1;
s2 =m2+1;
}
}
}
return A[s1]<B[s2] ? A[s1]:B[s2];
}
(3)算法的时间复杂度为O(nlogn),空间复杂度为O(1)
这尼玛什么主元素,说白了就是出现次数大于n/2,也就是一半以上就是主元素
也就是相当于找到数组中出现次数最多的元素,然后还得顺便计数,次数大于n/2就算主元素
int Majority (int A[],int n){
int i,c,count=1;//c来保存元素,count来计数
c=A[0];//刚开始A【0】是候选元素
for(i = 1;i<n;i++)//查找候选主元素
if(A[i]==c)
count++;//对A中的主元素计数
else
if(count>0)
count--;//处理不是主元素的情况
else{
c=A[i];
count=1;//更换主元素,重新计数
}
//整个上面一大片,其实都是在找出现次数最多的元素,下面是计数
if(count>0)
for(i = count = 0;i<n;i++)
if(A[i]==c)
count++;
if(count>n/2) return c;
else return -1;
}
时间复杂度o(n),空间o(1)
这道题其实最简单的是去排序再统计,是最好匹配的,虽然时间复杂度得【ologn】,但是只要正确,也能拿11分,不要花费太多时间在难题上
13.(2018统考真题)给定一个含n个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小整肃,例如,{-5,3,2,3}中未出现的最小正整数是1,数组{1,2,3}中未出现的最小正整数为4.要求:
(1).给出算法的设计思想
(2).根据设计思想,采用C/C++或Java语言描述算法,关键之处给出注释
(3).说明你所设计的算法的时空复杂度
(1)。要求时间上尽可能高效,因此采用空间换时间的算法。
分配一个用于标记的数组B【】,用来记录A中是否出现了1~n中的正整数,B【0】对应的
正整数1,B[n-1]对应的是正整数n,初始化B全为【0】.由于A中含有n个整数,因此
可能返回的值是1~n,当A中n个数恰好为1~n时返回n+1,当数组A中出现小于等于0或者大于n的
值时,会导致B中1~n出现空余位置,返回结果必然在1~n中
经过上述分析可以得到算法流程:从A【0】开始遍历A,若A【i】在1~N之间,则令
B【A【i】-1】=1;否则不做操作,对A遍历结束后,开始遍历数组B,若能查到第一个满足
B【i】==0的下标i,返回i+1即可,此时说明A中未出现的最小正整数在1~N中。若B【i】
全不为0,返回i+1(跳出循环时i=n,i+1等于n+1),此时说明A中未出现的最小正整数是n+1
int findMissMin(int A[],int n){
int i,*B;//标记数组
B= (int *)malloc(sizeof(int)*n);//分配空间
memset(B,0,sizeof(int)*n);
for(i=0;i<n;i++)
if(A[i]>0&&a[i]<=n)
B[A[i]-1]=1;//若A【i】介于1~n,则标记数组B
for(i = 0 ;i<n;i++)
if(B[i]==0) break;//扫描数组B,找到目标值
return i+1;
时间复杂度,遍历A,B各一次,也就是O(n),空间复杂度O(n);
}