线性表课后习题
单链表结构的定义
typedef struct LNode{
int data;
struct LNode *next;
}LNode, *LinkList;
第一题:
递归算法实现删除不带头结点的单链表L中所有值为x的结点
void delete_x(LinkList& L, int x){
LNode* p;
if(L == NULL) return;
else if(L->data == x)
{
p = L;
L = L->next;
free(p);
delete_x(L, x);
}
else
{
delete_x(L->next, x);
}
}
第二题:
非递归实现删除所有x的结点 需要设置一个工作结点和前驱结点
void delete_x_1(LinkList& L,int x){
LNode *p = L->next, *pre = L, *q;
while(p != NULL){
if(p->data == x){
q = p;
p = p->next;
pre->next = p;
free(q);
}
else{
pre = p;
p = p->next;
}
}
}
第三题:
逆序打印链表
void r_print(LinkList L){
if(L->next != NULL) r_print(L->next);
if(L != NULL) cout << L->data << ' ';
}
第四题:
删除单链表中的最小值结点
算法思路 : 依次扫描比较 然后记录最小值的结点和其前驱结点
void delete_min(LinkList& L){
LNode* pre = L, *p = L->next;
LNode *minpre = pre,*minp = p;
while(p != NULL){
if(p->data < minp->data){
minpre = pre;
minp = p;
}
pre = p;
p = p->next;
}
// 删除最小值结点
minpre->next = minp->next;
free(minp);
}
第五题 :
使用空间复杂度O(1)的方法使得一个单链表逆置 可以使用头插法的方式实现逆置
方法一:
LinkList reverse_list(LinkList& L){
LNode* p = L->next, *r;
L->next = NULL;
while(p != NULL){
r = p->next;
p->next = L->next;
L->next = p;
p = r;
}
return L;
}
方法二:
//也可以用三个工作指针 实现指针指向的改变
LinkList reverse_list(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;
}
第六题:
排序单链表
方法一:
// 时间负责度O(nlogn) 空间复杂度O(n) 以空间换时间的方式做
void Sort(LinkList& L){
LNode* p = L->next;
int tmp[100],cnt = 0;
while(p != NULL){ // 在链表中取出所有数据
tmp[cnt++] = p->data;
p= p->next;
}
sort(tmp,tmp+cnt);
p = L->next; // 恢复工作指针
int c = 0;
while(p != NULL){ // 将排好序的数据存回链表中
p->data = tmp[c++];
p = p->next;
}
}
方法二:
// 利用直接插入排序的原理实现链表的排序
void Sort(LinkList& L){
LNode *p = L->next, *pre; // p是工作指针 , pre指针是用来从头开始遍历链表的 因为他不像数组一样可以随机访问
LNode *r = p->next; // p的后继指针,保证不断链
p->next = NULL;
p = r;
while(p != NULL){
r = p->next;
pre = L;
while(pre->next != NULL && pre->next->data < p->data) pre = pre->next;
p->next = pre->next;
pre->next = p;
p = r;
}
}
第七题 :
删除单链表中元素值介于(a,b)之间的值
void delete_seg(LinkList &L, int a,int b){
LNode *p = L->next, *pre = L;
while(p != NULL){
if(p->data < b && p->data > a){
pre->next = p->next;
free(p);
p = pre->next;
}else {
pre = p;
p = p->next;
}
}
}
第八题:
求两个单链表中的公共结点
分析:如果两个链表存在一个公共结点,由于存在唯一next的域,因此从第一个公共结点开始,之后之后所有的结点都是重合的。
从拓扑结构上来看 是Y而不是X
LinkList search_commonNode(LinkList &L1,LinkList &L2){
// 思路:如果链表等长直接依次比较,如果下一个结点指向相同 则含有相同结点。
// 若不等长,去掉长链表前部分,变的等长,然后按照等长的思路去做
int dist;
int len1 = 0,len2 = 0;
// 分别计算两链表的长度
LNode *p = L1->next;
while(p != NULL){
len1 ++;
p = p->next;
}
p = L2->next;
while(p != NULL){
len2 ++;
p = p->next;
}
// 找出长链表和短链表
LinkList longList,shortList;
if(len1 > len2){
longList = L1;
shortList = L2;
dist = len1 - len2;
}else {
longList = L2;
shortList = L1;
dist = len2 - len1;
}
while(dist--) longList = longList->next;
while(longList != NULL){
if(longList == shortList) return longList;
else {
longList = longList->next;
shortList = shortList->next;
}
}
// 无公共结点返回NULL
return NULL;
}
第九题:
递增输出单链表中的结点,并销毁该链表(不允许使用数组作为辅助空间)
void min_delete(LinkList &L){
while(L->next != NULL){
LNode *pre = L,*p = L->next,*u;
while(p->next != NULL){
if(p->next->data < pre->next->data){
pre = p; // 记录最小结点的前驱结点
}
p = p->next;
}
cout << pre->next->data << ' ';
u = pre->next;
pre->next = u->next;
free(u);
}
free(L);
}
第十题:
将单链表A分成两个带头结点的单链表A和B,A含原表的奇数序号的元素,B含偶数序号的元素。
LinkList resolveList(LinkList& A){
int cnt = 0; // 记录当前是奇数序号还是偶数序号
LinkList B = new LNode; // 创建B链表的头结点
B->next = NULL;
// 创建A B链表的尾指针 以及工作指针
LNode *ra = A, *rb = B, *p;
p = A->next;
A->next = NULL;
while(p != NULL){
i ++; // 这种方法可以规避掉偶数和奇数不等的情况
if(i % 2 == 1){
ra->next = p;
ra = p;
}else{
rb->next = p;
rb = p;
}
p = p->next;
}
ra->next = NULL;
rb->next = NULL;
return B;
}
第十一题:
和前面类似只不过需要将B链表进行逆序,并且本题中奇数和偶数个数相同。
LinkList resolveList(LinkList& A){
// 只要B是头插法即可
LinkList B = new LNode;
B->next = NULL;
LNode *p = A->next, *ra = A;
A->next = NULL;
while(p != NULL){
ra->next = p;
ra = p;
p = p->next;
if(p != NULL){ // 其实题目保证了是偶数 所以不用判断也行,加了也无所谓
auto u = p->next;
p->next = B->next;
B->next = p;
p = u;
}
}
ra->next = NULL;
return B;
}
第十二题:
去除链表当中的重复元素
void del_deplicates(LinkList &L){
LNode *p = L->next, *q; // q为指向p后继指针
// 依次比较两个指针指向的元素的值 相同则删除后继结点q,不同则指针后移
while(p->next != NULL){
q = p->next;
if(q->data == p->data){
p->next = q->next;
free(q);
}else{
p = p->next;
}
}
}
第十三题:
将两个按递增顺序的单链表合并的成一个递减顺序的单链表
void merge_list(LinkList &La,LinkList &Lb){
LNode *r,*pa = La->next, *pb = Lb->next;
La->next=NULL; // 合并的结果存放在La中
while(pa && pb){
if(pa->data < pb->data){
r = pa->next;
pa->next = La->next;
La->next = pa;
pa = r;
}else{
r = pb->next;
pb->next = La->next;
La->next = pb;
pb = r;
}
}
// La 或者 Lb一方链表可能会剩余 特殊处理
if(pb) pa = pb;
while(pa){
r = pa->next;
pa->next = La->next;
La->next = pa;
pa = r;
}
}
第十四题:
从A和B两个递增的单链表的公共元素生成C,不能破坏原结点
LinkList findSameOutList(LinkList &La,LinkList &Lb){
LinkList Lc = new LNode;
// pa pb 为工作结点, r为Lc的尾结点方便尾插,u为记录pa pb的后继结点,防止断链
LNode *pa = La->next, *pb = Lb->next, *r, *u;
r = Lc;
while(pa && pb){
if(pa->data < pb->data) pa = pa->next;
else if(pa->data > pb->data) pb = pb->data;
else{
u = new LNode; // 不能破坏原结点 因此需要开辟新结点 并存入相关的值
u->data = pa->data;
r->next = u;
r = u;
pa = pa->next;
pb = pb->next;
}
}
r->next = NULL;
return Lc;
}
第十五题:
求两个链表元素中的交集(考频高)返回一个链表,最后并销毁所有结点
LinkList intersection(LinkList &La,LinkList &Lb){
LinkList Lc = new LNode;
LNode *pa = La->next, *pb = Lb->next, *r , *u;
r = Lc;
while(pa && pb){
if(pa->data == pb->data){
u = new LNode;
u->data = pa->data;
r->next = u;
r = u;
// 释放pa pb指向的结点
u = pa;
pa = pa->next;
free(u);
u = pb;
pb = pb->next;
free(u);
}else if(pa->data < pb->data){
u = pa;
pa = pa->next;
free(u);
}else {
u = pb;
pb = pb->next;
free(u);
}
}
if(pb) pa = pb;
while(pa){
u = pa;
pa = pa->next;
free(u);
}
r->next = NULL;
free(La);
free(Lb); //释放一下表头结点
return Lc;
}
第十六题:
判断是一个单链表是否是另一个单链表的子序列
bool judge_seg(LinkList &La, LinkList &Lb){
// 判断Lb是不是La的子序列
// 该算法时间复杂度为O(nm) 双指针算法
LNode *pa = La->next, *pb = Lb->next, *pre = pa;
while(pa && pb){
if(pa->data == pb->data){
pa = pa->next;
pb = pb->next;
}else{
pre = pre->next;
pa = pre;
pb = Lb->next;
}
}
if(!pb) return 1; // pb 为空表示匹配成功
else return 0;
}
第十七题:
判断一个带头结点的循环双链表是否对称
typedef struct DNode{
int data;
struct DNode *prior, *next;
}DNode,*DLinkList;
int symmetry(DLinkList &L){
DNode *pre = L->prior, *ne = L->next;
while(pre->prior != ne->next && pre->prior != ne){
if(pre->data == ne->data){
pre = pre->prior;
ne = ne->next;
}else return 0;
}
return 1;
}
第十八题:
将两个循环单链表合并
void mergeCircleList(LinkList &La, LinkList &Lb){
// 找到La的尾结点 让其指向Lb头结点, 然后找到Lb的尾结点让其指向La的头结点即可
LNode *ra = La->next, *rb = Lb->next;
while(ra->next != La) ra = ra->next;
while(rb->next != Lb) rb = rb->next;
// 合并两个单链表
ra->next = Lb->next; // Lb的头结点不存储信息,应该跳过
rb->next = La; // 最终合并之后的结果存放在La中
}
第十九题:
依次删除循环单链表中的最小值,并释放该最小值的结点
void delete_min(LinkList &L){
// 删除最小值,定义pre指针指向最小值的前一个结点
LNode *p = L->next,*pre = L;
LNode *minp,*minpre;
while(L->next != L){
p = L->next;
pre = L;
while(p != L){
if(p->data < minp->data){
minp = p;
minpre = pre;
}
p = p->next;
pre = pre->next;
}
cout << minp->data << ' ';
minpre->next = minp->next;
free(minp);
}
free(L);
}
第二十题:
编写一个Locate(L,x) 函数,在带头结点的双向链表中,令元素值为x的结点中freq域的值+1,然后按freq非增排序,最近访问的排在前面。
LNode* Locate(LinkList &L,int x){
LNode *p = L->next, *pre;
while(p && p->data != x) p = p->next;
if(!p) return NULL; //链表中无值为x的结点
else{
p->freq++;
// 然后往前找到一个位置 保证其不减即可
pre = p;
p = p->prior;
while(p != L && p->data <= pre->data) p = p->prior;
// 将该结点插入p所指向的结点后面
// 删除pre结点
pre->prior->next = pre->next;
pre->next->prior = pre->prior;
// 在p结点后插入pre结点
pre->next = pre->next;
p->next->prior = pre;
p->next = pre;
pre->prior = p;
}
}
第二十一题:
单链表判环
算法思路:分别设置slow和fast指针都指向head结点,然后slow每次走一步,fast每次走两步。如果存在环的话,必然使得slow和fast在环上相遇。记head到环结点的长度为a,环上的长度为r ,x为环的入口点到像雨点的距离。必然满足 2*(a + x) = a + n * r + x ->> a = n * r - x ->> 最后一个点从起始点另一点从相遇点同时出发且步长一致,最后他们的相遇点必然是入口点。
LinkList judge_circle(LinkList &L){
LNode *slow = L, *fast = L;
while(fast != NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
if(fast == slow) break;
}
if(!fast || !fast->next) return NULL;
else {
slow = slow;
fast = L;
while(slow != fast){
slow = slow->next;
fast = fast->next;
}
}
return slow;
}
第二十二题:
在带头结点的单链表中找到倒数第k个结点,输出其值,并返回查找状态
算法思路:
1) 直接遍历单链表的长度n,然后在遍历单链表找到第n - L位置即可
2) 定义一个指针p和q,想让p走k步,然后让p和q同时走,当p走到末尾的时候,此时q走到的一定是倒数第k个结点
bool findList_k(LinkList &L,int k){
int cnt = k;
LNode *p = L->next, *q = L->next;
while(k -- ) {
p = p->next;
if(p) return 0; // 如果p在走k步之前就已经是空的话 一定不存在倒数第K个数
}
// 否则之后一定存在倒数第k个数
while(p){
p = p->next;
q = q->next;
}
cout << q->data << endl;
return 1;
}
第二十三题:
找出两个带头结点的单链表的公共后缀的起始点
算法思路:
假设st1比st2长的(但是不知道谁长也无所谓的),定义两个指针p1和p2分别指向st1和st2,然后同时扫描单链表,当其中一个指针指向为空的时候,令其指向另一个st,然后等到最长的那个扫描结束,即可或者与最短那个st等长的起始位置。接下来,分别扫描链表,当他们都指向相同结点的时候,那么这个结点一定是公共后缀的起始点。
LinkList findSameSuffix(LinkList &st1,LinkList &st2){
LNode *p1 = st1->next, *p2 = st2->next;
// 首先需要确定st1 和 st2的长度
int len1 = 1,len2 = 1;
while(p1) p1 = p1->next,len1++;
while(p2) p2 = p2->next,len2++;
// 我们始终保证st1 > st2
if(len2 > len1) {
auto tmp = stl;
st1 = st2;
st2 = tmp;
}
p1 = st1->next;
p2 = st2->next;
while(p1){
p1 = p1->next;
p2 = p2->next;
if(!p2) p2 = st1->next;
}
p1 = st2->next;
while(p2){
if(p2 == p1) return p1;
p1 = p1->next;
p2 = p2->next;
}
return NULL;
}
第二十四题:
对于一个单链表删除绝对值相同的值,只保留一个。
算法思路:用一个辅助数组去记录该值是否出现过即可。
const int N = 1e5+10;
void delete_SameValue(LinkList &L){
int vis[N];
memset(vis,0,sizeof vis); // 将辅助数组全部清空成零
LNode *p = L->next,*pre = L; // 定义工作指针和前驱指针
while(p){ // 遍历单链表
if(vis[p->data]){
pre->next = p->next;
free(p);
p = pre->next;
}else{
vis[p->data] ++;
pre = p;
p = p->next;
}
}
}
第二十五题:
将线性表(a1,a2,a3…,an-1,an)变成(a1,an,a2,an-1,a3,an-2,…),该线性表为带头结点的单链表。
算法思路:将后半段指针指向逆序,然后分别用指针指向开头和最后,交替合并成一个链表。
首先要实现后半段的逆序,需要找到中点,设立一个指针slow和fast,slow走一步,fast走两步,当fast走到链表结束的时候,此时slow也就走到链表中点了。最后就是交替插入结点。
void Sort(LinkList &L){
LNode *head = L, *rear, *slow = L, *fast = L;
while(fast->next != NULL){
slow = slow->next;
fast = fast->next;
if(fast->next != NULL) fast = fast->next; // fast走两步
}
fast = slow->next;
slow->next = NULL;
while(fast != NULL){
rear = fast->next;
fast->next = slow;
slow = fast;
fast = rear;
}
// 最后slow是指向尾结点的
head = L->next;
rear = slow;
L->next = NULL;
while(rear){
auto p = rear->next;
rear->next = head->next;
head->next = rear;
head = rear->next;
rear = p;
}
}