数据结构2.1 线性表习题
一、线性表的顺序表示
01.从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删元素的值。空出的位置由最后一个元素填补,若顺序表为空,则显示出错信息并退出运行。
bool Del_Min(SqList &L, ElemType &value) {
//删除顺序表 L 中最小值元素结点,并通过引用型参数 value 返回其值
//若删除成功,则返回 true;否则返回 false
if (L.length == 0)
return false; //表空,中止操作返回
value = L.data[0];
int pos = 0; //假定 0 号元素的值最小
for (int i = 1; i < L.length; i++) //循环,寻找具有最小值的元素
if (L.data[i] < value) { //让value记忆当前具有最小值的元素
value = L.data[i];
pos = i;
}
L.data[pos] = L.data[L.length - 1]; //空出的位置由最后一个元素填补
L.length--;
return true; //此时,value为最小值
}
02.设计一个高效算法,将顺序表L的所有元素逆置,要求算法的空间复杂度为O(1)。
void Reverse(SqList &L){
ElemType 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;
}
}
03.对长度为n的顺序表L,编写一个时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除顺序表中所有值为x的数据元素。
void del_x_1(SqList &L, ElemType x) {
//本算法实现删除顺序表 L 中所有值为 x 的数据元素
int k=0, i; //记录值不等于 x 的元素个数
for (i=0; i<L.length; i++)
if (L.data[i] != x) {
L.data[k] = L.data[i];
k++; //不等于 x 的元素增 1
}
L.length = k; //顺序表 L 的长度等于 k
}
void del_x_2(SqList &L, ElemType x) {
int k=0, i=0; //k 记录值等于 x 的元素个数
while (i < L.length) {
if (L.data[i] == x)
k++;
else
L.data[i - k] = L.data[i]; //当前元素前移 k 个位置
i++;
}
L.length = L.length - k; //顺序表 L 的长度递减
}
04.从顺序表中删除其值在给定值s和t之间(包含s和t,要求s<t)的所有元素,若s或t不合理或顺序表为空,则显示出错信息并退出运行
bool Del_s_t(SqList &L, ElemType s, ElemType t) {
//删除顺序表 L 中值在给定值 s 和 t(要求 s<t)之间的所有元素
int i, k = 0;
if (L.length == 0 || s >= t)
return false; //线性表为空或 s、t 不合法,返回
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]; //当前元素前移 k 个位置
} //for
L.length -= k; //长度减小
return true;
}
05.从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同
bool Delete_Same(SeqList& L) {
if (L.length == 0)
return false;
int i, j; //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;
}
06.将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表
bool Merge(SeqList A, SeqList B, SeqList &C) {
//将有序顺序表 A 与 B 合并为一个新的有序顺序表 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;
return true;
}
07.已知在一维数组A[m+n]中依次存放两个线性表(a1,a2,a3...,am)和(b1,b2,b3..,bn)。编写一个函数,将数组中两个顺序表的位置互换,即将(b1,b2,b3,...,bn)放在(a1,a2,a3,...,am)的前面。
//算法思想:首先将数组 A[m+n]中的全部元素
//(a₁, a₂, a₃, …, aₘ, b₁, b₂, b₃, …, bₙ)原地逆置为
//(bₙ, bₙ₋₁, bₙ₋₂, …, b₁, aₘ, aₘ₋₁, aₘ₋₂, …, a₁),
//然后对前 n 个元素和后 m 个元素分别使用逆置算法,
//即可得到(b₁, b₂, b₃, …, bₙ,a₁, a₂, a₃, …, aₘ),
//从而实现顺序表的位置互换。
typedef int DataType;
void Reverse(DataType A[],int left,int right,int arraySize){
//逆转(a[left],a[left+1],a[left+2],…,a[right])为(a[right],a[right-1],…,a[left])
if(left>=right||right>=arraySize)
return;
int mid=(left+right)/2;
for(int i=0;i<=mid-left;i++){
DataType temp=A[left+i];
A[left+i]=A[right-i];
A[right-i]=temp;
}
}
void Exchange(DataType A[],int m,int n,int arraySize){
/*数组 A[m+n]中,从 0 到 m-1 存放顺序表(a1,a2,a3,…,am),从 m 到 m+n-1 存放顺序表
(b1,b2,b3,…,bn),算法将这两个表的位置互换*/
Reverse(A,0,m+n-1,arraySize);
Reverse(A,0,n-1,arraySize);
Reverse(A,n,m+n-1,arraySize);
}
08.线性表(a1,a2,a3,..,an)中的元素递增有序且按顺序存储于计算机内。要求设计一个算法,完成用最少时间在表中查找数值为x的元素,若找到,则将其与后继元素位置相交换,若找不到,则将其插入表中并使表中元素仍递增有序。
//算法思想
//顺序存储的线性表递增有序,可采用顺序查找或折半查找,
//题目要求用最少的时间查找数值为x的元素,因此选用折半查找法。
void SearchExchangeInsert(ElemType A[],ElemType x){
int low=0,high=n-1,mid; //low和high指向顺序表下界和上界的下标
while(low<=high){
mid=(low+high)/2; //找中间位置
if(A[mid]==x) break; //找到x,退出while循环
else if(A[mid]<x) low=mid+1; //到中点mid的右半部去查
else high=mid-1; //到中点mid的左半部去查
}
//下面两个if语句只会执行一个
if(A[mid]==x&&mid!=n-1){ //若最后一个元素与x相等,则不存在与其后继交换的操作
t=A[mid]; A[mid]=A[mid+1]; A[mid+1]=t;
}
if(low>high){ //查找失败,插入数据元素x
for(i=n-1;i>high;i--) A[i+1]=A[i]; //后移元素
A[i+1]=x; //插入x
} //结束插入
}
09.给定三个序列A、B、C,长度均为n,且均为无重复元素的递增序列,请设计一个时间上尽可能高效的算法,逐行输出同时存在于这三个序列中的所有元素。例如,数组A为{1,2,3},数组B为{2,3,4},数组C为{-1,0,2},则输出2。要求:
1)给出算法的基本设计思想。
2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。
3)说明你的算法的时间复杂度和空间复杂度。
1)算法的基本设计思想。
使用三个下标变量从小到大遍历数组。当三个下标变量指向的元素相等时,
输出并向前推进指针,否则仅移动小于最大元素的下标变量,直到某个下标变量移出数组范围,即可停止。
2)算法的实现。
void samekey(int A[],int B[],int C[],int n){
int i=0,j=0,k=0; //定义三个工作指针
while(i<n&&j<n&&k<n){ //相同则输出,并集体后移
if(A[i]==B[j]&&B[j]==C[k]){
printf("%d\n",A[i]);
i++;j++;k++;
}else{
int maxNum=max(A[i],max(B[j],C[k]));
if(A[i]<maxNum) i++;
if(B[j]<maxNum) j++;
if(C[k]<maxNum) k++;
}
}
}
3)每个指针移动的次数不超过n次,且每次循环至少有一个指针后移,
所以时间复杂度为O(n),算法只用到了常数个变量,空间复杂度为O(1)。
10.
1)算法的基本设计思想:
可将问题视为把数组ab转换成数组ba(a代表数组的前p个元素,b代表数组中余下的n−p个元素),
先将a逆置得到a⁻¹b,再将b逆置得到a⁻¹b⁻¹,最后将整个a⁻¹b⁻¹逆置得到(a⁻¹b⁻¹)⁻¹= ba。
设Reverse函数执行将数组逆置的操作,对abcdefgh向左循环移动3(p=3)个位置的过程如下:
Reverse(0,p−1)得到cbadefgh;
Reverse(p,n−1)得到cbahgfed;
Reverse(0,n−1)得到defghabc。
注:在Reverse中,两个参数分别表示数组中待转换元素的始末位置。
2)使用C语言描述算法如下:
void Reverse(int R[],int from,int to) {
int i,temp;
for(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);
}
3)上述算法中三个Reverse函数的时间复杂度分别为O(p/2)、O((n−p)/2)和O(n/2),
故所设计的算法的时间复杂度为O(n),空间复杂度为O(1)。
11. 
1)算法的基本设计思想如下:
分别求两个升序列A、B的中位数,设为a和b,求序列A、B的中位数过程如下:
① 若a = b,则a或b为所求中位数,算法结束。
② 若a < b,则舍弃序列A中较小的一半,同时舍弃序列B中较大的一半,要求两次舍弃的长度相等。
③ 若a > b,则舍弃序列A中较大的一半,同时舍弃序列B中较小的一半,要求两次舍弃的长度相等。
在保留的两个升序序列中,重复过程①、②、③,
直到两个序列中均只含一个元素时为止,较小者为所求的中位数。
2)本题代码如下:
int M_Search(int A[],int B[],int n){
int s1,d1,m1,s2,d2,m2;
s1=0;d1=n-1;
s2=0;d2=n-1;
while(s1!=d1 || s2!=d2){
m1=(s1+d1)/2;
m2=(s2+d2)/2;
if(A[m1]==B[m2])
return A[m1]; //满足条件①
if(A[m1]<B[m2]){ //满足条件②
if((s1+d1)%2==0){ //若元素个数为奇数
s1=m1; //舍弃A中间点以前的部分,且保留中间点
d2=m2; //舍弃B中间点以后的部分,且保留中间点
}
else{ //元素个数为偶数
s1=m1+1; //舍弃A的前半部分
d2=m2; //舍弃B的后半部分
}
}
else{ //满足条件③
if((s1+d1)%2==0){ //若元素个数为奇数
d1=m1; //舍弃A中间点以后的部分,且保留中间点
s2=m2; //舍弃B中间点以前的部分,且保留中间点
}
else{ //元素个数为偶数
d1=m1; //舍弃A的后半部分
s2=m2+1; //舍弃B的前半部分
}
}
}
return A[s1]<B[s2]? A[s1]:B[s2];
}
3)算法的时间复杂度为O(log2 n),空间复杂度为O(1)。
注:两个数组都是升序的,我们不用看全部数字,只需要「互相比较、砍掉无用的一半」,直到最后只剩 2 个数比大小。为什么能这么砍?举个例子你就懂:比如 A 数组中位数是 3,B 数组中位数是 7 → 因为数组都是从小到大排的,A 里比 3 小的数,肯定都太小了,不可能是最终的中位数;B 里比 7 大的数,肯定都太大了,也不可能是最终的中位数。所以我们可以直接把 A 的小半边、B 的大半边一起砍掉!砍掉后剩下的 A 和 B,长度还是一样的,然后在剩下的部分里,继续重复「比中位数、砍一半」的操作。砍到最后,两个数组里都只剩 1 个数字了,这两个数里,小的那个就是我们要的最终中位数
12. 

