赞
踩
目录
动态内存分配(malloc)详解-CSDN博客https://blog.csdn.net/TheWhiteFox/article/details/108502906
问题:
1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?下面给出了链表的结构来看看。
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
- #define _CRT_SECURE_NO_WARNINGS 1
- #pragma once
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <assert.h>
-
- typedef int SLTDataType;
-
- typedef struct SListNode
- {
- SLTDataType data; // val
- struct SListNode* next; // 存储下一个节点的地址
- }SListNode, SLN;
-
- void SListPrint(SListNode* phead);
- void SListPushBack(SListNode** pphead, SLTDataType x);
- void SListPushFront(SListNode** pphead, SLTDataType x);
- void SListPopBack(SListNode** pphead);
- void SListPopFront(SListNode** pphead);
- SListNode* SListFind(SListNode* phead, SLTDataType x);
-
- // 在pos位置之前插入
- void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x);
- // 删除pos 位置
- void SListErase(SListNode** pphead, SListNode* pos);
-
- // 在pos之后插入
- void SListInsertAfter(SListNode* pos, SLTDataType x);
- // 删除pos位置后面的值
- void SListEraseAfter(SListNode* pos);
-
- void SListDestroy(SListNode** pphead);
注意以下几点:
1).每个节点都有一个data并且存放着下一个节点的地址;
2).单链表的每一次都要用malloc开辟一个新的空间,由于堆区每次malloc开辟的空间不一定是连续的,所以链表在物理上看上去连续,但是其实不连续,它只是在逻辑上是连续的。
3).链表有一个头指针变量,它可以说是第0个节点,称为整个单链表的头结点,其存放的是一个地址,一般不存放任何数据,只是存放第一个节点的地址,以找到该链表。
4).只有链表不为空的时候,才需要对单链表进行空间分配,需要一个节点,就开辟一个节点进行连接。
5).记住最后一个节点所存放的地址被要求为空,作为链表的结束,代表后续没有节点。
- SListNode* BuySListNode(SLTDataType x)
- {
- SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
- if (newnode == NULL)
- {
- printf("BuySListNode error\n");
- exit(-1);
- }
- else
- {
- newnode->data = x;
- newnode->next = NULL;
- }
- return newnode;
- }
- void SListPrint(SListNode* phead)
- {
- SListNode* cur = phead;
- while (cur->next != NULL)
- {
- printf("%d->", cur->data);
- cur = cur->next;
- }
- printf("NULL\n");
- }
注意:传址,头指针的改变
- //空链表的创建
- //SListNode* SList = NULL;
- //SListPushBack(SList,i)
- //void SListPushBack(SListNode* phead,SLDataType x)尾插
- //SList是一个指向SListNode的指针,phead也是一个指向SListNode的指针,属于传值传参
- //这样只是一份临时拷贝,形参的改变不会影响实参
- //c语言指针有一个这样的原则:若想改变int变量,就需要传int*变量
- //&SList,SList的地址可以这么理解,SList存放的是SListNode的地址,解引用才能得到&SList
- //所以相当于传&SList可以改变SList
- //所以若这里传址的话即SListPushBack(&SList,i)
- //pphead是指向SListNode*类型的指针,即指向指针(该指针指向SListNode类型)的指针
- //即存放指向SListNode类型的指针的地址
- //&SList是指针的地址,而该指针存放的是SListNode的地址
- void SListPushBack(SListNode** pphead, SLTDataType x)
- {
- assert(pphead);//空指针的解引用是耍流氓
- SListNode* newnode = BuySListNode(x);
- if (*pphead == NULL)//pphead接收的&SList,*pphead就是SList本身
- {
- //这就是说该链表无节点,可以直接插入
- *pphead = newnode;//现在让SList指向newnode即可插入成功
- }
- else
- {//找尾,尾插即可
- SListNode* tail = *pphead;
- while (tail->next != NULL)
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
- }
- void SListPushFront(SListNode** pphead, SLTDataType x)
- {
- assert(pphead);
- SListNode* newnode = BuySListNode(x);
- newnode->next = *pphead;//如果是空链表的时候,*pphead == NULL,先让newnode->next=NULL也是合理的
- *pphead = newnode;
- }
分情况讨论
1)单节点2)多节点3)空脸变
- void SListPopBack(SListNode** pphead)
- {
- assert(pphead);
- if (*pphead == NULL)
- {
- //空链表
- return;
- }
- else
- {
- //单节点的时候,改变了头结点
- if ((*pphead)->next == NULL)
- {
- free(*pphead);//释放第一个节点
- *pphead = NULL;
- }
- else
- {
- //多节点找尾尾删
-
- /*经典错误,
- 单链表是以尾结点指向空作为结束,free(tail)后,会让上一个结点指向野指针
- while (tail->next != NULL)
- {
- tail = tail->next;
- }
- free(tail);
- tail = NULL;*/
- SListNode* tail = *pphead;
- SListNode* prev = NULL;
- while (tail->next != NULL)
- {
- prev = tail;//记录前一个指针
- tail = tail->next;
- }
- free(tail);
- tail = NULL;
- prev->next = NULL;//单链表的结束
- }
- }
- }
- void SListPopFront(SListNode** pphead)
- {
- assert(pphead);
- if (*pphead == NULL)
- {
- return;
- }
- if ((*pphead)->next == NULL)//一个节点
- {
- free(*pphead);
- *pphead = NULL;
- }
- else//多个节点
- {
- SListNode* next = (*pphead)->next;
- free(*pphead);
- *pphead = next;
- }
- }
注意:别忘记尾结点也要查找得到才行
- SListNode* SListFind(SListNode* phead, SLTDataType x)
- {
- assert(phead);
- SListNode* cur = phead;
- while (cur != NULL)//只有这样才能检查最后一个节点
- {
- if (cur->data == x)
- {
- return cur;
- }
- cur = cur->next;
- }
- return NULL;//找不到返回NULL
- }
思考:pos位置后插入更简单!
- // 在pos位置之前插入
- void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x)
- {
- assert(pphead);
- assert(pos);
- //1.空链表
- if (*pphead == NULL)
- {
- return;//空链表都没有pos位置,无意义
- }
- //2.单节点
- else if ((*pphead)->next == NULL)
- {
- SListPushFront(pphead, x);//pphead是SList的地址
- }
- //3.多节点
- else
- {
- SListNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
- SListNode* newnode = BuySListNode(x);
- prev->next = newnode;
- newnode->next = pos;
- }
- }
-
- //在pos位置之后插入
- void SListInsertAfter(SListNode* pos, SLTDataType x)
- {
- assert(pos);
- //SListNode* next = pos->next;
- //SListNode* newnode = BuySListNode(x);
-
- //pos->next = newnode;
- //newnode->next = next;
-
- SListNode* newnode = BuySListNode(x);
- newnode->next = pos->next;
- pos->next = newnode;
- }
- // 删除pos 位置
- void SListErase(SListNode** pphead, SListNode* pos)
- {
- assert(pphead);
- assert(pos);
- if (*pphead == NULL)
- {
- return;
- }
- else if ((*pphead) == pos)
- {
- SListPopFront(pphead);
- }
- else
- {
- SListNode* cur = *pphead;
- while (cur->next != pos)
- {
- cur = cur->next;
- }
- cur->next = pos->next;
- free(pos);
- pos = NULL;
- }
- }
- void SListEraseAfter(SListNode* pos)
- {
- assert(pos);
-
- SListNode* next = pos->next;
- if (next)
- {
- pos->next = next->next;
- free(next);
- next = NULL;
- }
- }
同样注意尾结点也需要销毁。
- void SListDestroy(SListNode** pphead)
- {
- assert(*pphead);
- SListNode* cur = *pphead;
- SListNode* next = NULL;
- while (cur)//这样才能free掉尾结点
- {
- next = cur->next;
- free(cur);
- cur = next;
- }
- *pphead = NULL;
- }
多动手画图!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。