顺序表与链表(二)

简介: 顺序表与链表

2. 链表


2.1 链表的概念及其结构


   基本概念:

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。这里以单链表为例,说明其特征,如图1

image.png

  • 存储空间不连续,数据元素之间使用指针相连,每个数据元素只能访问周围的一个元素;
  • 长度不固定,可以任意增删;
  • 要访问指定元素,要从头开始遍历元素,直到找到那个元素位置,时间复杂度为O(N)
  • 在指定的数据元素插入或删除不需要移动其他元素,时间复杂度为O(1);

   链表结构:

链表的结构非常多样,以下情况组合起来一共有8中链表结构:

1. 单向、双向 2. 带头、不带头 3. 循环、非循环


(注释:


单向:只包含指向下一个结点的指针,只能单向遍历;


双向:包含指向下一个结点的指针也包含指向前一个结点的指针,因此可以双向遍历;


带头:1.头结点是为了操作的统一和方便而设立的,放在链表第一个元素之前,其数据域大多无意义,但也可以用来保存链表长度。2.有了头结点,对链表头部的插入和删除操作就统一了。3.头结点不是链表的必要元素。


循环:将尾节点与首节点链接起来,形成了一个环状结构,在某些情况下会非常有用)


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


这里由于篇幅原因,只讲解第一个无头单向非循环链表.

image.png

2.2 单链表的插入(头插,尾插,指定位置插入)


1)尾插(无头结点):1.创建新结点2.判断链表是否为空3.为空则让头指针指向新结点4.否则找到最后一个指针,指向新结点;

image.png

SLTNode* BuyListNode(SLTDateType x)
{
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  if (newnode == NULL)
  {
    printf("malloc fail\n");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}
void SListPushBack(SLTNode** pphead, SLTDateType x)
{
  assert(pphead);
  SLTNode* newnode = BuyListNode(x);//创建新结点
  if (*pphead == NULL)
  {//判断链表是否为空
    *pphead = newnode;
  }
  else {
    //找到最后一个结点
    SLTNode* tail = *pphead;
    while (tail->next != NULL)
    {
      tail = tail->next;
    }
    //将最后一个结点指向新结点
    tail->next = newnode;
  }
}

2)头插(无头结点):1.创建新结点 2.新结点指向原链表 3.头指针指向新结点;(注:2,3顺序不能颠倒,否则新结点将找不到原链表的地址)

image.png

SLTNode* BuyListNode(SLTDateType x)
{
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  if (newnode == NULL)
  {
    printf("malloc fail\n");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}
void SListPushFront(SLTNode** pphead, SLTDateType x)
{
  SLTNode* newnode = BuyListNode(x);
  newnode->next = *pphead;
  *pphead = newnode;
}

3)在pos位置之前插入结点:1.创建新结点;2.判断第一个是否是其指定的位置3.如果是再来个头插,否则,找到pos的前一个位置posPrev;4将其新结点插入到pos位置之前,posPrev之后;

image.png

SLTNode* BuyListNode(SLTDateType x)
{
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  if (newnode == NULL)
  {
    printf("malloc fail\n");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}
// 在pos位置之前去插入一个节点
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
  assert(pphead);
  assert(pos);
  SLTNode* newnode = BuyListNode(x);//创建新结点
    //判断第一个是否等于pos
  if (*pphead == pos)
  {
    newnode->next = *pphead;
    *pphead = newnode;
  }
  else {
        //找到pos的前一个位置
    SLTNode* posprev = *pphead;
    while (posprev->next != pos)
    {
      posprev = posprev->next;
    }
    posprev->next = newnode;
    newnode->next = pos;
  }
}

4)在pos位置之后插入结点:1.创建新结点; 2.新结点的next指向pos的next;3.pos的next指向newnode;(注:2,3不能交换位置,否则将丢失pos之后的地址)

image.png

SLTNode* BuyListNode(SLTDateType x)
{
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
  if (newnode == NULL)
  {
    printf("malloc fail\n");
    exit(-1);
  }
  newnode->data = x;
  newnode->next = NULL;
  return newnode;
}
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
  assert(pos);
  SLTNode* newnode = BuyListNode(x);
  newnode->next = pos->next;
  pos->next = newnode;
}

2.3 单链表的删除(头删,尾删,指定位置删除)


1)尾删:1.判断链表是否为空,为空则则停止删除;2.若只有一个结点,释放其结点,让其链表为空;3.若有两个或两个以上的结点,找到最后一个和其前驱;4.其前驱next为空,释放最后一个结点;

image.png

void SListPopBack(SLTNode** pphead)
{
  assert(*pphead != NULL);
  //1.一个的情况
  if ((*pphead)->next == NULL)
  {
    free(*pphead);
    *pphead = NULL;
  }
  //2.两个或者两个以上
  else {
    SLTNode* tail = *pphead;
    while (tail->next->next != NULL)
    {
      tail = tail->next;
    }
    free(tail->next);
    tail->next = NULL;
  }
}

2)头删: 1.判断链表是否为空;2.头指针指向第一个结点的next;3.释放第一个结点;

void SListPopFront(SLTNode** pphead)
{
  assert(*pphead != NULL);//判断连表是否为空·
  SLTNode* cur = (*pphead)->next;
  free(*pphead);
  *pphead = NULL;
  *pphead = cur;
}

3)指定位置删除:1.判断链表是否为空;2.如果pos等于第一个结点,则采用头删;3.找出pos的前一个prev 4.将prev的next指向pos的next;5.释放pos;

image.png

void SListErase(SLTNode** pphead, SLTNode* pos)
{
  assert(pphead);
  assert(pos);
  if (*pphead == pos)
  {
    SListPopFront(pphead);//头删,详见上面代码
    }
  else {
    SLTNode* prev = *pphead;
    while (prev->next != pos)
    {
      prev = prev->next;
    } 
    prev->next = pos->next;
    free(pos);
  }
}

2.4 单链表的查找


SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
  SLTNode* cur = phead;
  while (cur != NULL)
  {
    if (cur->data == x)
    {
      return cur;
    }
    else {
      cur = cur->next;
    }
  }
  return NULL;
}

2.5 单链表的接口实现(供大家考察是否掌握)


// 1、无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
 SLTDateType data;
 struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);

3. 顺序表与链表的优缺点


3.1 存取方式


顺序表:可以顺序存取,也可以随机存取;

单链表:只能从表头顺序进行存取元素;

3.2 逻辑结构与物理结构


顺序表:逻辑上相邻的元素,对应的物理存储位置也相邻;

单链表:逻辑上相邻的元素,物理存储位置不一定相邻,其逻辑关系是通过指针链接来表示的;

3.3 时间性能


顺序表支持随机访问,时间复杂度为O(1);

顺序表插入和删除需要依次移动元素,时间复杂度为O(N);

单链表要访问指定元素,需要从头遍历,时间复杂度为O(N);

单链表的插入和删除不需要移动元素,时间复杂度为O(1);

3.4 空间性能


顺序表:需要预先分配存储空间,分配过多就会造成存储空间的浪费,而分配过少则会发生上溢。动态存储分配虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且若内存中没有更大的连续存储空间,则会导致分配失败。

单链表:按需分配,且元素个数不受限制

以上是顺序表与链表的相关知识点,有不足的地方或者对代码有更好的见解,欢迎评论区留言共同商讨,共同进步!!

image.png



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