今天来写一下单链表的增删查改。
先来说一下链表的分类:
链表由特点的不同分为:
(1)单向链表; (2)双向链表
(1)不带环; (2)带环
(1)不带头结点;(2)带头结点
三种特点结合,总共有八种链表,其中 [ 单向、不带环、不带头结点 ]链表和 [ 双向、带环、带头结点 ]链表 比较常见。
今天要写的单链表为:单向、不带环、不带头结点的链表。
下面上代码:
linklist.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
//单向、不带环、不带头结点链表
typedef char LinkNodeType;
typedef struct LinkNode
{
LinkNodeType data;
struct LinkNode* next;
} LinkNode;
//把链表头放入结构体
typedef struct LinkList
{
LinkNode* head;
} LinkList;
//定义一个结构体变量,头结点指针,就表示了整个链表
LinkList list;
//创建新节点
LinkNode * CreateNode(LinkNodeType value);
//销毁节点
void DestroyNode(LinkNode * node);
//创建空链表
LinkNode* LinkListInit(LinkNode ** phead);
//销毁链表
void LinkListDestroy(LinkNode ** phead);
//尾插
void LinkListPushBack(LinkNode ** phead, LinkNodeType value);
//尾删
void LinkListPopBack(LinkNode * phead);
//头插
void LinkListPushFront(LinkNode ** phead, LinkNodeType value);
//头删
void LinkListPopFront(LinkNode ** phead);
//在位置pos之后插入value
void LinkListInsert(LinkNode * pos, LinkNodeType value);
//在pos之前插入value
void LinkListInsertBefore(LinkNode ** phead, LinkNode * pos, LinkNodeType value);
void LinkListInsertBefore2( LinkNode * pos, LinkNodeType value);
//删除指定位置的值
void LinkListErase(LinkNode ** phead, LinkNode * pos);
//找下标
LinkNode * LinkListFind(LinkNode * head, LinkNodeType to_find);
//删除指定值
void LinkListRemove(LinkNode ** phead, LinkNodeType to_remove);
//删除全部值
void LinkListRemoveAll(LinkNode ** phead, LinkNodeType to_remove);
//判断是否为空链表
int LinkListEmpty(LinkNode * head);
//链表节点数
size_t LinkListSize(LinkNode * head);
//返回第一个节点
LinkNode * LinkListFront(LinkNode * head);
//返回最后一个
LinkNode * LinkListBack(LinkNode * head);
linklist.c
#include
#include
#include "linklist.h"
#define TESTHEAD printf("================ %s ===============\n",__FUNCTION__)
//创建一个新节点
LinkNode * CreateNode(LinkNodeType value)
{
LinkNode * new_node = (LinkNode *)malloc(sizeof(LinkNode));//【 结构体的大小??】
new_node->data = value;
new_node->next = NULL;
return new_node;
}
//销毁节点
void DestroyNode(LinkNode * node)
{
free(node);
}
//创建一个空链表
LinkNode* LinkListInit(LinkNode ** phead)//【 思考这里为啥要定义一个LinkNode** 】
{
*phead = NULL;
}
//销毁链表
void LinkListDestroy(LinkNode ** phead)
{//TODO
if (phead == NULL)
{
return;
}
if (*phead == NULL)
{
return;
}
LinkNode* cur = *phead;
while (cur)
{
LinkNode * to_delete = cur;
cur = cur->next;
DestroyNode(to_delete);
}
*phead = NULL;
}
//尾插
void LinkListPushBack(LinkNode ** phead, LinkNodeType value) //这里为LinkNode**
{
if (phead == NULL)
{//非法输入
return;
}
if (*phead == NULL)
{//空链表
//需要修改head的值
//创建节点
*phead = CreateNode(value);
return;
}
//此时链表非空
//不需要修改head的值
LinkNode * cur = *phead;
while (cur->next != NULL)
{
cur = cur->next;
}
//此时cur指向最后一个元素,cur->next指向NULL
LinkNode * new_node = CreateNode(value);
cur->next = new_node;
//new_node->next=NULL不需要,在CreateNode()已经完成
return;
}
//尾删
void LinkListPopBack(LinkNode ** phead)
{
if (phead == NULL)
{//非法输入
return;
}
if (*phead == NULL)
{//空链表
return;
}
if ((*phead)->next == NULL)
{//只有一个元素
//*phead=NULL; //错误,内存泄漏。不能直接指向NULL,*phead也就是head是malloc出来的,要free
DestroyNode(*phead); //先free掉
*phead = NULL;//再让head指向NULL
return;
}
LinkNode* cur = *phead;
LinkNode * pre = NULL;
while (cur->next)
{
pre = cur;
cur = cur->next;
}
//当循环结束,cur指向最后一个节点,pre指向倒数第二个
//删除cur。还是先让pre->next指向空,再把cur free了
pre->next = NULL;
DestroyNode(cur);
return;
}
//头插
void LinkListPushFront(LinkNode ** phead, LinkNodeType value)
{
if (phead == NULL)
{//非法输入
return;
}
//不用判定空链表
LinkNode * new_node = CreateNode(value);
new_node->next = *phead; //修改原链表的头指针,在原头指针前面插入新节点
*phead = new_node; //定义新的头指针
}
//头删
void LinkListPopFront(LinkNode ** phead)
{
if (phead == NULL)
{//非法输入
return;
}
if (*phead == NULL)
{//空链表
return;
}
//删除时,先移动head,再删除head
//如果先删除head指向的空间,再去移动head,就相当于删除一个未定义的空间
LinkNode* to_earse = *phead;
*phead = (*phead)->next;
DestroyNode(to_earse);
return;
}
//在位置pos之后插入value
void LinkListInsert(LinkNode * pos, LinkNodeType value)
{
if (pos == NULL)
{//非法输入
return;
}
LinkNode * new_node = CreateNode(value);
new_node->next = pos->next;
pos->next = new_node;
return;
}
//在pos之前插入value
void LinkListInsertBefore(LinkNode ** phead, LinkNode * pos, LinkNodeType value)
{
if (phead == NULL||pos==NULL)
{//非法输入
return;
}
if (*phead == pos)
{//头插
LinkListPushFront(phead, value);
return;
}
//找到pos前一个位置cur,进行cur的后插即可
LinkNode * cur = *phead;
for (; cur != NULL; cur = cur->next)
{
if (cur->next = pos)
{
break;
}
}
//循环结束后,要判定循环结束原因
//找到pos,还是未找到
if (cur == NULL)
{//此时未找到pos
return;
}
//找到了
LinkListInsert(cur, value);
return;
}
//以上方法时间复杂度为O(n),遍历了链表,以下将时间复杂度优化为O(1),不遍历链表
void LinkListInsertBefore2( LinkNode * pos, LinkNodeType value)
{
if (pos == NULL)
{
return;
}
//法1:先把pos的值后插到pos->next的位置,再令pos位置的值等于value
//LinkListInsert(pos, pos->data);
//pos->data = value;
//法2:
LinkNode* new_node = CreateNode(pos->data);
new_node->next = pos->next;
pos->next = new_node;
pos->data = value;
}
//删除指定位置的值
void LinkListErase(LinkNode ** phead, LinkNode * pos)
{
if (phead == NULL||pos==NULL)
{//非法输入
return;
}
if (*phead == NULL)
{//空链表
return;
}
LinkNode * cur = *phead;
for (; cur != NULL; cur = cur->next)
{
if (cur->next == pos)
{
break;
}
}
//循环结束后,判断原因,找到了?没找到?
if (cur == NULL)
{//没找到
return;
}
cur->next = pos->next;
DestroyNode(pos);
return;
}
//找下标
LinkNode * LinkListFind(LinkNode * head, LinkNodeType to_find)
{
if (head == NULL)
{//空链表
return NULL;
}
LinkNode * cur = head;
while (cur)
{
if (cur->data == to_find)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//删除指定值
void LinkListRemove(LinkNode ** phead, LinkNodeType to_remove)
{
//法1:
//LinkNode* to_find = LinkListFind(*phead, to_remove);
//LinkListErase(*phead, to_find);
//虽然封装函数中对指针也会进行判定,但以下指针的安全判定也是必要的,是双重判定。
if (phead == NULL)
{//非法输入
return;
}
if (*phead == NULL)
{//空链表
return;
}
if ((*phead)->data == to_remove)
{//头删
//要先把头指针保存起来,一上来就删除的话,就找不到next了
LinkNode * to_delete = *phead;
*phead = (*phead)->next;
DestroyNode(to_delete);
return;
}
//============================???中断===================
LinkNode * cur = *phead;
for (; cur->next != NULL; cur = cur->next)//遍历
{
if (cur->next->data == to_remove)
{//cur已经指向要删除元素的前一个位置
LinkNode* to_delete = cur->next;
cur->next = to_delete->next;
DestroyNode(to_delete);
}
}
}
//删除全部值
void LinkListRemoveAll(LinkNode ** phead, LinkNodeType to_remove)
{
if (phead == NULL)
{
return;
}
if (*phead == NULL)
{
return;
}
while (1)
{
LinkNode* pos = LinkListFind(*phead, to_remove);
if (pos == NULL)
{
return;
}
LinkListErase(phead, pos);
return;
}
}
//判断是否为空链表
int LinkListEmpty(LinkNode * head)
{
return head == NULL ? 1 : 0;
}
//链表节点数
size_t LinkListSize(LinkNode * head)
{
if (head == NULL)
{
return 0;
}
LinkNode* cur = head;
size_t size = 0;
while (cur)
{
++size;
cur = cur->next;
}
return size;
}
//返回第一个节点
LinkNode * LinkListFront(LinkNode * head)
{
return head;
}
//返回最后一个
LinkNode * LinkListBack(LinkNode * head)
{
if (head == NULL)
{
return NULL;
}
LinkNode * cur = head;
while (cur->next)
{
cur = cur->next;
}
return cur;
}
//以下是测试函数
//打印测试结果
void LinkListPrintChar(LinkNode * head, const char * msg)
{
printf("[%s]\n", msg);
LinkNode * cur = head;
while (cur)
{
printf("[ %c -> %p ]", cur->data, cur);
cur = cur->next;
}
printf("\n");
}
//测试初始化
void TestInit()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPrintChar(head, "初始化链表为空链表");
}
//测试尾插
void TestPushBack()
{
TESTHEAD;
LinkNode *head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListPrintChar(head, "尾插三个元素");
}
//测试尾删
void TestPopBack()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListPopBack(&head);
LinkListPrintChar(head, "尾删一个元素");
}
//测试头插
void TestPushFront()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushFront(&head, 'a');
LinkListPrintChar(head, "空链表,头插一个元素");
LinkListPushFront(&head, 'b');
LinkListPushFront(&head, 'c');
LinkListPrintChar(head, "头插两个元素");
}
//测试头删
void TestPopFront()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListPopFront(&head);
LinkListPrintChar(head, "头删一个元素");
}
//测试在pos后插入
void TestInsert()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListInsert(head->next, 'k');
LinkListPrintChar(head, "在头结点下一个之后插入一个元素");
}
//测试在pos之前插入,法一
void TestInsertBefore()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListInsertBefore(&head,head->next,'x');
LinkListPrintChar(head, "在b之前插入一个元素");
}
//法2
void TestInsertBefore2()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListInsertBefore2( head->next, 'x');
LinkListPrintChar(head, "在b之前插入一个元素");
}
//测试删除指定位置的值
void TestErase()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListErase(&head, head->next);
LinkListPrintChar(head, "删除b");
}
//测试找下标
void TestFind()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkNode * pb=LinkListFind(head, 'b');
printf("b的下标= %p\n", pb);
LinkNode * px = LinkListFind(head, 'x');
printf("x的下标= %p \n", px);
}
//测试删除指定值
void TestRemove()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListRemove(&head, 's');
LinkListPrintChar(head, "删除s");
LinkListRemove(&head, 'b');
LinkListPrintChar(head, "删除b");
}
//测试删除指定的全部值
void TestRemoveAll()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListRemoveAll(&head, 'b');
LinkListPrintChar(head, "删除全部b");
}
//测试判断是否为空链表
int TestEmpty()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
printf("是否为空链表:[ %d ]\n", LinkListEmpty(head));
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
printf("是否为空链表:[ %d ]\n", LinkListEmpty(head));
}
//测试链表节点数
void TestSize()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
size_t size = LinkListSize(head);
printf("LinkList Node Size = %d\n ", size);
}
//测试返回第一个节点
void TestFront()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
printf("The First Node is [ %p ]\n",LinkListFront(head));
}
//测试返回最后一个
void TestBack()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
printf("The Last Node is [ %p ]\n", LinkListBack(head));
}
//测试销毁链表
void TestDestroy()
{
TESTHEAD;
LinkNode * head;
LinkListInit(&head);
LinkListPushBack(&head, 'a');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'b');
LinkListPushBack(&head, 'c');
LinkListPrintChar(head, "当前链表");
LinkListDestroy(&head);
LinkListPrintChar(head, "销毁链表");
}
//以下写个main函数调用一下测试函数
int main()
{
TestInit();
TestPushBack();
TestPopBack();
TestPushFront();
TestPopFront();
TestInsert();
TestInsertBefore();
TestInsertBefore2();
TestErase();
TestFind();
TestRemove();
TestRemoveAll();
TestEmpty();
TestSize();
TestFront();
TestBack();
TestDestroy();
system("pause");
return;
}
总结:
主要注意以下几点
(1)函数的命名,最好是对仗的;
(2)函数传参,考虑清楚是传[ * ]还是[ ** ];
(3)销毁指针时一定要先把指针保存起来,再free掉那段空间;
(4)时间复杂度的优化;