2-1
1、从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删元素值。空出位置由最后一个元素填补,若顺序表为空,显示出错信息并退出返回。--简单题
bool Delete_min_Value(SqList &L,Elem &e)//e用于保存被删除的值
{
if(L.length==0) return false;
int value=L.data[0];//假设第一个数为最小值
int pos=0;//用pos记录最小值的位置
for(int i=0;i<L.length;i++)
{
if(value>L.data[i])
{
value=L.data[i];
pos=i;
}
}
e=value;
L.data[pos]=L.data[L.length-1];
L.length--;
return true;
}
2、设计一个高效算法,将顺序表L的所有元素逆置,要求算法的时间复杂度为O(1)
//解法一 此题我第一想法是再开一个顺序表 将原始表从0到L.length-1;新表从L.length-1到0
//存储一遍 最后再复制回原表 以空间换时间
//王道解答:从中间断开
void reverse(SqList &L)
{
Elem temp;//用于交换的临时变量
for(int i=0;i<L.length/2;i++)
{
temp=L.data[i];
L.data[i]=L.data[L.length-i-1];
L.data[L.length-i-1]=temp;
}
}
3、对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据
//此题我试着找等于x的个数 但边找却不能想到更新的方法(太菜了)
//王道解法:
void dele_1(SqList &L,Elem x)
{
int k=0;//k代表x的个数
for(int i=0;i<L.length;i++)
{
if(L.data[i]==x) k++;
else L.data[i-k]=L.data[i];//这步非常巧妙 在扫描x的过程就更新了
}
L.length-=k;//更新表长
}
//反向思考 找不等于x的元素
void dele_2(SqList &L,Elem x)
{
int k=0;//k代表不等于x的元素个数
for(int i=0;i<L.length;i++)
{
if(L.data[i]!=x)
{
L.data[k]=L.data[i];
k++;//只有不等于x时 才更新k值
}
}
L.length=k;
}
4、从有序表中删除其值在给定值s与t之间(要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示处错误信息并退出运行。
//受上一题启发 找不在该区间的元素
bool dele_s_t(SqList &L,Elem s,Elem t)
{
if(s>t||L.length==0) return false;
int k=0;//不在s-t之间的元素个数
for(int i=0;i<L.length;i++)
{
if(L.data[i]<s||L.data[i]>t)
{
L.data[k]=L.data[i];
k++;
}
}
L.length=k;//更新表长度
return true;
}
//解法二
//由于是有序顺序表 可以先找到大于等于s的第一个数 再找到大于t的第一个数
//确定这两个数的位置 再移动元素即可
bool Dele_2(SqList &L,ElemType s,ElemType t)
{
int i,j;//i代表大于等于s的第一个元素的位置 j代表大于t的第一个元素的位置
//判断删除位置是否合理
if(s>t||L.length==0) return false;//s>t 或顺序表为空时 程序终止
//找大于等于s的第一个位置
for(i=0;i<L.length&&L.data[i]<s;i++);//注意是'<'
//找大于t的第一个数
for(j=i;j<L.length&&L.data[i]<=t;j++);//注意是'<='
for(;j<L.length;i++;j++) L.data[i]=L.data[j];//前移
L.length=i;//因为出循环后i加1 正好代表顺序表长度
return true;
}
5、从顺序表中删除其值在给定值s与t之间(要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示处错误信息并退出运行。
//思路:因为顺序表是无序的 找不在给定区间中的元素即可
//有两种写法 可以参考第四题中解法一的写法 下面给出另一种写法
bool Dele_3(SqList &L,ElemType s,ElemType t)
{
int k=0;//记录在所给区间中元素的个数
if(s>t||L.length==0) return false;//s与t不合理 或顺序表为空
for(int i=0;i<L.length;i++)
{
if(L.data[i]>=s&&L.data[i]<=t) k++;
else
{
L.data[i-k]=L.data[i];//根据更新过程中k的个数 移动元素
}
}
L.length-=k;//修改顺序表长度
return true;
}
6、从有序表中删除所有其值重复的元素,使表中所有元素的值均不同
//删除顺序表中所有重复元素---双指针去重
bool Dele_Same(SqList &L)
{
int i,j;//双指针
if(L.length==0) return false;
for(i=0,j=1;j<L.length;j++)
{
if(L.data[i]!=L.data[j])//若相等 则代表重复 让j++
{
L.data[++i]=L.data[j];//当前元素与下一个元素不相等 才移动元素
}
}
L.length=i+1;
return true;
}
7、将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表
//合并有序顺序表 --典型双指针应用
bool Merge_List(SqList L1,SqList L2,SqList &L)//L1、L2分别表示有序表1、2 L表示新顺序表
{
if(L1.length+L2.length>L.length) return false;//这一步判断感觉挺牵强的。。。
int i,j,k=0;//维护三个指针
for(i=0,j=0;i<L1.length&&j<L2.length;i++,j++)
{
if(L1.data[i]<=L1.data[j])
{
L.data[k++]=L1.data[i];
}else
{
L1.data[k++]=L2.data[j];
}
}
//剩下一个表没处理完 直接将剩下的元素移入新表
while(i<L1.length) L.data[k++]=L1.data[i++];
while(i<L2.length) L.data[k++]=L2.data[j++];
L.length=k;
return true;
}
8、已知一位数组A[M+N]中依次存放两个线性表(a1 a2 .. am)和(b1 b2..bn)。编写一个算法,将数组中两个顺序表的位置互换,即将(b1 b2..bn)放在(a1 a2..am)的前面
//此题有个最朴实的方法 就是再开一个数组将其按照b1 b1... bn a1 a2... am的顺序存储一遍
//再复制回原数组
//王道解法:感觉这道题逆置的解法比较常用 而且比上面一道题的逆置更有普适性
//先将a1 a2 .. am b1 b2 .. bn整个表逆序为bn bn-1 ..b1 am am-1 a1
//再在[0,n-1]区间内逆置为b1 b2... bn 在[n,m+n-1]区间内逆置为a1 a2 ..am
void Reverse(ElemType A[],int left,int right,int ArraySize)
{//left表示待逆置区间的左端点 right表示待逆置区间的右端点
if(left>=right||right>=ArraySize) return ;//输入数据不合理
int mid=(left+right)/2;
for(int i=0;i<=mid-left;i++)//因为左侧端点不一定为0 所以是left+i
{
ElemType temp=A[left+i];
A[left+i]=A[right-i];
A[right-i]=temp;
}
}
void Exchange(ElemType A[],int 0,int m+n-1,int ArraySize)
{//先将整个区间逆置 再分别将小区间逆置
Reverse(A,0,m+n-1,ArraySize);
Reverse(A,0,n-1,ArraySize);
Reverse(A,n,m+n-1,ArraySize);
}
9、线性表(a1 a2...an)中的元素递增且按顺序存储在计算机内,设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到,将其与后继元素值交换,若找不到,将其插入表中,并使表中元素仍递增有序
//此题有多种解法 解法一是看了y总的二分之后想到的 但是容易漏情况 解法二是王道上的
//解法一:
bool Find_Or_Insert(SqList &L,int x,int l,int r)//x 表示要查找/插入 元素;l、r分别代表左右端点
{
while(l<r)
{
int mid=l+r>>1;
if(L.data[mid]>=x) r=mid;
else l=mid+1;
}
if(L.data[l]==x)
{
printf("%d的位置为%d:",x,l+1);
return true;
}
else
{
if(l>=L.length-1&&x>L.data[L.length-1])//当x是不存在于L中、但插入后是最大的元素时 直接将L.data[L.length]=x
{
L.data[L.length]=x;
}
else//否则 移动元素
{
for(int i=L.length-1;i>=l;i--)
{
L.data[i+1]=L.data[i];
}
L.data[l]=x;
}
L.length++;
return true;
}
}
解法二
//使用折半查找
void Search_Or_Insert(ElemType A[],ElemType x)
{
int low=0,high=n-1,mid;
mid=(low+high)/2;
while(low<=high)
{
mid=(low+high)/2;//确定中轴点
if(A[mid]==x) break;//找到 直接跳出
else if(A[mid]<x) low=mid+1;
else high=mid-1;
}
if(A[mid]==x&&mid!=n-1)//当找到x值 且mid不是最后一个元素 则与其后继元素交换
{
temp=A[mid];
A[mid]=A[mid+1];
A[mid+1]=temp;
}
if(low>high)//这里有些易错点 比如for循环里面为什么是i>high 这里最好代几个值推一下
{
for(int i=n-1;i>high;i--)
{
A[i+1]=A[i];//后移元素
}
A[i+1]=x;//出循环后 i=high,但A[i+1]此时是空的 所以插入
}
}
10、【2010】设将n(n>1)个整数存放到一位数组R中。设计一个在时间和空间两方面都尽可能高效的算法。将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(X0 X1 ..Xn-1)变为(Xp Xp+1...Xn-1 X0 ..Xp-1)。
//与上面一道题几乎相同
//先将前p个元素逆置 再将后面n-p个元素逆置
//最后再将整个区间逆置即可
void Reverse(ElemType A[],int left,int right)//left、right代表区间左右端点 更具代表性
{
int mid=left+right>>1;
int temp;//做交换的临时变量
for(int i=0;i<=mid-left;i++)
{
temp=A[left+i];
A[left+i]=A[right-i];
A[right-i]=temp;
}
}
//王道解法 Reverse更简便 但原理相同
void Reverse(ElemType A[],int low,int high)
{
int i,temp;
for(i=0;i<(high-low+1)/2;i++)//取上整
{
temp=A[low+i];
A[low+i]=A[high-i];
A[high-i]=temp;
}
}
void Converse(ElemType A[])
{
Reverse(A,0,p-1);//将前p个元素逆置
Reverse(A,P,n-1);//将后n-p个元素逆置
Reverse(A,0,n-1);//最后将n个元素逆置
}
//时间复杂度为O(n) 空间复杂度为O(1)
// 解法二
//借助辅助数组
//将前p个元素存在另一个数组中 同时将后n-p个元素左移p
//最后将辅助数组中的元素复制到原数组后p个位置中去
//此解法的时间复杂度为O(n) 空间复杂度为O(p)
11、
//找出两个合并的升序序列的中位数
//直接想法:
//1、维护两个指针,分别指向两个升序序列,按照归并的写法将
//较小的数加入一个新数组中,最终新数组为所有元素的升序序列
//然后再取中位数即可
int merge_and_found(int n,int A[],int B[],int C[])//A B等长
{
int i,j,k;//i j分别是遍历A[]和B[]的指针
for(i=0,j=0;i<n&&j<n;k++)
{
if(A[i]<B[i]) C[k]=A[i++];
else C[k]=B[j++];
}
while(i<n) C[k++]=A[i++];
while(j<n) C[k++]=B[j++];
return C[k+1>>1];
}
//时间复杂度O(n) 空间复杂度O(n)
//二:归并进行判断 但是不排序 指针扫到一半则停止
int found2(int n,int A[],int B[])
{
int i=0,j=0;
int cnt=0;//用于计数 当cnt扫描至两个序列的一半时 输出该元素
while(cnt<2*n)
{
if(A[i]<B[j])
{
i++,cnt++;
if(cnt==(2*n)>>1) return A[i-1];//将坐标恢复
}
else
{
j++,cnt++;
if(cnt==(2*n)>>1) return B[j-1];//将坐标恢复
}
}
}
//时间复杂度为O(n) 空间复杂度为O(1)
//官方解法
//先分别求A B两个序列的中位数 记为a b 然后比较
//若a==b 则找到所求 结束
//若a<b 则舍弃序列A中较小的一半,同时舍弃序列B中较大的一半(要求舍去元素个数相等 这样原始中位数的位置不变)
//若a>b 则舍弃A中J较大的一半,同时舍弃B中较小的一半(舍去元素个数相等)
//在新的序列中 重复上述过程 直至两个序列均只含一个元素位置 选择较小的元素输出
int M_search(int A[],int B[],int n)
{
int st1=0,ed1=n-1,st2=0,ed2=n-1;//st表示起始 ed表示结束
int mid1,mid2;
while(st1!=ed1||st2!=ed2)//只有两个序列都是一个元素时才出循环
{
mid1=st1+ed1>>1;
mid2=st2+ed2>>1;
if(A[mid1]==B[mid2])
{
return A[mid1];
}
if(A[mid1]<B[mid2])
{
if((st1+ed1)%2==0)//A中元素个数为奇数
{
st1=mid1;//舍弃A中间点以前的部分且保留中间点
ed2=mid2;//舍弃B中间点以后的部分且保留中间点
}else//A中元素为偶数
{
st1=mid1+1;//舍弃A中间点以前的元素包括中间点
ed2=mid2;//舍弃B中间点以后部分但保留中间点
}
}else//A[mid1]>=B[mid2]
{
if((st2+ed2)%2==0)//B中元素个数为奇数
{
ed1=mid1;//舍弃A中间点以后的部分且保留中间点
st2=mid2;//舍弃B中间点以前的部分且保留中间点
}else//A中元素个数为偶数
{
ed1=mid1;//舍弃A中间点以后的部分 保留中间点
st2=mid2+1;//舍弃B中间点以前的部分包括中间点
}
}
}
//出while时 st1==ed1 st2==ed2
return A[st1]<B[st2]?A[st1]:B[st2];
}
//时间复杂度A(logn) 空间复杂度O(1)
12、
//第一种朴素想法 空间换时间 开一个数组用于计数 记录扫描过程中某个元素出现的次数
//当某个元素出现的次数大于整个数组元素个数的一半时 则说明找到主元素
const int N=100010;
int s[N];//用于计数
//找主元素
int Find(int A[],int n)//n代表A中元素个数
{
for(int i=0;i<n;i++)
{
s[A[i]]++;
if(s[A[i]]>(n>>1))//当某一个元素的次数大于整个数组元素个数的一半时 则为主元素
{
return A[i];
}
}
return -1;//未找到主元素 输出-1
}
//时间复杂度为O(n) 空间复杂度也是O(n)
//王道解法
int Majority(int A[],int n)
{
int i,c,count=1;//i用作扫描数组的指针 c用于保存候选主元素 count用于计数
c=A[0];//将第一个元素视为候选主元素
for(i=1;i<n;i++)
{
if(A[i]==c)
count++;//若扫描的元素等于候选主元素 则count+1
else//扫描的元素不等于候选主元素主元素
{
if(count>0)//当count技术支持大于0时 将其-1
count--;
else//count计数值为0 应该更换候选主元素
{
c=A[i];
count=1;
}
}
}//结束while后 c一定是可能成为主元素的元素
//再扫描一遍数组 统计c出现的次数
for(i=0,count=0;i<n;i++)
{
if(A[i]==c)
count++;
}
if(count>n/2)//找到主元素 返回
return c;
return -1;//未找到主元素 返回-1
}
13、
#include <iostream>
#include <string.h>
using namespace std;
const int N=10010;
//找出未出现的最小整数
//要求: 时间上尽可能高效----空间换时间
//思路: 扫描一遍原数组 记录下出现的正整数 将其放在一个新数组中(新数组赋初值为0)
//再扫描一遍新数组 找第一个其值为0的下标(表示该元素在数组中未出现) 返回
int A[N];
int findMissMin(int A[],int n)
{
int i=0;
int *B;//开辟新数组 用于记录原数组中的值是否出现
B=new int[n];//下标从0开始 建立0~n-1 到1~n的映射
//注意memset赋值 要加上头文件 而且memset是给字节赋值 当传递参数是数组时 要传数组的长度
memset(B,0,sizeof(int)*n);//给B赋初值为0 表示起初没有一个元素出现
for(i=0;i<n;i++)
if(A[i]>0&&A[i]<=n)//大于n的值不必记录 因为该值不可能是要求的值
B[A[i]-1]=1;//A中的元素n实际对应B中为n-1的记录
for(i=0;i<n;i++)
if(B[i]==0) break;
return i+1;
}
int main()
{
int n;
cin>>n;
cout<<"输入元素:";
for(int i=0;i<n;i++)
cin>>A[i];
// int len=sizeof(A);
// cout<<len<<endl;
cout<<"A中元素为:";
for(int i=0;i<n;i++) cout<<A[i]<<" ";
cout<<endl;
int res=findMissMin(A,n);
cout<<"未出现的最小正整数为:"<<res<<endl;
}
14、
#include <iostream>
using namespace std;
#define INT_Dis 0x7FFFFFFF //int的最大值
const int N=10010;
int A[N],B[N],C[N];
//思路:
//只有改变三个元素中的最小值才可能使得总距离最小
//首先 假设一个最小值(设为无穷大) 当计算所得的结果大于该最小值则更新最小值
//更新的是时候每次都更新三个元素中的最小值 让其坐在集合的坐标+1
//计算绝对值
int abs_(int a)
{
return (a>0)?a:(-a);
}
//找三个元素的最小值
bool min(int a,int b,int c)
{
if(a<=b&&a<=c)
return true;
else
return false;
}
//找最小距离
int findMin_Dis(int A[],int m,int B[],int n,int C[],int p)
{//m n p分别代表三个升序数组的元素个数
int D_min=INT_Dis,D;
int i=0,j=0,k=0;//分别表示三个数组的下标
while(i<m&&j<n&&k<p&&D_min>0)
{
D=abs_(A[i]-B[j])+abs_(B[j]-C[k])+abs_(C[k]-A[i]);
if(D<D_min) D_min=D;//更新最小距离
//更新最小元素值的下标
if(min(A[i],B[j],C[k]))
i++;
else if(min(B[j],A[i],C[k]))
j++;
else
k++;
}
return D_min;
}
int main()
{
int m,n,p;//代表三个升序数组的元素个数
cout<<"输入三个升序数组维度:"<<endl;
cin>>m>>n>>p;
//输入数组A
cout<<"输入数组A:";
for(int i=0;i<m;i++) cin>>A[i];
//输入数组B
cout<<"输入数组B:";
for(int i=0;i<n;i++) cin>>B[i];
//输入数组C
cout<<"输入数组C:";
for(int i=0;i<p;i++) cin>>C[i];
int res=findMin_Dis(A,m,B,n,C,p);
cout<<"最小距离为:"<<res<<endl;
return 0;
}
2-2
1、设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点
//删除不带头结点的单链表中所有值为x的值---递归算法
bool Dele_x(LinkList &L,ElemType x)
{
LNode *p;//临时结点
//注意不带头结点 判空为 L==NULL
if(L==NULL) return false;//链表为空 返回false
if(L->data==x)//当数据域 是要删除的值时
{
p=L;
L=L->next;//修改头指针 指向下一个结点
free(p);
Dele_x(L,x);
}else
{
Dele_x(L->next,x);
}
return true;
}
2、在带头结点的单链表中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,编写算法
//这个题用第一题的递归写法应该也可以做 这里提供王道的 另一种写法
bool Dele_x1(LinkList &L,ElemType x)
{
//带头结点
if(L->next==NUll) return false;//表为空 返回false
LNode *p=L->next,*prev=L,*q;//维护三个节点 p初始为首结点 prev初始为头指针 q为临时结点
while(p!=NULL)
{
if(p->data==x)//当首节点的值是目标值时
{
q=p;
p=p->next;//断链
prev->next=p;
free(q);
}else//否则 p后移 同时prev也保持p的前驱结点关系
{
prev=p;
p=p->next;
}
}
return ture;
}
//用尾插法建立 当元素不为x时 则插入尾部
void Dele_2(LinkList &L,ElemType x)
{
LNode *p=L->next,*r=L,*q;//r表示尾指针 q表示临时结点
while(p!=NULL)
{
if(p->data!=x)//当值不为x时 链接在链尾
{
r->next=p;
r=p;
p=p->next;
}else
{
q=p;
p=p->next;
free(q);
}
}
r->next=NULL;//将尾指针置空
}
3、设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的 值。
参考思路:王道 用栈的思想 即递归的方法来实现
//反向输出
void R_print(LinkList L)
{
if(L->next!=NULL)
{
R_print(L->next);//递归
}
if(L!=NULL) printf("%d ",L->data);//先输出尾结点元素 再递归输出其他元素
}
//调用时 R_print(L->next); 从第一个元素节点开始
4、试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。
//带头结点的单链表中删除一个最小值结点的高效算法
//思路:维护两个指针 一个指向当前结点 另一个指向当前节点的前驱结点
//再维护两个指针 一个用于指向最小权值结点的指针 一个用于指向最小权值结点指针的前驱结点
//逐个比较每一结点的权值 若扫描到的结点的权值比当前及结点的权值小
//则修改最小指针以及最小指针结点的前驱结点
void Delete_Min(LinkList &L)
{
LinkNode *pre=L,*p=pre->next;
LinkNode *minp=p,*minpre=pre;//初始化最小权值结点为首元素结点
while(p!=NULL)
{
if(p->data<minp->data)
{
minp=p;//修改最小结点指针的指向
minpre=pre;//同时修改最小权值结点的前驱结点
}
pre=p;
p=p->next;
}
//结束循环后一定找到了最小元素权值结点
minpre->next=minp->next;//修改指针
free(minp);
}
5、试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1)
//就地逆转单链表
//1、头插法的思想
LinkList Reverse_1(LinkList L)
{
LNode *p,*r;//p指向当前结点 r指向p的后继结点
p=L->next;//处理首元素结点
L->next=NULL;
while(p!=NULL)
{
r=p->next;//保存p的后继结点
p->next=L->next;//插入到头结点之后
L->next=p;
p=r;//修改p指针 进行下一个结点的逆置
}
return L;
}
//2、指针反转
LinkList Reverse_2(LinkList L)
{
LNode *pre,*p,*r;
p=L->next,r=p->next;//p代表第一个元素结点 r是p的后继结点
p->next=NULL;//将第一个元素结点的指针域置空 此时p与r之间的链断开
while(r!=NULL)
{
pre=p;
p=r;
r=r->next;//找到下一个后继结点
p->next=pre;//指针反转
}
//出循环时 r为NULL p是最后一个元素结点
L->next=p;//将L的next域指向最后一个元素结点 则最后一个元素结点作为首元素结点
return L;
}
6、有一个带头结点的单链表L,设计一个算法使其元素递增有序
//直接想法 空间换时间
//1、开一个数组 将链表中元素复制到数组中
//2、将数组中元素排序(升序)
//3、 排序完成后 再将数组中元素插入链表中
//王道解法
//扫描单链表 寻找元素结点合适的插入位置
void Sort(LinkList L)
{
LNode *pre,*p,*r;//p指向待处理元素的第一个结点 r指向p的后继结点
p=L->next;
r=p->next;//存储下一个结点
p->next=NULL;//断链 此时只含一个数据结点
p=r;//指向r
while(p!=NULL)
{
r=p->next;
pre=L;
while(pre->next!=NULL&&pre->next->data<p->data)
pre=pre->next;//用于寻找合适的插入位置
//出循环有两种情况
//1、pre==NULL 说明已排好序的元素中 p结点的权值是最大的 则将其插入尾部
//2、pre->next->data>=p->data 说明此时pre的结点的权值是小于p结点的 但下一个结点的权值大于p结点
//则p结点应插在pre之后
p->next=pre->next;//将该元素结点插入pre后(该元素结点的正确位置)
pre->next=p;
p=r;//修改p指针 持续进行
}
}
7、设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素的元素(若存在)
//删除位于给定两个元素之间的值的结点
void Delete(LinkList &L,int l,int r)
{
LNode *pre,*p;//p用于扫描整个单链表 pre指向其前驱
pre=L,p=L->next;//初始化pre 和 p
while(p!=NULL)
{
if(p->data>l&&p->data<r)//删除
{
pre->next=p->next;
free(p);
p=pre->next;//修改p指针
}else//修改pre 和 p指针 继续向后扫描
{
pre=p;
p=p->next;
}
}
}
8、给定两个单链表,编写算法找出两个链表的公共结点
//直接想法
//两个while循环
//以其中第一个单链表的首元素结点为始 遍历另一个单链表中 找到相同权值的元素即输出
//然后再移动第一个单链表的结点指向下一个结点 再遍历整个链表
//时间复杂度 为 O(len1*len2)
//王道解法
//找两个链表的公共结点
//注:公共结点是指指针相同 而不是数据域相同
//即从公共结点后两个链表完全重合
//思路:因为是公共链表 所以尾部一定是对齐的
//先判断长度 让更长的链表移到与更短的链表对齐
LinkList Find_Commom(LinkList L1,LinkList L2)
{
int len1,len2,dist;//存储两个链表的长度 dist表示两个链表的长度差值
LinkList LongerList,ShorterList;//分别表示两个不同长度的链表
len1=Length(L1),len2=(Length L2);//计算两个链表的长度
if(len1>len2)
{
LongerList=L1->next;
ShorterList=L2->next;
dist=len1-len2;
}else
{
LongerList=L2->next;
ShorterList=L1->next;
dist=len2-len1;
}
while(dist--)
LongerList=LongerList->next;//让长链表移动到与短链表头同步的位置
while(LongerList!=NULL)
{
if(LongerList==ShorterList)
return LongerList;//找到公共结点
else//第一个结点不是公共结点 同步往后移动
{
LongerList=LongerList->next;
ShorterList=ShorterList->next;
}
}
return NULL;//没有公共结点返回NULL
}
//时间复杂度 O(len1+len2)
9、
//思路:每次找单链表结点的最小权值的结点
//然后修改指针 并将其free 再找余下的结点中具有最小权值的结点
void Print_IncreaseOrder(LinkList &head)
{
LNode *p,*pre,*q;//p指向最小权值的结点 pre指向其前驱结点 q暂存最小权值的结点
p=head->next,pre=head;
while(head->next!=NULL)
{
while(p->next!=NULL)
{
if(p->next->data<pre->next->data)//说明p的后继结点比p的权值小 即p不是最小权值的结点
pre=p;//修改pre指针 使pre指向当前最小权值结点的前驱结点
p=p->next;//若p是当前具有最小权值的结点 则向后扫描
}
//出while后pre一定是最小权值结点的前驱结点
print(pre->next->data);
q=pre->next;
pre->next=q->next;
free(q);
}
free(head);//输出完毕 释放头指针
}
10、讲一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变
//思路:第一个结点保存在A中 下一个结点保存在B中同时删除该结点
LinkList ReadjustLinkList(LinkList &A)
{
int i=0;//记录奇偶数
LinkList B=(LinkList)malloc(sizeof(LNode));
B->next=NULL;//初始化B
LNode *s=(LNode *)malloc(sizeof(LNode)),*rb=B;//s用来存A中的偶数结点 r代表尾指针用尾插法插入 B
LNode *p=A->next,*ra=A;//p为工作指针 从首元素结点开始
A->next=NULL;//让A断开 进行尾插
while(p!=NULL)
{
i++;
if(i&1==1)//奇数 则不用对A的结点做任何处置
{
ra->next=p;
ra=p;//尾插法插入A中
}
else//偶数结点
{
s->data=p->data;//将偶数结点尾插入B
r->next=s;
r=s;
}
p=p->next;//工作指针向后扫描
}
ra->next=NULL;
rb->next=NULL;
return B;
}
11、
//与上题思路相同 B表采用头插法即可
LinkList ReadjusstLinkList(LinkList &A)
{
LinkList B=(LinkList)malloc(sizeof(LNode));
B->next=NULL;
LNode *p=A->next,*ra=A;
A->next=NULL;
while(p!=NULL)
{
ra->next=p;//将第一个元素尾插入A
ra=p;//修改尾指针
p=p->next;
if(p!=NULL)
{//头插法插入B
LNode *q=p->next;//保存p的后一个结点 下面操作可能会断链
p->next=B->next;
B->next=p;
p=q;//将新结点赋给p
}
}
ra->next=NULL;//将A的尾指针置为NULL
return B;
}
12、在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复的元素,例如{7,10,10,21,30,42,42,42,51,70}将变为{7,10,21,30,42,51,70}
//删除递增有序单链表中的重复元素
void Delete_Same(LinkList &L)
{
LNode *prior,*p=L->next;//p代表工作指针 prior为其前驱指针
if(p==NULL)
return ;
while(p!=NULL)
{
prior=p;
p=p->next;
if(p->data==prior->data)//如果元素值相同
{//修改指针
prior->next=p->next;
}
}
}
13、假设有两个按元素值递增次序排列的线性表,均以单链表存储。编写算法将这两个单链表归为一个按元素值递减次序排列的单链表,并要求利用原来两个表的结点存放归并后的单链表。
//将两个顺序单链表 合并 成一个逆序单链表
//要求:要用到两个原表的结点
//思路:利用头插法逆序 与归并排序操作类似
void MergeList(LinkList &La,LinkList Lb)
{//La 用来存放最终合并后的单链表
LNode *r,*pa=La->next,*pb=Lb->next;//r为工作指针
//将La断链 以便头插
La->next=NULL;
while(pa&&pb)
{
if(pa->data<pb->data)
{//pa指向的元素权值小于pb指向的元素权值
r=pa->next;//先将pa的后一结点保存 后面将修改pa
//头插
pa->next=La->next;
La->next=pa;
pa=r;//保持pa不断
}else
{
r=pb->next;
pb->next=La->next;
La->next=pb;
pb=r;
}
}
//最终一定有一个表有剩余元素
if(pa)//处理pa
{
pb=pa;//放到pb中处理
}
if(pb)//pb有剩
{
while(pb)
{
r=pb->next;
pb->next=La->next;
La->next=pb;
pb=r;
}
}
//归并结束 销除Lb
free(Lb);
}
14、设A和B是两个单链表(带头点),其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C,要求不破坏A、B的结点。
//在两个递增有序链表中产生公共元素 用一新链表接收
//思路:设置两个指针 分别从两个链表起始比较
//若权值相同 则插入C 若A中元素更大 则让B中指针指向下一个元素 反之 则让A中指针指向下一个元素
void CommonElement(LinkList A,LinkList B,LinkList &C)
{
LNode *p=A->next,*q=A->next;//p q表示两个工作指针
C=(LinkList)malloc(sizeof(LNode))
LNode *r=C,*s;//r代表尾插法插入C的尾指针 s用于接收共同元素结点
while(p&&q)
{
if(p->data<q->data)
{
p=p->next;
}else if(p->data>q->data)
{
q=q->next;
}else//权值相同
{//尾插
s=(LNode *)malloc(sizeof(LNode));
s->data=p->data;
r->next=s;
r=s;
//更新p q指针
p=p->next;
q=q->next;
}
}
r->next=NULL;
}
15、已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,将A与B的交集,并存放于A链表中。
//此题与上题类似 但王道用的归并的方法 但思路是相同的
void Union(LinkList &La,LinkList &Lb)
{
LNode *pa=La->next,*pb=Lb->next;//工作指针
La->next=NULL;
LNode *pc=La,*u;//u是临时结点
while(pa&&pb)
{
if(pa->data==pb->data)
{//权值相同 两个指针同时后移
pc->next=pa;
pc=pa;
pa=pa->next;
u=pb;
pb=pb->next;
free(u);//Lb中结点释放
}else if(pa->data>pb->data)
{//pa权值更大 让pb指针后移
u=pb;
pb=pb->next;
free(u);
}else
{//pa指针后移
u=pa;
pa=pa->next;
free(u);
}
}
//两个单链表有剩
while(pa)
{//pa有剩
u=pa;
pa=pa->next;
free(u);
}
while(pb)
{//pb有剩
u=pb;
pb=pb->next;
free(u);
}
pc->next=NULL;
free(Lb);
}
16、两个整数序列A=a1,a2,a3,...am和B=b1,b2,b3,...bn已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。
//16、判断B是否是A的连续子序列
//王道解法 -类似于字符串匹配思想
int Pattern(LinkList A,LinkList B)
{
LNode *p=A->next;
LNode *pre=p;//pre记住每次比较中A链表的开始结点
LNode *q=B->next;
while(p&&q)
{
//若匹配成功 则每次同步更新A B的下一个元素
if(p->data==q->data)
{
p=p->next;
q=q->next;
}
//若匹配不成功 更改A指针指向上一轮开始结点的下一个节点 q每次回溯到链表首元素 继续比对
pre=pre->next;
p=p->next;
q=B->next;
}
if(q==NULL)
{//B序列比较结束 说明B是A的子序列
return 1;
}else
{
return 0;
}
}
17、设计一个算法用于判断带头结点的循环双链表是否对称。
//17、判断循环双链表是否对称
bool Judge_DLinkSymmetry(DLinkList L)
{
DNode *p=L->next,*q=L->prior;//p指向首元素结点 q指向尾元素结点
while(p!=q&&p->next==q)//循环跳出
{
if(p->data!=q->data)
return false;//不是中心对称
else
{//若两者相等 则让p、q指向下一个结点 持续比较
p=p->next;
q=q->prior;
}
}
return true;
}
18、有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。
//18、将循环单链表h2链接到循环单链表h1之后
//思路:先找到两个循环单链表的尾结点 之后修改结点即可
void Link_h2Toh1(LinkList &h1,LinkList h2)
{
LNode *p,*q;//p 和 q分别表示L1和L2的工作指针
p=h1,q=h2;
while(p->next!=h1)//找到h1的尾结点
p=p->next;
while(q->next!=h2)//找到h2的尾结点
q=q->next;
//修改h1的尾结点指针指向h2 h2的尾结点指针指向h1
p->next=h2;
q->next=h1;
}
19、设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表为空,再删除表头结点。
void findMinAndDelete(LinkList &L)
{
LNode *p,*pre,*min,*minpre;//p--工作指针 pre--p的前驱指针 min--最小结点指针 minpre--最小结点指针的前驱指针
while(L->next!=L)//一直循环到L为空
{//每循环一次找到并输出一个最小值
p=L->next,pre=L,min=p,minpre=pre;
while(p->next!=L)//找出最小值结点
{
if(min->data>p->data)
{
//修改最小值结点指针
min=p;
minpre=pre;
}else
{
//往后扫描
pre=p;
p=p->next
}
}
//出第二个while后一定找到了此时链表内的最小值结点
printf("%d",min->data);
//修改指针 删除最小值结点
minpre->next=min->next;
free(min);
}
//出外while后 必然L内元素为空
free(L);
}
//此题做法对单链表一样适用 只是把while内条件修改为L->next!=NULL 以及p->next!=NULL
20、
//定位元素结点 并按频度域排序
LNode* Locate(LinkList &L,int x)
{
LNode *p=L->next;//p为工作指针 用于定位元素值为x的结点
LNode *q=L->next,*qpre=L;//q表示寻找插入位置的指针 qpre指向待插入位置的前驱
LNode *ppre,*pnext;//分别代表定位结点的前驱结点 以及后继结点
//先找到插入的位置
while(p->data!=x)
{
p=p->next;
}
if(!p)//p循环到链尾NULL 说明没有找到该权值的结点
{
return NULL;
}else//存在该权值的结点
{
//该结点频度域+1
int fre=++p->freq;
ppre=p->pred;
pnext=p->next;
p->next=NULL,p->pred=NULL;
//将该结点摘除 与其之前的结点的频度域比较 插在适合的位置
//printf("此时首元素结点为:%d\n",q->data);
//找到要该结点插入的位置
while(fre<q->freq)
{
qpre=q;
q=q->next;
}
//修改指针指向
p->next=qpre->next;
qpre->next=p;
q->pred=p;
p->pred=qpre;
if(!pnext)//p是最后一个节点 则直接将ppre指向NULL
ppre->next=NULL;
else//p不是最后一个节点 将链表连接
{
ppre->next=pnext;
pnext->pred=ppre;
}
return p;
}
}
//优化--由于是双向链表 可以直接从p的pred结点开始寻找
LNode* Locate(LinkList &L,int x)
{
LNode *p=L->next,*q;//p为工作指针 q为p的前驱
while(p&&p->data!=x)
p=p->next;
if(!p)
exit(0);
else{
p->freq++;
if(p->pred==L||p->freq<p->pred->freq)
return p;//p是第一个结点 或者p的前驱结点的频度域严格大于p的频度域 直接返回
if(p->next!=NULL)
p->next->pred=p->pred;//将p从链表摘下
p->pred->next=p->next;
q=p->pred;
while(q!=L&&q->pred<=p->pred)
q=q->pred;//从p向前找到第一个大于p的频度域的结点
//修改指针
p->next=q->next;
if(q->next!=NULL) q->next->pred=p;//让p成为相同频度域的第一个结点
p->pred=q;
q->next=p;
}
return p;
}
21、
//21、判断单链表有环
//思路:使用快慢指针
//两个指针起初都指向head 快指针每次走两步 慢指针每次走一步 当快指针等于慢指针时则说明有环
//否则 快慢指针都会走到最后为NULL
bool HaveRing(LNode *head)
{
LNode *fast=head,*slow=head;
while(fast=NULL&&fast->next!=NULL)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
return true;
}
return false;
}
//若要找到环的入口
LNode *FindLoopStart(LNode *head)
{
LNode *fast=head,*slow=head;
while(fast=NULL&&fast->next!=NULL)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
break;
}
if(slow==NULL||fast->next==NULL)
return NULL;//无环
LNode *p1=head,*p2=slow;//让p1指向开始点 p2指向相遇点
while(p1!=p2)
{
p1=p1->next;
p2=p2->next;
}
return p1;//返回入口点
}
22、
//22、输出链表中倒数第k个位置上的结点
//直接思路:
//开一个数组 顺序扫描链表 将每个结点的值依序赋值到数组中 同时设置一个计数器统计链表长度L
//访问倒数第k个元素结点 即是访问正序第L-k+1个结点
//当k<=L时 说明存在这个结点 通过数组直接访问A[L-k+1] k>L则不存在这个结点
//时间复杂度为O(n)(扫描O(n)+访问O(1)) 空间复杂度为O(n)
//高效解法
//思路:设置两个指针p q 初始时均指向首元素结点
//起初 p指针沿链表移动 q指针保持不动 当p移动到第k个结点时 p和q同步移动
//则当p移动到链表尾部时 q指针就移到倒数第k个结点
int Search_K(LinkList list,int k)
{
LNode *p=list->next,*q=list->next;
int cnt=0;//用于与k比较
while(p!=NULL)
{
if(cnt<k)//当cnt<k时 只移动p指针
cnt++;
else//当cnt==k时 同时移动指针p和指针q
q=q->next;
p=p->next;
}
if(cnt<k)
{//表明循环完了所有结点 但是结点个数仍小于k 则不存在要找的结点
return 0;
}
printf("%d",q->data);
return 1;
}
23、
//23、题意:已经给定了两个拥有共同后缀的字符序列 只需判断地址(即指针)相同即可 不必判断结点内的字符
//思路:首先让两个链表 对齐
//设置两个指针 同步扫描 直到找到相同的地址 即:两个指针指向的下一个结点相同
int length(LinkList L)
{//求链表长度 以便于将更长的链表移到与更短的链表长度相同的结点处同步比较
int len=0;
while(L->next!=NULL)
{
len++;
L=L->next;
}
return len;
}
LNode* find_ComAddr(LinkList str1,LinkList str2)
{
int len1=length(str1),len2=length(str2);
LNode *p=str1->next,*q=str2->next;//p q为两个工作指针 初始指向首元素
if(len1>len2)
{//str1长度大于str2长度 将str1移动到与str2长度相同处
for(int i=len1;len1>len2;len1--)
p=p->next;
}
if(len1<len2)
{//str2长度大于str1长度 将str2移动到与str1长度相同处
for(int i=len2;len2>len1;len2--)
q=q->next;
}
//两链表均同步后 同步后移
while(p->next!=NULL&&p->next!=q->next)
{//当p和q的下一个结点相同时 跳出while
p=p->next;
q=q->next;
}
return p->next;
}
//时间复杂度为 O(len1+len2) 或 O(max(len1,len2))
24、
//24--空间换时间
//维护一个数组 维度为n+1 (因为|data|<=n) 初始为0
//顺序扫描链表 若每个元素是第一次出现(数组中值为0)则保留 否则删除
void Delete_Sameabs(LinkList &head,int n)
{
LNode *p=head->next,*pre=head,*r;
int *q,val;//*q代表数组
q=(int *)malloc(sizeof(int)*(n+1));
//数组内元素初始化
for(int i=0;i<n+1;i++)
*(q+i)=0;
while(p!=NULL)
{
val=p->data>0?p->data:(-p->data);
if(*(q+val)==0)
{//val是第一次出现 保留
*(q+val)=1;
pre=p;
p=p->next
}else
{//val重复出现 删除
r=p;
pre->next=p->next;
p=p->next;//p指针指向后一节点
free(r);
}
}
free(q);
}
25、
//25、调整单链表
//思路:先将单链表的后半段结点逆置 为此需找到单链表的中间结点
//设置两个指针p q, p每次移动一步 q每次移动两步 当q移动到链表尾时 p移动到链表中点
//将后半段链表逆置后 再按要求将元素插入链表即可---蕴含头插法的思想
void Readjust_List(LNode* head)
{
LNode *p=head,*q=head;//用于找到链表中点 初始时都指向头指针
LNode *r,*s;//r用于逆置后半段链表 保证链不断 s用于前半段链表的调整
while(q->next!=NULL)
{
p=p->next;//p走一步
q=q->next;
if(q->next!=NULL)
q=q->next;//每次q走两步
}
q=p->next;//q是后半段链表的第一个节点
p->next=NULL;//从p处断开
while(q!=NULL)
{
r=q->next;//记录q的后一个结点 保证不断链
q->next=p->next;//从中点开始头插 使后半断链表逆置
p->next=q;
q=r;
}
s=head->next;
q=p->next;
p->next=NULL;
while(q!=NULL)
{
r=q->next;//r指向后半段的下一个插入点
q->next=s->next;
s->next=q;
s=q->next;//s指向前半段的下一个插入点
q=r;
}
}