【数据结构】单链表的增、删、查、改等

今天来写一下单链表的增删查改。

先来说一下链表的分类:

链表由特点的不同分为:

      (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)时间复杂度的优化;








你可能感兴趣的:(数据结构)