算法-链表之简单算法题

上一阶段的字符串系列之后很长时间都没有更新文章,现在接着来,本阶段是链表系列的,链表系列的算法题我不会再用大量篇幅写一个问题的解决方案,针对一个问题写一个解决方案,所以篇幅会小很多。这篇文章会介绍和链表相关的简单算法题,后面还会介绍复杂算法题。

  • 添加/删除结点
  • 从尾到头打印链表
  • 翻转单链表
  • 在O(1)时间内删除链表结点

1.添加/删除结点

我们都知道,链表的添加和删除操作相比数组是很方便的,因为链表不要求结点的物理顺序和逻辑顺序相同,所以添加删除结点的时候不需要像数组一样移动大量的结点,借助指针,修改指针指向,我们就可以很方便的实现添加和删除结点的操作。

添加/删除结点是链表类算法题中比较简单的操作了,不会有太大问题。主要是在注意细节,添加/删除的时候要注意判断链表是否为空,如果链表为空,就要修改头指针的指向,也就是修改头指针指向的地址。

代码

链表的数据结构的定义:
注意:这些声明中包含了一个自引用结构的列子。在定义结构ListNode之前,已经定义了指向该结构的指针。C语言允许定义指向尚不存在的类型的指针。

typedef struct ListNode *list_pointer;
typedef struct ListNode
{
    int value;
    list_pointer link;
};
list_pointer pHead;

在链表尾添加节点
有在前端,中间插入结点的,也有在尾部,这里的例子是在链表尾插入结点。注意,如果链表为空,那么添加结点需要修改头指针的指向,所以这里接收的参数是头指针的地址。

//pHead是指向指针的指针  ListNode** p
void addToTail(list_pointer *pHead, int value){

     list_pointer node = (list_pointer)malloc(sizeof(ListNode));
     if (node == NULL)
     {
        fprintf(stderr, "Faile\n");
        exit(1);
     }
    node->value = value;
    node->link = NULL;

    if (*pHead == NULL)
    {
    *pHead = node;
    }
    else{
        list_pointer p = *pHead;
        while (p->link != NULL){
            p = p->link;
        }
        p->link = node;
   }
}

删除中间结点:
删除指定值的结点,我们不知道该结点在什么位置,所以需要遍历链表找到结点。

//如果删除首节点,那么需要改变首节点指针的指向
bool deleteNode(list_pointer *pHead, int value){
    if (*pHead == NULL)
    {
        fprintf(stderr, "The linklist is empty!\n");
        exit(1);
     }
    ListNode *node = NULL;
    if ((*pHead)->value == value){//删除首节点
        node = *pHead;
        *pHead = (*pHead)->link;
        free(node);
        return true;
    }
    else{
        list_pointer p = *pHead;
        while (p->link != NULL && p->link->value != value){
            p = p->link;
        }
        if (p->link != NULL && p->link->value == value)
        {
            node = p->link;
            p->link = p->link->link;
            free(node);
        }
    }

}

2.从尾到头打印链表

看到这到道题的第一反应是从头到尾打印链表会比较简单,所以我们可以改变链表的指针指向,但是这样会改变链表原来的结构,是否允许改变链表的结构,这个取决于面试官。这里的例子是不改变链表结构。

算法思想

从尾到头打印链表也就是说先存入的元素后输出,后存入的先输出,和栈“后进先出”的思想一样,所以我们可以在遍历链表的时候先将元素存入栈中,再循环遍历栈输出元素。
想到了使用栈,而递归的本质就是使用栈存储,那么我们使用递归也能实现同样的效果,下面的例子就是用递归实现的。
代码:

void PrintListReversingly(list_pointer pHead) {
    list_pointer p = pHead;
    if (p) {
        if (p->link) {
            PrintListReversingly(p->link);
        }
        printf("链表元素:%d\n", p->value);      
    }
}

使用递归代码会简洁很多,但是当链表非常长的时候,函数调用的层级会很深,可能会导致函数调用栈溢出,显式的使用栈基于循环来遍历的鲁棒性会好一些。

3.翻转单链表

题目:写一个函数,输入链表的头结点,翻转该链表,并返回翻转后的链表的头结点。

算法思想

翻转链表

看上面的图,如果是对结点i翻转链表,就是改变i的link的指向,将它指向前一个结点h,但是这样会导致i指向j的链丢失,所以我们需要存储下这个值,也就是说,在一次改变指针指向的操作中,我们需要存储下前一个结点h,后一个结点j,和当前结点i。此外,我们还需要明确,在翻转了链表之后,原来的头结点变成了尾结点,而现在的尾节点呢,就是NULL。分析完这些之后很容易写出代码。

代码

//翻转链表
list_pointer Invert(list_pointer pHead)
{
    list_pointer middle, trail;
    middle = NULL;
    //当pHead指向原链表的最后一个结点的link时,退出循环
    //此时middle刚好指向原链表的最后一个结点
    while (pHead) {
        trail = middle;
        middle = pHead;//此时middle和pHead指向的地址相同
        pHead = pHead->link;
        middle->link = trail;
    }
    return middle;
}

4.在O(1)时间内删除链表结点

常规的思维,删除链表结点需要知道它的前一个结点,简单的三句代码,就是判断p->link->value == value,然后修改p->link = p->link->link,释放p->link.这样就需要遍历链表,知道要删除的结点的前一个,时间复杂度为O(n)。如果换一种思路呢?

算法思想

假设结点h,i,j是链表中相邻的3个结点,现在要删除i,可以进行下面3步:

  1. 将结点j的值复制到结点i上;
  2. 修改i的link;
  3. 释放结点j。
    现在需要考虑一下特殊情况下是否也满足。当删除的节点是头结点时,需要修改头结点的link指针。当删除的是尾结点时,它没有下一个结点,所以需要遍历链表,得到它的前一个结点。当链表只有一个结点时,删除的结点是头结点(尾节点),需要将头指针的指向置为NULL。

代码

//在O(1)内删除一个结点
void DeleteNode(list_pointer *pHead, ListNode *node)
{
    if (!(*pHead) || !node)
        return;

    //要删除的不是尾结点
    if (node->link)
    {
        ListNode *pNext = node->link;
        node->value = pNext->value;
        node->link = pNext->link;
        free(pNext);
    }
    //链表中只有一个结点删除头结点,也是尾结点
    else if (*pHead == node)//node->link为NULL
    {
        free(node);
        *pHead = NULL;
    }
    else//链表中有多个结点,删除的是尾结点
    {
        list_pointer p = *pHead;
        while (p->link != node)
        {
            p = p->link;
        }
        p->link = NULL;
        free(node);
    }
}

总结

这些题难度都不大,使用指针很灵活,但是也很容易出错,主要是关注细节。这篇就到这里了,不足之处,还请多指教~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容