头文件是为了后面初始化和打印链表的时候不用重复定义,实现在t1.c和t2.c两个文件中
#ifndef T2_H
#define T2_H
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
// 定义一个链表结点的结构体
typedef int ElemType;
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
// 初始化带头结点的指针
bool InitList_withHead(LinkList *L);
// 初始化不带头结点的指针
bool InitList_withoutHead(LinkList *L);
// 打印一个带头结点的指针
void printfLinkList2(LinkList L);
// 打印一个不带头结点的链表
void printfLinkList(LinkList L);
#endif
- 在带头结点的单链表L中删除所有值为x的结点,并释放其空间,假设值为x的结点不唯一
#include "t2.h"
// init
bool InitList_withHead(LinkList *L){
*L=(LNode *)malloc(sizeof(LNode));
if(*L==NULL)
return false;
(*L)->next=NULL;
return true;
}
void delete_x_node2(LinkList *L,ElemType x){
LNode *p=(*L)->next;//指向下一个结点
LNode *q=*L;//指向前一个结点
if(p==NULL)
return;
while(p!=NULL){
if(p->data==x){
q->next=p->next;
free(p);
p=q->next;
}else{
q=q->next;
p=p->next;
}
}
}
void printfLinkList2(LinkList L){
if(L->next==NULL)
return;
LNode *p=L->next;
while(p!=NULL){
printf("%d->",p->data);
p=p->next;
}
printf("\n");
}
void test2(){
LinkList L;
InitList_withHead(&L);
LNode *p=L;
for(int i=0;i<4;i++){
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=i+1;
s->next=NULL;
p->next=s;
p=p->next;
}
printfLinkList2(L);
delete_x_node2(&L,1);
printfLinkList2(L);
}
// void main(){
// test2();
// }
- 设计一个递归算法,删除不带头结点的单链表L所有值为x的结点
#include"t2.h"
// init
bool InitList_withoutHead(LinkList *L){
(*L)==NULL;
return true;
}
void delete_x_node(LinkList *L,ElemType x){
if(*L==NULL)//如果当前是一个空表,直接返回true;
return;
if((*L)->data==x){//如果当前节点的值是x,进行删除
LNode *p=*L;
(*L)=p->next;
free(p);
delete_x_node(L,x);//递归调用
}
delete_x_node(&((*L)->next),x);//递归调用
}
void printfLinkList(LinkList L){
LNode *p=L;
while(p!=NULL){
printf("%d->",p->data);
p=p->next;
}
printf("\n");
}
void test1(){
LinkList L;
InitList_withoutHead(&L);
LNode *p=L;
for(int i=0;i<4;i++){
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=i+1;
s->next=NULL;
if(i==0){//第一个结点的插入,不带头结点的链表需要特殊处理
L=s;
p=L;
}
else{
p->next=s;
p=p->next;
}
}
printfLinkList(L);
delete_x_node(&L,1);
printfLinkList(L);
}
// void main(){
// test1();
// }
- 设L为带头结点的单链表,编写算法实现从尾到头反向输出每一个结点
#include "t2.h"
// 实际使用中,我们可以直接先编写一个函数,去掉头指针结点,再进行递归
void Invert_printf(LinkList L,LNode *p){
if(p==L&&p->next==NULL)//表示空链表
return;
if(p->next==NULL){//链表最后一个结点
printf("%d->",p->data);
return;
}
Invert_printf(L,p->next);
if(p!=L)//排除掉带头结点干扰
printf("%d->",p->data);
return;
}
// void main(){
// LinkList L;
// InitList_withHead(&L);
// LNode *p=L;
// for(int i=0;i<4;i++){
// LNode *s=(LNode *)malloc(sizeof(LNode));
// s->data=i+1;
// s->next=NULL;
// p->next=s;
// p=p->next;
// }
// p=L;
// printfLinkList2(L);
// Invert_printf(L,p);
// }
- 在带头结点的单链表L中删除一个最小值结点的高效算法,假设最小值结点是唯一的
#include "t2.h"
bool delete_min(LinkList *L,ElemType x){
if((*L)->next==NULL)//空表
return false;
LNode *q=*L;//指向最小节点的前一个结点
LNode *p=*L;//动态寻找最小值结点
ElemType min=p->next->data;
while(p->next!=NULL){
if(p->next->data<min){
q=p;
}
p=p->next;
}
p=q->next;
q->next=p->next;
free(p);
return true;
}
- 将带头结点的单链表就地逆置,所谓“就地”是指辅助空间复杂度为O(1)
#include "t2.h"
// 遍历找到尾结点,然后从头结点的位置开始逐个删除头结点,把删除的结点逐个头插法到尾结点
bool InvertLinkList(LinkList *L){
if((*L)->next==NULL)
return true;
LNode *s=*L;//表示要删除的结点
LNode *q=*L;//尾结点
while(q->next!=NULL){
q=q->next;
}
// 注意倒置后q结点就是第一个节点,所以循环结束的条件就是!=q
while((*L)->next!=q){
// 删除头结点
s=(*L)->next;
(*L)->next=s->next;
// 头插法插入结点
s->next=q->next;
q->next=s;
}
return true;
}
// 尾插法建立单链表
void List_TailInsert(LinkList *L){
int e;
LNode *s,*r=*L;
scanf("%d",&e);
while(e!=999){
s=(LNode *)malloc(sizeof(LNode));
s->data=e;
r->next=s;
r=s;
scanf("%d",&e);
}
// 如果这里不添加NULL,则r的后面就会随意指向一个地址,将出现大问题
r->next=NULL;
}
void test5(){
LinkList L;
InitList_withHead(&L);
List_TailInsert(&L);
printfLinkList2(L);
InvertLinkList(&L);
printfLinkList2(L);
}
// void main(){
// test5();
// }
解法2思路:解法1容易出现循环结束条件的错误,解法2就是将头结点单独摘下,其他结点作为一个单独链表,因为这个链表空间本身存在的,所以空间复杂度还是O(1),然后从第一个节点开始逐个删除不带头结点的单链表(拆下的新链表)的头结点,头插法插入到L中。
解法3思路:直接法next指针域修改为前一个指针,简单明了,只是需要多来几个节点指针LNode而已。
- 有一个带头结点的单链表L,设计一个算法使其元素插入时递增有序
#include "t2.h"
// 循环找到当前链表中比x大的前向节点,执行在此前向节点后插入结点操作。这种算法的时间复杂度为
void incleaseOrder(LinkList *L,ElemType x){
LNode *p=*L;//指向当前要插入的结点的前向结点
while(p->next!=NULL){
if(p->next->data>x)//如果p的后一个结点的数据大于x,直接结束循环
break;
p=p->next;
}
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=x;
s->next=p->next;
p->next=s;
}
void insert(LinkList *L){
ElemType x=0;
scanf("%d",&x);
while(x!=999){
incleaseOrder(L,x);
scanf("%d",&x);
}
}
void test6(){
LinkList L;
InitList_withHead(&L);
insert(&L);
printfLinkList2(L);
}
void main(){
test6();
}
- 有一个带头结点的单链表L,设计一个算法使其元素递增有序(人话就是给单链表递增排序,一开始理解错写成上面的代码了)
时间复杂度为O(n*n)
#include "t2.h"
// 将当前链表切分成两个,头结点指向的链表动态维护一个递增有序的链表,而另一个切分的链表用来逐个增加头结点指向的链表
void sort(LinkList *L){
LNode *p,*s;//p用来获取一个需要增加的节点,s是为了防止第二个链表断链
if((*L)->next==NULL)//空链表
return;
s=(*L)->next;
(*L)->next=NULL;
while(s!=NULL){
p=s;
s=s->next;
incleaseOrder(L,p);//在L中按序插入结点p
}
}
void incleaseOrder(LinkList *L,LNode *s){
LNode *p=*L;//指向当前要插入的结点的前向结点
while(p->next!=NULL){
if(p->next->data>s->data)//如果p的后一个结点的数据大于x,直接结束循环
break;
p=p->next;
}
s->next=p->next;
p->next=s;
}
// 尾插法建立单链表
// void List_TailInsert(LinkList *L){
// int e;
// LNode *s,*r=*L;
// scanf("%d",&e);
// while(e!=999){
// s=(LNode *)malloc(sizeof(LNode));
// s->data=e;
// r->next=s;
// r=s;
// scanf("%d",&e);
// }
// // 如果这里不添加NULL,则r的后面就会随意指向一个地址,将出现大问题
// r->next=NULL;
// }
void test6(){
LinkList L;
InitList_withHead(&L);
List_TailInsert(&L);
printfLinkList2(L);
sort(&L);
printfLinkList2(L);
}
void main(){
test6();
}
-
设一个带头结点的单链表中所有元素结点数据值都无序,试编写一个函数,删除表中所有介于给定两个值(作为函数参数给出)之间的元素的元素结点(若存在)
思路:遍历单链表,动态维护一个当前结点的前向结点,如果当前结点的元素值满足要求,就删除,时间复杂度为O(n) -
给定两个单链表,编写算法找到两个链表的公共结点
思路:暴力破解,遍历一个链表,每次遍历得到链表值都到另一个链表中遍历寻找是否有对应的值,时间复杂度为O(n*m),这种思路不能说不对,只能说没理解题目的精髓,题目说的是两个链表有公共结点,不仅仅是结点的值相同,而且指针域也要相同,换句话就是遍历找到第一个公共结点后,后面的节点就是重合的,两条链表就完全汇聚成一条,类似于Y结构。因此基于这种精髓的理解,代码写起来复杂度就低很多。
#include "t2.h"
LinkList sameNode(LinkList *L,LinkList *S){
int len1=0;//L长度
int len2=0;//S长度
LNode *p=*L;
LNode *q=*S;
// 分别计算两个链表的长度
while(p->next!=NULL||q->next!=NULL){
if(p->next!=NULL){
len1++;
p=p->next;
}
if(q->next!=NULL){
len2++;
q=q->next;
}
}
p=*L;
q=*S;
// 链表长度的差值,长的先遍历,因为公共链表长度一定是相等的
while(len1>len2&&p->next!=NULL){
p=p->next;
len1--;
}
while(len1<len2&&q->next!=NULL){
q=q->next;
len2--;
}
// 相同长度为0
if(p->next==NULL||q->next==NULL){
return NULL;
}
while(p->next!=NULL){
if(p->next->data==q->next->data){
return p;
}else{
p=p->next;
q=q->next;
}
}
return NULL;
}
有感:算法不是要到出错误才去修改,而是在思考问题的时候,就已经把可能出现的错误给考虑到位,这是需要学习的。