数据结构专栏:
本章目录
- **递归删除不带头结点链表中指定值**
- **带头结点链表删除指定值**
- **反向输出链表值**
- **带头结点的单链表删除最小值节点**
- **带头结点单链表就地逆置**
- **单链表排序**
- **删除指定范围值**
- **递增输出并删除**
- **按奇偶拆分成单链表**
- **有序链表去重**
- **归并单链表**
- **提取公共元素**
- **求两链表交集**
- **判断是否是子序列**
- **判断循环双链表是否对称**
- **将B链表链接到A链表**
- **循环输出最小值并删除**
- **按频度排序**
- **寻找倒数第K个元素-2009**
- **寻找公共后缀-2012**
- **删除绝对值相等的元素-2015**
- **寻找环的入口**
- **就地穿插单链表节点-2019**
递归删除不带头结点链表中指定值
/*
设计一个递归算法,删除一个不带头结点的单链表中所有值为x的节点
分析:
首先我们要创建单链表,并赋值,然后递归去判断值,进行删除
*/
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
struct Link
{
int value;
struct Link *next;
};
void deleteX(Link *&p, int delNum) {//这里的第一个函数参数必须是引用值,不然会导致断链
struct Link *pre;//定义一个指针,进行删除
if (p == NULL) return;
if (p->value == delNum) {
pre = p;
p = p->next;
free(pre);
deleteX(p, delNum);
}
else
deleteX(p->next, delNum);
}
Link *create() {
struct Link *p, *rear, *head;
head = (struct Link *)malloc(sizeof(struct Link));
rear = (struct Link *)malloc(sizeof(struct Link));
head = NULL;
rear = NULL;
int value;
printf("请输入链表各节点的值,以9999结束:");
scanf("%d", &value);
while (value != 9999) {//依次创建节点
p = (struct Link *)malloc(sizeof(struct Link));//创建一个新的节点
p->value = value;
p->next = NULL;
if (head == NULL) {
rear = p;
head = p;
}
else {
rear->next = p;
rear = p;
}
scanf("%d", &value);
}
rear->next = NULL;
return head;
}
Link *create2() {
struct Link *p, *rear, *head;
head = (struct Link *)malloc(sizeof(struct Link));
head = NULL;
int value;
printf("请输入链表各节点的值,以9999结束:");
scanf("%d", &value);
while (value != 9999) {//依次创建节点
p = (struct Link *)malloc(sizeof(struct Link));
p->value = value;
p->next = NULL;
if (head == NULL) {
head = p;
}
else {
p->next = head->next;
head->next = p;
}
scanf("%d", &value);
}
return head;
}
int main() {
int delNum;
struct Link *head, *q;
head = create();
q = head;
printf("打印链表:");
while (q != NULL) {
printf("%d ", q->value);
q = q->next;
}
q = head;
printf("请输入想要删除的节点的值:");
scanf("%d", &delNum);
deleteX(q, delNum);
printf("删除后链表:");
while (q != NULL) {
printf("%d ", q->value);
q = q->next;
}
return 0;
}
带头结点链表删除指定值
/*
在带头结点的单链表L中,删除所有值为X的节点,并释放其空间,假设值为X的节点不唯一
分析:
和上题相似,只是多了一个头结点。
*/
struct Link {
int data;
struct Link *next;
};
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
void deleteX(Link *p,int delNum) {
/*struct Link* q;//这是递归方法
if (p == NULL) return;
if (p->data==delNum) {
q = p;
p = p->next;
free(q);
deleteX(p,delNum);
}
else {
deleteX(p->next, delNum);
}*/
//不采取递归,直接遍历
struct Link *pre = p, *q = p->next,*r;
while (q) {
if (q->data==delNum) {
r = q;//r指向待删除节点
q = q->next;//
pre->next = q;//删除节点
free(r);//释放节点
}
else {
pre = q;
q = q->next;
}
}
}
int main() {
//创建节点
struct Link *head = (struct Link*)malloc(sizeof(struct Link));
struct Link *q = (struct Link*)malloc(sizeof(struct Link));
q = head;
head->next = NULL;
int n,data,delNum;
printf("请输入节点个数:");
scanf("%d",&n);
for (int i = 0; i < n;i++) {
printf("请输入第%d个节点值:",i+1);
struct Link *p = (struct Link*)malloc(sizeof(struct Link));
scanf("%d",&data);
p->data = data;
head->next = p;
head = p;
}
head->next = NULL;//这里要将指针的next指向NULL,不然后面的判断会出问题,而且这也是应该养成的好习惯
head = q;//head回到头结点
printf("当前链表值为:");
while (head->next) {
printf("%d ",head->next->data);
head = head->next;
}
printf("\n");
printf("请输入要删除的值:");
scanf("%d",&delNum);
head = q;//head回到头结点
deleteX(head,delNum);
printf("删除后链表值为:");
while (head->next) {
printf("%d ", head->next->data);
head = head->next;
}
return 0;
}
反向输出链表值
/*
有一带头结点的链表,设计一算法从尾到头的输出每个节点的值。
分析:
这种类型就有点像是栈的性质,我们可以利用递归来处理,出口便是尾元素
*/
struct Link {
int data;
struct Link* next;
};
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
void reverseOutput(Link *p) {
if (p == NULL) return;
else {
reverseOutput(p->next);
printf("%d ",p->data);
}
}
int main() {
int n,data;
printf("请输入创建链表的节点个数:");
scanf("%d",&n);
struct Link *q;
struct Link *head =(struct Link*) malloc(sizeof(struct Link));
head->next = NULL;
q = head;
for (int i = 0; i < n;i++) {
struct Link *newP = (struct Link*) malloc(sizeof(struct Link));
printf("请输入第一个节点的值:");
scanf("%d",&data);
newP->data = data;
newP->next = NULL;
head->next = newP;
head = head->next;//head要始终指向最新节点
}
head->next = NULL;
head = q;//最后head要指向头结点
reverseOutput(head->next);
return 0;
}
带头结点的单链表删除最小值节点
/*
删除链表中唯一的最小值
分析:
目前能想到的就是遍历整个链表,记录下最小节点的指针,然后进行删除,时间复杂度为O(n).
*/
struct Link {
int data;
struct Link* next;
};
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
void deleteMin(Link *p) {
struct Link *preMinp = p, *minP = p->next,*preQ=p->next, *q = p->next->next,*f;
while (q) {
if (q->data < minP->data) {//如果比当前值更小
minP = q;//更换
preMinp = preQ;//前驱一起更换
}
preQ = q;//继续前进
q = q->next;
}
f = minP;
preMinp->next = minP->next;//删除
free(f);//释放
}
int main() {
int n,data;
printf("请输入创建链表的节点个数:");
scanf("%d",&n);
struct Link *q;
struct Link *head =(struct Link*) malloc(sizeof(struct Link));
head->next = NULL;
q = head;
for (int i = 0; i < n;i++) {
struct Link *newP = (struct Link*) malloc(sizeof(struct Link));
printf("请输入第一个节点的值:");
scanf("%d",&data);
newP->data = data;
newP->next = NULL;
head->next = newP;
head = head->next;//head要始终指向最新节点
}
head->next = NULL;
head = q;//最后head要指向头结点
printf("打印链表:");
while (head->next) {
printf("%d ", head->next->data);
head = head->next;
}
head = q;
deleteMin(head);
printf("删除后链表值为:");
while (head->next) {
printf("%d ", head->next->data);
head = head->next;
}
return 0;
}
带头结点单链表就地逆置
/*
就地逆置链表
分析:
我们采用头插法进行逆置。
*/
struct Link {
int data;
struct Link* next;
};
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
//void reverse(Link *h) {
//struct Link *pre = h, *p = h->next, *q = h->next,*r;
pre记录操作节点p上一个节点,q记录第一个节点,之后需要指向NULL,r用于指向每一次操作时p的后一个节点,防止断链
//while (p) {//遍历操作,修改指针指向
// r = p->next;
// p->next = pre;
// pre = p;
// p = r;
//}
//h->next = pre;//头指针指向最后一个节点
//q->next = NULL;//第一个节点指向NULL,不然就是循环单链表了
//我们也可以采用头插法进行逆置,两个方法的时间复杂度均为O(N)
struct Link *l = h, *p = h->next,*r;
l->next = NULL;
while (p) {
r = p->next;
p->next = l->next;
l->next = p;
p = r;
}
h = l;
}
int main() {
struct Link *head = (struct Link*) malloc(sizeof(struct Link));
Link *createLink(int);
head = createLink(0);
reverse(head);
printf("逆置后链表值为:");
while (head->next) {
printf("%d ", head->next->data);
head = head->next;
}
return 0;
}
单链表排序
/*
有一个带头结点的单链表L,设计一个算法使其递增有序
分析:
我们可以采用冒泡排序对其操作,使其递增有序,时间复杂度为O(n^2)。
*/
struct Link {
int data;
struct Link *next;
};
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
void bubbleSort(Link *h) {//冒泡排序
int flag = 0;//排序标志,产生过变动就置为1
int count = 0;//记录链表长度
struct Link *pre=h, *p = h->next,*r;
while (p) {
count++;
p = p->next;
}
p = h->next;
for (int i = 0; i < count;i++) {
flag = 0;
while (p->next) {//开始第i+1轮冒泡
if (p->data > p->next->data) {//前者大于后者,则需要交换
r = p->next->next;//r指向下一个节点,防止断链
pre->next = p->next;
p->next->next = p;
pre = p->next;
p->next = r;
flag = 1;//有改动,flag置为1
}
else {
pre = p;
p = p->next;
}
}
if (!flag) break;//若某一轮链表未作变换,则认定已经排好序,退出循环
pre = h;//重新从头开始遍历
p = h->next;
}
}
int main() {
Link* createLink(int);
struct Link *head;
head = createLink(0);
bubbleSort(head);
printf("排序后链表值为:");
while (head->next) {
printf("%d ", head->next->data);
head = head->next;
}
return 0;
}
删除指定范围值
/*
设一个带头结点的单链表所有元素的数据值无序,试编写函数删除表中介于给定的两个值(作为函数参数给出)之间的元素。
分析:
分别设置pre,p,r指针,遍历,符合条件便进行删除。
*/
struct Link {
int data;
struct Link *next;
};
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
void deleteNum(Link *h,int min,int max) {
struct Link *pre = h, *p = h->next, *r;
while (p) {
if (p->data>min&&p->data<max) {//符合条件,进行删除
r = p->next;
pre->next = p->next;
free(p);
p = r;
}
else {
pre = p;
p = p->next;
}
}
}
int main() {
int min, max;
struct Link*head;
Link *createLink(int);//创建链表的代码我单独封装了一个文件
void printfNowLink(Link*);
head = createLink(0);
printf("请输入要删除的值所在的范围:\n");
printf("min=");
scanf("%d",&min);
printf("max=");
scanf("%d", &max);
deleteNum(head,min,max);
printfNowLink(head);
return 0;
}
递增输出并删除
/*
按递增次序输出单链表各节点的数据元素,并释放节点所占的存储空间。
分析:
我们可以先进行排序,然后依次输出,并释放节点空间,我们也可以直接进行遍历,找到目前最小值进行输出,然后释放,注意不要断链
我们这里采取第二种方式
*/
struct Link {
int data;
struct Link *next;
};
#include <stdio.h>
#include <stdlib.h>
void printAndDel(Link *h) {
struct Link *pre = h, *p = h->next, *r,*min=h->next,*minPre=h;//为了操作的顺利进行,我们需要时刻保存节点的前驱与后继
int count = 0;
while (p) {
count++;
p = p->next;
}
p = h->next;
printf("输出顺序为:\n");
for (int i = 0; i < count;i++) {
while (p) {
if (p->data < min->data) {
minPre = pre;
min = p;
}
pre = p;
p = p->next;
}
printf("%d ",min->data);
r = min->next;
minPre->next = min->next;
free(min);
pre = minPre = h;
p = min = h->next;
}
printf("\n");
}
int main() {
struct Link *head;
Link *createLink(int);
void printfNowLink(Link*);
head = createLink(0);
printAndDel(head);
printfNowLink(head);
return 0;
}
按奇偶拆分成单链表
/*
将一个带头结点单链表A分解成两个带头结点的单链表A和B,使得A中含有原表中序号为奇数的元素,B中为偶数,且保持其相对位置不变
分析:
首先我们需要分配一个节点空间为B作为头节点,然后设置一个flag,为0时认为是奇数,链给A,为1时认为是
偶数,连给B
*/
struct Link {
int data;
struct Link *next;
};
#include <stdio.h>
#include <stdlib.h>
void divide(Link *lb,Link *la) {
int flag = 0;//做奇偶判定,因为奇数之后一定是偶数,偶数之后一定是奇数
struct Link *l=la, *p = la->next,*rb=lb,*ra=l;
l->next = NULL;//原链表头结点置空
while (p) {//要使其顺序不变,采用尾插法
if (!flag) {
ra->next = p;
ra = p;
flag = 1;
}
else {
rb->next = p;
rb = p;
flag = 0;
}
p = p->next;
}
ra->next = NULL;//要记得将末尾节点的指针指向NULL,不然就仍然是之前的指针,导致结果不正确
rb->next = NULL;
}
int main() {
struct Link *head;
Link *createLink(int);
void printfNowLink(Link *);
head = createLink(0);
struct Link *b = (struct Link*)malloc(sizeof(struct Link));//开辟节点空间
divide(b,head);
printfNowLink(b);
printf("\n");
printfNowLink(head);
return 0;
}
有序链表去重
/*
在一个递增有序的线性表中,有数值相同的元素存在,若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复的元素。
分析:
对于这类去重,我们肯定需要进行遍历,而这道题给我们的是递增有序的序列,我们便可以进行一一比较,后一个元素与当前元素相同
时便删除当前元素
*/
struct Link {
int data;
struct Link *next;
};
#include <stdio.h>
#include <stdlib.h>
void deleteRep(Link *h) {
struct Link *pre = h, *p = h->next, *r;//删除节点必备三剑客
while (p->next) {
if (p->data==p->next->data) {//当前节点与后续节点值相同,则删除并释放空间
r = p->next;//必须先保存后续节点,防止断链
pre->next = p->next;
free(p);
p = r;
continue;
}
pre = p;
p = p->next;
}
}
int main() {
struct Link * head;
Link *createLink(int);
void printfNowLink(Link *);
head = createLink(0);
deleteRep(head);
printfNowLink(head);
return 0;
}
归并单链表
/*
假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写算法将这两个单链表归并为一个按元素递减的单链表,
并要求利用原来两个单链表的节点存放归并后的单链表。
分析:
因为链表递增,所以我们可以采用头插法进行处理,以a链为起始链,进行归并
*/
struct Link {
int data;
struct Link *next;
};
#include <stdio.h>
void merge(Link *ha,Link *hb) {
struct Link *l = ha, *pa = ha->next, *pb = hb->next, *ra, *rb;
l->next = NULL;
while (pa&&pb) {
if (pa->data<pb->data) {
ra = pa->next;
pa->next = l->next;
l->next = pa;
pa = ra;
}
else {
rb = pb->next;
pb->next = l->next;
l->next = pb;
pb = rb;
}
}
while (pa) {
ra = pa->next;
pa->next = l->next;
l->next = pa;
pa = ra;
}
while (pb) {
rb = pb->next;
pb->next = l->next;
l->next = pb;
pb = rb;
}
}
int main() {
struct Link *ha,*hb;
Link *createLink(int);
void printfNowLink(Link *);
ha = createLink(0);
hb = createLink(0);
merge(ha,hb);
printfNowLink(ha);
return 0;
}
提取公共元素
/*
设A、B是两个单链表(带头结点),其中元素递增有序,设计一个算法从A、B中的公共元素产生单链表C,要求不破坏A、B节点
分析:
要求不破坏A、B节点,故我们需要重新创建分配节点空间,来进行连接。为寻找公共元素,每遍历A中的一个元素,都去
与B中元素一一比较,同则取值给另一空间节点,连接到C上。时间复杂度为O(n^2)。
因为这两个链表是递增有序的,那么我们可以设置两个指针同步比较,相同则加入c,不同小的那个往后移,直至到链表末尾
这样的时间复杂度为O(n).
*/
struct Link {
int data;
struct Link *next;
};
#include <stdlib.h>
#include <stdio.h>
void linkCommon(Link *a, Link *b, Link *c ) {
struct Link *lc = c,*la=a->next,*lb=b->next,*rc=lc;
while (la) {
while (lb) {
if (la->data==lb->data) {//如果是公共元素
struct Link *p = (struct Link*)malloc(sizeof(struct Link));
p->data = la->data;
rc->next = p;//采用尾插法
rc = p;
break;
}
lb = lb->next;
}
la = la->next;
lb = b->next;
}
rc->next = NULL;//最后一个节点指针需要指向NULL。
}
void listCommon(Link *a,Link *b,Link *c) {
struct Link *rc = c, *la = a->next, *lb = b->next;
while (la&&lb) {
if (la->data==lb->data) {
struct Link *p = (struct Link*)malloc(sizeof(struct Link));
p->data = la->data;
p->next = NULL;
rc->next = p;
rc = p;
la = la->next;
lb = lb->next;
}
else {
la->data < lb->data ? la = la->next : lb = lb->next;
}
}
rc->next = NULL;
}
int main() {
struct Link *a, *b;
Link *createLink(int);
void printfNowLink(Link *);
a = createLink(0);
b = createLink(0);
struct Link *c = (struct Link*)malloc(sizeof(struct Link));
c->next = NULL;
//linkCommon(a,b,c);
listCommon(a,b,c);
printfNowLink(c);
return 0;
}
求两链表交集
/*
已知两个链表A、B分别表示两个集合,其元素递增排列,编制函数,求A与B的交集,并存放于A链表中。
分析:
与上题类似,因为链表本身递增排序,我们可以设置两个指针,同时遍历A、B链表,同则保留,异则删除
*/
struct Link {
int data;
struct Link *next;
};
#include <stdio.h>
void listCommon(Link *a,Link *b) {
struct Link *pA = a->next, *pB = b->next,*r,*la=a;//用la指向a,便可直接链在a后面
la->next = NULL;
while (pA&&pB) {
if (pA->data==pB->data) {
la->next = pA;
la = pA;
pA = pA->next;
pB = pB->next;
}
else {
pA->data < pB->data ? pA = pA->next : pB = pB->next;
}
}
la->next = NULL;
}
int main() {
struct Link *a, *b;
Link *createLink(int);
void printfNowLink(Link *);
a = createLink(0);
b = createLink(0);
listCommon(a,b);
printfNowLink(a);
return 0;
}
判断是否是子序列
/*
存在两个单链表序列A、B,设计函数判断B是否为A的子序列。
分析:
最直接的方法:循环遍历,从A链的第一个元素开始与B链对比,如遇见不同,从A链下一个又开始,直至到达链尾
*/
struct Link {
int data;
struct Link *next;
};
#include <stdio.h>
void subList(Link *a,Link *b) {
struct Link *la = a ,*pA = la->next, *pB = b->next;
while (pA&&pB) {
if (pA->data==pB->data) {//相等继续对比下一个
pA = pA->next;
pB = pB->next;
}
else {
pB = b->next;//pb从头开始与pa对比
la = la->next;//失败一次,la往后移动一个节点
pA = la->next;//pa从下一个节点又开始
}
}
pB == NULL ? printf("true") : printf("false");//如果pb为NULL,说明已比对完成
}
int main() {
struct Link *a, *b;
Link *createLink(int);
a = createLink(0);
b = createLink(0);
subList(a,b);
return 0;
}
判断循环双链表是否对称
// createDouLoopLink.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
struct Link {
int data;
struct Link *next;
struct Link *pre;
};
Link *createDouLoopLink() {
int n, data;
struct Link *head = (struct Link *)malloc(sizeof(struct Link));
head->next = NULL;
head->pre = NULL;
struct Link *p = head;
printf("请输入节点个数:n=");
scanf("%d", &n);
for (int i = 0; i < n; i++) {
printf("请输入第%d个节点值:", i + 1);
scanf("%d", &data);
struct Link *newP = (struct Link*)malloc(sizeof(struct Link));
newP->data = data;
newP->pre = p;
p->next = newP;
p = newP;
}
p->next = head;
head->pre = p;
return head;
}
将B链表链接到A链表
/*
有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2连接到h1之后,要求连接后的链表仍保持循环链表形式。
分析:
首先我们要找到h1的尾结点,找到尾结点后将尾结点的next指向h2的首节点,然后找到h2的尾结点,将其next指针指向h1,
就大功告成了。
*/
struct Link {
int data;
struct Link *next;
};
#include <stdlib.h>
#include <stdio.h>
void linkTwoLists(Link *h1,Link *h2) {
struct Link *p1 = h1->next, *p2 = h2->next;
while (p1->next != h1) p1 = p1->next;//这里要去判断p1->next是否等于h1,进而判断出是否到达尾结点
p1->next = p2;
while (p2->next != h2) p2 = p2->next;
p2->next = h1;
free(h2);//释放h2
}
int main() {
struct Link *h1, *h2,*p;
Link *createSinLoopLink();
h1 = createSinLoopLink();
h2 = createSinLoopLink();
linkTwoLists(h1,h2);
p = h1->next;
while (p!=h1) {
printf("%d ",p->data);
p = p->next;
}
return 0;
}
循环输出最小值并删除
/*
设有一个带头结点的单链表,其节点均为正值,设计一个算法,反复找出单链表中最小值输出并删除,直到单链表为空,最后删除头结点
分析:
按照以往的经验,我们需要遍历它,为了保证不断链,我们需要设置pre,p,minPre,min,r等5个指针。
*/
struct Link {
int data;
struct Link *next;
};
#include <stdlib.h>
#include <stdio.h>
void inputAndDeleteLink(Link *h) {
struct Link *pre = h, *minPre = h, *p = h->next, *min = h->next, *r;
while (h->next!=h) {//如果头结点后面还有值,说明未结束
while (p!=h) {//寻找最小值
if (min->data>p->data) {
minPre = pre;
min = p;
}
pre = p;
p = p->next;
}
printf("%d ",min->data);
r = min->next;
minPre->next = min->next;//删除当前最小值
free(min);//释放节点空间
p = h->next;//又重头开始遍历
pre = h;
minPre = h;
min = h->next;
}
free(h);//最后释放头结点
}
int main() {
struct Link *head;
Link *createSinLoopLink();
head = createSinLoopLink();
inputAndDeleteLink(head);
return 0;
}
按频度排序
/*
给一个非循环双向链表增加一个freq值,用以表示它的访问频率,每访问一次freq就+1,然后需要将该链表按照非增的顺序
排列,同时最近访问的节点排在相同频度节点的前面,以便使频繁访问的节点总是靠近表头。试编写符合上述要求的Locate(L,x)
函数,该运算为函数过程,返回找到节点的地址,类型为指针型。
分析:
这道题咋一看很唬人,还引入了一个新概念,其实并不难,拆分开来其实就是 查找+排序;我们需要先找到我们要访问的节点
,更改它的freq值,然后按照freq值的大小从表头寻找插入位置,这样便完成了一次locate操作。
*/
struct Link {
int data;
struct Link *pre;
struct Link *next;
int freq;
};
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
void locate(Link *h,int num) {
int flag = 0;//找到标志
struct Link *pre=h, *p = h->next, *t,*preQ=h,*q;
while (p) {
if (p->data==num) {//如果找到
flag = 1;//表示找到
p->freq++;//freq+1
t = p;//将该节点保存起来
if (p->next) {
pre->next = p->next;//将该节点抠出来
p->next->pre = pre;
}
else {
pre->next = NULL;//这里也要注意,如果我们查找的节点是最后一个节点,我们要将next指向NULL,不然之后遍历时会出问题
}
q = h->next;//这里需要注意,q应该始终指向改变了的首节点,之前我在定义时去指定,会出现bug
while (q) {//进行排序
if (t->freq >= q->freq) {//当找到的节点的freq大于等于当前遍历节点时,插入
t->next = q;
q->pre = t;
preQ->next = t;
t->pre = preQ;
break;
}
preQ = q;
q = q->next;
}
break;
}
pre = p;
p = p->next;
}
if (!flag) {
printf("未找到该元素,序列不做改变");
}
}
int main() {
int n, data,num;
struct Link *head = (struct Link *)malloc(sizeof(struct Link));
head->next = NULL;
head->pre = NULL;
struct Link *p = head;
printf("请输入节点个数:n=");
scanf("%d", &n);
for (int i = 0; i < n; i++) {
printf("请输入第%d个节点值:", i + 1);
scanf("%d", &data);
struct Link *newP = (struct Link*)malloc(sizeof(struct Link));
newP->data = data;
newP->pre = p;
newP->freq = 0;
p->next = newP;
p = newP;
}
p->next = NULL;
do {
printf("请输入要查找的值,输入9999代表结束:num=");
scanf("%d",&num);
if (num == 9999)continue;//如果num=9999,跳入下一次循环
locate(head,num);
//p = head->next;
printf("调整后链表为:\n");
while (p) {
printf("%d ",p->data);
p = p->next;
}
} while (num!=9999);
return 0;
}
寻找倒数第K个元素-2009
/*
有一个带头结点的单链表,设计一个函数找到指定的倒数第k个节点,输出节点值,并返回1,否则返回0,前提不能改变链表,尽可能高效
分析:
我们可以先统计出总共的节点数count,那么该节点的顺序数num=count-k+1,当然如果k>count,直接返回0,时间复杂度为O(n)
这里还有另一种更加便捷的方法,只需对链表遍历一次,我们设立两个指针,最开始均指向首节点,然后让q先移动k个节点,之后p
q同步移动,当q为NULL时,p所在的位置便是倒数第k个节点的位置
*/
struct Link {
int data;
struct Link *next;
};
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int findTheReciprocalK(Link *h,int k) {//这是第一种解法
struct Link *p = h->next;
int count = 0,num;
while (p) {//统计元素个数
count++;
p = p->next;
}
p = h->next;
if (k > count) {
return 0;
}
else {
num = count - k + 1;
while (--num) {//这里要用--num,如果用num--,会导致p为下一个元素,注意
p = p->next;
}
printf("%d",p->data);
return 1;
}
}
int findTheReciprocalK2(Link *h,int k) {//这是第二种解法
struct Link *p = h->next, *q = h->next;
int count = k;
while (count--) {
if (q==NULL) {
printf("倒数第%d个节点不存在",k);
return 0;
}
q = q->next;
}
while (q!=NULL) {
p = p->next;
q = q->next;
}
printf("倒数第%d个节点值为:%d",k,p->data);
return 1;
}
int main() {
int k;
struct Link *head;
Link *createLink(int);
head = createLink(0);
printf("请输入要查倒数第几个数:k=");
scanf("%d",&k);
//findTheReciprocalK(head,k);
findTheReciprocalK2(head, k);
return 0;
}
寻找公共后缀-2012
/*
存在这样一种情况,如果两个单词有相同的后缀,那我们可以将后缀作为公共部分存储,比如being和loading,其中ing就可以作为
公共部分,现在存在两个链表,含有公共部分,设计一个高效算法找到其公共后缀其实位置。
分析:
我们可以这样想,如果我们单纯的让两条链表的指针同步移动,那么只有两条链表长度相同时才可能在公共部分的起始位置相遇,
所以我们应该让他们处于同一起跑线上,故而我们应该让较长的链表先走,具体走多少,应该是走过两条链表的长度之差。
*/
struct Link {
union
{
int data;
char letter;
};
Link *next;
};
#include <stdio.h>
#include <stdlib.h>
Link *findCommonSuffix(Link *h1,Link *h2) {
struct Link *p = h1->next, *q = h2->next;
int countP =0, countQ = 0,gap;
while (p) {//遍历,获取链表长度
countP++;
p = p->next;
}
while (q) {
countQ++;
q = q->next;
}
if (countQ>countP) {//让p指针始终指向较长的那条链表
p = h2->next;
q = h1->next;
gap = countQ - countP;
}
else {
p = h1->next;
q = h2->next;
gap = countP - countQ;
}
while (gap--) p = p->next;//长链表指针先行移动gap位
while (q != p && q != NULL) {//当两指针不同或不为NULL时继续向后移动
q = q->next;
p = p->next;
}
return p;
}
int main() {
struct Link *h1,*h2,*com,*p1,*p2,*start;
Link *createLink(int);
char p[] = "letter";//数据类型,char
h1 = createLink(1);
h2 = createLink(1);
com = createLink(1);//公共部分
p1 = h1->next;
p2 = h2->next;
while (p1->next)p1 = p1->next;//到达链尾
while (p2->next)p2 = p2->next;
p1->next = com->next;//链接公共部分
p2->next = com->next;
p1 = h1->next;
p2 = h2->next;
while (p1) {
printf("%c",p1->letter);
p1 = p1->next;
}
printf("\n");
while (p2) {
printf("%c",p2->letter);
p2 = p2->next;
}
printf("\n");
start=findCommonSuffix(h1,h2);
printf("%c",start->letter);
return 0;
}
删除绝对值相等的元素-2015
/*
用单链表保存m个整数,节点的结构为[data][next],且|data|<=n。现要求设计一个时间复杂度尽可能高效算法,
对于链表中绝对值相等的节点,仅保留第一次出现的节点而删除其余绝对值相等的节点。
分析:
题目中提到时间复杂度尽可能高效,其本上就是暗示我们采用空间换时间的方式。因为数据是小于等于n的,我们可以开辟一块
大小为n的数组,初始值为0,之后我们遍历链表,节点值既是我们寻找的下标,如果下标所在的数组值为0,则将值变为1,如果
数组值已经为1,则说明在此之前我们遇见过绝对值相同的元素,故将此节点删除。
*/
struct Link {
union {
int data;
}type;
struct Link *next;
};
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
void deleteRepAbs(Link *h,int n) {
int *arr = (int *)malloc(sizeof(int)*n);//这里记住大小是sizeof(int)*n,如果只写n是不对的
for (int i = 0; i < n;i++) {//赋初值0
*(arr + i) = 0;
}
struct Link *pre = h, *p = h->next, *r;
while (p) {
if (*(arr+abs(p->type.data))==0) {//首次访问,作上记录
*(arr + abs(p->type.data)) = 1;
pre = p;
p = p->next;
}
else {
r = p->next;
pre->next = p->next;//删除
free(p);//释放
p = r;
}
}
}
int main() {
struct Link *head,*p;
int n = 100;//我们这里默许创建的链表里的节点值的绝对值<=100
Link *createLink(int);
head = createLink(0);//0代表我要创建的链表值类型为int
deleteRepAbs(head,n);
p = head->next;
printf("去重后链表为:");
while (p) {
printf("%d ",p->type.data);
p = p->next;
}
return 0;
}
寻找环的入口
/*
设计一个算法判断一个链表是否有环
分析:我们可以想象一下,在一个有环的赛道上,有两个人跑步,一个人跑得快,一个人跑得慢,试想,时间充足的情况下,跑得快
的那个人是不是会再次遇到跑的慢的人呢?所以对于这道题,我们也可以通过快慢指针来处理,p指针一次移动两个节点,q指针一次移动
一个节点,如果他们再次相遇了,说明链表有环,如果p指针为NULL了,说明无环。同时我们需要记录p、q各走的步数,用以确定
环的入口点
*/
struct Link {
union {
int data;
}type;
struct Link *next;
};
#include <stdio.h>
Link *isLoop(Link *h) {
struct Link *fast = h, *slow = h;
while (slow&&fast&&fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {//再次相遇,说明有环
break;
}
}
if (slow==NULL||fast==NULL||fast->next==NULL) {
return NULL;
}
fast = h;
while (fast != slow) {
fast = fast->next;
slow = slow->next;
}
return fast;
}
int main() {
struct Link *head,*l,*s;
int count = 0;
Link *createLink(int);
head = createLink(0);
l = head;
while (l->next) {
l = l->next;
}
l->next = head->next->next->next;//手动设置一个环
s=isLoop(head);
if (s) {
printf("链表环起始节点值为:%d",s->type.data);
}
else {
printf("该链表无环");
}
return 0;
}
就地穿插单链表节点-2019
/*
设线性表L=(a1,a2,a3,...,an-2,an-1,an),采用带头结点的单链表保存,设计一个空间复杂度为O(1)的
算法,得到L'=(a1,an,a2,an-1,a3,an-2...)
分析:
这道题还是有一点复杂的,不过我们慢慢分析它。我们可以这样来操作,我们设置两个指针slow和fast,其中fast每次走两步,
slow每次走一步,当fast到达链尾时,slow刚好处于链表中间节点,之前我们学过链表逆置,接下来我们把slow后面的节点逆置,
链表就变成了(a1,a2,a3,...,an,an-1,an-2,...),然后我们从中间开始遍历,依次将节点插入到前面节点后面,即可完成要求
*/
struct Link {
union {
int data;
}type;
struct Link *next;
};
#include <stdlib.h>
#include <stdio.h>
void crossTheLink(Link *h) {
void reverse(Link *);
struct Link *fast = h->next, *slow = h->next,*mid;
mid = (struct Link*)malloc(sizeof(struct Link));
while (fast->next&&fast->next->next) {//寻找中间节点
fast = fast->next->next;
slow = slow->next;
}
mid->next = slow->next;
reverse(mid);//逆转mid后面的节点
//slow->next = mid->next;
fast = h->next;
slow->next = NULL;
slow = mid->next;
while (slow) {//逐个进行插入
mid = slow->next;
slow->next = fast->next;
fast->next = slow;
slow = mid;
fast = fast->next->next;//因为将slow插入了,所以之前的fast下一个是现在的下两个
}
}
void reverse(Link *h) {//头插法逆置
struct Link *p = h->next,*r;
h->next = NULL;
while (p) {
r = p->next;
p->next = h->next;
h->next = p;
p = r;
}
}
int main() {
struct Link *head,*p;
Link *createLink(int);
head = createLink(0);
crossTheLink(head);
printf("交叉后的链表为:");
p = head->next;
while (p) {
printf("%d ", p->type.data);
p = p->next;
}
return 0;
}