设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void deleteRecursive(LNode &p, Elemtype x) { if (p == null) { return; } if (p->data == x) { LNode temp = p; p = p->next; free(temp); deleteRecursive(p, x); } else { deleteRecursive(p->next, x); } }
|
在带头结点的单链表L中,删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一,试编写算法以实现上述操作。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void deleteX(LinkList &L, Elemtype x) { while(L != null && L->next != null) { if (L->next->data == x) { LNode *temp = L->next; if (L->next->next != null) { L->next = L->next->next; } else { L->next = null; } free(temp); } else { L = L->next; } } }
|
设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| LinkList headInsert(LinkList &head, Elemtype value) { temp = (LNode*)malloc(sizeof(LNode)); temp->data = value; temp->next = head->next; head->next = temp; }
void printReverse(LinkList L) { LNode current = L->next; Linklist reverseList = malloc(sizeof(LNode)); reverseList->next = null; while(current != null) { headInsert(reverseList, current->data); current = current->next; } while(reverseList != null) { print(reverseList->data); reverseList = reverseList->next; } }
|
1 2 3 4 5 6 7 8 9
| void printReverse(LinkList L) { if (L->next != null) { printReverse(L->next); } if (L != null) { print(L->data); } }
|
试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void deleteMin(LinkList &L) { LNode *minPrior = L->next; LNode *p = L->next; while(p->next != null) { if (p->next->data < min->data) { minPrior = L; } p = p->next; } LNode *min = minPrior->next; minPrior->next = min->next->next; free(min); }
|
试编写算法将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为\(O(1)\)。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| LinkList headInsert(LinkList &head, Elemtype value) { temp = (LNode*)malloc(sizeof(LNode)); temp->data = value; temp->next = head->next; head->next = temp; }
void Reverse(LinkList &L) { LNode *current = L->next; L->next = null; while(current != null) { headInsert(L, current->data); current = current->next; } }
|
有一个带头结点的单链表L,设计一个算法使其元素递增有序。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void sortList(LinkList &L) { LNode *current = L->next; while(current->next != null) { if (current->next->data < current->data) { LNode *locate = L; while(locate->next !=null) { if (locate->next->data > current->next->data) { LNode *next = current->next->next; current->next->next = locate->next; locate->next = current->next; current->next = next; break; } locate = locate->next; } } else { current = current->next; } } }
|
设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素的元素(若存在)。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void rangeDelete(LinkList &L, int min, int max) { LNode *p = L->next, *pre = L; while(p != null) { if (p->data > min && p->data < max) { pre->next = p->next; free(p); p = pre->next; } else { pre = p; p = p->next; } } }
|
给定两个单链表,编写算法找出两个链表的公共结点。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
int getLength(LinkList L) { int length = 0; while(L != null) { L = L->next; length++; } return length; }
Linklist findCommon(LinkList L1, LinkList L2) { int len1 = getLength(L1), len2 = getLength(L2); if (len1 > len2) { int distance = len1 - len2; while (distance != 0) { L1 = L1->next; distance--; } while (len2 != 0) { if (L1 == L2) { return L1; } else { L1 = L1 -> next; L2 = L2 -> next; } } return null; } else { int distance = len2 - len1; while (distance != 0) { L2 = L2->next; distance--; } while (len1 != 0) { if (L1 == L2) { return L1; } else { L1 = L1 -> next; L2 = L2 -> next; } } return null; } }
|
给定一个带表头结点的单链表,设head 为头指针,结点结构为(data,next),data为整型元素,next为指针,试写出算法: 按递增次序输出单链表中各结点的数据元素,并释放结点所占的存储空间(要求: 不允许使用数组作为辅助空间)。
因为不能使用辅助空间,由第6题可知,空间复杂度为\(O(1)\)时,排序算法时间复杂度为\(O(n^2)\)。
而暴力解法的时间复杂度也是\(O(n^2)\),所以直接两重循环,每次寻找最小元素输出。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void deleteMin(LinkList &L) { while(L->next != null) { LNode *pre = L, *p = L->next; while (p->next != null) { if (p->next->data < pre->next->data) { pre = p; p = p->next; } } print(pre->next->data); LNode *del = pre->next; pre->next = pre->next->next; free(del); } free(L); }
|
将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变。
算法具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| LinkList split(LinkList &A) { int i = 1; LNode *p = A->head; LNode *pre = A; LinkList B = (LinkList)malloc(sizeof(LNode)); B->next = null; while(p != null) { if(i % 2 == 0) { LNode *temp = p->next; tailInsert(B, p->data); pre->next = temp; p = pre; p = p->next; } else { pre = p; p = p->next; } i++; } return B; }
|
设\(C=\{a_1,b_1,a_2,b_2,..,a_m,b_n\}\)为线性表,采用带头结点的 hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得\(A=\{a_1,a_2,...,a_n\},B=\{b_n,...,b_2,b_1\}\)。
和上一题类似,生成链表B的时候采取头插法,则可得到倒置的链表B。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| LinkList split(LinkList &A) { int i = 1; LNode *p = A->head; LNode *pre = A; LinkList B = (LinkList)malloc(sizeof(LNode)); B->next = null; while(p != null) { if(i % 2 == 0) { LNode *temp = p->next; headInsert(B, p->data); pre->next = temp; p = pre; p = p->next; } else { pre = p; p = p->next; } i++; } return B; }
|
在一个递增有序的线性表中,有数值相同的元素存在。 若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复的元素,例如\((7,10,10,21,30,42,42,42,51,70)\)将变为\((7,10,21,30,42,51,70)\)。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void deleteSame(LinkList &L) { LNode *p = L->next; LNode *next; while(p->next != null) { next = p->next; if (next->data == q->data) { p->next = next->next; free(next); } else { p = p->next; } } }
|
假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void mergeList(LinkList &L1, Linklist &L2) { LNode *p1 = L1->next, *p2 = L2->next; LNode *temp; L1->next = null; while(p1 != null && p2 != null) { if (p1->data < p2->data) { temp = p->next; p1->next = L1->next; L1->next = p1; p1 = temp; } else { temp = p->next; p2->next = L2->next; L1->next = p2; p2 = temp; } while(p2 != null) { temp = p2->next; p2->next = L1->next; L1->next = p2; p2 = temp; } free(L2); } }
|
设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C,要求不破坏A,B的结点。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void getCommon(LinkList &A, LinkList &B) { LNode *p = A->next, *q = B->next; LinkList C = malloc(sizeof(LNode)); LNode *tail; C->next = null; while(p != null && q != null) { if (p->data == q->data) { LNode temp = (LNode*)malloc(sizeof(LNode)); temp->data = p->data; tail->next = temp; tail = temp; p = p->next; q = q->next; } else if (p->data < q->data) { p = p->next; } else { q = q->next; } } tail->next =null; }
|
已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交集,并存放于A链表中。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| void union(LinkList &A, LinkList &B) { LNode *p1 = A->next, *p2 = B->next; LNode *pre = A; while(p1 != null || p2 != null) { if (p1->data == p2->data) { p1 = p1->next; LNode *temp = p2; p2 = p2->next; pre = pre->next; free(temp); } else if(p1->data < p2->data) { LNode *temp = p1; pre->next = p->next; p1 = p1->next; free(temp); } else { LNode *temp = p2; p2 = p2->next; free(temp); } while(p1 != null) { LNode *temp = p1; p1 = p1->next; free(temp); } while(p2 != null) { LNode *temp = p2; p2 = p2->next; free(temp); } pre->next = null; free(B); }
|
两个整数序列\(A=a_1,a_2,a_3,...,a_m\)和\(B=b_1,b_2,b_3,..,b_n\)已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列。
默认为带头节点的单链表,类似于模式匹配算法。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| bool isSubseq(LinkList A, LinKList B) { LNode *p1 = A->next, *p2 = B->next; LNode *pre = p; while(p1 != null && p2!=null) { if(p1->data == p2->data) { p1 = p1->next; p2 = p2->next; } else { pre = pre->next; p1 = pre; p2 = B->next; } } if (p2 == null) { return true; } else { return false; } }
|
设计一个算法用于判断带头结点的循环双链表是否对称。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12
| bool symmetry(DLinkList L) { DNode *p = L->next, *q = L->prior; while(p != q && p->prior != q) { if (p->data == q->data) { p = p->next; q = q->prior; } else { return false; } } return true; }
|
有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2链接到链表h1之后,要求链接后的链表仍保持循环链表形式。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12
| LinkList link(LinkList &h1, LinkList &h2) { LNode *p = h1, *q = h2; while(p->next != p) { p = p->next; } while(q->next != q) { q = q->next; } p->next = h2; q->next = h1; return h1; }
|
设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表空为止,再删除表头结点。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void deleteAll(LinkList &L) { LNode *p, *pre, *minp, *minpre; while(L->next != L){ p = L->next; pre = L; minp = p; minpre = pre; while(p != L) { if(p->data < minp->data){ minp=p; minpre=pre; } pre = p; p = p->next; } printf("%d", minp->data); minpre->next = minp->next; free(minp); } free(L); }
|
设头指针为L的带有表头结点的非循环双向链表,其每个结点中除有pred(前驱指针),data(数据)和next(后继指针)域外,还有一个访问频度域freq。在链表被启用前,其值均初始化为零。每当在链表中进行一次Locate(L,x)运算时,令元素值为x的结点中 freq域的值增1,并使此链表中结点保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点前面,以便使频繁访问的结点总是靠近表头。 试编写符合上述要求的 Locate(L,x)运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| DLinkList Locate(DLinkList &L, ElemType x){
DNode *p = L->next, *q; while(p != null && p->data != x) { p = p->next; } if( p == null) { printf("不存在值为x的结点"); return 0; } else { p->freq++; if(p->next != NULL) { p->next->pred = p->pred; } p->pred->next = p->next; q = p->pred; while(q != L && q->freq <= p->freq) { q = q->pred; } p->next = q->next; q->next->pred = p; p->pred = q; q->next = p; } return p; }
|
已知一个带有表头结点的单链表,结点结构为(data,link),假设该链表只给出了头指针list。 在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第k个位置上的结点(k为正整数)。 若查找成功。算法输出该结点的 data 域的值,并返回1;否则,只返回0。要求:
描述算法的基本设计思想。
描述算法的详细实现步骤。
根据设计思想和实现步骤,采用程序设计语言描述算法(使用C、C++或 Java语言实现),关键之处请给出简要注释。
解答:
暴力解法可以通过遍历两次解决,时间复杂度为\(O(n^2)\)。问题的关键如何通过链表的一次遍历,找到倒数第k个结点的位 置。算法的基本设计思想是: 定义两个指针变量p和q,初始时均指向头结点的下一个结点(链表的第一个结点),p指针沿 链表移动; 当p指针移动到第k个结点时,q指针开始与p指针同步移动;当p指针移动到最后一个结点时,q指针所指示结点为 倒数第k个结点。 以上过程对链表仅进行一遍扫描.
步骤:
count初始化为0,计数,p,q指向链表的第一个节点;
p为空,转5;
如果count == k
,p,q同时后移,直到p到链表尾部;否则, count++
,p单独后移(阶段一),转2。
如果count < k
,说明k值超过了链表长度,返回false
。反之 count == k
,查找成功,输出q->data
,算法结束。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| bool searchK(Linklist L, int k) { LNode *p = L->next, *q = L->next; int count = 0; while(p != null) { if (count < k) { p = p->next; count++; } else { p = p->next; q = q->next; } } if (count < k) { return false; } else { print("%d", q->data); return true; } }
|
解答:
如题8,目的是找到两链表的第一个公共节点。计算两链表的长度差值dist,设置指针分别指向两链表,让指向长链表的指针先前进dist距离。之后,两指针同步后移,比较两指针指向的地址。如果地址相同则找到了目标节点。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| int getLength(LNode *str) { int length = 0; while(str != null) { str = str->next; length++; } return length; } LNode findCommon(LNode *str1, LNode *str2) { int len1 = getLength(str1), len2 = getLength(str2); if (len1 > len2) { int distance = len1 - len2; while (distance != 0) { str1 = st1->next; distance--; } while (len2 != 0) { if (str1 == str2) { return str1; } else { str1 = str1 -> next; str2 = str2 -> next; } } return null; } else { int distance = len2 - len1; while (distance != 0) { str2 = str2->next; distance--; } while (len1 != 0) { if (str1 == str2) { return L1; } else { str1 = str1 -> next; str2 = str2 -> next; } } return null; } }
|
时间复杂度为\(O(max(len1,len2))\)。
解答:
因为\(|data|\leq n\),考虑构建长度为\(n+1\)的辅助数组,初始化为零。用来判断该值是否出现过。在此情况下,只需遍历一次,即可完成全部操作。
单链表结点的数据类型定义如下:
1 2 3 4 5
| typedef struct node { int data; struct node *next; }NODE; typedef NODE *Pnode;
|
具体算法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void deleteNode(Pnode target, Pnode pre) { pre->next = target->next; free(target); } void deleteAbs(Pnode head, int n) { int array[n + 1]; for(int i = 0; i < n + 1, i++) { array[i] = 0; } while(head->next != null) { int absValue = (head->next->data > 0) ? (head->next->data) : -(head->next->data); if(array[absValue] == 0) { array[absValue] = 1; head = head->next; } else { deleteNode(head->next, head); } } }
|
时间复杂度\(O(m)\),空间复杂度\(O(n)\)。
判断一个链表是否有环,如果有,找出环的入口点并返回,否则返回 NULL。
设置快慢两个指针分别为fast和slow,初始时都指向链表头head。slow 每次走一步,即slow=slow->next
;fast每次走两步,即fast=fast->next->next
。由于 fast 比 slow走得快,如果有环,fast 一定会先进入环,而slow 后进入环。 当两个指针都进入环后,经过若干次操作后两个指针定能在环上相遇。这样就可以判断一个链表是否有环。 如下图所示,当slow 刚进入环时,fast 早已进入环。因为fast 每次比slow 多走一步且fast与slow的距离小于环的长度,所以fast与slow相遇时,slow所走的距离不超过环的长度。
如下图所示,设头结点到环的入口点的距离为\(a\),环的入口点沿着环的方向到相遇点的距离为\(x\),环长为\(r\),相遇时fast绕过了\(n\)圈。则有 \(2(a+x)=a+n*r+x\),即\(a=n*r-x\)。显然从头结点到环的入口点的距离等于 \(n\) 倍的环长减去环的入口点到相遇点的距离。 因此可设置两个指针,一个指向 head,一个指向相遇点,两个指针同步移动(均为一次走一步),相遇点即为环的入口点。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| LNode* findLoop(LinkList L) { Lnode *fast = L->next->next, *slow = L->next; while(fast != slow && fast != null) { slow = slow->next; fast = fast->next->next; } if(fast == null) { return null; } else { LNode *p1 = L; while(p1 != slow) { p1 = p1->next; slow = slow->next; } return p1; } }
|
解答:
新链表由原链表的前半部分,以及后半部分倒序交错组成。思路:找到中间节点,通过头插法将后半部分倒序。在对两部分链表交替取值,以原链表头节点为基础,通过尾插法生成结果链表。
中间节点可以通过设置指针p和q,指针p每走一步,q走两步,这样当q到链尾时,指针p恰好指向中间节点。
算法的代码描述如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| void resort(LNode *head) { LNode *p = head, *q =head; LNode *temp, *insert; while(q->next != null) { p = p->next; q = q->next; if (q->next != null) { q = q->next; } } q = p->next; p->next = null; while(q != null) { temp = q->next; q->next = p->next; p->next = q; q = temp; } insert = head->next; q = p->next; p->next = null; while(q!) { temp = q->next; q->next = insert->next; insert->next = q; insert = q->next; q = temp; } }
|
时间复杂度:寻找中间节点\(O(n)\),逆置时间复杂度\(O(n)\),合并链表的时间复杂度\(O(n)\)。所以整体的时间复杂度为\(O(n)\)。