文章目录
前言
顺序表与链表的某些区别
1.任意位置删除或插入时:对于顺序表需要搬迁元素,而链表就只需修改指针指向;
2.增容时:顺序表每次扩容是呈2~3倍的增长,势必会有一定的空间浪费,例如当此时的空间是50,增容后为100,而我们只需要插入5个数据,这时就造成了空间浪费,且增容时申请新空间,拷贝数据,释放旧空间会有不小的损耗;
但链表没有容量的概念,每次插入时只用动态申请一块内存,不会有过多的空间浪费;
针对与以上顺序表的问题,有了今天要说的主题:链表
链表的概念与种类
1.链表的概念的基本结构
概念::链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
把链表的结构具象化以后就像是
各个节点通过地址由指针连接起来;
链表中的节点都是在堆上申请的,所以两快空间可能连续,也可能不连续;
由此我们可知链表在物理上不一定连续,但在逻辑上一定连续;
2. 链表的分类;
通过以下几种情况可以把链表分为八种;
1.单向、双向;
2.带头、不带头;
3.循环、不循环;
2.1单链表、双向链表:
2.2带头、不带头:
2.3循环、不循环:
在这八种当中,我们最常用的两种分别是
1.无头单向非循环链表:
2.有头双向循环链表:
3. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
4. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。
另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
单链表的实现
用结构体构造出链表的类型,并对一些基本操作进行声明;
// 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位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
实现
申请空间、打印
//向内存申请一个新节点
STLNode* BuySTLNode(STLDataType x)
{
STLNode* newnode = (STLNode*)malloc(sizeof(STLNode));
newnode->next = NULL;
newnode->data = x;
return newnode;
}
//打印链表中的元素
void SListPrint(STLNode* phead)
{
STLNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
尾插
//尾插
void SlistPushBack(STLNode** pphead, STLDataType x)
{
STLNode* newnode =BuySTLNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
STLNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
头插
//头插
void SlistPushFront(STLNode** pphead, STLDataType x)
{
STLNode * newnode = BuySTLNode(x);
newnode->next= *pphead;
*pphead = newnode;
}
尾删
尾删和头删时在删完后记得把删除的节点free掉;
//尾删
void SlistPopBack(STLNode** pphead)
{
//1.空节点
//2.1个节点
//3.多个节点
if ((*pphead) == NULL)
{
return;
}
else if ((*pphead)->next== NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
STLNode* prev = NULL;
STLNode* tail = *pphead;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
头删
在删除后让第二个节点变成头节点
![//头删
void SlistPopFront(STLNode** pphead)
{
STLNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
查找
//查找
STLNode* SlistFind(STLNode* phead, STLDataType x)
{
STLNode* cur = phead;
while (cur != NULL)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
随机插入
在插入之前要先把pos以及pos之后的节点用一个指针保存下来;
//在pos前面插入x
void SlistInsert(STLNode** pphead,STLNode* pos, STLDataType x)
{
if (*pphead == pos)
{
SlistPushFront(pphead, x);
}
else
{
STLNode* newnode = BuySTLNode(x);
STLNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
newnode = cur->next;
newnode->next = pos;
}
}
拓展
既然能在pos之前插入就能在pos之后插入这时我们要找到pos节点,保存(pos->next)节点防止插入时丢失
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
//pos -> newnode -> pos->next
newnode->next = pos->next;
pos->next = newnode;
}
既然能插入就能随机删除,在删除时当pos为头节点或尾节点时可以直接用头删或尾删:
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;
//pos del del->next
pos->next = del->next;
free(del);
del = NULL;
}
删除pos 位置的值
//删除pos 位置的值
void SlistErase(STLNode** pphead, STLNode* pos)
{
if (pos == *pphead)
{
SListPopFront(pphead);
}
else
{
STLNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
//销毁链表
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//pcur
*pphead = NULL;
}
完整代码
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)//pcur != NULL
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//*pphead 就是指向第一个节点的指针
//空链表和非空链表
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
//ptail指向的就是尾结点
ptail->next = newnode;
}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
//newnode *pphead
newnode->next = *pphead;
*pphead = newnode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
//链表不能为空
assert(pphead && *pphead);
//链表只有一个节点
if ((*pphead)->next == NULL) //-> 优先级高于*
{
free(*pphead);
*pphead = NULL;
}
else {
//链表有多个节点
SLTNode* prev = *pphead;
SLTNode* ptail = *pphead;
while (ptail->next)
{
prev = ptail;
ptail = ptail->next;
}
//prev ptail
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
//链表不能为空
assert(pphead && *pphead);
SLTNode* next = (*pphead)->next; //-> 优先级高于*
free(*pphead);
*pphead = next;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* pcur = phead;
while (pcur)//等价于pcur != NULL
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
//pcur == NULL
return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead);
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
//若pos == *pphead;说明是头插
if (pos == *pphead)
{
SLTPushFront(pphead, x);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev -> newnode -> pos
newnode->next = pos;
prev->next = newnode;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = SLTBuyNode(x);
//pos -> newnode -> pos->next
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead);
assert(pos);
//pos是头结点/pos不是头结点
if (pos == *pphead)
{
//头删
SLTPopFront(pphead);
}
else {
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
//prev pos pos->next
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos && pos->next);
SLTNode* del = pos->next;
//pos del del->next
pos->next = del->next;
free(del);
del = NULL;
}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* next = pcur->next;
free(pcur);
pcur = next;
}
//pcur
*pphead = NULL;
}
双向链表的实现
// 2、带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* plist);
// 双向链表打印
void ListPrint(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* plist);
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* plist);
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的结点
void ListErase(ListNode* pos);
实现
//申请新节点
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//初始化
ListNode* ListInit()
{
ListNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
头删
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
//ListErase(phead->next);
}
尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* prev = tail->prev;
prev->next = phead;
phead->prev = prev;
free(tail);
tail = NULL;
//ListErase(phead->prev);
}
尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* tail = phead->prev;
ListNode* newnode = BuyListNode(x);
tail->next = newnode;
newnode->prev = tail;
newnode->next = phead;
phead->prev = newnode;
//ListInsert(phead, x);
}
头插
void ListPushFront(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* first = phead->next;
ListNode* newnode = BuyListNode(x);
// phead newnode first
phead->next = newnode;
newnode->prev = phead;
newnode->next = first;
first->prev = newnode;
}
在pos之后插入
// pos位置之后插入x
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = LTBuyNode(x);
//pos newnode pos->next
newnode->next = pos->next;
newnode->prev = pos;
pos->next->prev = newnode;
pos->next = newnode;
}
打印
void ListPrint(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
pos之前插入
// pos位置之前插入x
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(x);
// prev newnode pos
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
删除pos
// 删除pos位置的值
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
销毁
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}