1.链表的基本介绍
链表的实现又很多种方式,通常来说常用的有两种,无头单向非循环链表和带头双向循环链表。无头单链表通常是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。而带头双向循环链表,通常作为独立的结构出现,C++中的list就是使用这个结构实现。
2.链表的设计
C语言中怎么对一个结点进行描述呢?通过结构体来描述,一个结点。那么创建一个SingleList.h来声明Node结构体,typedef 一个 DataType数据类型,可以更加灵活处理数据。接着就是正常定义结构体。
#include <stdio.h>
typedef int DataType;
typedef struct SingleList
{
DataType data;
struct SingleList* next;
}Node;
接下来创建一个main.c文件来定义一个单链表。定义一个单链表,只需要定义一个结构体Node的指针指向单链表的头结点的地址,就能处理这个链表。一开始,链表为NULL。
#include "SingleList.h"
int main()
{
Node* plist = NULL;
return 0;
}
3.链表的增删改查
1)链表的尾插法: 在SingleList.h中经行函数声明,然后创建一个SingleList.c完成函数实现。尾插法最朴素的想法是找到尾部tail 然后让tail->next = newNode(新建结点)。
如何找到尾部呢?tail->next == NULL就说明了这是最后一个结点,是尾部。单链表找尾部只能通过循环遍历的方式查找。
void PushBack(Node** pphead,DataType data);
函数的实现,当链表为NULL时需要单独处理一下。
void PushBack(Node** pphead,DataType data)
{
assert(pphead != NULL);
Node* newNode = BuyNode(data);
// 链表为空时需要单独去判断,不然tail = *pphead 为NULL,tail->next崩溃了
if(*pphead == NULL)
{
*pphead = newNode;
}
else
{
Node* tail = *pphead; // 让tail从头开始
while(tail->next != NULL /*这里是循环的条件,当taile->next == NULL 循环结束找到尾*/)
{
tail = tail->next;
}
// 链接结点
tail->next = newNode;
}
}
链表的增加需要向系统申请内存结点,这部分的代码相对固定所以封装到BuyNode函数中。另外为了测试方便编写一个PrintList函数打印链表。
Node* BuyNode(DataType data)
{
Node* node = (Node*)malloc(sizeof(Node));
if(node == NULL)
{
perror("malloc create node fail\n");
return NULL;
}
// 创建结点成功!初始化结点
node->data = data;
node->next = NULL;
return node;
}
如果细心可以发现,PrintfList传入的是一级指针,而PushBack需要传入二级指针。PrintList只是访问Node的内容,所以定义Node 接收Node的地址,就能访问到内容。而PushBack需要改变Node结点中的next,改变Node* 的内容就需要二级指针 Node**。
void PrintList(Node* phead)
{
Node* cur = phead;
while(cur != NULL)
{
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL\n");
}
2)链表的头插法: 头插法直接让newNode指向原头结点,然后更新头结点即可。
void PushFront(Node** pphead,DataType data)
{
assert(pphead != NULL);
Node* newNode = BuyNode(data);
// 直接让newNode指向头,*pphead为NULL也不怕,然后更新头节点
newNode->next = *pphead;
*pphead = newNode;
}
3)链表的尾删: 删除操作要保证链表不为NULL。另外尾删要注意,为尾部的前一个next不要指向free的空间。而是让它指向NULL。所以要找到尾的前一个prev。根据经验,链表、树中NULL应该看成一个结点。
void PopBack(Node** pphead)
{
assert(pphead);
assert(*pphead != NULL); // 链表为NULL,那就不能删了
// 尾删的时候是找到为尾部的前一个,让perv->next = NULL.所以只有一个结点的时候单独处理.不然下面的语句prev->next->next就出错了
if((*pphead)->next == NULL)
{
free(*pphead);
}
else
{
Node* prev = *pphead;
while(prev->next->next != NULL) {prev = prev->next;}
free(prev->next);
prev->next = NULL;
}
}
4)链表的头删: 需要提前记录一个头结点的下一个。
void PopFront(Node** pphead)
{
assert(pphead != NULL);
assert(*pphead != NULL);
Node* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
5)链表的查找: 查找的是第一个data相同的结点。遍历寻找即可。
Node* Find(Node* phead,DataType data)
{
Node* cur = phead;
while(cur != NULL)
{
if(cur->data == data) return cur;
cur = cur->next;
}
return NULL;
}
6)链表从pos位置前插入:
void Insert(Node** pphead,Node* pos,DataType data)
{
assert(pphead);
assert(pos);
if(pos == *pphead){PushFront(pphead,data);}
else
{
Node* prev = *pphead;
// 一般来说: Insert接口是配合Find接口使用的,传入进来的pos要么是pos要么是链表中的结点
// 如果pos不是链表中的元素,查找的时候就会出错.
while(prev->next != pos){ prev = prev->next; }
Node* newNode = BuyNode(data);
prev->next = newNode;
newNode->next = pos;
}
}
7)链表删除pos位置结点:
void Erase(Node** pphead,Node* pos /*删除pos位置*/)
{
assert(*pphead != NULL);
assert(pos);
if(*pphead == pos){PopFront(pphead);}
else
{
Node* prev = *pphead;
// 一般来说: Erase接口也是配合Find接口使用的,传入进来的pos要么是pos要么是链表中的结点
while(prev->next != pos){ prev = prev->next; }
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
4.完整代码
这里要注意的一点是编译的时候记得两个 .c 文件都要一起编译。
gcc -o test Main.c SingleList.c
SingleList.h 文件
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int DataType;
typedef struct SingleList
{
DataType data;
struct SingleList* next;
}Node;
void PushBack(Node** pphead,DataType data);
void PrintList(Node* phead);
void PushFront(Node** pphead,DataType data);
void PopBack(Node** pphead);
void PopFront(Node** pphead);
Node* Find(Node* phead,DataType data);
void Insert(Node** pphead,Node* pos,DataType data);
void Erase(Node** pphead,Node* pos);
SingleList.c 文件
#include "SingleList.h"
Node* BuyNode(DataType data)
{
Node* node = (Node*)malloc(sizeof(Node));
if(node == NULL)
{
perror("malloc create node fail\n");
return NULL;
}
// 创建结点成功!初始化结点
node->data = data;
node->next = NULL;
return node;
}
void PushBack(Node** pphead,DataType data)
{
assert(pphead != NULL);
Node* newNode = BuyNode(data);
// 链表为空时需要单独去判断,不然tail = *pphead 为NULL,tail->next崩溃了
if(*pphead == NULL)
{
*pphead = newNode;
}
else
{
Node* tail = *pphead; // 让tail从头开始
while(tail->next != NULL /*这里是循环的条件,当taile->next == NULL 循环结束找到尾*/)
{
tail = tail->next;
}
// 链接结点
tail->next = newNode;
}
}
void PrintList(Node* phead)
{
Node* cur = phead;
while(cur != NULL)
{
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL\n");
}
void PushFront(Node** pphead,DataType data)
{
assert(pphead != NULL);
Node* newNode = BuyNode(data);
// 直接让newNode指向头,*pphead为NULL也不怕,然后更新头节点
newNode->next = *pphead;
*pphead = newNode;
}
void PopBack(Node** pphead)
{
assert(pphead);
assert(*pphead != NULL); // 链表为NULL,那就不能删了
// 尾删的时候是找到为尾部的前一个,让perv->next = NULL.所以只有一个结点的时候单独处理.
if((*pphead)->next == NULL)
{
free(*pphead);
}
else
{
Node* prev = *pphead;
while(prev->next->next != NULL) {prev = prev->next;}
free(prev->next);
prev->next = NULL;
}
}
void PopFront(Node** pphead)
{
assert(pphead != NULL);
assert(*pphead != NULL);
Node* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
Node* Find(Node* phead,DataType data)
{
Node* cur = phead;
while(cur != NULL)
{
if(cur->data == data) return cur;
cur = cur->next;
}
return NULL;
}
void Insert(Node** pphead,Node* pos,DataType data)
{
assert(pphead);
assert(pos);
if(pos == *pphead){PushFront(pphead,data);}
else
{
Node* prev = *pphead;
// 一般来说: Insert接口是配合Find接口使用的,传入进来的pos要么是pos要么是链表中的结点
// 如果pos不是链表中的元素,查找的时候就会出错.
while(prev->next != pos){ prev = prev->next; }
Node* newNode = BuyNode(data);
prev->next = newNode;
newNode->next = pos;
}
}
void Erase(Node** pphead,Node* pos /*删除pos位置*/)
{
assert(*pphead != NULL);
assert(pos);
if(*pphead == pos){PopFront(pphead);}
else
{
Node* prev = *pphead;
// 一般来说: Erase接口也是配合Find接口使用的,传入进来的pos要么是pos要么是链表中的结点
while(prev->next != pos){ prev = prev->next; }
prev->next = pos->next;
free(pos);
pos = NULL;
}
}
Main.c 文件
#include "SingleList.h"
int TestPushBack()
{
Node* plist = NULL;
PushBack(&plist,1);
PushBack(&plist,2);
PushBack(&plist,3);
PushBack(&plist,4);
PushBack(&plist,5);
PrintList(plist);
return 0;
}
int TestPushFront()
{
Node* plist = NULL;
PushFront(&plist,1);
PushFront(&plist,2);
PushFront(&plist,3);
PushFront(&plist,4);
PushFront(&plist,5);
PrintList(plist);
return 0;
}
int TestPopBack()
{
Node* plist = NULL;
PushBack(&plist,1);
PushBack(&plist,2);
PushBack(&plist,3);
PushBack(&plist,4);
PushBack(&plist,5);
PrintList(plist);
PopBack(&plist);
PrintList(plist);
PopBack(&plist);
PopBack(&plist);
PrintList(plist);
return 0;
}
int TestPopFront()
{
Node* plist = NULL;
PushFront(&plist,1);
PushFront(&plist,2);
PushFront(&plist,3);
PushFront(&plist,4);
PushFront(&plist,5);
PrintList(plist);
PopFront(&plist);
PrintList(plist);
PopFront(&plist);
PopFront(&plist);
PopFront(&plist);
PrintList(plist);
return 0;
}
int TestInsert()
{
Node* plist = NULL;
PushFront(&plist,1);
Insert(&plist,plist,2);
Insert(&plist,plist,3);
Insert(&plist,plist,4);
Node* pos = Find(plist,3);
Insert(&plist,pos,5);
Insert(&plist,pos,6);
Insert(&plist,pos,7);
PrintList(plist);
}
int TestErase()
{
Node* plist = NULL;
PushFront(&plist,1);
Insert(&plist,plist,2);
Insert(&plist,plist,3);
Insert(&plist,plist,4);
PrintList(plist);
Node* pos = Find(plist,3);
Erase(&plist,pos);
PrintList(plist);
}
extern Node* BuyNode(DataType);
void TestOtherNode()
{
Node* node = BuyNode(5);
Node* plist = NULL;
PushBack(&plist,1);
PushBack(&plist,2);
PushBack(&plist,3);
PushBack(&plist,4);
PushBack(&plist,5);
// 测试Insert不和find使用,就出错了.
Insert(&plist,node,6);
}
int main()
{
TestOtherNode();
return 0;
}