//主元素:这个数在列表里出现的次数,得超过列表长度的一半。
//摩尔投票法
//如果数组中存在主元素,那么这个元素的「势力」足够强 —— 它和其他所有元素一对一抵消后,
//最后一定还能剩下;如果没有主元素,抵消到最后也会剩下一个「假候选」,需要后续验证。
1)算法的基本设计思想:算法的策略是从前向后扫描数组元素,标记出一个可能成为主元素的元素Num。
然后重新计数,确认Num是否是主元素。
算法可分为以下两步:
① 选取候选的主元素:依次扫描所给数组中的每个整数,将第一个遇到的整数Num保存到c中,
记录Num的出现次数为1;若遇到的下一个整数仍等于Num,则计数加1,否则计数减1;
当计数减到0时,将遇到的下一个整数保存到c中,计数重新记为1,开始新一轮计数,
即从当前位置开始重复上述过程,直到扫描完全部数组元素。
② 判断c中元素是否是真正的主元素:再次扫描该数组,统计c中元素出现的次数,
若大于n/2,则为是主元素;否则,序列中不存在主元素。
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=0,count=0;i<n;i++) //统计候选主元素的实际出现次数
if(A[i]==c)
count++;
if(count>n/2) return c; //确认候选主元素
else return -1; //不存在主元素
}
3)实现的程序的时间复杂度为O(n),空间复杂度为O(1)。
注:本题若采用先排序再统计的方法(时间复杂度为O(nlogn)),则只要解答正确,最高可拿11分。
即便是写出O(n²)的算法,最高也能拿10分,
因此对于统考算法题,花费大量时间去思考最优解法是得不偿失的。
本算法的方法非常典型,需牢固掌握。
13. 
1)算法的基本设计思想:
要求在时间上尽可能高效,因此采用“空间换时间”的办法。
分配一个用于标记的数组B[n],用来记录A中是否出现了1~n中的正整数,
B[0]对应正整数1,B[n-1]对应正整数n,初始化B中全部为0。
A中含有n个整数,因此可能返回的值是1~n+1,当A中n个数恰好为1~n时返回n+1。
当数组A中出现了小于或等于0或大于n的值时,会导致1~n中出现空余位置,返回结果必然在1~n中,
因此对于A中出现了小于或等于0或大于n的值,可以不采取任何操作。
经过以上分析可以得出算法流程:
从A[0]开始遍历A,若0<A[i]≤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。
2)算法实现:
int findMissMin(int A[], int n)
{
int i, *B; //标记数组
B = (int *)malloc(sizeof(int)*n); //分配空间
memset(B, 0, sizeof(int)*n); //赋初值为0
for (i=0; i<n; i++)
if (A[i]>0 && A[i]<=n) //若A[i]的值介于1~n,则标记数组B
B[A[i]-1] = 1;
for (i=0; i<n; i++) //扫描数组B,找到目标值
if (B[i]==0) break;
return i+1; //返回结果
}
3)时间复杂度: 遍历A一次,遍历B一次,两次循环内操作步骤为O(1)量级,因此时间复杂度为O(n)。
空间复杂度: 额外分配了B[n],空间复杂度为O(n)。
14.


