线性表之链表
链表概述
-
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
-
线性表的链式存储结构称为链表,其中每个存储结点不仅包含元素本身的信息(数据域),而且包含表示元素之间逻辑关系的信息,在C/C++语言中采用指针实现,称为指针域
-
链表的结构很多,有单向/双向、带头/不带头、循环/非循环
-
常用的只有无头单向不循环和带头双向循环链表
-
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
-
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单.
-
如图:
-
单链表 – 无头单向不循环
单链表的接口
/* 这里是SingleLinkedList.h文件 */
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct SingleListNode
{
ElemType data; //存放元素值
struct SingleListNode* next; //指向后继结点
}SLinkNode; //单链表结点类型
/*
接口函数的声明
*/
/* 动态申请一个结点 */
SLinkNode* CreateLinkNode(ElemType x);
/* 打印单链表 */
void PrintSingleLinkedList(SLinkNode* plist);
/* 单链表尾插 */
void PushBackSingleLinkedList(SLinkNode** pplist, ElemType x);
/* 单链表头插 */
void PushFrontSingleLinkedList(SLinkNode** pplist, ElemType x);
/* 单链表尾删 */
void PopBackSingleLinkedList(SLinkNode** pplist);
/* 单链表头删 */
void PopFrontSingleLinkedList(SLinkNode** pplist);
/* 单链表查找 */
SLinkNode* SearchSingleLinkedList(SLinkNode* plist, ElemType x);
/* 单链表插入 */
void InsertSingleLinkedList(SLinkNode** pplist, SLinkNode* pos, ElemType x);
/* 单链表删除 */
void RemoveSingleLinkedList(SLinkNode* pos);
/* 单链表销毁 */
void DestorySingleLinkedList(SLinkNode** pplist);
单链表接口的实现
/* 这里是SingleLinkedList.c文件*/
#include "SingleLinkedList.h"
/* 动态申请一个结点 */
SLinkNode* CreateLinkNode(ElemType x)
{
SLinkNode* newNode = (SLinkNode*)malloc(sizeof(SLinkNode));
if (newNode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
/* 打印单链表 */
void PrintSingleLinkedList(SLinkNode* plist)
{
assert(plist);
SLinkNode* cur = plist;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("\n");
}
/* 单链表尾插 */
void PushBackSingleLinkedList(SLinkNode** pplist, ElemType x)
{
assert(pplist);
SLinkNode* newNode = CreateLinkNode(x);
if (*pplist == NULL)
{
*pplist = newNode;
}
else
{
SLinkNode* tail = *pplist;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
/* 单链表头插 */
void PushFrontSingleLinkedList(SLinkNode** pplist, ElemType x)
{
assert(pplist);
SLinkNode* newNode = CreateLinkNode(x);
newNode->next = *pplist;
*pplist = newNode;
}
/* 单链表尾删 */
void PopBackSingleLinkedList(SLinkNode** pplist)
{
assert(pplist);
//判断是否存在元素
assert(*pplist);
SLinkNode* tail = *pplist;
if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
/* 单链表头删 */
void PopFrontSingleLinkedList(SLinkNode** pplist)
{
assert(pplist);
//判断是否有元素
assert(*pplist);
SLinkNode* head = *pplist;
*pplist = (*pplist)->next;
free(head);
}
/* 单链表查找 */
SLinkNode* SearchSingleLinkedList(SLinkNode* plist, ElemType x)
{
assert(plist);
SLinkNode* cur = plist;
while (plist)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
/* 单链表插入 */
//注意是在pos位置之后插入新的元素
void InsertSingleLinkedList(SLinkNode** pplist, SLinkNode* pos, ElemType x)
{
assert(pplist && pos);
SLinkNode* newNode = CreateLinkNode(x);
SLinkNode* cur = *pplist;
while (cur != pos)
{
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
}
/* 单链表删除 */
//注意是在pos位置之后删除元素
void RemoveSingleLinkedList(SLinkNode* pos)
{
assert(pos);
assert(pos->next);
SLinkNode* next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
/* 单链表销毁 */
void DestorySingleLinkedList(SLinkNode** pplist)
{
SLinkNode* curFront = *pplist;
SLinkNode* cur = curFront->next;
while (cur)
{
free(curFront);
curFront = cur;
cur = cur->next;
}
free(curFront);
curFront = NULL;
}
双链表 – 带头双向循环
双链表的接口
/* 这里是DoubleLinkedList.h文件 */
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int ElemType;
typedef struct DoubleLinkNode
{
ElemType data; //数据域
struct DoubleLinkNode* next; //指向后继结点
struct DoubleLinkNode* prev; //指向前驱结点
}DLinkNode; //双链表结点类型
/*
接口函数的声明
*/
/* 创建头节点 */
DLinkNode* InitDoubleLinkedList();
/* 动态申请一个结点 */
DLinkNode* CreateNode(ElemType x);
/* 销毁双链表 */
void DestroyDoubleLinkedList(DLinkNode* phead);
/* 打印双向链表 */
void PrintDoubleLinkedList(DLinkNode* phead);
/* 双向链表尾插 */
void PushBackDoubleLinkedList(DLinkNode* phead, ElemType x);
/* 双向链表尾删 */
void PopBackDoubleLinkedList(DLinkNode* phead);
/* 双向链表头插 */
void PushFrontDoubleLinkedList(DLinkNode* phead, ElemType x);
/* 双向链表头删 */
void PopFrontDoubleLinkedList(DLinkNode* phead);
/* 双向链表查找 */
DLinkNode* SearchDoubleLinkedList(DLinkNode* phead, ElemType x);
/* 双向链表在pos的前面进行插入 */
void InsertDoubleLinkedList(DLinkNode* pos, ElemType x);
/* 双向链表删除pos位置的节点 */
void RemoveDoubleLinkedList(DLinkNode* pos);
双链表接口的实现
/* 这里是DoubelLinkedList.c文件 */
#include "DoubelLinkedList.h"
/* 创建头节点 */
DLinkNode* InitDoubleLinkedList()
{
//哨兵位头结点
DLinkNode* phead = (DLinkNode*)malloc(sizeof(DLinkNode));
if (phead == NULL)
{
printf("malloc fail\n");
exit(-1);
}
phead->next = phead;
phead->prev = phead;
return phead;
}
/* 动态申请一个结点 */
DLinkNode* CreateNode(ElemType x)
{
DLinkNode* newNode = (DLinkNode*)malloc(sizeof(DLinkNode));
if (newNode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
newNode->prev = NULL;
return newNode;
}
/* 打印双向链表 */
void PrintDoubleLinkedList(DLinkNode* phead)
{
DLinkNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
/* 双向链表尾插 */
void PushBackDoubleLinkedList(DLinkNode* phead, ElemType x)
{
assert(phead);
DLinkNode* tail = phead->prev;
DLinkNode* newNode = CreateNode(x);
tail->next = newNode;
newNode->prev = tail;
newNode->next = phead;
phead->prev = newNode;
/* 实现了在pos位置前面插入元素的函数之后,直接复用 */
InsertDoubleLinkedList(phead, x);
}
/* 双向链表尾删 */
void PopBackDoubleLinkedList(DLinkNode* phead)
{
assert(phead);
//如果 phead->next == phead,那么链表为空,无法继续删除
assert(phead->next != phead);
DLinkNode* tail = phead->prev;
tail->prev->next = phead;
phead->prev = tail->prev;
free(tail);
/* 实现了删除pos位置的函数之后,直接复用 */
RemoveDoubleLinkedList(phead->prev);
}
/* 双向链表头插 */
void PushFrontDoubleLinkedList(DLinkNode* phead, ElemType x)
{
assert(phead);
DLinkNode* newNode = CreateNode(x);
newNode->next = phead->next;
phead->next->prev = newNode;
newNode->prev = phead;
phead->next = newNode;
/* 实现了在pos位置前面插入元素的函数之后,直接复用 */
InsertDoubleLinkedList(phead->next, x);
}
/* 双向链表头删 */
void PopFrontDoubleLinkedList(DLinkNode* phead)
{
assert(phead);
assert(phead->next != phead);
DLinkNode* cur = phead->next;
phead->next = cur->next;
cur->next->prev = phead;
free(cur);
/* 实现了删除pos位置的函数之后,直接复用 */
RemoveDoubleLinkedList(phead->next);
}
/* 双向链表查找 */
DLinkNode* SearchDoubleLinkedList(DLinkNode* phead, ElemType x)
{
assert(phead);
DLinkNode* cur = phead;
while (cur->next != phead)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
}
/* 双向链表在pos的前面进行插入 */
void InsertDoubleLinkedList(DLinkNode* pos, ElemType x)
{
assert(pos);
DLinkNode* newNode = CreateNode(x);
pos->prev->next = newNode;
newNode->prev = pos->prev;
newNode->next = pos;
pos->prev = newNode;
}
/* 双向链表删除pos位置的节点 */
void RemoveDoubleLinkedList(DLinkNode* pos)
{
assert(pos);
pos->next->prev = pos->prev;
pos->prev->next = pos->next;
free(pos);
}
/* 销毁双向链表 */
void DestroyDoubleLinkedList(DLinkNode* phead)
{
assert(phead);
DLinkNode* cur = phead->next;
while (cur != phead)
{
cur = cur->next;
free(cur->prev);
}
free(phead);
}
链表的优缺点
-
注意这里指的是带头双向循环链表
-
优点:
- 任意位置插入效率高,时间复杂度为O(1)
- 按需申请释放空间
-
缺点:
- 不支持随机访问(用下标访问),查找的时间复杂度为O(N);这意味着一些排序,二分查找等在这种结构上不适用
- 链表存储数据的同时要存储指针,存在一定的消耗
- CPU高速缓存命中率低