今天来写一下单链表的增删查改。
先来说一下链表的分类:
链表由特点的不同分为:
(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 <stdio.h> #include<windows.h> #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)时间复杂度的优化;