15.

二、线性表的链式表示
01. 在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。
//解法1: 用p从头到尾扫描单链表,pre指向*p结点的前驱。
//若p所指结点的值为x,则删除,并让p移向下一个结点,
//否则让pre、p指针同步后移一个结点。
void Del_X_1(Linklist &L,ElemType x){
LNode *p=L->next,*pre=L,*q;//置p和pre的初始值
while(p!=NULL){
if(p->data==x){
q=p; //q指向被删结点
p=p->next;
pre->next=p; //将*q结点从链表中断开
free(q); //释放*q结点的空间
}
else{ //否则,pre和p同步后移
pre=p;
p=p->next;
}//else
}//while
}
//本算法是在无序单链表中删除满足某种条件的所有结点,这里的条件是结点的值为x。
//实际上,这个条件是可以任意指定的,只要修改if条件即可。
//比如,我们要求删除值介于mink和maxk之间的所有结点,
//则只需将if语句修改为`if(p->data>=mink&&p->data<=maxk)`。
//解法2: 采用尾插法建立单链表。
//用p指针扫描L的所有结点,当其值不为x时,将其链接到L之后,否则将其释放。
void Del_X_2(Linklist &L,ElemType x){
LNode *p=L->next,*r=L,*q; //r指向尾结点,其初值为头结点
while(p!=NULL){
if(p->data!=x){ //*p结点值不为x时将其链接到L尾部
r->next=p;
r=p;
p=p->next; //继续扫描
}
else{ //*p结点值为x时将其释放
q=p;
p=p->next; //继续扫描
free(q); //释放空间
}
}//while
r->next=NULL; //插入结束后置尾结点指针为NULL
}
//上述两个算法扫描一遍链表,时间复杂度为O(n),空间复杂度为O(1)。
02. 试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设该结点唯一)。

