一、题目描述:
二、算法思想:
考虑到遍历链表的时间开销比较大,题目中仅要求实现最大的时间效率,所以可以采用用空间换时间的方法,具体操作就是用一个数组存储已经出现的数字,这样只需要遍历一次链表即可。
三、核心代码:
struct Link*rmv_cf(struct Link*str1,int n)
{
int i;
int j=1;
int A[n+1];//定义一个数组用来存储所有可能的值
int p;//用来标志是否在记录数组中命中记录
Link*start=str1;//保存链表指针
Link*tmp=NULL;//用来暂存要删除的节点指针
A[0]=str1->data;//第一个值不可能重复,直接加入A[]。
while(str1->link!=NULL)//遍历链表,同时对每个节点在A[]中查找,当前指针不是指向最后节点则循环
{ //如果存在则删除节点,不存在则将值加入A[]。
p=0;//每次循环p更新为0
for(i=0;i<n;i++)
{
if(str1->link->data==A[i])//在A[]中查找到值,删除节点。
{
tmp=str1->link;//暂存要删除的节点指针
str1->link=str1->link->link;//将指针往后移动
p=1;//将标志置为1,表示命中
free(tmp);//释放该节点所占内存
break;//停止在A[]中是匹配
}
}
if(p==0)//如果未在A[]中查找到值,
{
A[j]=str1->link->data;//则将值添加到A[]
// printf("A[%d]=%d\n",j,A[j]);
j++;
//将指针往后移动
if(str1->link!=NULL)//确定当前指针不是指向最后节点,可以移动
str1=str1->link;//链表指针往后移动
else//当前指针已经指向最后一个节点,无法往后移动,也无需循环,直接退出
break;//细心的亲可能会问,为啥不判断最后一个节点呢?
//其实我们在匹配的时候是使用if(str1->link->data==A[i]),
//不是使用str1->data,也就说当指针指向倒数第二个节点时
//就在判断最后一个节点的数据是否在A[]中匹配,为啥要这样?
//因为我们这是单链表,如果我们使用str1->data判断当前节点,
//是无法通过改变指针,直接实现删除的,因为当前的节点的前驱
//无法直接读取,必须重新遍历。
}
}
return start ;
}
四、完整代码:
#include<stdio.h>
#include<stdlib.h>
int listlen(struct Link*head);/*求链表长度函数*/
struct Link*rmv_cf(struct Link*str1,int n);/*找到共同后缀的起始地址*/
struct Link*AppendNode(struct Link*head,int c);/*在链表末尾添加一个节点*/
void DeleteMemory(struct Link*head);/*删除动态分配的内存空间*/
void DisplyLink(struct Link*head);/*遍历链表*/
/*定义链表节点*/
struct Link{
char data;
struct Link*link;
};
int main()
{
int n;
int i;
int c;
struct Link*str1=NULL;//定义str1的头节点指针
//建立链表str1
printf("please input the number of the node: ");
scanf("%d",&n);
printf("Please input the data:");
for(i=0;i<n;i++)
{
scanf("%d",&c);
str1=AppendNode(str1,c);//向head为头指针的链表末尾添加节点
}
printf("the List is:");
DisplyLink(str1);//打印初始链表
printf("After rmv_cp:");
DisplyLink(rmv_cf(str1,n));//打印删去重复元素的链表
//释放链表所占的内存
DeleteMemory(str1);
return 0;
}
/*求链表长度函数*/
int listlen(struct Link*head){
int len=0;
while(head->link!=NULL){
len++;
head=head->link;
}
return len;
}
/*删除链表中的重复元素*/
struct Link*rmv_cf(struct Link*str1,int n)
{
int i;
int j=1;
int A[n+1];//定义一个数组用来存储所有可能的值
int p;//用来标志是否在记录数组中命中记录
Link*start=str1;//保存链表指针
Link*tmp=NULL;//用来暂存要删除的节点指针
A[0]=str1->data;//第一个值不可能重复,直接加入A[]。
while(str1->link!=NULL)//遍历链表,同时对每个节点在A[]中查找,当前指针不是指向最后节点则循环
{ //如果存在则删除节点,不存在则将值加入A[]。
p=0;//每次循环p更新为0
for(i=0;i<n;i++)
{
if(str1->link->data==A[i])//在A[]中查找到值,删除节点。
{
tmp=str1->link;//暂存要删除的节点指针
str1->link=str1->link->link;//将指针往后移动
p=1;//将标志置为1,表示命中
free(tmp);//释放该节点所占内存
break;//停止在A[]中是匹配
}
}
if(p==0)//如果未在A[]中查找到值,
{
A[j]=str1->link->data;//则将值添加到A[]
// printf("A[%d]=%d\n",j,A[j]);
j++;
//将指针往后移动
if(str1->link!=NULL)//确定当前指针不是指向最后节点,可以移动
str1=str1->link;//链表指针往后移动
else//当前指针已经指向最后一个节点,无法往后移动,也无需循环,直接退出
break;//细心的亲可能会问,为啥不判断最后一个节点呢?
//其实我们在匹配的时候是使用if(str1->link->data==A[i]),
//不是使用str1->data,也就说当指针指向倒数第二个节点时
//就在判断最后一个节点的数据是否在A[]中匹配,为啥要这样?
//因为我们这是单链表,如果我们使用str1->data判断当前节点,
//是无法通过改变指针,直接实现删除的,因为当前的节点的前驱
//无法直接读取,必须重新遍历,这是实现这个算法的关键细节。
}
}
return start ;
}
//函数功能:新建一个节点并添加到链表末尾,返回添加节点后的表头指针
struct Link*AppendNode(struct Link*head,int data)
{
struct Link*p=NULL;
struct Link*pr=head;
p=(struct Link*)malloc(sizeof(struct Link));
if(p==NULL)
{
printf("no enough memory to allocate!\n");
exit(0);
}
if(head==NULL)
{
head=p;
}
else
{
while(pr->link!=NULL)
{
pr=pr->link;
}
pr->link=p;
}
p->data=data;
p->link=NULL;
return head;
}
//释放head指向链表中的所有节点所占用的内存
void DeleteMemory(struct Link*head)
{
struct Link*p=head;
struct Link*pr=NULL;
while(p!=NULL)
{
pr=p;
p=p->link;
free(pr);
}
}
//显示链表中所有节点的节点好和该节点中的数据项内容
void DisplyLink(struct Link*head)
{
struct Link*p=head;
int j=1;
while(p!=NULL)
{
printf("->[%d--%d] ",j,p->data);
p=p->link;
j++;
}
printf("\n");
}
五、测试分析:
时间复杂度O(nlgn),空间复杂度O(n)