单链表练习题-删除无序单链表中所有的重复元素(两种方法,C语言实现)
一、前言
与前一篇单链表练习题-删除有序单链表中的重复元素(C语言实现)不同,本次实现的是删除单链表中所有的重复元素,无论数据是否有序。
二、两种实现方式与优劣
2.1 使用哈希存储
哈希存储是一种典型的以空间换时间的方法,之前用它来统计数字出现的次数,参见:C语言统计数字出现的次数
所以其优点是:时间复杂度为O(n)
缺点是空间复杂度较高,尤其是数据比较稀疏的时候,显然有点浪费内存。
实现代码如下:
bool Del_All_duplicate1(LinkList *L)
{
int arr[N] = {0};//初始化为0
LinkList *pre = L->next;
LinkList *p = L->next;
while(p->next!=NULL)
{
if(arr[p->data]>0)//如果这个元素已经出现过了,那就删除p指向的结点
pre->next = p->next;
else
{
arr[p->data]++;//该元素是第一次出现就加一
pre = p;
}
p = p->next;
}
return true;
}
如上,用数组arr存储数字出现的次数,我这里定义了N为100,所以数据输入时应该不大于99,用了一个pre方便执行删除操作。只用了一个while循环就可以实现删除所有重复的元素,因为这是边遍历边删除。时间复杂度为O(n)。
2.2 使用两重循环
思路:定义两个指针,p表示外循环,q表示内循环,同样用一个pre记录前驱方便删除。外循环p正常遍历链表,例如当p指向第一个元素时,令pre等于p,令q指向第二个元素,依次往后扫描是否有出现与p值相等的情况,如果有即删除,没有内循环就继续往后遍历,直到链表末尾,当外循环也走到末尾时,循环结束,此时已经得到了不重复的序列。
优点:省内存 缺点:由于是双重循环,所以时间复杂度较高,为O(n2)
实现代码如下:
bool Del_All_duplicate2(LinkList *L)
{
LinkList *p = L->next;
LinkList *q,*pre;//用pre是为了方便删除操作
while(p->next!=NULL)//外循环
{
pre = p;
q = p->next;
while(q->next!=NULL)//内循环
{
if(q->data==p->data)
pre->next = q->next;//删除q指向的结点
//q后移的同时也修改pre
pre = q;
q = q->next;//q后移
}
p = p->next;//p后移
}
return true;
}
三、全部代码
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h> //根据C99标准,C语言使用bool类型需要添加这个头文件
#define N 100 //定义哈希表的最大长度,使用这个方法时数据最大不得超过99
typedef int ElemType;
typedef struct LinkNode
{
ElemType data;
struct LinkNode *next;
}LinkList;
//------------ 函数声明 ----------//
void MainMenu();
bool InitLinkList(LinkList *L);//初始化
bool InsertLinkList(LinkList *L,ElemType e);//插入
void PrintAll(LinkList *L);//输出所有元素
bool Del_All_duplicate1(LinkList *L);//删除重复元素--哈希表
bool Del_All_duplicate2(LinkList *L);//删除重复元素--双重循环
//------------ 函数声明 ----------//
int main()
{
LinkList L;
int ch;
ElemType element;
if(InitLinkList(&L))
printf("初始化成功!\n");
else
printf("初始化失败!\n");
while(1)
{
MainMenu();
printf("请输入您要执行的操作:");
scanf("%d",&ch);
switch(ch)
{
case 0: printf("感谢使用,已退出!"); exit(0); break;
case 1: printf("请输入您要插入的元素:\n");
scanf("%d",&element);
if(InsertLinkList(&L,element))
printf("插入成功!\n");
else
printf("插入失败!\n");
break;
case 2: PrintAll(&L);
break;
case 3: if(Del_All_duplicate1(&L))
printf("删除成功!\n");
else
printf("删除失败!\n");
break;
default: printf("您输入的操作指令有误!请重新输入!");
}
}
return 0;
}
//主菜单,显示
void MainMenu()
{
printf("\n\n\n");
printf("\t **** 删除单链表中所有的重复元素 ****\n\n");
printf("\t ------- 0.退出 \n\n");
printf("\t ------- 1.插入元素\n\n");
printf("\t ------- 2.输出所有元素\n\n");
printf("\t ------- 3.删除重复元素\n\n");
printf("\t *************************************\n");
}
//初始化单链表(带头结点)
bool InitLinkList(LinkList *L)
{
//先申请一个头结点
LinkList *head = (LinkList *)malloc(sizeof(LinkList));
L->next = head;
head->next = NULL;//头结点之后一开始还没元素
return true;
}
//插入
bool InsertLinkList(LinkList *L,ElemType e)
{
//头插法
LinkList *p = L;//
//申请一个新的结点
LinkList *s = (LinkList *)malloc(sizeof(LinkList));
s->data = e;//赋值
//修改指针 将结点s插入到结点p之后
s->next = p->next;//s指针指向
p->next = s;
return true;
}
void PrintAll(LinkList *L)
{
LinkList *q;
q = L->next;
//打印出所有元素
while(q->next!=NULL)
{
printf("%d ",q->data);
q = q->next;
}
}
bool Del_All_duplicate1(LinkList *L)
{
int arr[N] = {0};//初始化为0
LinkList *pre = L->next;
LinkList *p = L->next;
while(p->next!=NULL)
{
if(arr[p->data]>0)//如果这个元素已经出现过了,那就删除p指向的结点
pre->next = p->next;
else
{
arr[p->data]++;//该元素是第一次出现就加一
pre = p;
}
p = p->next;
}
return true;
}
bool Del_All_duplicate2(LinkList *L)
{
LinkList *p = L->next;
LinkList *q,*pre;//用pre是为了方便删除操作
while(p->next!=NULL)//外循环
{
pre = p;
q = p->next;
while(q->next!=NULL)//内循环
{
if(q->data==p->data)
pre->next = q->next;//删除q指向的结点
//q后移的同时也修改pre
pre = q;
q = q->next;//q后移
}
p = p->next;//p后移
}
return true;
}
四、测试
1.先随便输入几次元素,得到一个序列:
2.接着执行删除重复元素操作:
3.查看删除的结果:
经测试,两种方法的结果都正确。