数据结构 – 链表
一、单链表
1.概念及结构
单链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针次序来实现的。
其结构定义如下:
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
2.接口实现
代码如下:
1.直接创建节点
void SLTTest1()
{
//创建结点
SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n1);
SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n2);
SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n3);
SLTNode* n4 = (SLTNode*)malloc(sizeof(SLTNode));
assert(n4);
n1->data = 1;
n2->data = 2;
n3->data = 3;
n4->data = 4;
n1->next = n2;
n2->next = n3;
n3->next = n4;
n4->next = NULL;
SListPrint(n1);
SListPushBack(&n1, 5);
SListPrint(n1);
}
2.创建新节点
SLTNode* BuySListNode(SLTDataType x)//返回值是新节点的地址
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
assert(newnode);
newnode->data = x;
newnode->next = NULL;
return newnode;
}
3.打印
void SListPrint(SLTNode* phead)
{
assert(phead);
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
4.尾插
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = BuySListNode(x);
if (*pphead == NULL)
//当链表是空的时候,直接将指向头结点的指针指向创建的新结点
//但是由于是函数传参,若需要改变原头结点指针的指向,则需要传二级指针,并在函数中解引用进行改变
{
*pphead = newnode;
}
else
//否则,找到尾结点
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
5.头插
void SListPushFront(SLTNode** pphead, SLTDataType x)//头插也需要改变头结点指针的指向,所以也传二级指针
{
assert(pphead);
SLTNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
6.尾删
void SListPopBack(SLTNode** pphead)//当链表只有一个节点的时候,也需要改变头结点指针的指向,所以传二级指针
{
assert(pphead);
assert(*pphead);//若头结点指向空,则代表链表是空的,在此断言
if ((*pphead)->next == NULL)//只有一个结点
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* tailPrev = NULL;//用来保存尾结点的前一个结点的指针
SLTNode* tail = *pphead;
while (tail->next != NULL)//找到尾结点的前一个结点
{
tailPrev = tail;
tail = tail->next;
}
free(tail);
tailPrev->next = NULL;
}
}
7.头删
//头删
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);
SLTNode* next = (*pphead)->next;//保存头节点的下一个节点指针
free(*pphead);//释放头节点指向的空间
*pphead = next;//头节点指针指向下一个节点
}
8.查找
//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
9.在指定位置之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos);
assert(pphead);
if (pos == *pphead)//如果是头节点
{
SListPushFront(pphead, x);
}
else
{
SLTNode* prev = *pphead;//保存pos前一个节点的指针
while (prev->next != pos)//找到pos前一个节点的指针
{
prev = prev->next;
}
SLTNode* newnode = BuySListNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
10.删除当前位置的节点
void SListErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)//如果是头节点
{
SListPopFront(pphead);
}
else
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
}
3.特性总结
链表的优点:任意位置插入删除的时间复杂度低,空间按需申请和释放;
缺点:不支持下标随机访问。
二、双向带头循环链表
1.概念及结构
双向带头循环链表是在单链表的基础上增加了哨兵位,并且将增加了一个指向前一个节点的指针,首尾节点循环。
其结构定义如下:
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* prev;
struct ListNode* next;
LTDataType data;
}LTNode;
2.接口实现
代码如下:
1.创建结点
LTNode* BuyListNode(LTDataType x)
{
LTNode* node = (LTNode*)malloc(sizeof(LTNode));
if (node == NULL)
{
perror("malloc");
exit(-1);
}
node->data = x;
node->next = NULL;
node->prev = NULL;
return node;
}
2.初始化
LTNode* ListInit()
{
LTNode* phead = BuyListNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
3.打印
void ListPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
4.在pos之前插入 x
void ListInsert(LTNode* pos, LTDataType x)
{
assert(pos);
LTNode* newnode = BuyListNode(x);
LTNode* prev = pos->prev;
pos->prev = newnode;
newnode->next = pos;
newnode->prev = prev;
prev->next = newnode;
}
5.删除pos位置
void ListErase(LTNode* pos)
{
assert(pos);
LTNode* next = pos->next;
LTNode* prev = pos->prev;
prev->next = next;
next->prev = prev;
free(pos);
}
6.尾插
void ListPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead, x);
}
7.头插
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
ListInsert(phead->next, x);
}
8.尾删
void ListPopBack(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//防止链表删空,把头结点删除,出现野指针
ListErase(phead->prev);
}
9.头删
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);//防止链表删空,把头结点删除,出现野指针
ListErase(phead->next);
}
10.判断链表是否为空
bool ListEmpty(LTNode* phead)
{
assert(phead);
return phead->prev == phead;//若链表为空,则头节点的前向指针prev就指向自己
}
11.计算链表长度
int ListSize(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
int size = 0;
while (cur != phead)//从哨兵位头结点开始转一圈
{
size++;
cur = cur->next;
}
return size;
}
10.双向链表销毁
void ListDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
ListErase(cur);
cur = next;
}
free(phead);
phead == NULL;
}
3.特性总结
双向链表的插入删除比较简单。
总结
本文简单介绍了单链表和双向带头循环链表的结构、实现即特点。