当前位置:   article > 正文

[数据结构]----链表(无头单向非循环链表的实现)_无头节点单向链表

无头节点单向链表

目录

1. 顺序表的缺陷

2. 链表

2.1 链表的概念

2.2.链表的结构

2.3 链表的分类

3. 无头单向非循环链表

3.1 链表的结构体定义

3.2 动态创建一个链表新结点

3.3 打印链表

3.4 尾插和尾删

3.5 头插和头删

3.6 找到x值的地址

3.7 在pos前面/后面插入一个数

3.8 删除某个位置/删除某个位置的后一个位置

3.9 销毁链表


1. 顺序表的缺陷

1. 中间/头部的插入删除,时间复杂度为O(N)


2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。


3. 增容一般是呈2倍的增长,势必会有一定的空间浪费

2. 链表

2.1 链表的概念

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

2.2. 链表的结构

 

 链表结构是有一个Data存储值/数据,有一个结构体指针存放下一个结点的地址

2.3 链表的分类

1.单向链表或双向链表

 2.带头(哨兵位)链表或不带头(哨兵位)链表

 3.循环链表或不循环链表

 每种类型都有两种不同的结构,所以一共能出8种(2*2*2)不同的结构

虽然链表的种类众多,但是我们主要用两种链表

无头单向非循环链表带头双向循环链表

1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构


2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势

3. 无头单向非循环链表

 

 注:为什么有的函数传递二级指针

        因为当链表的头结点需要被改变时,需要传递头结点的地址,以此才能改变头结点 

 

3.1 链表的结构体定义

  1. typedef int DataType;
  2. typedef struct Slist
  3. {
  4. DataType data;
  5. struct Slist* next;
  6. }SL;

3.2 动态创建一个链表新结点

函数声明:

SL* BuyListNode(DataType x);

 在VS中malloc一个内存块时,注意检查一下malloc是否成功,最后返回malloc出来的结点地址

函数定义:  

  1. SL* BuyListNode(DataType x)
  2. {
  3. SL* newnode = (SL*)malloc(sizeof(SL));
  4. if (newnode == NULL)
  5. {
  6. printf("malloc errno\n");
  7. exit(-1);
  8. }
  9. newnode->data = x;
  10. newnode->next = NULL;
  11. return newnode;
  12. }

3.3 打印链表

函数声明:

void SlPrint(SL* phead);

注:当while循环中是cur != NULL时,最后cur会指向链表最后一个元素存储的NULL

       当while循环中是cur->next !=NULL时,最后cur会指向链表的最后一个元素

函数定义:  

  1. void SlPrint(SL* ps)
  2. {
  3. SL* cur = ps;
  4. //while (cur)
  5. while (cur != NULL)
  6. {
  7. printf("%d->",cur->data);
  8. cur = cur->next;
  9. }
  10. printf("NULL\n");
  11. }

3.4 尾插和尾删

 尾插

函数声明: 

void SlPushBack(SL** pphead, DataType x);
无头单向非循环链表尾插需要通过遍历才能找到链表的尾,有些麻烦
尾插时,分两种情况:

1.链表中还没有存储数据,此时链表的头结点是NULL
   此时直接将malloc出来的结点给头结点

2.链表中已经有数据,此时链表的头结点不是NULL
    此时需要先找到尾结点,然后再把malloc出来的结点给tail->next 

函数定义:  

  1. void SlPushBack(SL** pphead, DataType x)
  2. {
  3. assert(pphead);
  4. SL* newnode = BuyListNode(x);
  5. if (*pphead == NULL)//1
  6. {
  7. *pphead = newnode;
  8. }
  9. else
  10. {
  11. //找尾
  12. SL* tail = *pphead;//2
  13. while (tail->next != NULL)
  14. {
  15. tail = tail->next;
  16. }
  17. tail->next = newnode;
  18. }
  19. }

尾删

函数声明: 

void SlPopBack(SL** pphead);

首先需要检查传进函数的指针不为空

尾删时,分两种情况:
1.链表中只有一个结点

  此时直接free掉这个结点,然后再让它为空

2.链表中不只有一个结点

  此时需要找到尾结点,同时也需要记录尾结点的前一个结点,最后让前一个节点的指向NULL

(原因:若只找到尾结点并释放掉的话,尾结点的前一个结点还指向原来的尾结点地址,如果打印时此时会造成指针的非法访问)

  

