当前位置:   article > 正文

详细说说链表

详细说说链表

一、链表的定义

百度百科

        链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

        链表的定义看着很复杂,实际上也不简单。简单来说,链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 逻辑上来说是连续的,由一个个结点构成,每个结点由数据域和指针域构成。

二、链表和顺序表的比较

         1.顺序存储

                线性表的顺序存储也就是我们前面学过的顺序表,是用一段物理地址连续的存储单元依次存储数据元素的顺序结构,一般用数组来实现。

        2.链式存储

                线性表的链式存储,其数据结点在内存中的位置是任意的,但在逻辑上是连续的。

逻辑顺序

    

 物理顺序

3.顺序表和链表的区别

不同点顺序表链表
存储空间物理上一定连续逻辑上连续 物理上不一定连续
随机访问支持O(1)不支持:O(N)
任意位置插入或删除元素

可能需要搬移元素

效率低

只需要修改指针指向
插入必要时扩容无容量的概念
应用场景元素高效存储 频繁访问任意位置插入和删除频繁

三、链表的结构

        链表是由一个一个结点组成的,结点一般都是在堆上申请的,从堆上申请的空间,可能连续,也可能不连续。

其中指针域是存储下一个结点的位置,实现两个结点的连接。一个一个的结点就组成了下图。

注意,箭头不是真实存在的,每一个结点也不是这么排列的,这只是我们想象出来的逻辑结构。

四、链表的实现

        1.结构体的建立

  1. typedef int DataType;//将int类型重命名 便于以后修改数据类型
  2. typedef struct SLNode {
  3. DataType value;//存储数据
  4. SLNode* next;//指针 存储下一个结点的地址
  5. }Node;

        2.各个功能模块

  1. void printNode(Node* phead);//打印链表
  2. void pushBack(Node** phead, DataType x); //尾插
  3. void pushFront(Node** head, DataType x); //头插
  4. void PopBack(Node** head); //尾删
  5. void PopFront(Node** head); // 头删
  6. SLNode* SLTFind(SLNode* head, DataType x);//查找
  7. void SLTInsert(SLNode** pphead, SLNode* pos, DataType x);//在pos位置之后插入x
  8. void SLTErase(SLNode** pphead, SLNode* pos);//删除pos位置
  9. void SLTEraseAfter(SLNode* pos);//删除pos位置之后的值
  10. void DestoryNode(Node** head);//销毁链表

        3.创建结点

  1. Node* CreateNode(DataType x)
  2. {
  3. Node* newNode = (Node*)malloc(sizeof(Node));//动态开辟Node类型的结点
  4. if (newNode == NULL)
  5. {
  6. perror("malloc");
  7. exit(-1);
  8. }
  9. //将结点初始化
  10. newNode->next = NULL;
  11. newNode->value = x;//将x赋给Node的数据域
  12. return newNode;
  13. }

4.打印链表

这里为什么传Node* phead,是因为打印链表只是对数据进行遍历,对原有的链表并没有做出修改。

  1. void printNode(Node* phead)
  2. {
  3. Node* p = phead;
  4. while (p != NULL)//当p不为空
  5. {
  6. printf("%d->", p->value);
  7. p = p->next;//每打印一次,指向下一个结点
  8. }
  9. printf("NULL\n");
  10. }

运行截图

5.尾插

这里为什么要传Node** phead?

我们可以想一想我们写过的交换两个数的值的自定义函数Swap

void Swap(int* a, int* b)

{

        int temp = *a;

        *a = *b;

        *b = temp;

}

由于形参是实参的拷贝,如果我们只是传原参而不是传地址,则在Swap函数运行完后,对原有的值并未做出修改。通过传递指针,我们可以直接访问和修改参数所指向的内存地址上的值。**phead同理,我们想要对Node结点中的*next做出修改,则需要指针的指针,也就是二级指针。

  1. void pushBack(Node** phead, DataType x)
  2. {
  3. Node* newNode = CreateNode(x);//先创建一个结点
  4. if (*phead == NULL)//判断目前是否存在结点
  5. {
  6. *phead = newNode;
  7. }
  8. else
  9. {
  10. Node* p = *phead;
  11. while (p->next != NULL)//循环到p->next = NULL时
  12. {
  13. p = p->next;
  14. }
  15. p->next = newNode;//将newNode下一个结点
  16. }
  17. }

6.头插

  1. void pushFront(Node** head, DataType x)
  2. {
  3. Node* newNode = CreateNode(x);
  4. newNode->next = *head;//直接将newNode->next 指向头结点
  5. *head = newNode;//将头结点转为newNode
  6. }

7.尾删

  1. void PopBack(Node** head)
  2. {
  3. assert(*head);
  4. if ((*head)->next == NULL)//如果只有一个结点的情况
  5. {
  6. free(*head);
  7. *head = NULL;
  8. }
  9. else
  10. {
  11. Node* p = *head;
  12. while (p->next->next != NULL)//找到尾结点的上一个结点
  13. {
  14. p = p->next;
  15. }
  16. free(p->next);
  17. p->next = NULL;
  18. }
  19. }

9.头删

  1. void PopFront(Node** head)
  2. {
  3. assert(*head);
  4. Node* p = *head;
  5. *head = (*head)->next;
  6. free(p);
  7. }

10.找到数据为X的结点

  1. SLNode* SLTFind(SLNode* head, DataType x)
  2. {
  3. SLNode* cur = head;
  4. while (cur)
  5. {
  6. if (cur->value == x)
  7. {
  8. return cur;
  9. }
  10. else
  11. {
  12. cur = cur->next;
  13. }
  14. }
  15. return NULL;
  16. }

11.在pos后面插入X

  1. void SLTInsert(SLNode** pphead, SLNode* pos, DataType x)
  2. {
  3. // 严格限定pos一定是链表里面的一个有效节点
  4. assert(pphead);
  5. assert(pos);
  6. assert(*pphead);
  7. if (*pphead == pos)
  8. {
  9. // 头插
  10. pushFront(pphead, x);
  11. }
  12. else
  13. {
  14. SLNode* prev = *pphead;
  15. while (prev->next != pos)
  16. {
  17. prev = prev->next;
  18. }
  19. SLNode* newnode = CreateNode(x);
  20. prev->next = newnode;
  21. newnode->next = pos;
  22. }
  23. }

12.删除pos位置

  1. void SLTErase(SLNode** pphead, SLNode* pos)
  2. {
  3. assert(pphead);
  4. assert(*pphead);
  5. assert(pos);
  6. if (*pphead == pos)
  7. {
  8. // 头删
  9. PopFront(pphead);
  10. }
  11. else
  12. {
  13. SLNode* prev = *pphead;
  14. while (prev->next != pos)
  15. {
  16. prev = prev->next;
  17. }
  18. prev->next = pos->next;
  19. free(pos);
  20. pos = NULL;
  21. }
  22. }

13.删除pos后面的元素

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

14.销毁链表

  1. void DestoryNode(Node** head)
  2. {
  3. Node* p = *head;
  4. while (p != NULL)
  5. {
  6. Node* tem = p;
  7. p = p->next;
  8. free(tem);
  9. }
  10. *head = NULL;
  11. }

五、总结

        链表是对我们综合实力的考量,集合了指针,结构体等综合知识,需要我们基础技能过关,四个字,熟能生巧。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/575511
推荐阅读
相关标签
  

闽ICP备14008679号