C语言手撕实战代码_单链表
题目清单:
| 单链表习题|
|--|
| 1.使用头插法构建带头结点的单链表 |
| 2.使用尾插法构建带头结点的单链表|
| 3.在第i个数据结点前插入元素e |
|4.设计算法高效查找带头单链表倒数第m个位置(m个正整数)的结点并输出该结点值 |
| 5.已知指针La和Lb分别指向两个无头结点的单链表。编写函数完成从La中删除第j个元素开始的共len个元素,并将这len个元素插入到表Lb中第j个元素之前 |
| 6.设单链表表头指针为L,节点数据域为字符。设计时间复杂度最低的算法判断前n/2个字符是否与后n/2字符依次相同,例如:xyx和xyxy中前一半字符与后一半字符是否相同|
|7.La和Lb按值非递减,归并La和Lb,得到新的单链表Lc,使得Lc也按值非递减且不含重复元素,并占用原来的空间 |
|8.带头单链表中所有元素的数据值按递增顺序排列,删除链表中大于min且小于max的元素 |
|9.设计一个算法,判断La是否为Lb的子链,子链的定义为:La中的从前到后的所有节点的数据域都按照原有顺序出现在Lb上 |
| 10.两个单词有相同的后缀时可共享相同的后缀存储空间,例如“act”和“dicf”,如下图所示,设La和Lb分别指向两个单词所在单链表的头结点,链表结点结构(data,next),设计算法查找公共后缀的起始位置。如图中标注的阴影数据结点部分分为"act"和“dict”公共后缀的起始位置|
1.使用头插法构建带头结点的单链表
构建大体思路,
写一个函数构建单链表(链表引用头,int数组,数组长度)
也就是说通过先读入数组,然后再构建单链表
#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;
typedef struct LinkNode
{
ElemType data;
struct LinkNode* next;
}LinkNode,*LinkList;
void creat_linklist_byinserthead(LinkList &L,int enterArra[],int enterArraLength)
{
//先定义头结点
L=(LinkNode *)malloc(sizeof(LinkNode));
L->next=NULL;
//循环头插结点
int index=0; //第一个数组下标
while(index<enterArraLength)
{
LinkNode *p=(LinkNode *)malloc(sizeof(LinkNode));
p->data= enterArra[index++];
p->next=L->next;
L->next=p;
}
}
void printLink(LinkList L)
{
LinkList p=L->next;
while (p) {
printf("%d->",p->data);
p=p->next;
}
printf("NULL\n");
}
int main()
{
LinkList L=NULL; //声明一个头指针
int enterArra[100]={
0};
int enterlength=0;
printf("请输入数组序列:\n");
int enterData=999999;//读入结束标记是999999,当读入999999时,输入就结束,循环就结束
while (scanf("%d",&enterData)&&999999>enterData) {
enterArra[enterlength++]=enterData;
} //结束数组的读入
creat_linklist_byinserthead(L, enterArra, enterlength);
printLink(L);
printf("\n");
return 1;
}
2.使用尾插法构建带头结点的单链表
void creat_linklist_byinserttail(LinkList &L,int enterArra[],int enterArraLength)
{
//先定义头结点
L=(LinkNode *)malloc(sizeof(LinkNode));
L->next=NULL;
//循环尾插结点
int index=0; //第一个数组下标
LinkList tail=L;
while(index<enterArraLength)
{
LinkNode *p=(LinkNode *)malloc(sizeof(LinkNode));
p->data= enterArra[index++];
tail->next=p;
tail=p;
}
tail->next=NULL;
}
3.在第i个数据结点前插入元素e
void insert_before_i(LinkList &L,int k,int e) //在第k个元素前插入,移动k-1下就插入
{
LinkNode *p=L;
for(int i=0;i<k-1;i++)
{
p=p->next;
} // 找到插入点
LinkNode *x=(LinkNode*)malloc(sizeof(LinkNode));
x->data=e;
x->next=p->next;
p->next=x;
}
4.设计算法高效查找带头单链表倒数第m个位置(m个正整数)的结点并输出该结点值
void getdata(LinkList L,int m)
{
LinkNode *cur=L->next;
LinkNode *pre=L->next;
while (m--) {
cur=cur->next;
}
while(cur!=NULL)
{
pre=pre->next;
cur=cur->next;
}
printf("元素是%d\n",pre->data);
}
5.已知指针La和Lb分别指向两个无头结点的单链表。编写函数完成从La中删除第j个元素开始的共len个元素,并将这len个元素插入到表Lb中第j个元素之前
分析:
本题中值得积累的点,在于人为加入头结点,来进行头结点的让控制链表的操作更简单。
至于这道题就是按步骤来就行
主函数
//定义两个现成的单链表La和Lb
int a[8]={
1,2,3,4,5,6,7,8};
int b[8]={
9,10,11,12,13,14,15,16};
LinkList La=NULL;
LinkList Lb=NULL;
creat_linklist_byinserttail(La, a, 8);
creat_linklist_byinserttail(Lb, b, 8);
printLink(La);
printLink(Lb);
operator_linklist(4, 3, La, Lb);
printLink(La);
printLink(Lb);
自定义函数
void operator_linklist(int j,int len,LinkList &La,LinkList &Lb)
{
//首先是从给La和Lb加上头结点
LinkNode* headLa=(LinkNode*)malloc(sizeof(LinkNode));
LinkNode* headLb=(LinkNode*)malloc(sizeof(LinkNode));
headLa->next=La;
headLb->next=Lb;
int count=j; //
LinkNode* p1=headLa;
//找到La第j个元素指向位置
while (count--) {
p1=p1->next;
} //循环结束后此时的p1指向第j个元素的前一个元素
count=len;
LinkNode* p2=p1;
while(count--)
{
p2=p2->next;
} // p2指向删除链上的最后一个元素
printf("第%d个元素的前一个元素是%d\n",j,p1->data);
printf("删除链的最后一个元素是%d\n",p2->data);
LinkNode* p3=p1; p1=p1->next; //p1指向删除链的第一个元素,p2指向删除链的最后一个元素,p3指向删除链的前一个元素
p3->next=p2->next; //完成La的断链
p2->next=NULL;
//记录L的第j个元素的前一个元素,和第j个元素
count=j-1;
LinkNode* p4=Lb;
while (count--) {
p4=p4->next;
} //循环结束后此时的p4指向第j个元素的前一个元素
LinkNode* p5=p4->next; //p5记录指向j个元素
p4->next=p1;
p2->next=p5;
free(headLa);
free(headLb);
}
6.设单链表表头指针为L,节点数据域为字符。设计时间复杂度最低的算法判断前n/2个字符是否与后n/2字符依次相同,例如:xyx和xyxy中前一半字符与后一半字符是否相同
思路:
利用快慢指针找到中间结点,然后依次从前往后对比就完了
7.La和Lb按值非递减,归并La和Lb,得到新的单链表Lc,使得Lc也按值非递减且不含重复元素,并占用原来的空间
思路:
La和Lb从头开始遍历,用尾插法建立单链表,然后利用尾插法建立单链表Lc,若元素和Lc表尾元素不同,加入Lc,相同就下一个。
void no_repeat_insert(LinkList &tailLc, LinkNode *&pnode)
{
// 判断元素是否重复, 使用尾插法插入
if (tailLc == NULL || tailLc->data != pnode->data)
{
tailLc->next = pnode;
tailLc = tailLc->next; // 尾插法,一直指向表尾
}
pnode = pnode->next; // 后移
}
void combineSortedLink(LinkList &Lc, LinkList &La, LinkList &Lb)
{
LinkNode *pcurLa = La;
LinkNode *pcurLb = Lb;
LinkNode *tailLc = Lc;
// 只有在两个链表都不为空的情况下,才能进行比较合并
while (pcurLa != NULL && pcurLb != NULL)
{
if (pcurLa->data <= pcurLb->data)
{
no_repeat_insert(tailLc, pcurLa);
}
else
{
no_repeat_insert(tailLc, pcurLb);
}
}
// 如果La有剩余,继续插入
while (pcurLa != NULL)
{
no_repeat_insert(tailLc, pcurLa);
}
// 如果Lb有剩余,继续插入
while (pcurLb != NULL)
{
no_repeat_insert(tailLc, pcurLb);
}
// 最后将链表末尾置空,确保链表的完整性
tailLc->next = NULL;
}
本题积累,
1.用函数的过程中,别忘了&,指针变量也要&,才能进行实际操作。
2.pcurLb != NULL和!pcurLb作为while循环的条件,意义不同
pcurLb != NULL是说它不是空指针
!pcurLb意思是只要pcurLb是空的就进行循环,完全是相反的语句。
8.带头单链表中所有元素的数据值按递增顺序排列,删除链表中大于min且小于max的元素
设计数据:
1.全部小于min或全部大于max,返回原先链表
2.有大于min小于max的部分,删除后返回
void delete_min_max(LinkList &head,int min,int max)
{
LinkNode*cur=head->next;
LinkNode*pre=head;
while(cur!=NULL)
{
if(cur->data>min&&cur->data<max)
{
pre->next=cur->next;
cur=cur->next;
}else
{
cur=cur->next;
pre=pre->next;
}
}
}
9.设计一个算法,判断La是否为Lb的子链,子链的定义为:La中的从前到后的所有节点的数据域都按照原有顺序出现在Lb上
思路核心:
遍历Lb,每当Lb的第一个结点与La的第一个结点相同时,比较随后的结点是否相同,若不相同则返回Lb的下一个结点,知道Lb结点遍历完,以此类推。
设计数据:
La是Lb子链的情况:
{1,2,3};
{1,2,1,2,2,1,2,3};
La不是Lb子链的情况:
int isSubLinkList(LinkList La, LinkList Lb)
{
LinkNode *pb = Lb;
LinkNode *pa;
while (pb != NULL)
{
printf("循环开始时pb指向:%d\n",pb->data);
// 每次新的匹配尝试时,重置 pa 到 La 的头部
pa = La;
LinkNode *falg = pb; // 记录当前的起始位置
printf("记录当前的起始位置flag:%d\n",falg->data);
// 进入匹配过程
while (pa != NULL && pb != NULL && pa->data == pb->data)
{
pa = pa->next;
pb = pb->next;
}
if (pa == NULL) // 如果 La 的所有节点都匹配完毕
{
printf("匹配成功\n");
return 1; // 匹配成功
}
// 如果匹配失败,重置 pb 到 falg 的下一个节点,继续检查
pb = falg->next;
}
return 0; // 匹配失败
}
注意以上代码是不带头结点的单链表,我要提醒自己了,敲代码的时候忘了我定义的单链表是带头结点的了,然后一直调试失败。我要提醒我自己了。
10.两个单词有相同的后缀时可共享相同的后缀存储空间,例如“act”和“dicf”,如下图所示,设La和Lb分别指向两个单词所在单链表的头结点,链表结点结构(data,next),设计算法查找公共后缀的起始位置。如图中标注的阴影数据结点部分分为"act"和“dict”公共后缀的起始位置
本题的本质就是力扣上的相交链表
本题思想:
求出La,Lb的长度,然后找到较长的那个,两个链表做差,得到链表差,然后让较长的链表先移动差值个单位,然后两个链表就等长了。就可以一直后移找公共的后缀。
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(headA == NULL || headB == NULL) return NULL; // 如果任一链表为空,返回 NULL
struct ListNode * pa=headA;
struct ListNode * pb=headB;
int length_la=0;
int length_lb=0;
int chazhi=0;
while(pa->next!=NULL)
{
length_la++;
pa=pa->next;
}
while(pb->next!=NULL)
{
length_lb++;
pb=pb->next;
}
printf("%d\n",length_lb);
pa=headA;
pb=headB;
if(length_la>=length_lb)
{
chazhi=length_la-length_lb;
while(chazhi--)
{
pa=pa->next;
}
}else
{
chazhi=length_lb-length_la;
printf("%d\n",chazhi);
while(chazhi--)
{
pb=pb->next;
}
}
while(pa!=NULL&&pb!=NULL)
{
printf("pa指向%d\n",pa->val);
printf("pb指向%d\n",pb->val);
if(pa==pb)
{
return pa;
}
pa=pa->next;
pb=pb->next;
}
return NULL;
}
本题注意点是最后比较的时候,是pa\==pb不是pa->next==pb->next