删除单链表中的重复节点(删除多余项)

题目:如何删除单链表中的重复节点(即保证每个元素只出现一次,删除多余的,且后来出现的元素)。
一个没有排序的单链表,如 list = {a, 1, x, b, e, f, f, e, a, g, h, b, m},请去掉重复项,并保留原顺序,以上链表去掉重复项后为 newList = {a, 1, x, b, e, f, g, h, m},请写出一个高效的算法。

思路分析:
第一种方法:因为要求算法高效,且考虑到时间比空间更重要,因此使用hash法。
步骤:
(1)建立一个hash表,key为链表中已经遍历的节点内容,初始时为空。
(2)从头开始遍历单链表中的节点。
<1> 如果节点内容已经在hash表中存在,则删除此节点,继续向后遍历。
<2> 如果节点内容不在hash表中,则保留此节点,将节点内容添加到hash表中,继续向后遍历。
(3)注意点:
<1> 删除时需要知道前一节点。
<2> 此处链表中存储的是char型变量,所以哈希表为含有256个元素的数组。
<3> 如果存储的是其他数据类型,则可以使用STL中的hash_set容器。

第二种方法:如果不能使用额外的存储空间,直接在原始链表上进行操作。(非递归)
步骤:
(1)建立指针p,用于遍历链表;
(2)建立指针q,q遍历p后面的结点,并与p数值比较;
(3)建立指针r,r保存需要删掉的结点,再把需要删掉的结点的前后结点相接。由此去掉重复值。

第三种方法:递归实现。

具体实现如下:

#include <iostream>

// 单链表节点结构如下
typedef struct node
{
    char data;
    struct node *next;
} NODE;


// 尾插法创建单链表(带头节点)
NODE *createEnd(char arr[], int len)
{
    NODE *head = (NODE *)malloc(sizeof(NODE)); // 生成头节点
    head->next = NULL;
    NODE *end = head;  // 尾指针初始化

    for (int i = 0; i < len; i++) {

        NODE *p = (NODE *)malloc(sizeof(NODE)); // 为每个数组元素建立一个节点
        p->data = arr[i];
        end->next = p;  // 将节点p插入到终端节点之后
        end = p;
    }

    end->next = NULL;  // 单链表建立完毕,将终端节点的指针域置空

    return head;
}

// 单链表打印
void print(NODE *head)
{
    if (head == NULL) return;

    NODE *p = head->next;
    while (p != NULL) {
        printf("%c  ", p->data);
        p = p->next;
    }
}


// 第三种方法:递归方法
NODE *delSame_3(NODE *head)
{
    NODE *p, *temp = head; // 递归过程中head是在不断变化的,但初始时temp都指向新的head。
    if (head->next == NULL)
        return head;

    head->next = delSame_3(head->next); // 递归到head->next指向链表的尾节点;此时head指向链表倒数第二个节点。
    p = head->next; // 让p指向head的下一个节点,注意此时temp=head;

    while (p != NULL)
    {
        if (head->data == p->data) // 单次递归中,head是不变的,每次都把head的数据和head之后所有节点的数据比较,若相同,则删除该节点。
        {
            temp->next = p->next;
            free(p);
            p = temp->next;
        }
        else
        {
            p = p->next;
            temp = temp->next; // temp初始时是指向新的head的,之后作为临时变量,随着p一起后移,始终作为p的前驱节点,是为了当p节点的数据和head数据一样时,在删除p节点时,用temp->next来保存p的后继节点。
        }
    }

    return head;
}


// 第二种方法:非递归实现去重
NODE *delSame_2(NODE *head)
{
    NODE *p,*q,*r;
    p = head->next; // 适用于有头节点的单链表;对于不带头节点的单链表,此处改为 p=head 即可。
    while(p != NULL)    // p用于遍历链表
    {
        q = p;
        while(q->next != NULL) // q遍历p后面的结点,并与p数值比较
        {
            if(q->next->data == p->data)
            {
                r = q->next; // r保存需要删掉的结点
                q->next = r->next;   // 需要删掉的结点的前后结点相接
                free(r);
            }
            else
                q = q->next;
        }

        p = p->next;
    }

    return head;
}


// 第一种方法:hash表去重
NODE *delSame(NODE *head)
{
    if (head == NULL || head->next == NULL)
        return head;

    const int size = 256;
    unsigned int count[size]; // 可用 unsigned int count[size] = {0}; 将数组中每一个元素都初始化为0for (int i = 0; i < size; i++) // 可能上述方法,不一定对所有编译器都适用,所以采用循环的方式更稳妥。
        count[i] = 0;

    // count[(unsigned char)(head->data)] = 1; // 当使用没有头节点的单链表时,打开此行代码即可。

    NODE *p = head; // head是单链表的头节点,注意,头节点没有数据,它的存在仅仅是为了方便计算。
    NODE *q = p->next;
    NODE *r;
    while (q != NULL)
    {
        if (count[(unsigned char)(q->data)] == 0)
        {
            count[(unsigned char)(q->data)] = 1;
            p = q;
            q = q->next; // 始终让p作为q的前驱节点
        }
        else
        {
            r = q; // r存储要删除的节点
            p->next = q->next;
            q = p->next; // 始终让p作为q的前驱节点
            free(r);
        }
    }

    return head;

}

int main(int argc, const char * argv[]) {

    char arr[] = "a1xbeffeaghbm";
    int len = (int)strlen(arr);

    NODE *head = createEnd(arr, len);

    // 去重之前
    print(head);
    printf("\n");

    delSame(head);

    // 去重之后
    print(head);
    printf("\n");


    return 0;
}
  • 63
    点赞
  • 152
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值