一维数据结构——链表

上一篇介绍了一维数据结构的关系,算是开篇了数据结构这个系列。这篇详细说一下链表的实现。
链表实现也是我面试时喜欢出的一道题,因为有层次,能提现区分度。看似简单的三个操作,需要处理的异常和边界情况并不少,能一次完美写对并不容易。
而且这里还有一个哨兵的概念,合理运用能减少边界处理,是一个递进的层次,可为候选人增加区分度。

链表的操作:
(1)SEARCH(x):链表的搜索
(2)INSERT(i,x):链表的插入,在第i个位置插入x
(3)DELETE(x):链表的删除

哨兵(sentinel):
为了减少边界条件的判断(是否为空链表等等),引入哨兵,使得链表永远不为“空”。

下面就细讲一下链表实现

插入

上面说了为了减少边界,我们使用了一个哨兵的概念,那么都有哪些边界呢?

1.不用哨兵

插入的核心操作如下:

// pre: 前置节点; p: 当前第i个节点
// item: 待插入节点
pre.next -> item;
item.next -> p;

进行插入,就需要知道两个节点指针,这时就出现了两个边界情况,是不存在前置节点的:

  • 链表为空
  • 插入位置是0

需要对两种情况做特殊处理,直接看代码

void insert(i,item) {
  // 空链表的处理
  if (head == NULL) {
    head == item;
  }

  // 插入到第一个位置的情况
  if (i == 0) {
    // 插入
    head.next -> item;
    item.next -> head;
    head = item;
  } else {
     // 第一个节点
    Node *pre = head;
    Node *p = head -> next;
    int idx = 0;
    // 添加 p 非空条件,处理 i > length 情况 
    while (idx < i && p != NULL) {
      pre = pre -> next;
      p = p -> next;
    }
    // 插入
    pre.next -> item;
    item.next -> p;
  }
}

处理下来27行,进行了两次if-else判断

2.使用哨兵

但如果引入哨兵概念,会怎么样呢?

void insert(i,item) {
  // 获取哨兵和实际上第一个节点
  Node *pre = sentinel;
  Node *p = sentinel -> next;
  int idx = 0;
  // 添加 p 非空条件,处理 i > length 情况 
  while (idx < i && p != NULL) {
    pre = pre -> next;
    p = p -> next;
  }
  // 插入
  pre.next -> item;
  item.next -> p;
}

可以看到,整个操作简单归一,14行就搞定了,没有额外边界处理逻辑处理,也不需要再进行头结点变更,哨兵的内存开销也不大。是一个优雅的解决方式。

再看看剩下两个方法的实现细节

搜索

比较简单,没有对前置节点的依赖,所以无需做特殊处理。

Node* search(x) {
    Node *p = head;
    // Node *p = sentinel -> next;

    while (p != NULL) {
        if (*p.data == x) {
            break;
        }
        p = p -> next;
    }

    return p;
}

删除

先简单介绍下两种链表删除操作

1.依赖前置节点pre

1>将 pre->next 指向 found->next
2>去除 found->next指向
3>删除found节点


方法一

但是对于单链表操作,拿到前置节点非常困难,为了避免使用前置节点,我们可以使用一次拷贝,将删除节点从found变成found->next,比如:

2.不依赖前置节点 pre

1>将next节点内容copy到found
2>found->next指向next->next
3>删除next


方法二

顺利避免了对前置节点依赖,还可以复用上面的 search 函数。但是这种设计,存在一个问题:就是当删除的是最后一个节点时,依然有对前置节点的依赖。这时引入一个末尾哨兵,保证我们的 next 节点永远不为 NULL,让我们避免这种情况。

需要对插入、查找做修改

3.引入末尾哨兵

init() {
  head_sentinel = new Node();
  tail_sentinel = new Node();
  head_sentinel->next = tail_sentinel;
  tail_sentinel->next=NULL:
}

void insert(i,item) {
  // 获取头部哨兵和实际上第一个节点
  Node *pre = head_sentinel;
  Node *p = head_sentinel -> next;
  int idx = 0;
  // 添加 p ≠ tail_sentinel,处理 i > length 情况 
  while (idx < i && p != tail_sentinel) {
    pre = pre -> next;
    p = p -> next;
  }
  // 插入
  pre.next -> item;
  item.next -> p;
}

Node* search(x) {
    Node *p = head_sentinel -> next;

        // 结束条件,变为不等于末尾哨兵
    while (p != tail_sentinel) {
        if (*p.data == x) {
            break;
        }
        p = p -> next;
    }

    return p;
}

void delete(x) {
    Node *found = search(x);
    if (found == NULL) return;

    Node *next = found->next;
    *found.data = copy(*next.data);
        // 因为有末尾哨兵的存在,next永远不为NULL
    found->next = next->next;
    next->next = NULL;
    delete(next);
}

小结

综上,哨兵的作用就是保持链表非空,将所有操作去差异化,减少边界条件的处理。从广义上来说,链表的操作过程中产生了对“邻居节点”的依赖,当这种依赖是不稳定的时候,我们就可以使用哨兵,将这种依赖补齐,或者是转化为对“哨兵”的稳定依赖。
此外,在删除过程中,因为单链表获取前置节点困难,所以针对该点进行优化,这也是很多数据结构和算法调优的思路。熟悉后可以举一反三。

参考

https://www.jianshu.com/p/afbfc784238a
https://www.zhihu.com/question/27155932

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

推荐阅读更多精彩内容