LinkList Delete_Min(LinkList &L) {
LNode *pre=L, *p=pre->next; //p为工作指针,pre指向其前驱
LNode *minpre=pre, *minp=p; //保存最小值结点及其前驱
while(p!=NULL){
if(p->data < minp->data){
minp=p; //找到比之前找到的最小值结点更小的结点
minpre=pre;
}
pre=p; //继续扫描下一个结点
p=p->next;
}
minpre->next=minp->next; //删除最小值结点
free(minp);
return L;
}
03. 试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1)。

LinkList Reverse_1(LinkList L){
LNode *p, *r; //p为工作指针,r为p的后继,以防断链
p=L->next; //从第一个元素结点开始
L->next=NULL; //先将头结点L的next域置为NULL
while(p!=NULL){ //依次将元素结点摘下
r=p->next; //暂存p的后继
p->next=L->next; //将p结点插入到头结点之后
L->next=p;
p=r;
}
return L;
}

LinkList Reverse_2(LinkList L) {
LNode *pre, *p=L->next, *r=p->next;
p->next=NULL; //处理第一个结点
while(r!=NULL) { //r为空,则说明p为最后一个结点
pre=p; //依次继续遍历
p=r;
r=r->next;
p->next=pre; //指针反转
}
L->next=p; //处理最后一个结点
return L;
}
04. 设在一个带表头结点的单链表中,所有结点的元素值无序,试编写一个函数,删除表中所有处于给定的两个值(作为函数参数给出)之间的元素(若存在)。
void LNode *Delete(LinkList &L, int min, int max){
LinkList pr=L,p=L->link; //p是检测指针,pr是其前驱
while(p!=NULL)
{
if(p->data>min&&p->data<max){ //寻找到被删结点,删除
pr->link=p->link;
free(p);
p=pr->link;
}else{ //否则继续寻找被删结点
pr=p;
p=p->link;
}
}
}
05. 给定两个单链表,试分析找出两个链表的公共结点的思想(不用写代码)。
两个单链表有公共结点,即两个链表从某一结点开始,它们的next都指向同一结点。
每个单链表结点只有一个next域,因此从第一个公共结点开始,之后的所有结点都是重合的,
不可能再出现分叉。所以两个有公共结点而部分重合的单链表,拓扑形状看起来像Y,而不可能像X。
本题极容易联想到“蛮”方法:在第一个链表上顺序遍历每个结点,每遍历一个结点,
在第二个链表上顺序遍历所有结点,若找到两个相同的结点,则找到了它们的公共结点。
显然,该算法的时间复杂度为O(len1×len2)。
接下来我们试着去寻找一个线性时间复杂度的算法。先把问题简化:如何判断两个单链表有没有公共结点?
应注意到这样一个事实:
若两个链表有一个公共结点,则该公共结点之后的所有结点都是重合的,即它们的最后一个结点必然是重合的。
因此,我们判断两个链表是不是有重合的部分时,只需要分别遍历两个链表到最后一个结点。
若两个尾结点是一样的,则说明它们有公共结点,否则两个链表没有公共结点。
然而,在上面的思路中,顺序遍历两个链表到尾结点时,并不能保证在两个链表上同时到达尾结点。
这是因为两个链表长度不一定一样。
但假设一个链表比另一个长k个结点,我们先在长的链表上遍历k个结点,之后再同步遍历,此时我们就能保证同时到达最后一个结点。
从第一个公共结点开始到链表的尾结点,这一部分是重合的,因此它们肯定也是同时到达第一公共结点的。
于是在遍历中,第一个相同的结点就是第一个公共的结点。
根据这一思路中,我们先要分别遍历两个链表得到它们的长度,并求出两个长度之差。
在长的链表上先遍历长度之差个结点之后,再同步遍历两个链表,直到找到相同的结点,或者一直到链表结束。
此时,该方法的时间复杂度为O(len1 + len2)。
06. 设C={a₁,b₁,a₂,b₂,…,aₙ,bₙ}为线性表,采用带头结点的单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a₁,a₂,…,aₙ},B={bₙ,…,b₂,b₁}。

