目录
1.创建三个文件,SList.h,SList.c,text.c
一、链表的概念及结构
1.链表概念:
链表是一种物理存储上非连续、非顺序的存储结构,数据的逻辑顺序是通过链表中的指针链接次序实现的。
2.链表结构
物理结构:
逻辑结构:
3.常见的几种形式
a.单向或双向
b.带头或不带
c.循环或非循环
本篇选择两种常用的链表书写代码:单链表,带头双向循环链表
二、单链表
1.创建三个文件,SList.h,SList.c,text.c
2.头文件函数声明
关于传参:可见传参时使用了二级指针
我们在进行测试时,定义的肯定是一个结构体指针充当后续操作的头节点,为此我们需要传指针的地址才能做到改变实参,那么传指针的地址自然就需要使用二级指针了。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;//方便修改数据类型
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* phead);
// 单链表尾插
void SListPushBack(SListNode** pphead, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pphead, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pphead);
// 单链表头删
void SListPopFront(SListNode** pphead);
// 单链表查找
SListNode* SListFind(SListNode* phead, SLTDateType x);
// 单链表在pos位置之前插入x
void SListInsert(SListNode**pphead,SListNode* pos, SLTDateType x);
// 单链表删除pos位置
void SListErase(SListNode** pphead,SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode** pphead);
3.尾插
可知这时候需要一个新的节点,且之后也需新增节点,所以分装一个函数
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SListPushBack(SListNode** pphead, SLTDateType x)
{
assert(pphead);
SListNode* newnode = BuySListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//使用tail逐步找尾
SListNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
4.打印
void SListPrint(SListNode* phead)
{
SListNode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL");
printf("\n");
}
5.头插
注意修改顺序,防止找不到1号结点位置
void SListPushFront(SListNode** pphead, SLTDateType x)
{
assert(pphead);
SListNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
6.尾删
注意:需要找到倒数第二个结点,而不是倒数第一个,因为单链表找不到上一个节点位置,只能找到下一个结点 。如图,此时应该对2号结点操作,而不是3号
void SListPopBack(SListNode** pphead)
{
assert(pphead);
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SListNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
7.头删
void SListPopFront(SListNode** pphead)
{
assert(pphead);
assert(*pphead);
SListNode* first = *pphead;
*pphead = first->next;
free(first);
first = NULL;
}
8.查找
SListNode* SListFind(SListNode* phead, SLTDateType x)
{
SListNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
9.指定位置删除
void SListErase(SListNode** pphead,SListNode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SListPopFront(pphead);
}
else
{
SListNode* cur = *pphead;
while (cur->next != pos)
{
cur=cur->next;
}
cur->next = pos->next;
free(pos);
pos = NULL;
}
}
10.插入
void SListInsert(SListNode** pphead, SListNode* pos, SLTDateType x)
{
assert(pos);
if (pos == *pphead)
{
SListPushFront(pphead, x);
}
else
{
SListNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
SListNode* newnode = BuySListNode(x);
cur->next = newnode;
newnode->next = pos;
}
}
11.销毁
void SListDestroy(SListNode** pphead)
{
SListNode* cur;
while (*pphead != NULL)
{
cur = *pphead;
*pphead = cur->next;
free(cur);
}
}
12.assert(),断言的使用
由上可见,函数中大量的使用了assert断言,assert()括号中若为假则会提示我们出错的位置,
此举可以快速地让我们定位到错误的位置。
根据前文提及pphead为传参过来的指针的地址,那么地址就一定不能为空
删除操作时,链表如果为空就没必要删除了,因此断言*pphead就表示链表不能为空
13.完整代码
a.SList.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SlistNode
{
SLTDateType data;
struct SlistNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* phead);
// 单链表尾插
void SListPushBack(SListNode** pphead, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pphead, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pphead);
// 单链表头删
void SListPopFront(SListNode** pphead);
// 单链表查找
SListNode* SListFind(SListNode* phead, SLTDateType x);
// 单链表在pos位置之前插入x
void SListInsert(SListNode**pphead,SListNode* pos, SLTDateType x);
// 单链表删除pos位置
void SListErase(SListNode** pphead,SListNode* pos);
// 单链表的销毁)
void SListDestroy(SListNode** pphead);
b.SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
void SListPrint(SListNode* phead)
{
SListNode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL");
printf("\n");
}
void SListPushBack(SListNode** pphead, SLTDateType x)
{
assert(pphead);
SListNode* newnode = BuySListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SListNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SListPushFront(SListNode** pphead, SLTDateType x)
{
assert(pphead);
SListNode* newnode = BuySListNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SListPopBack(SListNode** pphead)
{
assert(pphead);
assert(*pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
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* first = *pphead;
*pphead = first->next;
free(first);
first = NULL;
}
SListNode* SListFind(SListNode* phead, SLTDateType x)
{
SListNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void SListInsert(SListNode** pphead, SListNode* pos, SLTDateType x)
{
assert(pos);
if (pos == *pphead)
{
SListPushFront(pphead, x);
}
else
{
SListNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
SListNode* newnode = BuySListNode(x);
cur->next = newnode;
newnode->next = pos;
}
}
void SListErase(SListNode** pphead,SListNode* pos)
{
assert(pphead);
assert(pos);
if (*pphead == pos)
{
SListPopFront(pphead);
}
else
{
SListNode* cur = *pphead;
while (cur->next != pos)
{
cur=cur->next;
}
cur->next = pos->next;
free(pos);
pos = NULL;
}
}
void SListDestroy(SListNode** pphead)
{
SListNode* cur;
while (*pphead != NULL)
{
cur = *pphead;
*pphead = cur->next;
free(cur);
}
}
c.text.c
#include"SList.h"
int main()
{
SListNode* plist = NULL;
SListPushBack(&plist,1);
SListPushBack(&plist, 2);
SListPushBack(&plist, 3);
SListPushBack(&plist, 4);
SListPushBack(&plist, 5);
SListPushBack(&plist, 6);
SListPushFront(&plist, 7);
SListPushFront(&plist, 8);
SListPushFront(&plist, 9);
SListPopBack(&plist);
SListPopFront(&plist);
SListPrint(plist);
SListNode*ret= SListFind(plist, 4);
SListInsert(&plist, ret, 30);
SListPrint(plist);
SListErase(&plist, ret);
SListPrint(plist);
SListDestroy(&plist);
return 0;
}
运行结果
三、双向带头循环链表
掌握单链表其实这个就很简单了,下面直接放代码
1.List.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}ListNode;
//void LTInit(ListNode** pphead);
ListNode* LTInit();
// 创建返回链表的头结点.
ListNode* BuyListNode(LTDataType x);
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
bool ListEmpty(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);
2.List.c
#include"List.h"
bool ListEmpty(ListNode* pHead)
{
assert(pHead);
return pHead->next = pHead;
}
ListNode* LTInit()
{
ListNode* pHead = BuyListNode(-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);
}
ListNode* BuyListNode(LTDataType x)
{
ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
newnode->prev = NULL;
}
void ListPushBack(ListNode* pHead, LTDataType x)
{
ListNode* newnode = BuyListNode(x);
ListNode* tail = pHead->prev;
//pHead tail newnode
tail->next = newnode;
pHead->prev = newnode;
newnode->prev = tail;
newnode->next = pHead;
}
void ListPushFront(ListNode* pHead, LTDataType x)
{
ListInsert(pHead->next,x);
}
void ListPopBack(ListNode* pHead)
{
ListErase(pHead->prev);
}
void ListPopFront(ListNode* pHead)
{
ListErase(pHead->next);
}
void ListPrint(ListNode* pHead)
{
assert(pHead);
ListNode* cur = pHead->next;
printf("<=Head=>");
while (cur!=pHead)
{
printf("<=%d=>", cur->data);
cur = cur->next;
}
printf("\n");
}
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* newnode = BuyListNode(x);
ListNode* prev = pos->prev;
prev->next = newnode;
newnode->prev = prev;
newnode->next = pos;
pos->prev = newnode;
}
void ListErase(ListNode* pos)
{
assert(pos);
ListNode* next = pos->next;
ListNode* prev = pos->prev;
prev->next = next;
next->prev = prev;
free(pos);
}
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
assert(pHead);
ListNode* cur = pHead->next;
while (cur!=pHead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
3.text.c
#include"List.h"
int main()
{
ListNode* plist = LTInit();
ListPushBack(plist, 1);
ListPushBack(plist, 2);
ListPushBack(plist, 3);
ListPushBack(plist, 4);
ListPushBack(plist, 5);
ListPrint(plist);
ListPushFront(plist, 6);
ListPushFront(plist, 7);
ListPushFront(plist, 8);
ListPopBack(plist);
ListPopBack(plist);
ListPopFront(plist);
ListPopFront(plist);
ListPrint(plist);
ListNode* pos = ListFind(plist, 2);
if (pos)
{
ListErase(pos);
pos = NULL;
}
ListPrint(plist);
ListDestory(plist);
}
运行结果