为什么要学习链表?
前面我们所学习的顺序表是存在缺陷的
顺序表的缺陷:
1.空间不够用就需要扩容,扩容可能会是异地扩容,并且扩容是会要代价的,可能会造成空间的浪费
2.头部或者中间部分的删除,需要大量的移动数据,效率低下
优化方案:
1.按需申请空间
2.尽可能少的移动数据,或者是不移动数据
1.链表
1.1链表的概念及结构
概念:链表是一种物理存储结构上非连续,非顺序存储的结构,数据元素的逻辑顺序是通过链表中的指针将节点链接起来
链式结构在逻辑上是连续的,但是在物理上不一定连续,一般情况下,链表结构中的节点都是从堆上申请出来的,从堆上申请的空间可能连续,也可能不连续
从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
现实中的节点都是从堆上申请出来的
从对上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
1.2链表的分类
实际中链表的结构有很多,以下情况组合起来就有八种链表结构
1.单向或者双向
2.带头或者不带头
3.循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用的还是以下两种结构
上面两种结构各自的好处:
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接矩阵。另外这种结构在笔试和面试中出现很多
2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然复杂,但是在有些题目中会发现这个结构的优势
带哨兵位的链表(带头结点的链表)尾插更加方便一些,oj题基本上都是不带哨兵位的链表,所以oj题中使用这种结构,如果想要返回链表,则需要返回头结点的下一个节点,带哨兵位的链表虽然结构复杂,但是在尾插的时候却很方便,比如单链表中的很多操作需要判断是否为NULL很麻烦,使用这种结构不用判断是否为空
1.3链表的实现
SeqList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SLTNode
{
SLTDataType data;
struct SLTNode* next;
}SLTNode;
//尾插
void SLTPushBack(SLTNode** pphead,SLTDataType x);
//打印
void SLTPrint(SLTNode** pphead);
//头插
void SLTPushFront(SLTNode** pphead,SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//销毁
void SLTDestroy(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//在pos位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x);
//在pos位置之后插入
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置后面的值
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
//尾插
SLTNode* BuyNode(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);
SLTNode* cur = *pphead;
SLTNode* newnode = BuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
while (cur->next)
{
cur = cur->next;
}
cur->next = newnode;
}
}
//打印
void SLTPrint(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuyNode(x);
newnode->next = *pphead;
(*pphead) = newnode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* prev = NULL;
SLTNode* cur = *pphead;
while (cur->next)
{
prev = cur;
cur = cur->next;
}
prev->next = NULL;
free(cur);
cur = NULL;
}
//头删
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* del = *pphead;
(*pphead) = (*pphead)->next;
free(del);
del = NULL;
}
//销毁
void SLTDestroy(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* del = *pphead;
while (*pphead)
{
del = *pphead;
*pphead = (*pphead)->next;
free(del);
}
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//在pos位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead);
assert(pos);
SLTNode* newnode = BuyNode(x);
SLTNode* prev = NULL;
SLTNode* cur = *pphead;
if (*pphead == pos)
{
SLTPushFront(pphead, x);
}
else
{
while (cur->next != pos)
{
cur = cur->next;
}
newnode->next = pos;
cur->next = newnode;
}
}
//在pos位置之后插入
void SLTInsertAfter(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos);
assert(pphead);
SLTNode* newnode = BuyNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
assert(pphead);
if (*pphead == pos)
{
SLTPopFront(pphead);
}
else
{
SLTNode* cur = *pphead;
SLTNode* del = pos;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(del);
}
}
//删除pos位置后面的值
void SLTEraseAfter(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
assert(pos->next);
assert(pphead);
SLTNode* cur = pos->next;
pos->next = cur->next;
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
void Test1()
{
SLTNode* phead = NULL;
SLTPushBack(&phead, 1);
SLTPushBack(&phead, 2);
SLTPushBack(&phead, 3);
SLTPushBack(&phead, 4);
SLTPrint(&phead);
SLTNode* pos = SLTFind(phead, 1);
/*SLTErase(&phead, pos);
SLTPrint(&phead);*/
SLTEraseAfter(&phead, pos);
SLTPrint(&phead);
SLTDestroy(&phead);
}
int main()
{
Test1();
//SLTNode* phead = NULL;
//SLTPushBack(&phead, 1);
//SLTPushBack(&phead, 2);
//SLTPushBack(&phead, 3);
SLTPrint(&phead);
//SLTPushFront(&phead, 4);
//SLTPushFront(&phead, 5);
//SLTPushFront(&phead, 6);
//SLTPrint(&phead);
//printf("----βɾ------\n");
//SLTPopBack(&phead);
//SLTPrint(&phead);
//SLTPopBack(&phead);
//SLTPrint(&phead);
//printf("----ͷɾ------\n");
//SLTPopFront(&phead);
//SLTPrint(&phead);
//SLTPopFront(&phead);
//SLTPrint(&phead);
//printf("-----------\n");
//SLTNode* pos=SLTFind(phead, 4);
///*printf("%d\n", node->data);*/
//SLTInsert(&phead, pos, 10);
//SLTPrint(&phead);
//SLTInsertAfter(&phead, pos, 20);
//SLTPrint(&phead);
//SLTErase(phead, pos);
//SLTPrint(&phead);
//SLTDestroy(&phead);
}