L2-002 链表去重(25分)
题目描述
给定一个带整数键值的链表 L,你需要把其中绝对值重复的键值结点删掉。即对每个键值 K,只有第一个绝对值等于 K 的结点被保留。同时,所有被删除的结点须被保存在另一个链表上。例如给定 L 为 21→-15→-15→-7→15,你需要输出去重后的链表 21→-15→-7,还有被删除的链表 -15→15。
输入格式
输入在第一行给出 L 的第一个结点的地址和一个正整数 N( ≤ 1 0 5 10^5 105,为结点总数)。一个结点的地址是非负的 5 位整数,空地址 NULL 用 −1 来表示。 随后 N 行,每行按以下格式描述一个结点:
地址 键值 下一个结点
其中地址是该结点的地址,键值是绝对值不超过 1 0 4 10^4 104 的整数,下一个结点是下个结点的地址。
输出格式
首先输出去重后的链表,然后输出被删除的链表。每个结点占一行,按输入的格式输出。
输入样例
00100 5
99999 -7 87654
23854 -15 00000
87654 15 -1
00000 -15 99999
00100 21 23854
输出样例
00100 21 23854
23854 -15 99999
99999 -7 -1
00000 -15 87654
87654 15 -1
题目链接
思路
1,选择合适的数据结构及输入:
本题的数据结构及输入参见上一题 Reversing Linked List 一样,可以采用同样的思路来处理,不过这次可以改进上次的第二个不足之处,可以实现的堆空间的释放,不会造成内存泄漏。
对于堆空间的申请放在主函数中,将其作为参数传入,在完成操作后即可在主函数中释放,不会出现输入非链表节点的情况时造成内存释放不完全造成的内存泄漏。
后续会简单地运用到哈希表,下面再说。
#include <stdio.h>
#include <stdlib.h>
#define HASHSIZE 100001
// 数据点
typedef struct Node {
int currAdr; // 当前地址
int val;
int rearAdr; // 下个点位置
} Node;
// 链表结点
typedef struct ListNode {
Node data;
struct ListNode *next;
} List;
// 输入及链表的创建
List *listInit( int firAdr, int size, List *stack ) {
List node[size];
for (int i = 0; i < size; i ++) {
scanf("%d %d %d", &node[i].data.currAdr,
&node[i].data.val, &node[i].data.rearAdr);
node[i].next = NULL;
}
int idx = 0, value = 0, top = -1;
while ( firAdr != -1 ) {
if ( idx == size ) {
idx = 0;
}
value = node[idx].data.currAdr;
if ( value == firAdr ) {
stack[++ top] = node[idx];
firAdr = node[idx].data.rearAdr;
}
idx ++;
}
List *head = &stack[0];
for (int i = 1; i <= top; i ++) {
head->next = &stack[i];
head = &stack[i];
}
head->next = NULL;
head = &stack[0];
return head;
}
int main()
{
int firAdr, N;
scanf("%d %d", &firAdr, &N);
// 主函数中申请堆内存
List *stack = ( List * ) malloc( sizeof(List) * N );
List *l = listInit(firAdr, N, stack);
List *deletedList = listDeduplicate(l);
printList(l);
if ( deletedList ) {
printList(deletedList);
}
free(stack);
return 0;
}
2 :链表去重:
题目要求把链表中绝对值重复的键值结点删掉,且键值的绝对值不会超过 1 0 5 10^5 105 ,可以使用哈希表来进行判重,遍历整个主链表,标记每个结点值(+ 1),我们将重复的结点断开,接到另一条链表上,最后保留的主链表就是去重后的链表。具体如下:
listDeduplicate
函数接收一个指向链表的指针l
作为参数,并返回一个新的链表头节点,该链表中不包含重复的元素。
初始化一个固定大小的哈希表
hash
,大小为HASHSIZE
(未在代码中给出,但应该是一个足够大的常量),所有元素初始化为0。哈希表用于快速检查元素是否已经出现过。定义了几个指针变量:
curr
用于遍历主链表。deletedHead
用于记录去重链表的头节点。tail
用于记录去重链表的尾节点。tailTmp
用于记录尾节点的前一个节点。首先,计算第一个元素的绝对值,并在哈希表中对应的索引位置标记为1,表示该元素已经出现过。
遍历主链表的每个节点,对于当前节点的下一个节点:
- 计算其值的绝对值,并检查该值是否在哈希表中出现过(即哈希表中对应的值是否大于0)。
- 如果出现过,说明是重复的元素,需要进行处理:
- 如果
deletedHead
为NULL,说明这是第一个重复的元素,将其设为去重链表的头节点。- 否则,将重复元素从主链表中移除,即将当前节点的
next
指针指向重复元素的下一个节点。- 更新去重链表的尾节点
tail
。- 如果
tailTmp
为NULL,说明去重链表中只有一个元素,将其设为tailTmp
。- 否则,更新
tailTmp
的next
指针,使其指向新的尾节点,并更新新尾节点的rearAdr
(后继地址)。- 如果元素没有重复,更新哈希表对应的索引值为2,并继续遍历下一个节点。
遍历结束后,如果
tail
不为空,说明链表中有重复元素,需要将最后一个元素的next
指针设为NULL,并将其rearAdr
设为-1,表示链表的结束。最后,函数返回去重链表的头节点
deletedHead
。 总的来说,这段代码通过哈希表来快速检测重复元素,并在遍历过程中构建一个新的去重链表,最终返回去重链表的头节点。
代码如下:
// 主要目的,实现链表的去重,返回值为去重链表的头结点
List *listDeduplicate( List *l ) {
// 初始化哈希表的元素全部为零
int hash[HASHSIZE] = {0}, absValue = abs(l->data.val);
/* curr 记录主链表的头结点进行遍历,去重链表使用deletedHead来记录,
* tail为每次循环的去重链表的的尾结点,tailTmp记录tail的前一个结点。*/
List *curr = l, *deletedHead = NULL, *tail = NULL, *tailTmp = NULL;
hash[absValue] ++; // 在哈希表中对第一个元素进行映射,表示第一个元素已经出现过了
while ( curr->next ) {
absValue = abs(curr->next->data.val);
// 出现了重复的结点
if ( hash[absValue] ) {
if ( deletedHead == NULL ) {
deletedHead = curr->next;
}
else {
tail->next = curr->next;
}
tail = curr->next;
// 跳过当前出现重复的结点
curr->next = curr->next->next;
// 更改主链表结点的地址,若 curr->next 为零表示主链表结束,其下一个结点的地址应该标记为-1
if ( curr->next ) {
curr->data.rearAdr = curr->next->data.currAdr;
}
else {
curr->data.rearAdr = -1;
}
if ( tailTmp == NULL ) {
tailTmp = tail;
}
else {
tailTmp->next = tail;
tailTmp->data.rearAdr = tail->data.currAdr;
tailTmp = tail;
}
}
else {
curr = curr->next;
// 更新哈希表
hash[absValue] ++;
}
}
// 如果出现了重复的结点,tail必不为空
if ( tail ) {
tail->next = NULL;
tail->data.rearAdr = -1;
}
return deletedHead;
}
3,输出:
比较简单,由于选择了合适的数据结构,同时在链表去重一步已经更改了结点的地址,故可以直接输出,代码如下:
// 输出
void printList( List *head ) {
List *l = head;
while ( l->next ) {
printf("%05d %d %05d\n", l->data.currAdr, l->data.val, l->data.rearAdr);
l = l->next;
}
printf("%05d %d %d\n", l->data.currAdr, l->data.val, l->data.rearAdr);
}
全部代码
#include <stdio.h>
#include <stdlib.h>
#define HASHSIZE 100001
// 数据点
typedef struct Node {
int currAdr; // 当前地址
int val;
int rearAdr; // 下个点位置
} Node;
// 链表结点
typedef struct ListNode {
Node data;
struct ListNode *next;
} List;
// 输入及链表的创建
List *listInit( int firAdr, int size, List *stack ) {
List node[size];
for (int i = 0; i < size; i ++) {
scanf("%d %d %d", &node[i].data.currAdr,
&node[i].data.val, &node[i].data.rearAdr);
node[i].next = NULL;
}
int idx = 0, value = 0, top = -1;
while ( firAdr != -1 ) {
if ( idx == size ) {
idx = 0;
}
value = node[idx].data.currAdr;
if ( value == firAdr ) {
stack[++ top] = node[idx];
firAdr = node[idx].data.rearAdr;
}
idx ++;
}
List *head = &stack[0];
for (int i = 1; i <= top; i ++) {
head->next = &stack[i];
head = &stack[i];
}
head->next = NULL;
head = &stack[0];
return head;
}
// 主要目的,实现链表的去重,返回值为去重链表的头结点
List *listDeduplicate( List *l ) {
// 初始化哈希表的元素全部为零
int hash[HASHSIZE] = {0}, absValue = abs(l->data.val);
/* curr 记录主链表的头结点进行遍历,去重链表使用deletedHead来记录,
* tail为每次循环的去重链表的的尾结点,tailTmp记录tail的前一个结点。*/
List *curr = l, *deletedHead = NULL, *tail = NULL, *tailTmp = NULL;
hash[absValue] ++; // 在哈希表中对第一个元素进行映射,表示第一个元素已经出现过了
while ( curr->next ) {
absValue = abs(curr->next->data.val);
// 出现了重复的结点
if ( hash[absValue] ) {
if ( deletedHead == NULL ) {
deletedHead = curr->next;
}
else {
tail->next = curr->next;
}
tail = curr->next;
// 跳过当前出现重复的结点
curr->next = curr->next->next;
// 更改主链表结点的地址,若 curr->next 为零表示主链表结束,其下一个结点的地址应该标记为-1
if ( curr->next ) {
curr->data.rearAdr = curr->next->data.currAdr;
}
else {
curr->data.rearAdr = -1;
}
if ( tailTmp == NULL ) {
tailTmp = tail;
}
else {
tailTmp->next = tail;
tailTmp->data.rearAdr = tail->data.currAdr;
tailTmp = tail;
}
}
else {
curr = curr->next;
// 更新哈希表
hash[absValue] ++;
}
}
// 如果出现了重复的结点,tail必不为空
if ( tail ) {
tail->next = NULL;
tail->data.rearAdr = -1;
}
return deletedHead;
}
// 输出
void printList( List *head ) {
List *l = head;
while ( l->next ) {
printf("%05d %d %05d\n", l->data.currAdr, l->data.val, l->data.rearAdr);
l = l->next;
}
printf("%05d %d %d\n", l->data.currAdr, l->data.val, l->data.rearAdr);
}
int main()
{
int firAdr, N;
scanf("%d %d", &firAdr, &N);
List *stack = ( List * ) malloc( sizeof(List) * N );
List *l = listInit(firAdr, N, stack);
List *deletedList = listDeduplicate(l);
printList(l);
if ( deletedList ) {
printList(deletedList);
}
free(stack);
return 0;
}
/*
测试样例
00100 21 23854
23854 -15 99999
99999 -7 -1
00000 -15 87654
87654 15 -1
00100 5
99999 -7 -1
23854 -15 00000
87654 15 -1
00000 21 99999
00100 21 23854
00100 3
00010 4 10000
10000 3 -1
00100 -3 00010
00100 1
00100 1 -1
* */