缺陷:
1.空间不够了,增容。(增容会带来缺陷,增容会付出一定的性能消耗,其次可能存在一定的空间浪费)
例如:当我有一百个空间,满了之后,增容扩大两倍,但是我插入之后,只插入了10个数据,所以浪费了90个空间。
2.头部或者中部左右的插入删除的效率低。->O(N)
如何解决:
1.空间上,按需给空间。
2.不要求物理空间连续,头部和中部的插入,就不需要挪动数据。
解决方案:链表
1.单向链表:单向链表的特点是链表的链接方向是单向的,对链表的访问需要通过顺序读取从头部开始。
2.双向链表:双向链表也叫双链表,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。
3.循环链表:循环链表的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。
4.线性链表:线性链表是一种简单的链表结构,每个结点只包含一个指向下一个结点的指针。
5.树链表:树链表是一种层次化的链表结构,它的每个结点可以有多个子结点,通过指针连接。
6.网链表:网链表是一种复杂一点的链表结构,它的每个结点可以有多个指向前驱和后继的指针。
等等…
本次内容讲的是单向链表。
注意:最后一个指针应该指向空指针。
链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
从逻辑结构看:链表是线性的,是想象出来的。
从物理结构看:
第二个空间存的是第二个节点的地址。
节点的地址是malloc来的,也可以认为是随机的,malloc是操作系统随机找的一个空闲的空间。
最后一个节点存的是空指针。
顺序表和链表的区别就是:
物理上是连续的存储的,链表的物理空间是不连续的。
链表的定义:
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;//结构体中不能有结构体嵌套,但是可以是结构体指针,
//指针就是一个地址,表示4个字节,指针指向的下一个节点是结构体
}SLT;
注意:结构内部是不能用typedef的结构体类型。
注意:这里的SLT类型是上文定义的结构体类型
void SListPrint(SLT* plist)
{
SLT* cur=plist;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
SLT* BuySListNode(SLTDataType x)
{
SLT* newnode = (SLT*)malloc(sizeof(SLT));
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//尾插
void SListPushBack(SLT** pplist, SLTDataType x)
{
SLT* newnode = BuySListNode(x);
if (*pplist == NULL)
{
*pplist = newnode;//形参是实参的零时拷贝,出了定义域会销毁
}
else
{
SLT* tail = *pplist;
while (tail->next != NULL)
{
tail = tail->next;
}
//尾节点,链接新节点
tail->next = newnode;
}
}
//头插
void SListPushFront(SLT** pplist, SLTDataType x)
{
SLT* newnode = BuySListNode(x);
newnode->next = *pplist;
*pplist = newnode;
}
//尾删
void SListPopBack(SLT** pplist)
{
if (*pplist == NULL)
{
return;
}
else if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
return;
}
SLT* tail = *pplist;
SLT* prev = NULL;//保存上一个节点的指针变量
while (tail->next != NULL)
{
prev = tail;//将上一个节点存起来
tail = tail->next;//找下一个节点
}
free(tail);//free掉下一个空间
prev->next = NULL;//找到倒数第二个节点指向的下一个节点,并置为空指针。防止变为野指针
tail = NULL;//置位空指针
}
//头删
void SListPopFront(SLT** pplist)
{
SLT* tail = (*pplist)->next;//将下一个节点的位置给存起来
free(*pplist);//将这个节点给free掉
*pplist = NULL;//置空
*pplist = tail;//找到下一个节点,并将其赋值为头结点
}
//查找链表数据
SLT* SListFind(SLT* plist, SLTDataType x)
{
SLT* cur = plist;
while (cur != NULL)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//在pos位置前插入x
void SListInsert(SLT** pplist, SLT* pos, SLTDataType x)
{
if (*pplist == pos)
{
SListPushFront(pplist, x);
}
else
{
SLT* newnode = BuySListNode(x);
SLT* prev = *pplist;
while (prev->next != pos)
{
prev = prev->next;
}
newnode->next = pos;
prev->next = newnode;
}
}
//删除pos位置的数
void SListErase(SLT** pplist, SLT* pos)
{
if (pos == *pplist)
{
SListPopFront(pplist);
}
else
{
SLT* prev = *pplist;
while (prev->next != pos)
{
prev = prev->next;
}
SLT* tail = pos->next;
free(pos);
prev->next = tail;
}
}
void TestSlist3()
{
SLT* plist = NULL;
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPushFront(&plist, 4);
SListPrint(plist);
SLT* pos = SListFind(plist, 4);
if (pos)
{
SListInsert(&plist, pos, 30);
}
printf("\n");
SListPrint(plist);
}
int main()
{
TestSlist3();
return 0;
}
注意:删除pos位置的数也与上述代码雷同
#pragma once//防止重复包含
#include
#include
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;//结构体中不能有结构体嵌套,但是可以是结构体指针,
//指针就是一个地址,表示4个字节,指针指向的下一个节点是结构体
}SLT;
void SListPrint(SLT* plist);
void SListPushBack(SLT** plist, SLTDataType x);
void SListPushFront(SLT** pplist, SLTDataType x);
void SListPopBack(SLT** pplist);
void SListPopFront(SLT** pplist);
//查找位置
SLT* SListFind(SLT* plist, SLTDataType x);
//在pos前面插入一个数
void SListInsert(SLT** pplist,SLT*pos,SLTDataType x);
//删除pos位置的值
void SListErase(SLT** pplist,SLT*pos);
#include "Slist.h"
void TestSlist1()
{
SLT *plist=NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushBack(&plist, 5);
SListPrint(plist);
}
void TestSlist2()
{
SLT* plist = NULL;
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPushFront(&plist, 4);
SListPrint(plist);
SListPopFront(&plist);
SListPopBack(&plist);
printf("\n");
SListPrint(plist);
}
void TestSlist3()
{
SLT* plist = NULL;
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPushFront(&plist, 4);
SListPrint(plist);
SLT* pos = SListFind(plist, 4);
if (pos)
{
SListInsert(&plist, pos, 30);
}
printf("\n");
SListPrint(plist);
}
void TestSlist4()
{
SLT* plist = NULL;
SListPushFront(&plist, 1);
SListPushFront(&plist, 2);
SListPushFront(&plist, 3);
SListPushFront(&plist, 4);
SListPrint(plist);
SLT* pos = SListFind(plist, 3);
if (pos)
{
SListErase(&plist, pos);
}
printf("\n");
SListPrint(plist);
}
int main()
{
TestSlist4();
return 0;
}
注意:Slist.c文件就是上面函数,这里不过多重复阐述
链表相较于顺序表来说难度有所上升,但是掌握链表基本概念,练习一些算法题还是很好掌握的。