目录
1.链表的基本概念及结构
链表的基本结构:是一种在物理结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接在一起的。就拿单链表来说
注:1.我们会发现链式结构在逻辑上是连续的,但是在物理结构上却不是连续的
2.链表的分类
实际中链表的基本结构有很多,在这里只介绍8中,并重点介绍其中两种
1.单项或双向
2.带头或不带头
3.循环或不循环
在下面我会重点介绍其中的两种链表
3.单链表的实现
单链表的无头非循环链表
无头非循环链表的·结构简单,一般不会单独使用,在实际中更多的是作为其他的数据结构的子结构。
需要实现的基本内容就是增删查改
typedef int SLTDataType;
typedef struct SListNode
{
int data;
struct SListNode* next;
}SLTNode;
void SListPrint(SLTNode* phead);//打印
SLTNode* BuySListNode(SLTDataType x);//开辟新的结构体
//void* BuySListNode(SLTDataType x);//只需在BuySListNode前加上(SLTNode*)
void SListPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SListPushFront(SLTNode** pphead, SLTDataType x);//头插
void SListPopBack(SLTNode** pphead);//尾插
void SListPopFront(SLTNode** pphead);//头插
void* SListFind(SLTNode* phead, SLTDataType x);//查找
//SLTNode* SListFind(SLTNode* phead, SLTDataType x);//查找
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在pos位前插入
void SListErase(SLTNode** pphead, SLTNode* pos);//删除POS位的值
//关于SListInsert 以及SListErase 和SListFind联合使用较好
//在pos位之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x);
//单链表删除pos位之后的值
void SListEraseAfter(SLTNode* pos);
在实现中的顺序基本上就是
1.建立一个结构体
2.对其进行初始化,建立增加结构体的函数
3.就是基本上的增删查改
注:在对函数的命名上要有良好的习惯,这样会方便自己和其他读者的查阅。
解下来就是相应基本函数的具体实现
//打印
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;//指针赋给指针
}
printf("NULL\n");
}
//添加新的结构体
SLTNode* BuySListNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
assert(newnode);
newnode->data = x;
newnode->next = NULL;
}
//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
assert(newnode);
newnode->next = *pphead;
*pphead = newnode;
}
//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuySListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//插入
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
//头删
void SListPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//尾删
void SListPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
//查找指定位置
void* SListFind(SLTNode* phead, SLTDataType x)
//SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* tail = phead;
while (tail)
{
if (tail->data == x)
{
return tail;
}
tail = tail->next;
}
return NULL;
}
//在POS位前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos);
assert(pphead);
//头插
if (pos == *pphead)
{
SListPushFront(pphead,x);
}
else
{
SLTNode* tail = *pphead;
while (tail->next != pos)
{
tail = tail->next;
}
SLTNode* newnode = BuySListNode(x);
tail->next = newnode;
newnode->next = tail;
}
}
//删除pos位的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SListPopFront(pphead);
}
else
{
SLTNode* tail = *pphead;
while (tail->next != pos)
{
tail = tail->next;
}
tail->next = pos->next;
free(pos);
}
}
//在pos位之后插入
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
//删除pos位之后的值
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
if (pos->next == NULL)
return;
SLTNode* del = pos->next;
pos->next = del->next;
//pos->next = pos->next->next;
free(del);
del = NULL;
}
在基本实现后我们就会发下我们的头插尾插可以在我们的void SListInsertAfter中进行实现 我们的头删和尾删可以在void SListEraseAfter中进行实现,所以说我们以后可以直接写这两个函数,然后再头插尾插,头删尾删中套用,如果以后面试中,面试官让我们在规定时间中完成,这样的套用是不是会大大缩短我们所用的时间.
4.双向带头循环链表
双向带头循环链表的结构是最复杂的,一般用来单独存储数据,在实际生活中使用的都是双向带头循环链表,但相应的结构复杂,但其代码实现会简单很多。这也是生活中使用多的一个原因.
其基本要实现的也是以增删查改为基础
typedef int LTDataType;
typedef struct SListNode
{
LTDataType data;
struct SListNode* next;
struct SListNode* prev;
}ListNode;
void ListPrint(ListNode* phead);//打印
//void ListInit(ListNode** pphead);//初始化
ListNode* ListInit();
ListNode* BuyListNode(x);//添加新的结构体
void ListPushBack(ListNode* phead, LTDataType X);//尾插
void ListPushFront(ListNode* phead, LTDataType x);//头插
void ListPopBack(ListNode* phead);//尾删
void ListPopFront(ListNode* phead);//头删
void ListInsert(ListNode* pos, LTDataType X);//在pos位前插入x
void ListErase(ListNode* pos);//删除pos位的节点
其基本实现的步骤:
1.创建新的结构体
2.初始化和结构体的添加
3.基本的增删查改
//添加新的结构体
ListNode* BuyListNode(LTDataType X)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
assert(newnode);
newnode->data = X;
newnode->next = NULL;
newnode->prev = NULL;
return newnode;
}
//初始化
//void ListInit(ListNode** pphead)
//{
// *pphead = BuyListNode(-1);
// (*pphead)->next = *pphead;
// (*pphead)->prev = *pphead;
//}
ListNode* ListInit()
{
ListNode* phead = BuyListNode(-1);
phead->next = phead;
phead->prev = phead;
return phead;
}
void ListPrint(ListNode* phead)
{
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("head\n");
}
//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
assert(phead);
//在不使用嵌套函数情况下的实现方法
/*ListNode* newnode = BuyListNode(x);
ListNode* tail = phead->prev;
tail->next = newnode;
newnode->next = phead;
newnode->prev = tail;
phead->prev = newnode;*/
ListInsert(phead, x);
}
//头插
void ListPushFront(ListNode* phead, LTDataType X)
{
assert(phead);
//在不使用嵌套函数情况下的实现方法
/*ListNode* newnode = BuyListNode(X);
ListNode* next = phead->next;
newnode->next = next;
next->prev = newnode;
newnode->prev = phead;
phead->next = newnode;*/
ListInsert(phead->next, X);
}
//尾删
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
//在不使用嵌套函数情况下的实现方法
/*ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
free(tail);
tailPrev->next = phead;
phead->prev = tailPrev;*/
ListErase(phead->prev);
}
//头删
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
//在不使用嵌套函数情况下的实现方法
/*ListNode* tail = phead->next;
ListNode* next = phead->next->next;
free(tail);
phead->next = next;
next->prev = phead;*/
ListErase(phead->next);
}
//在pos位之前插入x
void ListInsert(ListNode* pos, LTDataType X)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* newnode = BuyListNode(X);
newnode->next = pos;
pos->prev = newnode;
newnode->prev = prev;
prev->next = newnode;
}
//删除pos位的节点
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* prev = pos->prev;
ListNode* next = pos->next;
next->prev = prev;
prev->next = next;
free(pos);
}
int ListSize(ListNode* phead)
{
assert(phead);
int size = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
size++;
cur = cur->next;
}
}
void ListDestory(ListNode* phead)
{
assert(phead);
int size = 0;
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
ListErase(cur);
cur = next;
}
free(phead);
}
我们会发现在本链表中也存在相应的函数嵌套使用,所以在书写具体的实现代码时,也可以直接书写在表中的插入和在表中的删除,也会大大节省我们的书写时间.
在这再多说一下,顺序表和链表的优缺点
首先我们知道,我们在实现顺序表的时候为了降低其头插和在表中插入的效率低下的情况,并且扩容的效率不确定,因为我们在realloc时开辟的空间是对某一值成倍的扩容,这其中就会造成一定的浪费,而使用了链表,但在链表的实现过程中我们却又发现了链表不能进行下标的随机访问,所以说链表和顺序表是相辅相成的