//循环遍历链表C,采用尾插法将一个结点插入表A,这个结点为奇数号结点,
//这样建立的表A与原来的结点顺序相同;
//采用头插法将下一结点插入表B,这个结点为偶数号结点,
//这样建立的表B与原来的结点顺序正好相反。
LinkList DisCreat_2(LinkList &A) {
LinkList B=(LinkList)malloc(sizeof(LNode));//创建B表表头
B->next=NULL; //B表的初始化
LNode *p=A->next,*q; //p为工作指针
LNode *ra=A; //ra始终指向A的尾结点
while(p!=NULL){
ra->next=p; ra=p; //将*p链到A的表尾
p=p->next;
if(p!=NULL){
q=p->next; //头插后,*p将断链,因此用q记忆*p的后继
p->next=B->next; //将*p插入到B的前端
B->next=p;
p=q;
}
}
ra->next=NULL; //A尾结点的next域置空
return B;
}
//该算法特别需要注意的是,采用头插法插入结点后,*p 的指针域已改变,
//若不设变量保存其后继结点,则会引起断链,从而导致算法出错。
注:
// 定义拆分函数:返回链表B的头指针,参数A是原链表C的头结点(引用传递,修改的是原链表) LinkList DisCreat_2(LinkList &A) { // 1. 给链表B创建头结点(申请内存,大小为一个链表结点) LinkList B=(LinkList)malloc(sizeof(LNode)); // 2. 初始化B链表:头结点后面暂时没有元素,next指向空 B->next=NULL; // 3. 定义工作指针p(初始指向A头结点的下一个结点,即原链表第一个元素a₁),临时指针q(后续记路用) LNode *p=A->next,*q; // 4. 定义ra指针(始终指向A的尾结点,方便尾插法),初始指向A的头结点 LNode *ra=A; // 5. 循环遍历原链表:只要p没走到链表末尾(p≠NULL),就继续处理 while(p!=NULL){ // 6. 尾插法:把p指向的奇数位结点(a₁/a₂/...)加到A的末尾 ra->next=p; // 让A当前尾结点的next指向p,把p接在A最后 ra=p; // 更新ra为新的尾结点(刚插入的p) // 7. p往后挪一步,指向原链表的下一个结点(偶数位结点b₁/b₂/...) p=p->next; // 8. 检查p是否为空(避免原链表最后只剩奇数位结点,没有偶数位结点的情况) if(p!=NULL){ // 9. 关键:用q记住p的下一个结点(因为头插法会改p的next,不记路会“迷路”) q=p->next; // 10. 头插法第一步:让p的next指向B头结点当前的下一个结点(把p接在B现有元素前面) p->next=B->next; // 11. 头插法第二步:让B头结点的next指向p,p成为B的第一个元素 B->next=p; // 12. p回到q记录的位置(原链表中bₙ的下一个aₙ₊₁),继续遍历 p=q; } } // 13. 给A收尾:A的尾结点ra的next置空,避免A后面还连无用结点 ra->next=NULL; // 14. 返回拆分后的链表B return B; }
07. 在一个递增有序的单链表中,存在重复的元素。设计算法删除重复的元素,例如 (7, 10, 10, 21, 30, 42, 42, 42, 51, 70) 将变为 (7,10,21,30,42,51,70)。

