一、链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。
注意:
1.链式结构在逻辑上是连续的,但在物理上不一定连续
2.现实中的结点一般都是从堆上申请出来的
3.从堆上申请的空间,是按照一定的策略来分配,两次申请的空间,可能连续,也可能不连续。
二、链表的分类
以下情况组合起来就有8种链表结构:
1.单向 or 双向
2.带头or不带头
3.循环or非循环
常用两种结构:无头单向非循环链表、带头双向循环链表
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结 构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。
三、单链表的实现
需要实现的接口:
// SList.h
#include<stdio.h>
#include<assert.h>
#include <stdlib.h>
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
typedef int SLNDataType;
typedef struct SLNode
{
SLNDataType data;
struct SLNode* next;
}SLNode;
// 动态申请一个节点
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
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
// 在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x);
// 删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos);
// 销毁
void SLTDestroy(SLNode** pphead);
具体实现:
//SList.c
#include"SList.h"
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode != NULL)
{
newnode->data = x;
newnode->next = NULL;
return newnode;
}
else
exit(-1);
}
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
//错误写法
//while(cur->next)
//while(cur->data)
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
//创建一个新节点
SListNode* newnode = BuySListNode(x);
//链表为空的情况
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
//链表不为空的情况
//找尾
SListNode* tail = *pplist;
while (tail->next)
{
tail = tail->next;
}
//尾指针指向新的节点
tail->next = newnode;
}
}
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
//创建一个新节点
SListNode* newnode = BuySListNode(x);
//让newnode指向下一节点
newnode->next = *pplist;
//让plist指向newnode
*pplist = newnode;
}
void SListPopBack(SListNode** pphead)
{
assert(pphead);
//先检查链表是否删空了若删空了,则不再删
//温柔的检查
//if ((*pphead)->next == NULL)
// return;
//暴力检查
assert(*pphead);
//分情况讨论
//若链表有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
//若链表有多个节点
else
{
//第一种写法
//找尾tail 找尾的前一个prev
SListNode* tail = *pphead;
SListNode* prev = NULL;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
//尾部前一个置空 尾部释放空间
free(tail);
prev->next = NULL;
第二种写法
//SListNode* tail = *pphead;
//while (tail->next->next != NULL)
//{
// tail = tail->next;
//}
//free(tail->next);//注意:释放的是指针指向的节点的内存空间,指针变量依然存在
//tail->next = NULL;
}
}
void SListPopFront(SListNode** pphead)
{
assert(pphead);
//空链表 不删除
assert(*pphead);
//多个结点 一个结点 都适用
//一种写法
//先保存下一个结点的地址
SListNode* tmp = (*pphead)->next;
//旧的头指针置空
free(*pphead);
//头指针指向新的头结点
*pphead = tmp;
另一种写法
//SListNode* tmp = *pphead;
先让plist指向下一个节点
//*pphead = (*pphead)->next;
再释放 旧的头节点
//free(tmp);
}
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
//遍历链表
SListNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
//创建一个新节点
SListNode* newnode = BuySListNode(x);
//新节点指向pos之后的下一节点
newnode->next = pos->next;
//pos指向新的节点
pos->next = newnode;
}
void SListEraseAfter(SListNode* pos)
{
assert(pos);
assert(pos->next);
SListNode* aft = pos->next;
pos->next = pos->next->next;
free(aft);
aft = NULL;
}
// 在pos的前面插入
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType x)
{
//两种断言方式
//1.严格规定pos一定是链表里面的一个有效节点
assert(pphead);
assert(pos);
assert(*pphead);
//2.要么都为空、要么都不为空
//assert(*pphead && pos || !(*pphead) && !pos);
//判断第一个节点是否是pos
if (*pphead==pos)
{
//头插
SListPushFront(*pphead, x);
}
else
{
SLNode* prev = *pphead;
//prev找pos的前一个结点
while (prev->next != pos)
{
prev = prev->next;
}
//创建新节点
SLNode* newnode= BuySListNode(x);
//prev指向新的节点
prev->next = newnode;
//newnode指向下一个节点pos
newnode->next = pos;
}
}
//思考:若只给pos位置指针,想在pos前面插入,如何做?
//再pos后面插入, 交换节点中的值
// 删除pos位置
void SLTErase(SLNode** pphead, SLNode* pos)
{
//空链表不删
assert(*pphead);
//要删的不能为空
assert(pos);
//防止乱传
assert(pphead);
//分情况讨论
//头节点就是Pos
if (*pphead == pos)
{
//头删
SListPopFront(pphead);
}
//头节点不是pos的情况
else
{
//利用prev找pos
SLNode* prev = *pphead;
//找pos上一节点
while (prev->next != pos)
{
prev = prev->next;
}
//pos前一节点指向pos下一节点
prev->next = pos->next;
//删除pos位置
free(pos);
pos == NULL;
}
}
void SLTDestroy(SLNode** pphead)
{
assert(pphead);
assert(*pphead);
SLNode* cur = *pphead;
while (cur)
{
//先保存下一个节点的地址
SLNode* tmp = cur->next;
//再释放当前节点
free(cur);
//再让cur指向下一节点
cur = tmp;
}
*pphead = NULL;
}
测试代码:
//test.c
#include"SList.h"
test1()
{
SListNode* plist = NULL;
SListPrint(plist);
SListPushBack(&plist, 1);
SListPrint(plist);
SListPushBack(&plist, 2);
SListPrint(plist);
SListPushBack(&plist, 3);
SListPrint(plist);
SListPushBack(&plist, 4);
SListPrint(plist);
}
test2()
{
SListNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
SListPushFront(&plist, 10);
SListPushFront(&plist, 9);
SListPrint(plist);
}
test3()
{
SListNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
SListNode* ret = SListFind(plist, 4);
printf("%d", ret->data);
}
test4()
{
SListNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
SListNode* pos = SListFind(plist, 2);
SListInsertAfter(pos, 20);
SListPrint(plist);
}
test5()
{
SListNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
SListNode* pos = SListFind(plist, 2);
SListEraseAfter(pos);
SListPrint(plist);
}
test6()
{
SListNode* plist = NULL;
SListPushBack(&plist, 1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPrint(plist);
//测试删除pos
SListNode* pos = SListFind(plist, 1);
SLTErase(&plist, pos);
SListPrint(plist);
//测试删除pos
pos = SListFind(plist, 3);
SLTErase(&plist, pos);
SListPrint(plist);
}
int main()
{
测试尾插
//test1();
测试头插
//test2();
测试找结点
//test3();
测试单链表在pos位置之后插入x
//test4();
删除pos位置之后的值
//test5();
//测试删除pos
test6();
return 0;
}
四、双向链表的实现
需要实现的接口:
//DList.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType _data;
struct ListNode* _next;
struct ListNode* _prev;
}ListNode;
//初始化函数
ListNode* ListInit();
// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x);
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
具体实现:
//DList.c
#include"DList.h"
ListNode* ListCreate(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->_data = x;
newnode->_next = NULL;
newnode->_prev = NULL;
return newnode;
}
ListNode* ListInit()
{
ListNode* pHead = ListCreate(-1);
pHead->_next = pHead;
pHead->_prev = pHead;
return pHead;
}
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;
}
void ListPrint(ListNode* pHead)
{
assert(pHead);
//不打印哨兵位
ListNode* cur = pHead->_next;
printf("pHead<=>");
while (cur!= pHead)
{
printf("%d<=>", cur->_data);
cur = cur->_next;
}
printf("\n");
}
尾插写法一:
//void ListPushBack(ListNode* pHead, LTDataType x)
//{
// assert(pHead);
// //创建新节点
// ListNode* newnode = ListCreate(x);
//
// ListNode* tail = pHead->_prev;
// tail->_next = newnode;
// newnode->_prev = tail;
// newnode->_next = pHead;
// pHead->_prev = newnode;
//}
尾插写法二:
void ListPushBack(ListNode* pHead, LTDataType x)
{
assert(pHead);
//创建新节点
ListNode* newnode = ListCreate(x);
//在pHead的前面插入就是尾插
ListInsert(pHead, x);
}
void ListPopBack(ListNode* pHead)
{
assert(pHead);
assert(pHead->_next);
ListNode* del = pHead->_prev;
ListNode* tail = del->_prev;
tail->_next = pHead;
pHead->_prev = tail;
free(del);
del = NULL;
}
头插写法一:
//void ListPushFront(ListNode* pHead, LTDataType x)
//{
// assert(pHead);
// //创建新节点
// ListNode* newnode = ListCreate(x);
//
// ListNode* next = pHead->_next;
// newnode->_next = next;
// next->_prev = newnode;
//
// pHead->_next = newnode;
// newnode->_prev = pHead;
//}
头插写法二:
void ListPushFront(ListNode* pHead, LTDataType x)
{
assert(pHead);
//创建新节点
ListNode* newnode = ListCreate(x);
//在pHead的后面插入就是头插
ListInsert(pHead->_next, x);
}
void ListPopFront(ListNode* pHead)
{
assert(pHead);
assert(pHead->_next != pHead);
ListNode* del = pHead->_next;
pHead ->_next = del->_next;
del->_next->_prev = pHead;
free(del);
del=NULL;
}
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
//链表为空 不查找
//assert(pHead->_next);
ListNode* cur = pHead->_next;
while (cur != pHead)
{
if (cur->_data == x)
return cur;
cur = cur->_next;
}
return NULL;
}
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
//创建新节点
ListNode* newnode = ListCreate(x);
ListNode* pre = pos->_prev;
pre->_next = newnode;
newnode->_prev = pre;
newnode->_next = pos;
pos->_prev = newnode;
}
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* pre = pos->_prev;
ListNode* next = pos->_next;
pre->_next = next;
next->_prev = pre;
free(pos);
pos = NULL;
}
测试代码:
//test.c
#include"DList.h"
test1()
{
ListNode* DList = ListInit();
ListPushBack(DList, 1);
ListPushBack(DList, 2);
ListPushBack(DList, 3);
ListPushBack(DList, 4);
ListPushBack(DList, 5);
ListPrint(DList);
}
test2()
{
ListNode* DList = ListInit();
ListPushBack(DList, 1);
ListPushBack(DList, 2);
ListPushBack(DList, 3);
ListPushBack(DList, 4);
ListPushBack(DList, 5);
ListPrint(DList);
ListPopBack(DList);
ListPrint(DList);
ListPopBack(DList);
ListPrint(DList);
ListPopBack(DList);
ListPrint(DList);
ListPopBack(DList);
ListPrint(DList);
ListPopBack(DList);
ListPrint(DList);
}
test3()
{
ListNode* DList = ListInit();
ListPushBack(DList, 1);
ListPushBack(DList, 2);
ListPushBack(DList, 3);
ListPushBack(DList, 4);
ListPushBack(DList, 5);
ListPrint(DList);
ListPushFront(DList, 10);
ListPushFront(DList, 9);
ListPushFront(DList, 8);
ListPushFront(DList, 7);
ListPrint(DList);
}
test4()
{
ListNode* DList = ListInit();
ListPushBack(DList, 1);
ListPushBack(DList, 2);
ListPushBack(DList, 3);
ListPushBack(DList, 4);
ListPushBack(DList, 5);
ListPrint(DList);
ListPopFront(DList);
ListPopFront(DList);
ListPopFront(DList);
ListPrint(DList);
}
test5()
{
ListNode* DList = ListInit();
ListPushBack(DList, 1);
ListPushBack(DList, 2);
ListPushBack(DList, 3);
ListPushBack(DList, 4);
ListPushBack(DList, 5);
ListPrint(DList);
ListNode* ret = ListFind(DList, 5);
printf("%d ", ret->_data);
}
test6()
{
ListNode* DList = ListInit();
ListPushBack(DList, 1);
ListPushBack(DList, 2);
ListPushBack(DList, 3);
ListPushBack(DList, 4);
ListPushBack(DList, 5);
ListPrint(DList);
ListNode* pos = ListFind(DList,4);
ListInsert(pos, 20);
ListPrint(DList);
}
test7()
{
ListNode* DList = ListInit();
ListPushBack(DList, 1);
ListPushBack(DList, 2);
ListPushBack(DList, 3);
ListPushBack(DList, 4);
ListPushBack(DList, 5);
ListPrint(DList);
ListNode* pos = ListFind(DList, 4);
ListErase(pos);
ListPrint(DList);
}
int main()
{
//测试尾插
//test1()
//测试尾删
//test2();
//测试头插
//test3();
//测试头删
//test4();
//测试查找函数
//test5();
//测试pos前插入
//test6();
//删除pos
test7();
return 0;
}