函数定义: 

  1. void SlPopBack(SL** pphead)
  2. {
  3. assert(*pphead);
  4. if ((*pphead)->next == NULL)
  5. {
  6. free(*pphead);
  7. *pphead = NULL;
  8. }
  9. else
  10. {
  11. SL* end = *pphead;
  12. SL* prve = NULL;
  13. while (end->next)
  14. {
  15. prve = end;
  16. end = end->next;
  17. }
  18. free(end);
  19. end = NULL;
  20. prve->next = NULL;
  21. }
  22. }

3.5 头插和头删

头插

函数声明: 

void SlPushFront(SL** pphead, DataType x);

 只需要检查一下指针不为NULL,然后将malloc出来的结点直接插入

函数定义:  

  1. void SlPushFront(SL** pphead, DataType x)
  2. {
  3. assert(pphead);
  4. SL* newnode = BuyListNode(x);
  5. newnode->next = *pphead;
  6. *pphead = newnode;
  7. }

头删

函数声明:

void SlPopFront(SL** pphead);

需要检查一下指针不为NULL,记录第二个结点地址,释放第一个结点,将第二个结点地址给头结点

函数定义:  

  1. void SlPopFront(SL** pphead)
  2. {
  3. assert(*pphead != NULL);
  4. assert(pphead);
  5. SL* next = (*pphead)->next;
  6. free(*pphead);
  7. *pphead = next;
  8. }

3.6 找到x值的地址

函数声明: 

SL* SlFind(SL* phead, DataType x);

遍历链表,找到返回结点地址,找不到返回NULL 

函数定义:  

  1. SL* SlFind(SL* phead, DataType x)
  2. {
  3. assert(phead);
  4. while (phead)
  5. {
  6. if (phead->data == x)
  7. {
  8. return phead;
  9. }
  10. else
  11. {
  12. phead = phead->next;
  13. }
  14. }
  15. return NULL;
  16. }

3.7 在pos前面/后面插入一个数

在pos前面插入一个数

函数声明:

void SlInsertBefore(SL** pphead, SL* pos, DataType x);

 1.pos是头结点,此时就是头结点的前插

 2.在其他位置前插一个数,需要记录pos位置的前一个结点的位置

函数定义:  

  1. void SlInsertBefore(SL** pphead, SL* pos, DataType x)
  2. {
  3. assert(*pphead);
  4. assert(pos);
  5. if (*pphead == pos)
  6. {
  7. SlPushFront(pphead,x);
  8. }
  9. else
  10. {
  11. SL* prev = *pphead;
  12. while (prev->next != pos)
  13. {
  14. prev = (*pphead)->next;
  15. }
  16. SL* newnode = BuyListNode(x);
  17. newnode->next = pos;
  18. prev->next = newnode;
  19. }
  20. }

在pos后面插入一个数

函数声明:

void SlInsertAfter(SL* pos, DataType x);

 只直接改变两个结点的next指针即可

函数定义:  

  1. void SlInsertAfter(SL* pos, DataType x)
  2. {
  3. SL* newnode = BuyListNode(x);
  4. newnode->next = pos->next;
  5. pos->next = newnode;
  6. }

3.8 删除某个位置/删除某个位置的后一个位置

删除某个位置 :

函数声明:

void SlErase(SL** pphead, SL* pos);

1. pos是头结点时,调用头删函数

2. pos是其他结点,通过遍历找到pos的前一个结点,将pos前一个结点的next指向pos的下一个结点,最后free掉pos

函数定义:  

  1. void SlErase(SL** pphead, SL* pos)
  2. {
  3. assert(*pphead);
  4. assert(pos);
  5. if(*pphead == pos)
  6. {
  7. SlPopFront(pphead);
  8. }
  9. else
  10. {
  11. SL* cur = *pphead;
  12. while (cur->next != pos)
  13. {
  14. cur = cur->next;
  15. }
  16. cur->next = pos->next;
  17. free(pos);
  18. pos = NULL;
  19. }

删除某个位置的后一个位置

函数声明:

void SListEraseAfter(SL* pos);

记录pos下两个结点的地址

将pos下两个结点的地址给pos->next 

函数定义:  

  1. void SListEraseAfter(SL* pos)
  2. {
  3. assert(pos);
  4. assert(pos->next);
  5. SL* next = pos->next;
  6. pos->next = next->next;
  7. free(next);
  8. next = NULL;
  9. }

3.9 销毁链表

函数声明:

void SlDestory(SL** pphead);

通过遍历链表释放每个结点

函数定义: 

  1. void SlDestory(SL** pphead)
  2. {
  3. assert(pphead);
  4. SL* cur = *pphead;
  5. while (cur)
  6. {
  7. SL* next = cur->next;
  8. free(cur);
  9. cur = next;
  10. }
  11. *pphead = NULL;
  12. }

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号