//题中链表是有序表,因此所有相同值域的结点都是相邻的。用p扫描递增单链表L,
//若*p结点的值域等于其后继结点的值域,则删除后者,否则p移向下一个结点
void Del_Same(LinkList &L){
LNode *p=L->next,*q; //p为扫描工作指针
if(p==NULL)
return;
while(p->next!=NULL){
q=p->next; //q指向*p的后继结点
if(p->data==q->data){ //找到重复值的结点
p->next=q->next; //释放*q结点
free(q); //释放相同元素值的结点
}
else
p=p->next;
}
}
08. 设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C,要求不破坏A、B的结点。

void Get_Common(LinkList A, LinkList B) {
LNode *p=A->next, *q=B->next, *r, *s;
LinkList C = (LinkList)malloc(sizeof(LNode)); //建立表C
r=C; //r始终指向C的尾结点
while(p!=NULL&&q!=NULL){ //循环跳出条件
if(p->data<q->data)
p=p->next; //若A的当前元素较小,后移指针
else if(p->data>q->data)
q=q->next; //若B的当前元素较小,后移指针
else{ //找到公共元素结点
s=(LNode*)malloc(sizeof(LNode)); //复制产生结点*s
s->data=p->data;
r->next=s; //将*s链接到C上(尾插法)
r=s;
p=p->next; //表A和B继续向后扫
q=q->next;
}
}
r->next=NULL; //置C尾结点指针为空
}
09. 已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交集,并存放到A链表中。

LinkList Union(LinkList &la, LinkList &lb) {
LNode *pa=la->next; //设工作指针分别为pa和pb
LNode *pb=lb->next;
LNode *pc=la; //结果表中当前合并结点的前驱指针pc
while(pa&&pb) {
if(pa->data==pb->data) { //交集并入结果表中
pc->next=pa; //A中结点链接到结果表
pc=pa;
pa=pa->next;
u=pb;
pb=pb->next;
free(u); //B中结点释放
}
else if(pa->data<pb->data) { //若A中当前结点值小于B中当前结点值
u=pa;
pa=pa->next; //后移指针
free(u); //释放A中当前结点
}
else{ //若B中当前结点值小于A中当前结点值
u=pb;
pb=pb->next; //后移指针
free(u); //释放B中当前结点
}
}//while结束
while(pa) { //B已遍历完,A未完
u=pa;
pa=pa->next;
free(u); //释放A中剩余结点
}
while(pb) { //A已遍历完,B未完
u=pb;
pb=pb->next;
free(u); //释放B中剩余结点
}
pc->next=NULL; //置结果链表尾指针为NULL
free(lb); //释放B表的头结点
return la;
}
10. 两个整数序列A=a1,a2,a3,...,am和B=b1,b2,b3,...,bn已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。

int Pattern(LinkList A,LinkList B){
//pre记A链表的工作指针,本题假定A和B均无头结点
LNNode *pre=A;
//pre记住每趟比较中A链表的开始结点
LNNode *p=pre;
LNNode *q=B;
//q是B链表的工作指针
while (p&&q)
{
if(p->data==q->data) //结点值相同
{
p=p->next;
q=q->next;
}
else{
pre=pre->next;
p=pre; //A链表新的开始比较结点
q=B; //q从B链表第一个结点开始
}
}
if(q==NULL) //B已经比较结束
return 1; //说明B是A的子序列
else
return 0; //B不是A的子序列
}
11. 设计一个算法用于判断带头结点的循环双链表是否对称。

