std::list 循环删除指针_数据结构和算法分析 2.5循环链表

回到家之后,多花时间陪陪家人,多干家务,少学习b06f17298874fb6ea9ad73164be80ad4.png

从单链表升级到双链表之后,很多操作一下子就变得简单容易了。 

但有一个很麻烦的问题始终没有解决:如何从任意结点出发通过遍历访问所有结点?

下面的循环链表就是这个问题的解决方案。

循环单链表

8ef687b37e32dccddcb2c91ec3f9c474.png将单链表尾结点的指针由 NULL 改为指向头结点,整个链表形成一个环。从表中任一结点出发均可找到链表中其他结点。

「循环单链表」「非循环单链表」的不同之处在于:

  • 循环单链表没有 NULL 指针
  • p 所指节点为尾节点的条件:p->next == L

例 1

某线性表最常用的操作是:在尾元素之后插入一个元素和删除第一个元素

采用下列哪种存储方式最节省运算时间

A. 单链表

B. 仅有头结点指针的循环单链表

C. 双链表

D. 仅有尾结点指针的循环单链表

四种结构删除第一个元素的时间复杂度都是 O(1),现在仅需考虑在尾结点后插入新元素的操作。要在尾结点之后插入元素首先要找到尾结点

  • 单链表遍历整个链表找到尾结点的时间复杂度为 O(n)
  • 仅有头结点的指针的循环单链表,尽管首尾相连,但只有一个指向头结点的指针,仍要通过遍历链表来找到尾结点,插入的时间复杂度为 O(n)
  • 双链表,尽管可以反向遍历,但首尾没有相连,仍需要遍历链表来找到尾结点,插入的时间复杂度也为 O(n)
  • 仅有尾结点指针的循环链表找到尾结点的时间复杂度为 O(1)
插入删除
单链表O(n)O(1)
仅有头结点指针的循环单链表O(n)O(1)
双链表O(n)O(1)
仅有尾结点指针的循环单链表O(1)O(1)

「仅有尾结点指针的循环链表」的插入函数和删除函数如下所示

voidlistInsert( node **l, int e ){
  node *s;
  s = ( node* )malloc( sizeof( node ) );
  s->data = e;
  s->next = (*l)->next;
  (*l)->next = s;
  *l = s;
}
voidlistDelete( node *l ){
  node *t;
 t = l->next;
  l->next = l->next->next;
  free( t );
}

循环双链表

601fe5b13a6d78709951604b2337e49f.png在双向链表的基础上修改头结点和尾结点的指针域,形成两个环。

「循环双链表」「非循环双链表」的不同之处:

  • 循环双链表没有 NULL 指针
  • p 所指结点为尾结点的条件:p->next == L
  • 由L可以直接找到尾结点:L->prior

例 2

如果含有 n( n > 1 )个元素的线性表的运算只有 4 种

  1. 删除第一个元素
  2. 删除尾元素
  3. 在第一个元素前面插入新元素
  4. 在尾结点的后面插入新元素

最好使用下列哪个存储方式

A. 只有尾结点指针没有头结点的循环单链表

B. 只有尾结点指针没有头结点的非循环双链表

C. 只有首结点指针没有尾结点指针的循环双链表

D. 既有头指针又有尾指针的循环单链表

和上面的例1类似,抓住四个操作的本质,分析它们在对应的存储结构下工作的时间复杂度

  1. A选项的结构删除尾元素时要通过遍历找到尾元素之前的结点
  2. B选项的结构对第一个元素操作时先要通过遍历找到头结点
  3. C选项的结构完成4个操作的时间复杂度都是O(1)
  4. D选项的结构删除尾元素时要通过遍历找到尾元素之前的结点
1234
AO(1)O(n)O(1)O(1)
BO(n)O(1)O(n)O(1)
CO(1)O(1)O(1)O(1)
DO(1)O(n)O(1)O(1)
可见 「只有首结点指针没有尾结点指针的循环双链表」 的效率最高,对应的函数如下所示
voidheadDelete( node **l ){
  node *t = *l;
  (*l)->prior->next = (*l)->next;
  (*l)->next->prior = (*l)->prior;
  *l = (*l)->next;
  free( t );
}
voidtailDelete( node *l ){
  node *t = l->prior;
  l->prior->prior->next = l;
  l->prior = l->prior->prior;
  free( t );
}
voidlistInsert( node *l, int e ){
  node *s;
  s = ( node* )malloc( sizeof( node ) );
  s->data = e;
  s->next = l;
  l->prior->next = s;
  s->prior = l->prior;
  l->prior = s;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`std::vector` 和 `std::list` 是两种不同的数据结构,各有自己的特点和适用场景。以下是它们之间的一些主要区别: 1. 存储方式:`std::vector` 是一个动态数组,它使用连续的内存块来存储元素,可以通过索引快速访问元素。而 `std::list` 是一个双向链表,它的元素通过指针链接在一起,不能通过索引直接访问元素,需要遍历链表来访问。 2. 插入和删除操作:由于 `std::vector` 使用连续的内存存储元素,插入和删除元素可能涉及到内存的重新分配和元素的移动,因此在特定位置进行插入和删除操作的成本较高。而 `std::list` 作为链表,在任意位置进行插入和删除操作的成本都是常数时间复杂度,不涉及元素的移动。 3. 访问和查找操作:由于 `std::vector` 使用连续的内存存储元素,可以通过索引直接访问元素,也可以通过指针进行迭代,因此在访问和查找操作上比较高效。而 `std::list` 作为链表,访问和查找操作需要遍历整个链表,时间复杂度较高。 4. 内存分配:由于 `std::vector` 使用连续的内存存储元素,它在内存分配上是连续的,有利于 CPU 缓存的利用。而 `std::list` 的内存分配是离散的,可能导致额外的内存碎片。 根据上述区别,可以根据实际需求选择合适的数据结构。如果需要频繁进行随机访问、插入和删除操作不频繁,并且对内存占用有限制,可以使用 `std::vector`。如果需要频繁进行插入和删除操作、访问和查找操作相对较少,并且不关心内存占用,可以使用 `std::list`。当然,在特定情况下,也可以根据具体需求选择其他数据结构,如 `std::deque` 或 `std::forward_list`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值