【编织时空四:探究顺序表与链表的数据之旅】(上)

简介: 【编织时空四:探究顺序表与链表的数据之旅】

本章重点


  • 链表的分类
  • 带头双向循环链表接口实现
  • 顺序表和链表的区别
  • 缓存利用率参考存储体系结构 以及 局部原理性。


一、链表的分类


实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:


1. 单向或者双向



2. 带头或者不带头



3. 循环或者非循环



虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:


  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。


二、带头双向循环链表接口实现



1.申请结点:struct ListNode* BuyLTNode(LTDataType x)


动态申请结点,函数返回的是一个指针类型,用malloc开辟一个LTNode大小的空间,并用node指向这个空间,再判断是否为空,如为空就perror,显示错误信息。反之则把要存的数据x存到newnode指向的空间里面,把指针置为空。

// 申请结点
struct ListNode* BuyLTNode(LTDataType x)
{
  struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
  if (node == NULL)
  {
    perror("malloc fail");
    exit(-1);
  }
  node->data = x;
  node->prev = node;
  node->next = node;
  return node;
}


2.初始化:struct ListNode* LTInit()


// 初始化
void LTInit(struct ListNode* phead)
{
  phead = BuyLTNode(-1);
  phead->prev = phead;
  phead->next = phead;
}


我们首先看看这个初始化有什么问题嘛?


phead为空指针,说明我们的初始化没有效果,这是因为phead是函数里面的形参,出了作用域就销毁,plsit仍然是空指针,形参的改变不能影响实参,但是我们可以通过返回phead的地址解决问题。


单链表开始是没有节点的,可以定义一个指向空指针的结点指针,但是此链表不同,需要在初始化函数中创建个头结点,它不用存储有效数据。因为链表是循环的,在最开始需要让头结点的next和pre指向头结点自己。


因为其他函数也不需要用二级指针(因为头结点指针是不会变的,变的是next和pre,改变的是结构体,只需要用结构体针即可,也就是一级指针)为了保持一致此函数也不用二级指针,把返回类型设置为结构体指针类型。

// 初始化 - 改变实参plsit
struct ListNode* LTInit()
{
  struct ListNode* phead = BuyLTNode(-1);
  phead->prev = phead;
  phead->next = phead;
  return phead;
}


3.尾插:void LTPushBack(struct ListNode* phead, LTDataType x)


尾插首先要找到尾结点,再将要尾插的结点与尾结点和带头结点链接,由于是带头结点,所以此处不需要关注头结点为空的问题。

// 尾插
void LTPushBack(struct ListNode* phead, LTDataType x)
{
  assert(phead);
  struct ListNode* tail = phead->prev;
  struct ListNode* newnode = BuyLTNode(x);
  newnode->prev = tail;
  tail->next = newnode;
  newnode->next = phead;
  phead->prev = newnode;
}


4.尾删:void LTPopBack(struct ListNode* phead)


尾删只需要找到尾结点的前驱结点,再把带头结点和前驱结点链接,释放尾结点就完成了尾删。

不过这里需要处理一下只有带头结点的删除,此时真正的链表为空,此时就不能删除了。

// 尾删
void LTPopBack(struct ListNode* phead)
{
  assert(phead);
  assert(phead->next != phead);//链表为空
  struct ListNode* tail = phead->prev;
  struct ListNode* tailPrev = tail->prev;
  free(tail);
  tailPrev->next = phead;
  phead->next = tailPrev;
}


5.头插:void LTPushFront(struct ListNode* phead, LTDataType x)


头插需要注意顺序,如果先让phead和newnode链接,那么就找不到phead结点的后续结点,这样就无法让newnode和phead结点的后续结点链接。

// 头插
void LTPushFront(struct ListNode* phead, LTDataType x)
{
  assert(phead);
  struct ListNode* newnode = BuyLTNode(x);
  //顺序不可颠倒
  newnode->next = phead->next;
  phead->next->prev = newnode;
  phead->next = newnode;
  newnode->prev = phead;
}


【编织时空四:探究顺序表与链表的数据之旅】(下):https://developer.aliyun.com/article/1424881

相关文章
|
19天前
|
存储
顺序表和链表(2)
【10月更文挑战第23天】
顺序表和链表(2)
|
20天前
|
存储 算法 数据管理
顺序表和链表(1)
【10月更文挑战第22天】
|
1月前
|
存储 安全 Java
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
【用Java学习数据结构系列】探索顺序表和链表的无尽秘密(附带练习唔)pro
24 3
|
5月前
|
存储 缓存 算法
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
数据结构和算法学习记录——总结顺序表和链表(双向带头循环链表)的优缺点、CPU高速缓存命中率
49 0
|
3月前
|
存储 算法
【初阶数据结构篇】顺序表和链表算法题
此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
30 0
|
3月前
|
存储 缓存
【数据结构】——顺序表与链表
【数据结构】——顺序表与链表
|
5月前
|
存储 索引
顺序表和链表
通过以上示例,我们可以看到顺序表和链表在实际应用中如何操作。顺序表适合于需要频繁读取数据的场景,而链表则更适用于频繁修改数据的情况。在选择使用哪种数据结构时,应考虑到实际应用的需求和上下文环境。
32 2
|
5月前
|
存储 缓存
【海贼王的数据航海】链表—双向链表
【海贼王的数据航海】链表—双向链表
28 0
|
5月前
|
存储
【海贼王的数据航海】链表—单链表
【海贼王的数据航海】链表—单链表
34 0
|
5月前
|
存储 算法
数据结构和算法学习记录——线性表之单链表(上)-初始单链表及其尾插函数(顺序表缺陷、单链表优点、链表打印)
数据结构和算法学习记录——线性表之单链表(上)-初始单链表及其尾插函数(顺序表缺陷、单链表优点、链表打印)
39 0