int Symmetry(DLinkList L){
DNNode *p=L->next, *q=L->prior; //两头工作指针
while (p!=q && p->next!=q) //循环跳出条件
{
if (p->data==q->data) { //所指结点值相同则继续比较
p=p->next;
q=q->prior;
}
else //否则,返回0
return 0;
}
return 1; //比较结束后返回1
}
12. 有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。

LinkList Link(LinkList h1, LinkList h2) {
//将循环链表h2链接到循环链表h1之后,使之仍保持循环链表的形式
LNode *p, *q; //分别指向两个链表的尾结点
p=h1;
while(p->next!=h1) //寻找h1的尾结点
p=p->next;
q=h2;
while(q->next!=h2) //寻找h2的尾结点
q=q->next;
p->next=h2; //将h2链接到h1之后
q->next=h1; //令h2的尾结点指向h1
return h1;
}
13. 设有一个带头结点的非循环双链表L,其每个结点中除有pre、data和next域外,还有一个访问频度域freq,其值均初始化为零。每当在链表中进行一次Locate(L,x)运算时,令值为x的结点中freq域的值增1,并使此链表中的结点保持按访问频度递减的顺序排列,且最近访问的结点排在频度相同的结点之前,以便使频繁访问的结点总是靠近表头。试编写符合上述要求的Locate(L,x)函数,返回到结点的地址,类型为指针型。

DLinkList Locate(DLinkList &L,ElemType x){
DNode *p=L->next,*q; //p为工作指针,q为p的前驱,用于查找插入位置
while (p&&p->data!=x) //查找值为x的结点
p=p->next;
if (!p) //不存在值为x的结点
exit(0);
else{
p->freq++; //令元素值为x的结点的freq域加1
if (p->pre==L||p->pre->freq>p->freq)
return p; //p是链表首结点,或freq值小于前驱
if (p->next!=NULL) p->next->pre=p->pre;
p->pre->next=p->next; //将p结点从链表上摘下
q=p->pre; //以下查找p结点的插入位置
while (q!=L&&q->freq<=p->freq)
q=q->pre;
p->next=q->next;
if (q->next!=NULL) q->next->pre=p; //将p结点排在同频率的第一个
p->pre=q;
q->next=p;
}
return p; //返回值为x的结点的指针
}
14. 设将n(n>1)个整数存放到不带头结点的单链表L中,设计算法将L中保存的序列循环右移k(0<k<n)个位置。例如,若k=1,则将链表{0,1,2,3}变为{3,0,1,2}。要求: 1)给出算法的基本设计思想。 2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。 3)说明你所设计算法的时间复杂度和空间复杂度。

2)
LNode *Convert(LNode *L,int k){
int n=1; //n用来保存链表的长度
LNode *p=L; //p为工作指针
while(p->next!=NULL){ //计算链表的长度
p=p->next;
n++;
}
p->next=L; //循环执行完后,p指向链表尾结点
//将链表连成一个环
for(int i=1;i<=n-k;i++) //寻找链表的第n−k个结点
p=p->next;
L=p->next; //令L指向新链表尾结点的下一个结点
p->next=NULL; //将环断开
return L;
}
3)本算法的时间复杂度为O(n),空间复杂度为O(1)。
15. 单链表有环,是指单链表的最后一个结点的指针指向了链表中的某个结点(通常单链表的最后一个结点的指针域是空的)。试编写算法判断单链表是否存在环。 1)给出算法的基本设计思想。 2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。 3)说明你所设计算法的时间复杂度和空间复杂度。


2)
LNode* FindLoopStart (LNode *head){
LNode *fast=head, *slow=head; //设置快慢两个指针
while (fast!=NULL && fast->next!=NULL){
slow=slow->next; //每次走一步
fast=fast->next->next; //每次走两步
if (slow==fast) break; //相遇
}
if (fast==NULL||fast->next==NULL)//没有环,返回NULL
return NULL;
LNode *p1=head, *p2=slow ; //分别指向开始点、相遇点
while (p1!=p2){
p1=p1->next;
p2=p2->next;
}
return p1; //返回入口点
}
3)当fast与slow相遇时,slow肯定没有遍历完链表,故算法的时间复杂度为O(n),空间复杂度为O(1)。
16. 设有一个长度n(n为偶数)的不带头结点的单链表,且结点值都大于0,设计算法求这个单链表的最大孪生和。孪生和定义为一个结点值与其孪生结点值之和,对于第i个结点(从0开始),其孪生结点为第n−i−1个结点。要求: 1)给出算法的基本设计思想。 2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释。 3)说明你的算法的时间复杂度和空间复杂度。

