12. 在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,去掉数值相同的元素,使表中不再有重复的元素。
void DeleteEle(LinkList &L)
{
LNode* p = L->next, *q;
if (p == NULL) return;
while (p->next != NULL)
{
q = p->next;
if (p->data == q->data)
{
p->next = q->next;
free(q);
}
else
p = p->next;
}
}
程序分析:
- 运行结果:
- 时间复杂度:O(n);空间复杂度:O(1)
13. 假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表。
void ConList(LinkList& LA, LinkList& LB)
{
LNode* pa = LA->next, * pb = LB->next, * preb, * prea, * r=LA;
LA->next = NULL;
while (pa != NULL && pb != NULL)
{
if (pa->data >= pb->data)
{
preb = pb;
pb = pb->next;
preb->next = LA->next;
LA->next = preb;
}
else if (pa->data < pb->data)
{
prea = pa;
pa = pa->next;
prea->next = LA->next;
LA->next = prea;
}
}
if (pa == NULL) pa = pb;
while (pa != NULL)
{
prea = pa;
pa = pa->next;
prea->next = LA->next;
LA->next = prea;
}
free(LB);
}
程序分析:
- 运行结果:
- 时间复杂度:O(m+n),其中m、n分别是两个单链表的长度;空间复杂度:O(1)
14. 设A和B是两个单链表(带头结点),其中元素递增有序。从A和B中的公共元素产生单链表C,要求不破坏A、B的结点。
- 算法思路:Link:第8题(2)
15. 已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交集,并存放于A链表中。
- 算法思想:第8题(2)
- 该题与14题的区别是,14题中要求不破坏A、B的结点,那么应该将结果放入新链表C中,而此题可以修改A、B中某条链表,从而不开辟新空间。14题的空间复杂度为:O(m+n),其中m、n分别为两个链表的长度;该题的空间复杂度为:O(1)。
16. 两个整数序列 A = a 1 , a 2 , a 3 , … , a n A=a_1,a_2,a_3,…,a_n A=a1,a2,a3,…,an和 B = b 1 , b 2 , b 3 , … , b n B=b_1,b_2,b_3,…,b_n B=b1,b2,b3,…,bn已经存入两个单链表中,判断序列B是否是序列A的连续子序列。
- 算法思想:因为两个整数序列已存入两个链表中,操作从两个链表的第一个结点开始,若对应数据相等,则后移指针;若对应数据不等,则A链表从上次开始比较结点的后继开始,B链表仍从第一个结点开始比较,直到B链表到尾表示匹配成功。A链表到尾而B链表未到尾表示失败。
操作中应记住A链表每次的开始结点,以便下次匹配时好从其后继开始。
bool Pattern(LinkList& L1,LinkList &L2)
{
LNode* p1 = L1->next, * p2 = L2->next, * pre = p1;
while (p1 != NULL && p2 != NULL)
{
if (p1->data == p2->data)
{
p1 = p1->next;
p2 = p2->next;
}
else
{
pre = pre->next;
p1 = pre;
p2 = L2->next;
}
}
if (p2 == NULL) return true;
else return false;
}
程序分析:
- 运行结果:
17. 设计一个算法用于判断带头结点的循环双链表是否对称。
- 算法思想:Link:第8题
18. 有两个循环单链表,链表头指针分别为h1和h2,将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。
- 算法思想:Link:第6题(第三种情况)
19. 设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表空为止,再删除表头结点。
- 算法思想:对于循环单链表L,在不空时循环:每循环一次查找一个最小结点(min指向最小结点,premin指向其前驱节点)并删除它,最后释放头结点。
void DeleteMin(LinkList& L)
{
LNode* p, * pre, * premin, * min;
while (L->next != L)
{
p = L->next;
pre = L;
premin = L;
min = L->next;
while (p != L)
{
if (p->data < min->data)
{
min = p;
premin = pre;
}
pre = p;
p = p->next;
}
printf("min=%d\n", min->data);
premin->next = min->next;
free(min);
}
free(L);
}
程序分析:
- 运行结果:
20. 设头指针为L的带有表头结点的非循环双向链表,其每个结点中除有pred(前驱指针)、data(数据)和next(后继指针)域外,还有一个访问频度域freq。在链表被启用前,其值均初始化为零。每当在链表中进行一次Locate(L,x)运算时,令元素值为x的结点中freq域的值增1,并使此链表中结点保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点前面,以便使频繁访问的结点总是靠近表头。试编写符合上述要求的 Locate(L,x)运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。
- 算法思想:第4题
21. 已知一个带有表头结点的单链表,结点结构为data、link,假设该链表只给出了头指针list。在不改变链表的前提下,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1;否则,只返回0。
- 算法思想:
① count=0,p和q同时指向首结点。
②若p为空,转⑤。
③若count等于k,则q指向下一个结点;否则,count= count+1。
④p指向下一个结点,转②
⑤若count等于k,则查找成功,输出该结点的ata域的值,返回1;否则,说明k值超过了线性表的长度,査找失败,返回0
⑥算法结東。
bool Search(LinkList L, int k)
{
LNode* p = L->link, * q = L->link;
int count = 0;
while (q != NULL)
{
if (count < k) count++; //将指针q移到指针p的后k位
else p = p->link; //指针p、q同步移动
q = q->link;
}
if (count < k) return false; //链表长度小于k
else
{
printf("倒数第%d个位置的data域为:%d\n", k, p->data);
return true;
}
}
程序分析:
- 运行结果:
22. 假定采用带头结点的单链表保存单词,当两个单词有相同的后缀时,可共享相同的后缀存储空间。设str1和str2分别指向两个单词所在单链表的头结点,链表结点结构为data、next。请设计一个时间上尽可能高效的算法,找出由str1和str2所指向两个链表共同后缀的起始位置(如图中字符i所在结点的位置p)。
- 算法思想:
①分别求出Str1和Str2所指的两个链表的长度m和n。
②将两个链表以表尾对齐:令指针p、q分别指向Str1和Str2的头结点,若m≥n,则p先走,使p指向链表中的第m-n+1个结点;若m<n,则使q指向链表中的第n-m+1个结点,即使指针p和q所指的结点到表尾的长度相等。
③反复将指p和q同步向后移动,当p、q指向同一位置时停止,即为共同后缀的起始位置,算法结束。
int ListLen(LinkList L)
{
LNode* p = L->next;
int len = 0;
while (p != NULL)
{
len++;
p = p->next;
}
}
LNode* FindAddr(LinkList Str1, LinkList Str2)
{
int m, n;
LNode* p, * q;
m = ListLen((Str1));
n = ListLen((Str2));
for (p = Str1; m > n; m--) p = p->next;
for (q = Str2; m < n; n--) q = q->next;
while (p != NULL && p->next != q->next)
{
p = p->next;
q = q->next;
}
return p->next;
}
程序分析:
23. 用单链表保存m个整数,结点的结构为[data][link],且|datal≤n(n为正整数)。现要求设计一个时间复杂度尽可能高效的算法,对于链表中data的绝对值相等的结点,仅保留第一次出现的结点而删除其余绝对值相等的结点。
- 算法思想:空间换时间,使用辅助数组记录链表中以及出现的数值。
void DeleEle(LinkList& L)
{
int a[10] = { 0 };
LNode* p = L->next, * pre = L;
while (p != NULL)
{
a[abs(p->data)]++;
if (a[abs(p->data)] > 1)
{
pre->next = p->next;
free(p);
p = pre->next;
}
else
{
pre = pre->next;
p = p->next;
}
}
}
程序分析:
- 运行结果:
- 时间复杂度:O(m);空间复杂度O(n):
24. 判断一个链表是否有环,如果有,找出环的入口点并返回,否则返回NULL。
- 算法思路:设置快慢两个指针分别为fast和slow,初始时都指向链表头head。slow每次走一步,即slow=slow->next,fast每次走两步,即fast=fast->next->next。由于fast比slow走得快,如果有环,那么fast一定会先进入环,而slow后进入环。当两个指针都进入环后经过若干操作后两个指针定能在环上相遇。这样就可以判断一个链表是否有环。
LNode* FindLoopStart(LNode* head)
{
LNode* fast = head, * slow = head; //设置快慢两个指针
while (slow != NULL && fast->next != NULL)
{
slow = slow->next; //每次走一步
fast = fast->next; //每次走两步
if (slow == fast) break; //相遇
}
if (slow == NULL || fast->next == NULL) return NULL; //没有环,返回NULL
LNode* p1 = head, * p2 = slow; //分别指向开始点、相遇点
while (p1 != p2)
{
p1 = p1->next;
p2 = p2->next;
}
return p1; //返回入口点
}
程序分析:
与之相关的四个问题:
①:判断单链表是否有环?
②:如果有环,环的入口?
③:求环长
④:求总长
- 问题一:如果单链表有环,设置快慢两个指针,经过若干次移动操作后,一定会在某一时刻相遇。否则,最终会移动到表尾(即NULL)。
- 问题二:设头结点到环的入口点的距离为a,环的入口点沿环的方向到相遇点的距离为x,环长为r,相遇时fast绕过了n圈。此时slow走过的距离d=(a+x);fast走过的距离2d=(a+x+nr)。则有2(a+x)=a+nr+x,因此可设置两个指针,一个指向head,一个指向相遇点,两个指针同步移动(均为一次走一步),相遇点即为环的入口点。
- 问题三:保存相遇点,然后令某一个指针从相遇点出发向后移动,记录走过的结点数,直到指针回到相遇点,即可求出环长。
- 问题四:令某一个指针从首结点开始向后移动,到相遇点停止,记录走过的结点数。将此长度加上环长即为表长。
25. 设线性表 L = ( a 1 , a 2 , a 3 , … , a n − 2 , a n − 1 , a n ) L=(a_1,a_2,a_3,…,a_{n-2},a_{n-1},a_n) L=(a1,a2,a3,…,an−2,an−1,an)采用带头结点的单链表保存,重新排列L中各结点,得到线性表 L ′ = ( a 1 , a n , a 2 , a n − 1 , a 3 , a n − 2 , . . . ) L'=(a_1,a_n,a_2,a_{n-1},a_3,a_{n-2},...) L′=(a1,an,a2,an−1,a3,an−2,...)。
- 算法思路:①先找出链表L的中间结点,为此设置两个指针p和q,指针p每次走一步,q每次走两步,当指针q到达链尾时,指针p正好在链表的中间结点;
②然后将L的后半段结点原地逆置;
③从单链表前后两段中依次各取一个结点,按要求重排。
void ChangeList(LinkList& L)
{
LNode* p = L, * q = L, * r, * s;
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 = L->next; //s指向前半段的第一个数据结点,即插入点
q = p->next; //q指向后半段的第一个数据结点
p->next = NULL;
while (q != NULL) //将链表后半段的结点插入到指定位置
{
r = q->next; //r指向后半段的下一个结点
q->next = s->next; //将q所指结点插入到所指结点之后
s->next = q;
s = q->next; //s指向前半段的下一个插入点
q = r;
}
}
程序分析:
- 运行结果:
- 时间复杂度:O(n);空间复杂度:O(1)
26. 若一个线性表采用顺序表L存储,其中所有元素为整数,每个元素的值只能取0、1或2。设计一个算法,将所有元素按0、1、2的顺序排列。
- 算法思想:用0~i表示0元素区间,k~n-1表示2元素区间,中间部分为1元素区间。初始时,i=-1,k=n表示这些区间为空。用j扫描顺序表L中部的所有元素,j的初始值为0,当j所指的元素为0时,说明它一定属于前部,i增1(扩大0元素区间),将该元素交换到位置i(从前面交换过来的元素一定是1),j前进;当j所指的元素为2时,说明它一定属于后部,k减1(扩大2元素区间),将该元素交换到位置k,若此时j前进则会导致该位置不能被交换到前部,所以j不前进;当j所指的元素为1时,说明它一定属于中部,保持原来的位置不动,j前进。
void move(SqList*& L)
{
int i = -1, j = 0, k = L->length;
while (j < k)
{
if (L->data[j] == 0)
{
i++;
swap(L->data[i], L->data[j]);
j++;
}
else if (L->data[j] == 2)
{
k--;
swap(L->data[k], L->data[j]);
}
else j++;
}
}
程序分析:
- 运行结果:
27. 将顺序表中所有元素划分成两部分,其中前半部分的每个元素均小于等于整数 k 1 k_1 k1,后半部分的每个元素均大于等于整数 k 2 k_2 k2。
- 解:当k1≤k2时,先将所有小于等于整数k1前移,置low=0,high=n-1,从左向右找大于k1的元素data[low]、再从右向左找小于等于k1的元素data[high],将两者交换,如此直到low=high为止。然后采用类似的方法将data[low~n-1]中所有大于等于k2的元素移到右半部分,最后返回真。如果k1>k2,直接返回假。
bool fun(SqList* L,int x,int y)
{
if (x > y) return false;
int low=0, high=L->length-1;
while (low < high)
{
while (L->data[low] <= x) low++;
while (L->data[high] >= x) high--;
if (low < high) swap(L->data[low], L->data[high]);
}
high = L->length - 1;
while (low < high)
{
while (L->data[low] < y) low++;
while (L->data[high] >= y) high--;
if (low < high) swap(L->data[low], L->data[high]);
}
return true;
}
程序分析:
- 运行结果:
28. 设顺序表A中前k个元素有序,后n-k个元素有序,试设计一个算法使得整个顺序表有序。
- 算法思想:先将整个顺序表进行原地逆置,再将前(length-k)个元素进行逆置,最后将后k个元素依次插入到前面的有序序列中,同时注意移动相应元素。这种方法适用于k较小的情况,如果k较大,则直接将后面的元素往前插入,此时不需要反复逆置,这样做效率反而会更快。
void reverse(SqList* L, int x, int y) //将元素逆置(x、y分别对应起始下标和终止下标)
{
int mid = (y - x) / 2;
int t;
for (int i = 0; i <= mid; i++)
{
t = L->data[x];
L->data[x] = L->data[y];
L->data[y] = t;
x++; y--;
}
}
void fun(SqList* L,int k)
{
reverse(L, 0, L->length - 1);
reverse(L, 0, L->length - 1 - k);
for (int i = L->length - k; i < L->length; i++)
{
int j = 0, t=L->data[i];
while (L->data[j] <= L->data[i]) j++;
for (int k = i - 1; k >= j; k--)
L->data[k + 1] = L->data[k];
L->data[j] = t;
}
}
程序分析:
- 运行结果:
- 时间复杂度:O(n²),空间复杂度:O(1)