2)本题代码如下:
int PairSum(LinkList L) {
LNode *fast=L->next,*slow=L;//利用快慢双指针找到链表的中间点
while(fast!=NULL&&fast->next!=NULL){
fast=fast->next->next; //快指针每次走两步
slow=slow->next; //慢指针每次走一步
}
LNode *newHead=NULL,*p=slow->next,*tmp;
//反转链表后半部分的元素,采用头插法
while(p!=NULL){
tmp=p->next; //p指向当前待插入结点,令tmp指向其下一结点
p->next=newHead; //将p所指结点插入到新链表的首结点之前
newHead=p; //newHead指向刚才新插入的结点,作为新的首结点
p=tmp; //当前待处理结点变为下一结点
}
int mx=0; p=L;
LNode *q=newHead;
while(q!=NULL){ //用p和q分别遍历两个链表
if (p->data+q->data>mx) //用mx记录最大值
mx=p->data+q->data;
p=p->next;
q=q->next;
}
return mx;
}
3)本算法的时间复杂度为O(n),空间复杂度为O(1)。
17.

3)
typedef int ElemType; //链表数据的类型定义
typedef struct LNode{ //链表结点的结构定义
ElemType data; //结点数据
struct LNode *link; //结点链接指针
}LNode, *LinkList;
int Search_k(LinkList list,int k){
LNode *p=list->link,*q=list->link; //指针p、q指示第一个结点
int count=0;
while(p!=NULL){ //遍历链表直到最后一个结点
if (count<k) count++; //计数,若count<k只移动p
else q=q->link; //之后让p、q同步移动
p=p->link;
}
if(count<k)
return 0; //查找失败返回0
else { //否则打印并返回1
printf("%d",q->data);
return 1;
}
}
18.


2)
typedef struct Node{
char data;
struct Node *next;
}SNode;
/*求链表长度的函数*/
int listlen(SNode *head) {
int len=0;
while (head->next!=NULL){
len++;
head=head->next;
}
return len;
}
/*找出共同后缀的起始地址*/
SNode* find_list(SNode *str1,SNode *str2) {
int m, n;
SNode *p,*q;
m=listlen(str1); //求str1的长度,O(m)
n=listlen(str2); //求str2的长度,O(n)
for(p=str1;m>n;m--) //若m≥n,使p指向链表中的第m-n+1个结点
p=p->next;
for(q=str2;m<n;n--) //若m<n,使q指向链表中的第n-m+1个结点
q=q->next;
while(q->next!=NULL&&p->next!=q->next) //查找共同后缀起始点
p=p->next; //两个指针同步向后移动
q=q->next;
return p->next; //返回共同后缀的起始地址
}
![]()
19.

2)
typedef struct node {
int data;
struct node *link;
}NODE;
Typedef NODE *PNODE;
3)
void func (PNODE h,int n) {
PNODE p=h, r;
int *q;
q=(int *)malloc(sizeof(int)*(n+1)); //申请n+1个位置的辅助空间
for(int i=0;i<n+1;i++) //数组元素初值置0
*(q+i)=0;
while (p->link!=NULL) {
m=*(q+m)=0? p->link->data:-p->link->data; //判断该结点的data
if (*(q+m)==0) { //首次出现
*(q+m)=1; //标记为已出现
p=p->link; //保留该结点
}
else{ //重复出现
r=p->link; //删除该结点
p->link=r->link;
free(r);
}
}
free(q);
}

20.

2)
void change_list(NODE*h){
NODE *p,*q,*r,*s;
p=q=h;
while(q->next!=NULL){ //寻找中间结点
p=p->next; //p走一步
q=q->next;
if(q->next!=NULL) q=q->next; //q走两步
}
q=p->next; //p所指结点为中间结点,q为后半段链表的首结点
p->next=NULL;
while(q!=NULL){ //将链表后半段逆置
r=q->next;
q->next=p->next;
p->next=q;
q=r;
}
s=h->next; //s指向前半段的第一个数据结点,即插入点
q=p->next; //q指向后半段的第一个数据结点
p->next=NULL;
while(q!=NULL){ //将链表后半段的结点插入到指定位置
r=q->next; //r指向后半段的下一个结点
q->next=s->next; //将q所指结点插入到s所指结点之后
s->next=q;
s=q->next; //s指向前半段的下一个插入点
q=r;
}
}

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)